import { IsAny, KebabToCamel, Prettify, Split, UnionToIntersection } from "./type-utils.mjs"; import { DataStore, StoreState } from "./data-store.mjs"; import { InputOf, OutputOf, RouteCallOptions } from "./route.mjs"; import { PluginStores } from "./stores.mjs"; import { SessionState } from "./session-store.mjs"; import { User } from "./types.mjs"; import { ClientPlugin, FieldsOf, PluginSchema } from "./define-plugin.mjs"; //#region src/infer.d.ts type IsParam = S extends `:${string}` ? true : false; /** * Turn a route path literal into its camelCased client-chain segments, dropping * leading slashes and `:param` segments. `"/otp/send"` -> `["otp", "send"]`. */ type PathSegments

= P extends `/${infer Rest}` ? PathSegments : P extends `${infer Head}/${infer Tail}` ? Head extends "" ? PathSegments : IsParam extends true ? PathSegments : [KebabToCamel, ...PathSegments] : P extends "" ? [] : IsParam

extends true ? [] : [KebabToCamel

]; type PathOf = R extends { path: infer P extends string; } ? P : never; /** * The resolved chain for one route: an absolute `as` wins, otherwise it is the * plugin's base-path segments followed by the route-path segments. */ type ChainSegments = R extends { as: infer A extends string; } ? Split : [...BasePrefix, ...PathSegments>]; type Nest = Segs extends readonly [infer Head extends string, ...infer Rest extends readonly string[]] ? string extends Head ? unknown : { [K in Head]: Nest } : Fn; /** * A no-input route (`I` is `void`) takes no input — only the optional, trailing * call options; everything else takes `input` followed by the call options. * Options are always the trailing argument so the runtime can place them * unambiguously (a lone argument is always the route input). */ type InferRouteFn = [I] extends [void] ? (input?: void, opts?: RouteCallOptions) => Promise : (input: I, opts?: RouteCallOptions) => Promise; type IsExposed = R extends { expose: false; } ? false : true; type RouteFn = InferRouteFn, OutputOf>; type InferOneRoute = IsExposed extends false ? never : Nest, RouteFn>; /** * Infer the public API tree for a list of route descriptors, given the owning * plugin's base-path segments. Each route mounts a callable at its resolved * chain; sibling keys merge via intersection, so `signin.credential` and * `signup.credential` coexist and both autocomplete. */ type InferRoutes = UnionToIntersection<{ [K in keyof Routes]: InferOneRoute }[number]>; /** * The client-only methods a plugin contributes. Widened `any` contributes * nothing so the client does not become permissive. */ type ActionsOf

= P extends { actions?: (ctx: never, run: never) => infer A; } ? IsAny extends true ? unknown : A extends object ? A : unknown : unknown; type InferPluginContribution

= P extends ClientPlugin ? InferRoutes> & ActionsOf

: unknown; type CombinedClientContributions = UnionToIntersection<{ [K in keyof Plugins]: InferPluginContribution }[number]>; /** * The extra fields a plugin contributes to one model (`M`) via `schema`. Widened * `any` and non-object declarations contribute nothing, so the folded type never * collapses into an open index signature. */ type ModelFieldsOf = P extends { schema?: infer S; } ? IsAny extends true ? Record : FieldsOf> : Record; /** Intersection of every registered plugin's contributions to one model. */ type PluginModelFields = UnionToIntersection<{ [K in keyof Plugins]: ModelFieldsOf }[number]>; /** A consumer's `TFields` widened with all plugin-contributed `user` fields. */ type InferUserFields = User & PluginModelFields; /** The value each store in a registration record holds. */ type StoreValues = { [K in keyof S]: S[K] extends DataStore ? StoreState : never }; /** * The store values one plugin registers. Widened `any` registers nothing, so * the merged map never collapses into an open index signature. */ type StoresOfPlugin

= P extends { stores?: infer S; } ? IsAny extends true ? Record : S extends ((ctx: never) => infer R) ? R extends PluginStores ? StoreValues : Record : Record : Record; /** Every registered plugin's store values, merged. */ type PluginStoresOf = UnionToIntersection<{ [K in keyof Plugins]: StoresOfPlugin }[number]>; /** The stores the core owns. Session carries the plugin-enriched user shape. */ type CoreStores = { session: SessionState>>; }; /** Every store on a client, as the values they hold. */ type StoresOf = CoreStores & PluginStoresOf; //#endregion export { CombinedClientContributions, CoreStores, InferPluginContribution, InferUserFields, StoreValues, StoresOf };