import type { DataContract, JsonObject, JsonSchemaTypeName, JsonValue } from './data-contract.js'; /** * Extract the JSON Schema `required` array from a schema literal as a * string union. Resolves to `never` when `required` is absent, empty, * or non-string-typed. * * Used by {@link SchemaToType} to split an object's `properties` into * required + optional sub-maps. Honors the spec's `required: string[]` * array; properties NOT listed are optional. */ export type RequiredKeysOf = S extends { readonly required: readonly (infer R)[]; } ? R extends string ? R : never : never; /** * Flatten an intersection of object types into a single object type. * * Why this exists: the required/optional split in {@link SchemaToType} * (and the parallel split in {@link InferProps}) emits an intersection * `{ required keys } & { optional keys }`. TypeScript's `Equal` helper * (used in our type tests) treats `A & B` and the equivalent flat * shape as DIFFERENT types under its `[() => …]` conditional * distribution trick, even though they're assignable in both * directions. `Prettify` walks every key one level deep and rebuilds * a flat shape, so `Equal` resolves correctly. * * Note: only one level deep — nested object properties keep their * own structure (which is fine because nested objects flow back * through `SchemaToType` themselves and emerge already-prettified). */ type Prettify = { [K in keyof T]: T[K]; } & {}; /** * Map a JSON Schema literal type to its corresponding TypeScript type. * * Works with `as const` literals to preserve exact types: * - `{ type: 'string' }` -> `string` * - `{ type: 'number' }` -> `number` * - `{ type: 'boolean' }` -> `boolean` * - `{ type: 'null' }` -> `null` * - `{ type: 'array', items: S }` -> `SchemaToType[]` * - `{ type: 'object', properties: { k: S }, required: ['k'] }` -> `{ k: SchemaToType }` * - `{ type: 'object', properties: { k: S } }` -> `{ k?: SchemaToType }` (no `required` ⇒ all optional, per JSON Schema draft-07) * - `{ type: 'object' }` (no properties) -> {@link JsonObject} * - `{ enum: ['a', 'b'] }` -> `'a' | 'b'` * - `{ const: 42 }` -> `42` * - `{ oneOf: [S1, S2] }` -> `SchemaToType | SchemaToType` * * Falls back to `unknown` for non-literal or unrecognized schemas. * Objects without explicit `properties` resolve to {@link JsonObject} (not `unknown`). * * Required-array honor: per JSON Schema draft-07, an object schema's * `required` array names the keys that MUST be present; properties NOT * listed are optional. The mapping splits `properties` into a required * sub-map (keys in `required`) and an * optional sub-map (the rest), then intersects them. When `required` is * absent or empty, all properties are optional. */ export type SchemaToType = S extends { readonly const: infer V; } ? V : S extends { readonly enum: readonly (infer E)[]; } ? E : S extends { readonly type: readonly (infer M extends JsonSchemaTypeName)[]; } ? M extends JsonSchemaTypeName ? SchemaToType & { readonly type: M; }> : never : S extends { readonly type: 'string'; } ? string : S extends { readonly type: 'number'; } ? number : S extends { readonly type: 'integer'; } ? number : S extends { readonly type: 'boolean'; } ? boolean : S extends { readonly type: 'null'; } ? null : S extends { readonly type: 'array'; readonly items: infer I; } ? SchemaToType[] : S extends { readonly type: 'object'; readonly properties: infer P; } ? Prettify<{ -readonly [K in keyof P as K extends RequiredKeysOf ? K : never]: SchemaToType; } & { -readonly [K in keyof P as K extends RequiredKeysOf ? never : K]?: SchemaToType; }> : S extends { readonly type: 'object'; } ? JsonObject : S extends { readonly oneOf: readonly (infer U)[]; } ? SchemaToType : S extends { readonly anyOf: readonly (infer U)[]; } ? SchemaToType : unknown; /** * Infer the TypeScript props type from a `PropsSpec` literal. * * Given `{ properties: { city: { schema: { type: 'string' }, required: true }, temp: { schema: { type: 'number' } } } }`, * infers `{ city: string; temp?: number }`. * * Falls back to {@link JsonObject} when no props spec is present (untyped usage). * * Required honor: each {@link PropEntry} carries a per-property * `required?: boolean` flag (NOT a `required: string[]` at the props * level — that's the JSON Schema convention used elsewhere by * {@link SchemaToType}). Entries with `required: true` map to * required keys; everything else (`required: false`, `required` omitted, * or any non-`true` value) maps to optional `?:` keys. The split uses * a key-remap, mirroring the JSON-Schema `required: string[]` handling * in {@link SchemaToType}. */ export type InferProps = T extends { readonly propsSpec: { readonly properties: infer P; }; } ? Prettify<{ -readonly [K in keyof P as P[K] extends { readonly required: true; } ? K : never]: P[K] extends { readonly schema: infer S; } ? SchemaToType : unknown; } & { -readonly [K in keyof P as P[K] extends { readonly required: true; } ? never : K]?: P[K] extends { readonly schema: infer S; } ? SchemaToType : unknown; }> : JsonObject; /** * Extract action names as a string literal union from a contract. * * `DataContract.actionSpec` is a flat `Record`; * this type matches that shape directly. * * The fallback (no `actionSpec` key on `T`) resolves to `never`, not * `string`. A `string` fallback would silently let generated code * pattern-match against arbitrary names when no contract was declared; * `never` makes `useAction('nonExistent')` a compile error instead. * Consumers that still need a broad name/payload shape (e.g. the * server-side untyped-handler default) keep it by going through * `TypedAction`, which falls back to `{ name: string; data: JsonValue }` * when Names narrows to `never`. */ export type InferActionNames = T extends { readonly actionSpec: infer A; } ? Extract : never; /** * Infer the payload type for a specific action. * Actions without a `schema` field have `void` payload (fire-and-forget). */ export type InferActionPayload = T extends { readonly actionSpec: infer A; } ? N extends keyof A ? A[N] extends { readonly schema: infer S; } ? SchemaToType : void : never : unknown; /** * Extract stream channel names as a string literal union from a contract. * * `DataContract.streamSpec` is a flat * `Record`; this type matches that * shape directly. * * The fallback (no `streamSpec` key on `T`) resolves to `never`, not * `string`. Parallel to `InferActionNames` — `useStream('nonExistent')` * becomes a compile error when no contract is declared. * `TypedStreamEvent` preserves the broad * `{ channel: string; payload: JsonValue; … }` fallback when names * narrow to `never`, so untyped-handler defaults still work. */ export type InferStreamNames = T extends { readonly streamSpec: infer C; } ? Extract : never; /** Infer the payload type for a specific stream channel. */ export type InferStreamPayload = T extends { readonly streamSpec: infer C; } ? N extends keyof C ? C[N] extends { readonly schema: infer S; } ? SchemaToType : unknown : never : unknown; /** * Extract agent-tool names as a string literal union. Used by the * generator's `AllWires` completeness manifest to enumerate the * catalog at type-time. * * The catalog is invoked by the AGENT, not the component — there is no * payload-type inference for component callers because the component * never calls these tools. The catalog is referenced from * `actionSpec[*].nextStep` and `streamSpec[*].source.tool`. */ export type InferAgentToolNames = T extends { readonly agentCapabilities: { readonly tools: infer Tools; }; } ? Extract : string; /** * Extract gadget EXPORT names as a string literal union — the union * of every export name across every package the contract declares on * `clientCapabilities.gadgets` (which is package-keyed: * `Record>` — there is no * `exports` wrapper; a package entry IS its export map). * * Gadgets are declarations, not RPC, so they have no input/output * schemas to narrow against. The value type for a declared gadget is * consumed via the runtime hook / rendered component (e.g., * `useMicrophone()` returns `GadgetHook`), not via a * contract-level type query — so the export NAME is the unit the * completeness manifest enumerates. */ export type InferGadgetNames = T extends { readonly clientCapabilities: { readonly gadgets: infer Pkgs; }; } ? { [K in keyof Pkgs]: Extract; }[keyof Pkgs] : string; /** * Discriminated union of all stream emissions in a contract. * * Each member has `{ channel: ChannelName; payload: PayloadType; complete?: boolean }` * — the agent-supplied fields of {@link GguiEmitInput} minus `sessionId` * (which is caller context, not per-delivery). * * `mode` / `seq` / transport details are intentionally NOT on this union: * `mode` is derived from `streamSpec[channel].mode` server-side, * and `seq` is server-assigned via `GguiSessionStreamBuffer`. Producers that * try to set either are drifting against the streamSpec design lock. * * Falls back to `{ channel: string; payload: JsonValue; complete?: boolean }` * when the contract has no `streamSpec` declared. */ export type TypedStreamEvent = [ InferStreamNames ] extends [never] ? { channel: string; payload: JsonValue; complete?: boolean; } : InferStreamNames extends infer Names extends string ? { [N in Names]: { channel: N; payload: InferStreamPayload; complete?: boolean; }; }[Names] : { channel: string; payload: JsonValue; complete?: boolean; }; /** * Discriminated union of all action events in a contract. * Each member has `{ name: ActionName; data: PayloadType }`. * * Falls back to `{ name: string; data: JsonValue }` when no action spec is present. */ export type TypedAction = [ InferActionNames ] extends [never] ? { name: string; data: JsonValue; } : InferActionNames extends infer Names extends string ? { [N in Names]: { name: N; data: InferActionPayload; }; }[Names] : { name: string; data: JsonValue; }; /** * Define a data contract with full type inference. * * The `const` type parameter preserves literal types from `as const`, * enabling automatic TypeScript type inference from JSON Schema definitions. * * @example * ```typescript * const contract = defineContract({ * intent: 'Show weather for a city with refresh control', * props: { properties: { * city: { schema: { type: 'string' } }, * temp: { schema: { type: 'number' } }, * }}, * actionSpec: { * refresh: { label: 'Refresh' }, * changeUnit: { label: 'Unit', schema: { type: 'object', properties: { unit: { type: 'string' } } } }, * }, * streamSpec: { * weatherUpdate: { schema: { type: 'object', properties: { temp: { type: 'number' }, conditions: { type: 'string' } } } }, * }, * } as const); * * // TypeScript infers: * // InferProps = { city: string; temp: number } * // InferActionNames = 'refresh' | 'changeUnit' * // InferActionPayload = { unit: string } * // InferStreamPayload = { temp: number; conditions: string } * ``` */ export declare function defineContract(contract: T): T; /** * Manual type map for cases where `SchemaToType` can't infer * (complex unions, conditional schemas, branded types, etc.). * * Both paths (auto-inferred via `defineContract` and manual via `ContractTypeMap`) * work with `useContract`, typed handlers, and typed MCP client methods. * * All map slots default to {@link JsonObject} (props, actions, streams) or * `Record` (tools) * when not overridden. * * @example * ```typescript * interface MyContract extends ContractTypeMap { * props: { city: string; temperature: number }; * actions: { refresh: void; changeUnit: { unit: 'C' | 'F' } }; * streams: { weatherUpdate: { temp: number; conditions: string } }; * } * ``` */ export interface ContractTypeMap { props?: JsonObject; actions?: JsonObject; streams?: JsonObject; agentCapabilities?: Record; /** * Per-gadget binding-name set. Values are intentionally typed * `unknown` — gadget hooks own their own typed `value` / `start()` * shape (see `GadgetHook` in `./gadget.ts`), which * is not derivable from a contract-level type query. */ clientCapabilities?: Record; } /** Extract action names from a manual ContractTypeMap. */ export type ActionNames = C extends { actions: infer A; } ? Extract : string; /** Extract action payload from a manual ContractTypeMap. */ export type ActionPayload> = C extends { actions: infer A; } ? N extends keyof A ? A[N] : unknown : unknown; /** Extract stream event names from a manual ContractTypeMap. */ export type StreamNames = C extends { streams: infer S; } ? Extract : string; /** Extract stream payload from a manual ContractTypeMap. */ export type StreamPayloadOf> = C extends { streams: infer S; } ? N extends keyof S ? S[N] : unknown : unknown; /** * Extract agent-tool names from a manual ContractTypeMap. Used by the * generator's `AllWires` completeness manifest to enumerate the * catalog at type-time. * * The catalog is invoked by the AGENT, never a component-side hook * surface, so there is no payload-type inference for component callers. */ export type AgentToolNames = C extends { agentCapabilities: infer T; } ? Extract : string; export {}; //# sourceMappingURL=contract-inference.d.ts.map