@scalar/oas-utils
Version:
Open API spec and Yaml handling utilities
173 lines • 7.27 kB
TypeScript
import type { AnyEventListener, ApiReferenceEvents, WorkspaceEventBus } from '@scalar/workspace-store/events';
import type { RequestFactory, VariablesStore } from '@scalar/workspace-store/request-example';
import type { OpenApiDocument } from '@scalar/workspace-store/schemas/v3.1/strict/openapi-document';
import type { OperationObject } from '@scalar/workspace-store/schemas/v3.1/strict/operation';
import type { Component, DefineComponent } from 'vue';
/** Shared fields present on every response body handler variant */
type ResponseBodyHandlerBase = {
/** MIME type patterns this handler matches (exact or glob like "application/vnd.*+json") */
mimeTypes: string[];
/** Custom decoder: transform raw bytes into displayable data */
decode?: (buffer: ArrayBuffer, contentType: string) => string | Blob | Promise<string | Blob>;
/** Custom component for the preview view */
previewComponent?: Component;
};
/**
* Describes how a plugin handles a specific content type in the response body.
*
* The raw view is configured with either:
* - `rawComponent`: A custom Vue component (receives `content` and `contentType` props).
* - `language`: A CodeMirror language hint for the built-in raw renderer.
*
* These two options are mutually exclusive — providing `rawComponent` means the
* built-in renderer is not used, so `language` would have no effect.
*/
export type ResponseBodyHandler = ResponseBodyHandlerBase & ({
rawComponent: Component;
language?: never;
} | {
rawComponent?: never;
language?: string;
});
/** A type representing the hooks that a client plugin can define */
type ClientPluginHooks = {
beforeRequest: (payload: {
/** Workspace-store request spec; mutable by pre-request scripts (headers, method). */
requestBuilder: RequestFactory;
document: OpenApiDocument;
operation: OperationObject;
variablesStore?: VariablesStore;
}) => void | Promise<void>;
responseReceived: (payload: {
response: Response;
/** Request builder object that was used to build the request. Mutating this object will not affect the request object. */
requestBuilder: RequestFactory;
/** Request object that was sent to the server. */
request: Request;
document: OpenApiDocument;
operation: OperationObject;
variablesStore?: VariablesStore;
}) => void | Promise<void>;
};
/** A vue component which accepts the specified props */
type ClientPluginComponent<Props extends Record<string, unknown>, Emits extends Record<string, (...args: any[]) => void> = {}> = {
component: DefineComponent<Props, {}, {}, {}, {}, {}, {}, Emits>;
additionalProps?: Record<string, unknown>;
};
type ClientPluginComponents = {
request: ClientPluginComponent<{
operation?: OperationObject;
}, {
'operation:update:extension': (payload: ApiReferenceEvents['operation:update:extension']['payload']) => void;
}>;
response: ClientPluginComponent<{
operation?: OperationObject;
}>;
};
/**
* ClientPlugin is used to extend the API Client with custom hooks and UI components.
*
* Example usage:
*
* const myPlugin: ClientPlugin = {
* hooks: {
* beforeRequest: ({ request }) => {
* request.headers.set('X-Custom-Header', 'foo');
* return { request };
* },
* responseReceived: async (response, operation) => {
* // Handle post-response logic
* const data = await response.json();
* console.log('Received:', data, 'for operation:', operation.operationId);
* }
* },
* components: {
* request: MyRequestComponent, // Custom Vue component for rendering the request section
* response: MyResponseComponent // Custom Vue component for rendering the response section
* },
* responseBody: [{
* mimeTypes: ['application/msgpack', 'application/x-msgpack'],
* decode: (buffer) => {
* const decoded = msgpack.decode(new Uint8Array(buffer));
* return JSON.stringify(decoded, null, 2);
* },
* language: 'json',
* }]
* }
*/
/** Lifecycle hooks for app-level plugin concerns (analytics, logging, etc.) */
type ClientPluginLifecycle = {
/** Called when the API client is initialized */
onInit?: (context?: {
config: Record<string, unknown>;
}) => void;
/** Called when the API client configuration changes */
onConfigChange?: (context: {
config: Record<string, unknown>;
}) => void;
/** Called when the API client is destroyed */
onDestroy?: () => void;
};
export type ClientPlugin = {
hooks?: Partial<ClientPluginHooks>;
components?: Partial<ClientPluginComponents>;
/** Lifecycle hooks for app-level concerns */
lifecycle?: ClientPluginLifecycle;
/**
* Subscribe to every event on the bus. The framework wires this up to
* `bus.onAny` and handles subscribe/unsubscribe automatically.
*
* The listener receives a single tagged-union argument `{ event, payload }`
* where `event` is the discriminant. Narrowing on `event` automatically
* narrows `payload` to the exact type for that event — no casts, no `as`
* assertions, and no manual runtime type checks just to satisfy the
* compiler. Destructuring in the parameter list works too.
*
* @example
* on: ({ event, payload }) => {
* if (event === 'log:user-login') {
* // payload is narrowed to { uid: string; email?: string; teamUid: string }
* posthog.identify(payload.uid, { email: payload.email })
* }
*
* if (event === 'operation:create:operation') {
* // payload is narrowed to the operation-create payload
* analytics.track('operation_created', payload)
* }
* }
*/
on?: AnyEventListener;
/** Custom response body handlers for specific content types */
responseBody?: ResponseBodyHandler[];
};
/**
* Subscribes a single plugin's `on` listener to the given event bus via `onAny`.
*
* The plugin's `on` is passed straight through to `bus.onAny`, so it will
* receive every event emitted on the bus as a `{ event, payload }` object.
* Plugins without an `on` listener get a no-op unsubscribe.
*
* Returns an unsubscribe function. Call it when the plugin is torn down or
* the bus is destroyed to remove the wildcard listener.
*
* @example
* const unsubscribe = subscribePluginEvents(eventBus, plugin)
* // later...
* unsubscribe()
*/
export declare const subscribePluginEvents: (eventBus: WorkspaceEventBus, plugin: ClientPlugin) => (() => void);
/**
* Maps hook names to their expected payload types.
* This ensures type safety when executing hooks with their corresponding payloads.
* Derived from the ClientPlugin hooks definition.
*/
type HookPayloadMap = {
[K in keyof ClientPluginHooks]: Parameters<ClientPluginHooks[K]>[0];
};
/**
* Execute any hook with type-safe payload handling.
* The payload type is inferred from the hook name to ensure correct usage.
*/
export declare const executeHook: <K extends keyof HookPayloadMap>(payload: HookPayloadMap[K], hookName: K, plugins: ClientPlugin[]) => Promise<HookPayloadMap[K]>;
export {};
//# sourceMappingURL=client-plugins.d.ts.map