| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import { GraphQLClient } from 'graphql-request';
- import { getSdk, Sdk, PageTreeMode } from './generated/graphql.js';
- const REQUEST_TIMEOUT_MS = 30000;
- export class WikiJSClient {
- private sdk: Sdk;
- private baseUrl: string;
- private defaultLocale: string;
- constructor(endpoint: string, authToken: string, defaultLocale: string = 'en') {
- const defaultHeaders = {
- Authorization: `Bearer ${authToken}`
- };
- // Remove trailing slash if present
- this.baseUrl = endpoint.replace(/\/+$/, '');
- this.defaultLocale = defaultLocale || 'en';
- const endpointWithGraphql = `${this.baseUrl}/graphql`;
- const client = new GraphQLClient(endpointWithGraphql, {
- headers: defaultHeaders
- });
- this.sdk = getSdk(client);
- }
- private signal(): AbortSignal {
- return AbortSignal.timeout(REQUEST_TIMEOUT_MS);
- }
- private buildUrl(path: string, locale?: string | null): string {
- const pathWithoutLeadingSlash = (path || '').replace(/^\/+/, '');
- const encodedPath = pathWithoutLeadingSlash
- .split('/')
- .filter(Boolean)
- .map((seg) => encodeURIComponent(seg))
- .join('/');
- const loc = (locale || this.defaultLocale).trim();
- // Wiki.js omits the locale segment only for the site's default locale.
- if (loc && loc !== this.defaultLocale) {
- return `${this.baseUrl}/${encodeURIComponent(loc)}/${encodedPath}`;
- }
- return `${this.baseUrl}/${encodedPath}`;
- }
- private addUrlToPage<T extends { path?: string | null; locale?: string | null }>(page: T | null): (T & { url: string }) | null {
- if (!page) return null;
- const url = this.buildUrl(page.path || '', page.locale);
- return { ...page, url };
- }
- private addUrlToPages<T extends { path?: string | null; locale?: string | null }>(pages: T[]): (T & { url: string })[] {
- return pages.map((page) => ({
- ...page,
- url: this.buildUrl(page.path || '', page.locale)
- }));
- }
- async searchPages(query: string, path?: string, locale?: string) {
- const result = await this.sdk.SearchPages({ query, path, locale }, undefined, this.signal());
- const search = result.pages?.search;
- if (!search) {
- return { results: [], suggestions: [], totalHits: 0 };
- }
- const filteredResults = (search.results || []).filter((r): r is NonNullable<typeof r> => r !== null);
- const withUrls = this.addUrlToPages(filteredResults);
- return {
- results: withUrls,
- suggestions: search.suggestions || [],
- totalHits: search.totalHits || 0
- };
- }
- async getAllPages({ limit, locale, tags }: { limit?: number; locale?: string; tags?: string[] } = {}) {
- const result = await this.sdk.GetAllPages({ limit, locale, tags }, undefined, this.signal());
- const pages = result.pages?.list || [];
- return this.addUrlToPages(pages);
- }
- async getPageById(id: number) {
- const result = await this.sdk.GetPageById({ id }, undefined, this.signal());
- const page = result.pages?.single || null;
- const withUrl = this.addUrlToPage(page);
- if (!withUrl) return null;
- return withUrl;
- }
- async getPageByPath(path: string, locale: string) {
- const result = await this.sdk.GetPageByPath({ path, locale }, undefined, this.signal());
- const page = result.pages?.singleByPath || null;
- const withUrl = this.addUrlToPage(page);
- if (!withUrl) return null;
- return withUrl;
- }
- async createPage(params: {
- title: string;
- content: string;
- description: string;
- path: string;
- editor?: string;
- isPublished?: boolean;
- isPrivate?: boolean;
- locale?: string;
- tags?: string[];
- }) {
- const result = await this.sdk.CreatePage(
- {
- title: params.title,
- content: params.content,
- description: params.description,
- path: params.path,
- editor: params.editor || 'markdown',
- isPublished: params.isPublished ?? true,
- isPrivate: params.isPrivate ?? false,
- locale: params.locale || 'en',
- tags: params.tags || []
- },
- undefined,
- this.signal()
- );
- const created = result.pages?.create;
- if (!created) return null;
- const page = created.page ? this.addUrlToPage(created.page) : null;
- return {
- responseResult: created.responseResult,
- page
- };
- }
- async updatePage(params: {
- id: number;
- title?: string;
- content?: string;
- description?: string;
- editor?: string;
- isPublished?: boolean;
- isPrivate?: boolean;
- locale?: string;
- tags?: string[];
- path?: string;
- }) {
- // Fetch existing page to avoid clobbering fields the caller did not supply.
- let existing: Record<string, unknown> | null = null;
- try {
- existing = (await this.getPageById(params.id)) as Record<string, unknown> | null;
- } catch {
- existing = null;
- }
- const tagsFromExisting: string[] =
- Array.isArray(existing?.tags) && existing.tags.length && typeof existing.tags[0] === 'object'
- ? (existing.tags as Array<{ tag?: string }>).map((t) => t.tag).filter((t): t is string => Boolean(t))
- : Array.isArray(existing?.tags)
- ? (existing.tags as unknown[]).filter((t): t is string => typeof t === 'string')
- : [];
- const merged = {
- id: params.id,
- title: params.title ?? (existing?.title as string | undefined) ?? '',
- content: params.content ?? (existing?.content as string | undefined) ?? '',
- description: params.description ?? (existing?.description as string | undefined) ?? '',
- editor: params.editor ?? (existing?.editor as string | undefined) ?? 'markdown',
- isPublished: params.isPublished ?? (existing?.isPublished as boolean | undefined) ?? true,
- isPrivate: params.isPrivate ?? (existing?.isPrivate as boolean | undefined) ?? false,
- locale: params.locale ?? (existing?.locale as string | undefined) ?? this.defaultLocale,
- tags: params.tags ?? tagsFromExisting,
- path: params.path ?? (existing?.path as string | undefined) ?? ''
- };
- if (!merged.content || merged.content.trim().length === 0) {
- throw new Error('content is required to update a page');
- }
- const result = await this.sdk.UpdatePage(merged, undefined, this.signal());
- const updated = result.pages?.update;
- if (!updated) return null;
- const page = updated.page ? this.addUrlToPage(updated.page) : null;
- return {
- responseResult: updated.responseResult,
- page
- };
- }
- async getPageHistory(id: number, offsetPage?: number, offsetSize?: number) {
- const result = await this.sdk.GetPageHistory({ id, offsetPage, offsetSize }, undefined, this.signal());
- return result.pages?.history || null;
- }
- async getPageVersion(pageId: number, versionId: number) {
- const result = await this.sdk.GetPageVersion({ pageId, versionId }, undefined, this.signal());
- return result.pages?.version || null;
- }
- async getPageTree(params: { path?: string; parent?: number; mode: 'FOLDERS' | 'PAGES' | 'ALL'; locale: string; includeAncestors?: boolean }) {
- const mode = (PageTreeMode as Record<string, PageTreeMode>)[params.mode.charAt(0) + params.mode.slice(1).toLowerCase()] ?? PageTreeMode.Pages;
- const result = await this.sdk.GetPageTree({ ...params, mode }, undefined, this.signal());
- return result.pages?.tree || null;
- }
- async listTags() {
- const result = await this.sdk.ListTags(undefined, undefined, this.signal());
- return result.pages?.tags || [];
- }
- async searchTags(query: string) {
- const result = await this.sdk.SearchTags({ query }, undefined, this.signal());
- return result.pages?.searchTags || [];
- }
- async getPageLinks(locale: string) {
- const result = await this.sdk.GetPageLinks({ locale }, undefined, this.signal());
- return result.pages?.links || null;
- }
- async checkConflicts(id: number, checkoutDate: string) {
- const result = await this.sdk.CheckConflicts({ id, checkoutDate }, undefined, this.signal());
- return result.pages?.checkConflicts ?? null;
- }
- async movePage(id: number, destinationPath: string, destinationLocale: string) {
- const result = await this.sdk.MovePage({ id, destinationPath, destinationLocale }, undefined, this.signal());
- return result.pages?.move || null;
- }
- async deletePage(id: number) {
- const result = await this.sdk.DeletePage({ id }, undefined, this.signal());
- return result.pages?.delete || null;
- }
- async restorePageVersion(pageId: number, versionId: number) {
- const result = await this.sdk.RestorePageVersion({ pageId, versionId }, undefined, this.signal());
- return result.pages?.restore || null;
- }
- async convertPageEditor(id: number, editor: string) {
- const result = await this.sdk.ConvertPageEditor({ id, editor }, undefined, this.signal());
- return result.pages?.convert || null;
- }
- async renderPage(id: number) {
- const result = await this.sdk.RenderPage({ id }, undefined, this.signal());
- return result.pages?.render || null;
- }
- }
|