/** * ------------------------------ * Plugin authoring helpers * ------------------------------ * * - `definePlugin`: identity wrapper that constrains a plugin's return to * `PluginProvides` while preserving the narrow inferred shape, so callers * can derive their `*PluginProvides` type via `ReturnType` * instead of declaring it by hand. * - `createPluginMethod` / `createPaginatedPluginMethod`: per-method * primitives that sit inside a `definePlugin` callback and build the * * { [name]: wrappedFn, context: { meta: { [name]: meta } } } * * fragment a plugin returns for a single method, wiring up * `createFunction` / `createPaginatedFunction`, the telemetry callback, * and the doubled `name` (function key + meta key) in one place. * * Two method helpers (rather than one with a `paginated: true` discriminant) * because the handler signature changes shape across pagination, and * discriminated unions on optional booleans produce noisy TS errors. */ import type { z } from "zod"; import type { PaginatedSdkResult } from "../types/functions"; import type { Plugin, PluginMeta, PluginProvides } from "../types/plugin"; /** * Identity helper that preserves the narrow inferred return type of a plugin. * * Wrapping a plugin function with `definePlugin(...)` lets callers derive the * plugin's contribution via `ReturnType` instead of declaring a * matching `*PluginProvides` interface by hand. The constraint * `TProvides extends PluginProvides` enforces the structural floor (must * return a valid plugin output); beyond that the inferred shape flows through. */ export declare function definePlugin(fn: (sdk: TSdk & { context: { meta: Record; }; }) => TProvides): (sdk: TSdk & { context: { meta: Record; }; }) => TProvides; /** * Method-level meta fields. Mirrors `PluginMeta` minus `inputSchema`, which is * passed at the top level alongside the handler and merged into the meta by * the helpers themselves. */ type MethodMeta = Omit; interface PluginMethodConfig extends MethodMeta { name: TName; /** * Schema for runtime input validation; drives the handler's `options` * type. For plugins that accept deprecated parameter aliases this is a * `z.union([CanonicalSchema, DeprecatedSchema])` — the registry * unwraps unions and exposes only the first variant (canonical) to * docs, CLI help, and MCP tool definitions. */ inputSchema?: z.ZodSchema; handler: (args: { sdk: TSdk; options: TInput; }) => Promise; } type PluginMethodReturn = { [K in TName]: (options?: TInput) => Promise; } & { context: { meta: { [K in TName]: PluginMeta; }; }; }; /** * Build the method fragment for a non-paginated SDK method. Used inside a * `definePlugin(...)` callback: * * export const getProfilePlugin = definePlugin( * (sdk: ApiPluginProvides & EventEmissionProvides) => * createPluginMethod(sdk, { * name: "getProfile", * categories: ["account"], * inputSchema: GetProfileSchema, * handler: async ({ sdk }) => { ... }, * }), * ); */ export declare function createPluginMethod(sdk: TSdk, config: PluginMethodConfig): PluginMethodReturn; interface PaginatedPluginMethodConfig extends MethodMeta { name: TName; /** Same semantics as `createPluginMethod`'s `inputSchema`. */ inputSchema?: z.ZodSchema; /** * Optional default page size when the caller doesn't pass one. Mirrors * `createPaginatedFunction`'s `defaultPageSize` arg. */ defaultPageSize?: number; /** * Page handler. Returns whatever shape the upstream API gives back, as * long as it has a `data: TItem[]` field. JSON:API envelopes * (`{ data, links, meta }`) work directly: pair them with * `extractCursor` below to pull the next-page cursor out of the response. */ handler: (args: { sdk: TSdk; options: TInput & { cursor?: string; pageSize?: number; }; }) => Promise; /** * Pull the next-page cursor out of the handler's response. Most JSON:API * endpoints will use `(response) => extractCursor(response.links)` from * `function-utils`. Provide a custom extractor for non-standard shapes * (e.g. `(response) => response.meta?.pagination?.end_cursor`). * * If omitted, the runtime falls back to its built-in detection * (`response.nextCursor` or `response.links?.next` URL parsing) — i.e. * the same auto-detection that worked before this helper existed. New * plugins should set `extractCursor` explicitly so the pagination shape * is visible at the call site. */ extractCursor?: (response: TResponse) => string | undefined; } type ItemOf = TResponse extends { data: readonly (infer TItem)[]; } ? TItem : never; type PaginatedPluginMethodReturn = { [K in TName]: (options?: TInput & { cursor?: string; pageSize?: number; maxItems?: number; }) => PaginatedSdkResult; } & { context: { meta: { [K in TName]: PluginMeta; }; }; }; /** * Paginated variant of `createPluginMethod`. The handler receives * `{ cursor?, pageSize? }` in `options` and returns whatever shape the * upstream API gives back, as long as it has a `data: TItem[]` field. * Pair JSON:API responses with `extractCursor` to make the cursor * extraction visible at the call site: * * createPaginatedPluginMethod(sdk, { * name: "listApps", * inputSchema: ListAppsSchema, * handler: ({ sdk, options }) => * sdk.context.api.get("/api/v0/apps", {...}), * extractCursor: (response) => extractCursor(response.links), * }); */ export declare function createPaginatedPluginMethod(sdk: TSdk, config: PaginatedPluginMethodConfig): PaginatedPluginMethodReturn>; /** * Maps a tuple of plugins to a tuple of their TSdk requirement types. * * SdkRequirementsOf<[Plugin<{ api }, _>, Plugin<{ options }, _>]> * = [{ api }, { options }] */ type SdkRequirementsOf[]> = { [K in keyof T]: T[K] extends Plugin ? Sdk : never; }; /** * Maps a tuple of plugins to a tuple of their TProvides output types. * * ProvidesOf<[Plugin<_, { hello }>, Plugin<_, { goodbye }>]> * = [{ hello }, { goodbye }] */ type ProvidesOf[]> = { [K in keyof T]: T[K] extends Plugin ? Provides : never; }; /** * Intersects every member of a tuple into a single combined type. The * result is an object that has every property of every member at once. * * IntersectAll<[{ api }, { options }]> = { api } & { options } * IntersectAll<[]> = {} * * Walks recursively: head & IntersectAll, base case is the empty * tuple. Why intersection (`&`) and not union (`|`): the composed plugin * must require ALL of the sub-plugins' needs at once — an SDK that has * both `api` AND `options` — not "either api or options." */ type IntersectAll = T extends readonly [ infer Head, ...infer Tail ] ? Head & IntersectAll : {}; /** * The TSdk a composed plugin requires: every sub-plugin's TSdk requirement, * all at once. Composing a plugin that needs `{ api }` with one that needs * `{ options }` yields a composed plugin that needs `{ api } & { options }`. */ type ComposeSdk[]> = IntersectAll>; /** * What a composed plugin provides: every sub-plugin's TProvides combined. * Composing a plugin that provides `{ hello }` with one that provides * `{ goodbye }` yields `{ hello } & { goodbye }`. */ type ComposeProvides[]> = IntersectAll>; /** * Bundle N plugins into a single plugin so a consumer can call * `addPlugin(combined)` once. Use this when an extension package wants to * expose granular sub-plugins for tree-shaking *and* a one-call convenience * entry point. * * Three commitments (per the boilerplate-reduction design): * 1. **Bag mode.** Sub-plugins must not depend on each other; order is * irrelevant. If you need pipeline composition (later sub-plugins * reading earlier sub-plugins' outputs), use a regular `definePlugin` * callback that calls each `createPluginMethod` in turn. * 2. **Over-approximation of TSdk.** The composed plugin requires the * intersection of every sub-plugin's requirements (all of them, all at * once). Works fine for homogeneous-deps groups; widens slightly for * heterogeneous ones, which keeps the variadic types tractable. * 3. **Collision detection.** Throws on duplicate root keys, duplicate * `context.meta` keys, and duplicate non-meta `context.*` keys * *inside* a single `composePlugins(...)` call. Cross-`addPlugin` * collisions are handled separately by `addPlugin` itself, which * warns-and-skips the colliding plugin (the first registration * wins). Pure meta-only overrides (e.g. `cliOverridesPlugin` * patching `meta.fetch.deprecated` without re-registering `fetch`) * stay legitimate in both code paths. * * Why prefer this over an `addPlugin([...])` array overload: TypeScript's * overload resolution interacts badly with arrays carrying a mix of * `TProvides` shapes — chained inference downstream of `addPlugin([...])` * tends to widen and lose individual method types. A single composed * plugin flows through the existing single-plugin `addPlugin` cleanly. */ export declare function composePlugins[]>(...plugins: Ts): Plugin, ComposeProvides>; export {}; //# sourceMappingURL=plugin-utils.d.ts.map