UNPKG

@scalar/types

Version:

Types to work with Scalar packages

431 lines (430 loc) 15.6 kB
import { z } from 'zod'; import { apiReferencePluginSchema } from './api-reference-plugin.js'; import { NEW_PROXY_URL, OLD_PROXY_URL, baseConfigurationSchema } from './base-configuration.js'; import { sourceConfigurationSchema } from './source-configuration.js'; // Zod Schemas don't work well with async functions, so we use a custom type instead. const fetchLikeSchema = z.custom(); /** * Standard configuration for the Api Reference. * * This is used internally to the configure the applications and does not include the sources. * * Sources should only be specified in the user facing configurations. * * In the the future it is likely sources will be removed completely from the configuration and instead * specified through a separate addDocument interface. */ export const apiReferenceConfigurationSchema = baseConfigurationSchema.extend({ /** * The layout to use for the references * @default 'modern' */ layout: z.enum(['modern', 'classic']).optional().default('modern').catch('modern'), /** * URL to a request proxy for the API client * @deprecated Use proxyUrl instead */ proxy: z.string().optional(), /** * Custom fetch function for custom logic * * Can be used to add custom headers, handle auth, etc. * * @deprecated Use `customFetch` instead. */ fetch: fetchLikeSchema.optional(), /** * Custom fetch function used both when loading the OpenAPI document and when sending requests from the API client. * * Can be used to add custom headers, attach credentials (for example `credentials: 'include'`), handle auth, etc. */ customFetch: fetchLikeSchema.optional(), /** * Plugins for the API reference */ plugins: z.array(apiReferencePluginSchema).optional(), /** * Allows the user to inject an editor for the spec * @default false */ isEditable: z.boolean().optional().default(false).catch(false), /** * Controls whether the references show a loading state in the intro * @default false */ isLoading: z.boolean().optional().default(false).catch(false), /** * Whether to show models in the sidebar, search, and content. * @default false */ hideModels: z.boolean().optional().default(false).catch(false), /** * Sets the file type of the document to download, set to `none` to hide the download button * @default 'both' */ documentDownloadType: z.enum(['yaml', 'json', 'both', 'direct', 'none']).optional().default('both').catch('both'), /** * Whether to show the "Download OpenAPI Document" button * @default false * @deprecated Use `documentDownloadType: 'none'` instead */ hideDownloadButton: z.boolean().optional(), /** * Whether to show the "Test Request" button * @default false */ hideTestRequestButton: z.boolean().optional().default(false).catch(false), /** * Whether to show the sidebar search bar * @default false */ hideSearch: z.boolean().optional().default(false).catch(false), /** * Whether to show the operationId * * @default false */ showOperationId: z.boolean().optional().default(false).catch(false), /** Whether dark mode is on or off initially (light mode) */ darkMode: z.boolean().optional(), /** forceDarkModeState makes it always this state no matter what */ forceDarkModeState: z.enum(['dark', 'light']).optional(), /** * Whether to show the dark mode toggle * @default false */ hideDarkModeToggle: z.boolean().optional().default(false).catch(false), /** * If used, passed data will be added to the HTML header * @see https://unhead.unjs.io/usage/composables/use-seo-meta */ metaData: z.any().optional(), // Using any for UseSeoMetaInput since it's an external type /** * Path to a favicon image * @default undefined * @example '/favicon.svg' */ favicon: z.string().optional(), /** * List of httpsnippet clients to hide from the clients menu * By default hides Unirest, pass `[]` to show all clients */ hiddenClients: z .union([z.record(z.string(), z.union([z.boolean(), z.array(z.string())])), z.array(z.string()), z.literal(true)]) .optional(), /** Determine the HTTP client that's selected by default */ defaultHttpClient: z .object({ targetKey: z.custom(), clientKey: z.string(), }) .optional(), /** Custom CSS to be added to the page */ customCss: z.string().optional(), /** onSpecUpdate is fired on spec/swagger content change */ onSpecUpdate: z .function({ input: [z.string()], output: z.void(), }) .optional(), /** onServerChange is fired on selected server change */ onServerChange: z .function({ input: [z.string()], output: z.void(), }) .optional(), /** onDocumentSelect is fired when the config is selected */ onDocumentSelect: z .function({ input: [], // Why no output? https://github.com/scalar/scalar/pull/7047 // output: z.union([z.void(), z.promise(z.void())]), }) .optional(), /** Callback fired when the reference is fully loaded */ onLoaded: z.function().optional(), /** Fired before the outbound request is built; callback receives a mutable request builder (RequestFactory). Experimental API. */ onBeforeRequest: z .function({ input: [ z.object({ request: z.instanceof(Request), requestBuilder: z.unknown(), envVariables: z.record(z.string(), z.string()), }), ], // Why no output? https://github.com/scalar/scalar/pull/7047 // output: z.union([z.void(), z.promise(z.void())]), }) .optional(), /** * onShowMore is fired when the user clicks the "Show more" button on the references * @param tagId - The ID of the tag that was clicked */ onShowMore: z .function({ input: [z.string()], // Why no output? https://github.com/scalar/scalar/pull/7047 // output: z.union([z.void(), z.promise(z.void())]), }) .optional(), /** * onSidebarClick is fired when the user clicks on a sidebar item * @param href - The href of the sidebar item that was clicked */ onSidebarClick: z .function({ input: [z.string()], // Why no output? https://github.com/scalar/scalar/pull/7047 // output: z.union([z.void(), z.promise(z.void())]), }) .optional(), /** * Route using paths instead of hashes, your server MUST support this * @example '/standalone-api-reference/:custom(.*)?' * @experimental * @default undefined */ pathRouting: z .object({ basePath: z.string(), }) .optional(), /** * MCP (Model Context Protocol) configuration * Optional. When provided, enables MCP integration with the given name and url. */ mcp: z .object({ /** Display name for the MCP server */ name: z.string().optional(), /** URL of the MCP server */ url: z.string().optional(), /** When true, disables the MCP integration */ disabled: z.boolean().optional(), }) .optional(), /** * Customize the heading portion of the hash * @param heading - The heading object * @returns A string ID used to generate the URL hash * @default (heading) => `#description/${heading.slug}` */ generateHeadingSlug: z .function({ input: [z.object({ slug: z.string().default('headingSlug') })], output: z.string(), }) .optional(), /** * Customize the model portion of the hash * @param model - The model object with a name property * @returns A string ID used to generate the URL hash * @default (model) => slug(model.name) */ generateModelSlug: z .function({ input: [z.object({ name: z.string().default('modelName') })], output: z.string(), }) .optional(), /** * Customize the tag portion of the hash * @param tag - The tag object * @returns A string ID used to generate the URL hash * @default (tag) => slug(tag.name) */ generateTagSlug: z .function({ input: [z.object({ name: z.string().default('tagName') })], output: z.string(), }) .optional(), /** * Customize the operation portion of the hash * @param operation - The operation object * @returns A string ID used to generate the URL hash * @default (operation) => `${operation.method}${operation.path}` */ generateOperationSlug: z .function({ input: [ z.object({ path: z.string(), operationId: z.string().optional(), method: z.string(), summary: z.string().optional(), }), ], output: z.string(), }) .optional(), /** * Customize the webhook portion of the hash * @param webhook - The webhook object * @returns A string ID used to generate the URL hash * @default (webhook) => slug(webhook.name) */ generateWebhookSlug: z .function({ input: [ z.object({ name: z.string(), method: z.string().optional(), }), ], output: z.string(), }) .optional(), /** * To handle redirects, pass a function that will receive: * - The current path with hash if pathRouting is enabled * - The current hash if hashRouting (default) * And then passes that to history.replaceState * * @example hashRouting (default) * ```ts * redirect: (hash: string) => hash.replace('#v1/old-path', '#v2/new-path') * ``` * @example pathRouting * ```ts * redirect: (pathWithHash: string) => { * if (pathWithHash.includes('#')) { * return pathWithHash.replace('/v1/tags/user#operation/get-user', '/v1/tags/user/operation/get-user') * } * return null * } * ``` */ redirect: z .function({ input: [z.string()], output: z.string().nullable().optional(), }) .optional(), /** * Whether to include default fonts * @default true */ withDefaultFonts: z.boolean().optional().default(true).catch(true), /** * Whether to expand the first tag in the sidebar when no specific URL target is present * @default true */ defaultOpenFirstTag: z.boolean().optional().default(true).catch(true), /** * Whether to expand all tags by default * * Warning this can cause performance issues on big documents * @default false */ defaultOpenAllTags: z.boolean().optional().default(false).catch(false), /** * Whether to expand all models by default * * Warning this can cause performance issues on big documents * @default false */ expandAllModelSections: z.boolean().optional().default(false).catch(false), /** * Whether to expand all responses by default * * Warning this can cause performance issues on big documents * @default false */ expandAllResponses: z.boolean().optional().default(false).catch(false), /** * Function to sort tags * @default 'alpha' for alphabetical sorting */ tagsSorter: z .union([ z.literal('alpha'), z.function({ input: [z.any(), z.any()], output: z.number(), }), ]) .optional(), /** * Function to sort operations * @default 'alpha' for alphabetical sorting */ operationsSorter: z .union([ z.literal('alpha'), z.literal('method'), z.function({ input: [z.any(), z.any()], output: z.number(), }), ]) .optional(), /** * Order the schema properties by * @default 'alpha' for alphabetical sorting */ orderSchemaPropertiesBy: z .union([z.literal('alpha'), z.literal('preserve')]) .optional() .default('alpha') .catch('alpha'), /** * Sort the schema properties by required ones first * @default true */ orderRequiredPropertiesFirst: z.boolean().optional().default(true).catch(true), }); /** Migrate the configuration through a transform */ // const migrateConfiguration = <T extends z.infer<typeof _apiReferenceConfigurationSchema>> /** Configuration for the Api Reference */ export const apiReferenceConfigurationWithSourceSchema = apiReferenceConfigurationSchema.extend(sourceConfigurationSchema.shape).transform((configuration) => { // Migrate hideDownloadButton to documentDownloadType if (configuration.hideDownloadButton) { console.warn(`[DEPRECATED] You're using the deprecated 'hideDownloadButton' attribute. Use 'documentDownloadType: 'none'' instead.`); configuration.documentDownloadType = 'none'; } // Remove the spec prefix if (configuration.spec?.url) { console.warn(`[DEPRECATED] You're using the deprecated 'spec.url' attribute. Remove the spec prefix and move the 'url' attribute to the top level.`); configuration.url = configuration.spec.url; delete configuration.spec; } if (configuration.spec?.content) { console.warn(`[DEPRECATED] You're using the deprecated 'spec.content' attribute. Remove the spec prefix and move the 'content' attribute to the top level.`); configuration.content = configuration.spec.content; delete configuration.spec; } // Migrate proxy URL if (configuration.proxy) { console.warn(`[DEPRECATED] You're using the deprecated 'proxy' attribute, rename it to 'proxyUrl' or update the package.`); if (!configuration.proxyUrl) { configuration.proxyUrl = configuration.proxy; } delete configuration.proxy; } // Migrate fetch to customFetch if (configuration.fetch) { console.warn(`[DEPRECATED] You're using the deprecated 'fetch' attribute. Use 'customFetch' instead.`); if (!configuration.customFetch) { configuration.customFetch = configuration.fetch; } delete configuration.fetch; } if (configuration.proxyUrl === OLD_PROXY_URL) { console.warn(`[DEPRECATED] Warning: configuration.proxyUrl points to our old proxy (${OLD_PROXY_URL}).`); console.warn(`[DEPRECATED] We are overwriting the value and use the new proxy URL (${NEW_PROXY_URL}) instead.`); console.warn(`[DEPRECATED] Action Required: You should manually update your configuration to use the new URL (${NEW_PROXY_URL}). Read more: https://github.com/scalar/scalar`); configuration.proxyUrl = NEW_PROXY_URL; } // Migrate showToolbar to showDeveloperTools if (configuration.showToolbar && configuration.showToolbar !== 'localhost') { console.warn(`[DEPRECATED] You're using the deprecated 'showToolbar' attribute. Use 'showDeveloperTools' instead.`); configuration.showDeveloperTools = configuration.showToolbar; // @ts-expect-error - We're deleting the deprecated attribute delete configuration.showToolbar; } return configuration; });