@emeryld/rrroutes-contract 2.4.12 → 2.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,8 +37,8 @@ import { z } from 'zod'
37
37
 
38
38
  // 1) Describe your API
39
39
  const leaves = resource('/v1')
40
- .sub('users', (users) =>
41
- users
40
+ .sub(
41
+ resource('users')
42
42
  .get({
43
43
  querySchema: z.object({
44
44
  search: z.string().optional(),
@@ -49,11 +49,13 @@ const leaves = resource('/v1')
49
49
  ),
50
50
  description: 'Find users',
51
51
  })
52
- .subId('userId', z.string().uuid(), (user) =>
53
- user.patch({
54
- bodySchema: z.object({ name: z.string().min(1) }),
55
- outputSchema: z.object({ ok: z.literal(true) }),
56
- }),
52
+ .sub(
53
+ resource(':userId', undefined, z.string().uuid())
54
+ .patch({
55
+ bodySchema: z.object({ name: z.string().min(1) }),
56
+ outputSchema: z.object({ ok: z.literal(true) }),
57
+ })
58
+ .done(),
57
59
  )
58
60
  .done(),
59
61
  )
@@ -87,10 +89,9 @@ const key = buildCacheKey({
87
89
  import { resource } from '@emeryld/rrroutes-contract'
88
90
  import { z } from 'zod'
89
91
 
90
- const leaves = resource('/api') // base path, optional inherited cfg
91
- .with({ feed: false }) // merges flags into descendants (extend NodeCfg via declaration merging if you add your own)
92
- .sub('projects', (projects) =>
93
- projects
92
+ const leaves = resource('/api')
93
+ .sub(
94
+ resource('projects')
94
95
  .get({
95
96
  feed: true, // infinite/feed for clients
96
97
  querySchema: z.object({
@@ -107,15 +108,15 @@ const leaves = resource('/api') // base path, optional inherited cfg
107
108
  outputSchema: z.object({ id: z.string(), name: z.string() }),
108
109
  description: 'Create a project',
109
110
  })
110
- .subId('projectId', z.string().uuid(), (project) =>
111
- project
111
+ .sub(
112
+ resource(':projectId', undefined, z.string().uuid())
112
113
  .get({ outputSchema: z.object({ id: z.string(), name: z.string() }) })
113
114
  .patch({
114
115
  bodySchema: z.object({ name: z.string().min(1) }),
115
116
  outputSchema: z.object({ id: z.string(), name: z.string() }),
116
117
  })
117
- .sub('avatar', (avatar) =>
118
- avatar
118
+ .sub(
119
+ resource('avatar')
119
120
  .put({
120
121
  bodyFiles: [{ name: 'avatar', maxCount: 1 }], // signals multipart upload
121
122
  bodySchema: z.object({ avatar: z.instanceof(Blob) }),
@@ -130,8 +131,8 @@ const leaves = resource('/api') // base path, optional inherited cfg
130
131
  .done()
131
132
  ```
132
133
 
133
- - `sub(name, [cfg], builder?)` nests paths (`/api/projects`).
134
- - `subId(name, schema, builder)` creates `/:name` segments and merges param schemas downward.
134
+ - `resource(segment, nodeCfg?, idSchema?)` scopes a branch. Pass a segment name (e.g. `'projects'`, `':projectId'`) plus optional per-node config. Supplying an `idSchema` along with a `:param` segment wires up the params schema for all descendants.
135
+ - `sub(childA, childB, ...)` mounts one or more child resources built elsewhere via `resource(...).get(...).done()`. Call it once per branch; pass multiple children at once when needed.
135
136
  - Methods (`get/post/put/patch/delete`) merge the active param schema unless you override via `paramsSchema`.
136
137
  - `done()` closes a branch and returns the collected readonly tuple of leaves.
137
138
 
@@ -201,8 +202,8 @@ const leaves = r
201
202
  },
202
203
  ({ collection }) =>
203
204
  collection
204
- .sub('stats', (stats) =>
205
- stats
205
+ .sub(
206
+ resource('stats')
206
207
  .get({
207
208
  outputSchema: z.object({ total: z.number() }),
208
209
  description: 'Extra endpoint alongside CRUD',
@@ -280,7 +281,7 @@ function onChatMessage(raw: unknown) {
280
281
 
281
282
  ### Edge cases and notes
282
283
 
283
- - `paramsSchema` on a method overrides the merged schema from `subId`—useful when you need stricter validation per verb.
284
+ - `paramsSchema` on a method overrides the merged schema from parent segments—useful when you need stricter validation per verb.
284
285
  - `bodyFiles` marks a route as multipart; servers can attach upload middleware, and clients should send `FormData`.
285
286
  - CRUD helper only emits create/update routes when the matching `bodySchema` is provided; delete can be disabled via `enable.remove: false`.
286
287
  - Feed-only behavior (`feed: true`) is intended for GET endpoints; clients treat them as infinite queries.
@@ -4,7 +4,6 @@ type ZodTypeAny = z.ZodTypeAny;
4
4
  type ParamZod<Name extends string, S extends ZodTypeAny> = z.ZodObject<{
5
5
  [K in Name]: S;
6
6
  }>;
7
- type MergedParamsResult<PS, Name extends string, P extends ZodTypeAny> = PS extends ZodTypeAny ? z.ZodIntersection<PS, ParamZod<Name, P>> : ParamZod<Name, P>;
8
7
  type BaseMethodCfg<C extends MethodCfg> = Merge<Omit<MethodCfg, 'querySchema' | 'outputSchema' | 'feed'>, Omit<C, 'querySchema' | 'outputSchema' | 'feed'>>;
9
8
  type FeedField<C extends MethodCfg> = C['feed'] extends true ? {
10
9
  feed: true;
@@ -22,9 +21,29 @@ type ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny
22
21
  paramsSchema: PS;
23
22
  };
24
23
  type EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true ? FeedField<C> & FeedQueryField<C> & OutputField<C> & ParamsField<C, PS> : FeedField<C> & NonFeedQueryField<C> & OutputField<C> & ParamsField<C, PS>;
25
- type EffectiveCfg<C extends MethodCfg, PS> = Prettify<Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>>;
26
- type BuiltLeaf<M extends HttpMethod, Base extends string, I extends NodeCfg, C extends MethodCfg, PS> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>;
27
- type MethodFns<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod | 'add'> = {
24
+ type WithNodeDefaults<C extends MethodCfg, I extends NodeCfg> = Merge<C, {
25
+ queryExtensionSchema: C['queryExtensionSchema'] extends ZodTypeAny ? C['queryExtensionSchema'] : NodeQueryExtension<I>;
26
+ outputMetaSchema: C['outputMetaSchema'] extends ZodTypeAny ? C['outputMetaSchema'] : NodeOutputMeta<I>;
27
+ }>;
28
+ type EffectiveCfg<C extends MethodCfg, PS, I extends NodeCfg> = Prettify<Merge<MethodCfg, BaseMethodCfg<WithNodeDefaults<C, I>> & EffectiveFeedFields<WithNodeDefaults<C, I>, PS>>>;
29
+ type NodeWithoutSchemas<I extends NodeCfg> = Omit<I, 'queryExtensionSchema' | 'outputMetaSchema'>;
30
+ type BuiltLeaf<M extends HttpMethod, Base extends string, I extends NodeCfg, C extends MethodCfg, PS> = Leaf<M, Base, Merge<NodeWithoutSchemas<I>, LowProfileCfg<EffectiveCfg<C, PS, I>>>>;
31
+ type StripParamName<Name extends string> = Name extends `:${infer P}` ? P : Name;
32
+ type NormalizeBaseLiteral<Base extends string, HasParam extends boolean> = HasParam extends true ? Base extends `:${string}` ? Base : Base extends '' ? '' : `:${Base}` : Base;
33
+ type ParamSchemaForBase<Base extends string, Param extends ZodTypeAny | undefined> = Param extends ZodTypeAny ? ParamZod<StripParamName<Base>, Param> : undefined;
34
+ type ResourceBase<Base extends string, Param extends ZodTypeAny | undefined> = NormalizeBaseLiteral<Base, Param extends ZodTypeAny ? true : false>;
35
+ type NodeQueryExtension<I extends NodeCfg> = I extends {
36
+ queryExtensionSchema?: infer QE;
37
+ } ? QE extends ZodTypeAny ? QE : undefined : undefined;
38
+ type NodeOutputMeta<I extends NodeCfg> = I extends {
39
+ outputMetaSchema?: infer OE;
40
+ } ? OE extends ZodTypeAny ? OE : undefined : undefined;
41
+ type SubResourceCollections = readonly [
42
+ ReadonlyArray<AnyLeafLowProfile>,
43
+ ...ReadonlyArray<AnyLeafLowProfile>[]
44
+ ];
45
+ type MergeAugmentedCollections<Base extends string, Param extends ZodTypeAny | undefined, Collections extends ReadonlyArray<ReadonlyArray<AnyLeafLowProfile>>, Acc extends readonly AnyLeafLowProfile[] = []> = Collections extends readonly [infer First, ...infer Rest] ? First extends ReadonlyArray<AnyLeafLowProfile> ? Rest extends ReadonlyArray<ReadonlyArray<AnyLeafLowProfile>> ? MergeAugmentedCollections<Base, Param, Rest, MergeArray<Acc, AugmentLeaves<Base, Param, First>>> : MergeArray<Acc, AugmentLeaves<Base, Param, First>> : MergeAugmentedCollections<Base, Param, Rest extends ReadonlyArray<ReadonlyArray<AnyLeafLowProfile>> ? Rest : [], Acc> : Acc;
46
+ type MethodFns<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod | 'add' | 'sub'> = {
28
47
  /**
29
48
  * Register a GET leaf at the current path.
30
49
  */
@@ -55,24 +74,13 @@ type MethodFns<Base extends string, Acc extends readonly AnyLeafLowProfile[], I
55
74
  }>, PS>>>, I, PS, Used | 'delete'>;
56
75
  };
57
76
  /** Builder surface used by `resource(...)` to accumulate leaves. */
58
- export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod | 'add' = never> extends MethodFns<Base, Acc, I, PS, Used> {
59
- /**
60
- * Mount a static subtree under `name`.
61
- * The `leaves` are built externally via `resource(...)` and will be
62
- * rebased so that their paths become `${Base}/${name}${leaf.path}` and their
63
- * paramsSchemas are merged with the parameters already accumulated on this branch.
64
- */
65
- sub<Name extends string, const R extends readonly AnyLeafLowProfile[]>(name: Name, leaves: R): Branch<Base, MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>, I, PS, Used>;
66
- /**
67
- * Mount a static subtree under `name` and merge extra node-level config.
68
- */
69
- sub<Name extends string, J extends NodeCfg, const R extends readonly AnyLeafLowProfile[]>(name: Name, cfg: J, leaves: R): Branch<Base, MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>, Merge<I, J>, PS, Used>;
77
+ export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod | 'add' | 'sub' = never> extends MethodFns<Base, Acc, I, PS, Used> {
70
78
  /**
71
- * Introduce a `:param` segment and mount a pre-built subtree beneath it.
72
- * The subtree paths are rebased to `${Base}/:${Name}${leaf.path}` and
73
- * their paramsSchemas are intersected with the accumulated params plus this new param.
79
+ * Mount one or more sub-resources that were built via `resource(...)`.
80
+ * Each resource carries its own base path (e.g. `users`, `:userId`, `posts`)
81
+ * so this branch simply prefixes its current `Base` and merges param schemas.
74
82
  */
75
- subId<Name extends string, P extends ZodTypeAny, const R extends readonly AnyLeafLowProfile[]>(name: Name, paramsSchema: P, leaves: R): Branch<Base, MergeArray<Acc, AugmentLeaves<`${Base}/:${Name}`, MergedParamsResult<PS, Name, P>, R>>, I, PS, Used>;
83
+ sub: 'sub' extends Used ? never : <const Collections extends SubResourceCollections, NextAcc extends readonly AnyLeafLowProfile[] = MergeArray<Acc, MergeAugmentedCollections<Base, PS, Collections>>>(...collections: Collections) => Branch<Base, NextAcc, I, PS, Used | 'sub'>;
76
84
  /**
77
85
  * Finish the branch and return the collected leaves.
78
86
  * @returns Readonly tuple of accumulated leaves.
@@ -85,7 +93,7 @@ export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProf
85
93
  * @param inherited Optional node configuration applied to all descendants.
86
94
  * @returns Root `Branch` instance used to compose the route tree.
87
95
  */
88
- export declare function resource<Base extends string = '', I extends NodeCfg = {}>(base?: Base, inherited?: I): Branch<Base, readonly [], I, undefined>;
96
+ export declare function resource<Base extends string = '', I extends NodeCfg = {}, Param extends ZodTypeAny | undefined = undefined>(base?: Base, inherited?: I, idSchema?: Param): Branch<ResourceBase<Base, Param>, readonly [], I, ParamSchemaForBase<Base, Param>>;
89
97
  /**
90
98
  * Merge two readonly tuples (preserves literal tuple information).
91
99
  * @param arr1 First tuple.
@@ -9,29 +9,39 @@ export type FileField = {
9
9
  maxCount: number;
10
10
  };
11
11
  /** Configuration that applies to an entire branch of the route tree. */
12
- export type NodeCfg = {
13
- /** @deprecated. Does nothing. */
14
- authenticated?: boolean;
15
- };
16
- export type RouteSchema<Output = unknown> = {
12
+ export interface NodeCfg {
13
+ /**
14
+ * Feed-specific query schema applied to all descendants unless they override it.
15
+ */
16
+ queryExtensionSchema?: ZodType;
17
+ /**
18
+ * Feed meta schema applied to all descendants unless they override it.
19
+ */
20
+ outputMetaSchema?: ZodType;
21
+ }
22
+ export type RouteSchema<Output = unknown, Input = Output> = {
17
23
  __out: Output;
24
+ __in?: Input;
18
25
  };
19
26
  export type FeedQueryField<C extends MethodCfg> = {
20
- querySchema: IntersectZod<C['querySchema'] extends ZodObject ? C['querySchema'] : undefined, C['feedQuerySchema']>;
27
+ querySchema: IntersectZod<C['querySchema'] extends ZodObject ? C['querySchema'] : undefined, C['queryExtensionSchema']>;
21
28
  };
22
29
  export type OutputField<C extends MethodCfg> = C['outputSchema'] extends ZodType ? {
23
30
  outputSchema: ZodObject<{
24
31
  out: C['outputSchema'];
25
- meta: C['feedOutputMetaSchema'] extends ZodType ? C['feedOutputMetaSchema'] : z.ZodOptional<z.ZodString>;
32
+ meta: C['outputMetaSchema'] extends ZodType ? C['outputMetaSchema'] : z.ZodOptional<z.ZodString>;
26
33
  }>;
27
34
  } : {
28
35
  outputSchema: ZodObject<{
29
- meta: C['feedOutputMetaSchema'] extends ZodType ? C['feedOutputMetaSchema'] : z.ZodOptional<z.ZodString>;
36
+ meta: C['outputMetaSchema'] extends ZodType ? C['outputMetaSchema'] : z.ZodOptional<z.ZodString>;
30
37
  }>;
31
38
  };
32
39
  export type RouteSchemaOutput<Schema extends RouteSchema> = Schema extends {
33
40
  __out: infer Out;
34
41
  } ? Out : unknown;
42
+ export type RouteSchemaInput<Schema extends RouteSchema> = Schema extends {
43
+ __in?: infer In;
44
+ } ? In : unknown;
35
45
  export declare const lowProfileParse: <T extends RouteSchema>(schema: T, data: unknown) => RouteSchemaOutput<T>;
36
46
  export declare const lowProfileSafeParse: <T extends RouteSchema>(schema: T, data: unknown) => ZodSafeParseResult<RouteSchemaOutput<T>>;
37
47
  export declare const routeSchemaParse: <T>(routeSchema: RouteSchema<T>) => ZodType<T>;
@@ -47,14 +57,14 @@ export declare function mergeSchemas<B extends ZodType>(a: undefined, b: B): B;
47
57
  export declare function mergeSchemas(a: ZodType | undefined, b: ZodType | undefined): ZodType | undefined;
48
58
  export declare function getZodShape(schema: ZodObject): any;
49
59
  export declare function collectNestedFieldSuggestions(shape: Record<string, ZodType> | undefined, prefix?: string[]): string[];
50
- export type ToRouteSchema<S> = S extends ZodType<infer Out> ? RouteSchema<Out> : S;
51
- export type LowProfileCfg<Cfg extends MethodCfg> = Prettify<Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feedOutputMetaSchema'> & {
60
+ export type ToRouteSchema<S> = S extends ZodType<infer Out, infer In> ? RouteSchema<Out, In> : S;
61
+ export type LowProfileCfg<Cfg extends MethodCfg> = Prettify<Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'outputMetaSchema'> & {
52
62
  bodySchema: ToRouteSchema<Cfg['bodySchema']>;
53
63
  querySchema: ToRouteSchema<Cfg['querySchema']>;
54
64
  paramsSchema: ToRouteSchema<Cfg['paramsSchema']>;
55
65
  outputSchema: ToRouteSchema<Cfg['outputSchema']>;
56
- feedOutputMetaSchema: ToRouteSchema<Cfg['feedOutputMetaSchema']>;
57
- feedQuerySchema: ToRouteSchema<Cfg['feedQuerySchema']>;
66
+ outputMetaSchema: ToRouteSchema<Cfg['outputMetaSchema']>;
67
+ queryExtensionSchema: ToRouteSchema<Cfg['queryExtensionSchema']>;
58
68
  }>;
59
69
  /** Per-method configuration merged with inherited node config. */
60
70
  export type MethodCfg = {
@@ -62,7 +72,7 @@ export type MethodCfg = {
62
72
  bodySchema?: ZodType;
63
73
  /** Zod schema describing the query string. */
64
74
  querySchema?: ZodType;
65
- /** Zod schema describing path params (Internal only, set through sub and subId). */
75
+ /** Zod schema describing path params (internal only, set through resource segments). */
66
76
  paramsSchema?: ZodType;
67
77
  /** Zod schema describing the response payload. */
68
78
  outputSchema?: ZodType;
@@ -71,8 +81,8 @@ export type MethodCfg = {
71
81
  /** Marks the route as an infinite feed (enables cursor helpers). */
72
82
  feed?: boolean;
73
83
  /** feed handlers */
74
- feedOutputMetaSchema?: ZodType;
75
- feedQuerySchema?: ZodType;
84
+ outputMetaSchema?: ZodType;
85
+ queryExtensionSchema?: ZodType;
76
86
  /** Optional human-readable description for docs/debugging. */
77
87
  description?: string;
78
88
  /**
@@ -127,12 +137,25 @@ export type Merge<A, B> = Prettify<Omit<A, keyof B> & B>;
127
137
  export type Append<T extends readonly unknown[], X> = [...T, X];
128
138
  /** Concatenate two readonly tuple types. */
129
139
  export type MergeArray<A extends readonly unknown[], B extends readonly unknown[]> = [...A, ...B];
140
+ type TrimTrailingSlash<S extends string> = S extends `${infer R}/` ? TrimTrailingSlash<R> : S;
141
+ type TrimLeadingSlash<S extends string> = S extends `/${infer R}` ? TrimLeadingSlash<R> : S;
142
+ type NormalizedBase<Base extends string> = TrimTrailingSlash<Base>;
143
+ type NormalizedChild<Child extends string> = TrimLeadingSlash<Child>;
144
+ export type JoinPath<Base extends string, Child extends string> = NormalizedBase<Base> extends '' ? Child : NormalizedChild<Child> extends '' ? NormalizedBase<Base> : `${NormalizedBase<Base>}/${NormalizedChild<Child>}`;
130
145
  export type IntersectZod<A extends ZodType | undefined, B extends ZodType | undefined> = B extends ZodType ? A extends ZodType ? z.ZodIntersection<A, B> : B : A extends ZodType ? A : undefined;
131
146
  type MergeRouteSchemas<Existing extends RouteSchema | undefined, Parent extends ZodType | undefined> = Existing extends RouteSchema<infer ExistingOut> ? Parent extends ZodType<infer ParentOut> ? RouteSchema<ExistingOut & ParentOut> : Existing : Parent extends ZodType<infer ParentOut> ? RouteSchema<ParentOut> : undefined;
132
147
  type AugmentedCfg<Cfg extends MethodCfgLowProfile, Param extends ZodType | undefined> = Prettify<Omit<Cfg, 'paramsSchema'> & {
133
148
  paramsSchema: MergeRouteSchemas<Cfg['paramsSchema'], Param>;
134
149
  }>;
135
- export type AugmentLeaves<P extends string, Param extends ZodType | undefined, R extends readonly LeafLowProfile[], Acc extends readonly LeafLowProfile[] = []> = R extends readonly [infer First, ...infer Rest] ? First extends LeafLowProfile ? AugmentLeaves<P, Param, Rest extends readonly LeafLowProfile[] ? Rest : [], Append<Acc, LeafLowProfile<First['method'], `${P}${First['path']}`, AugmentedCfg<First['cfg'], Param>>>> : never : Acc;
150
+ export type AugmentLeaves<P extends string, Param extends ZodType | undefined, R extends readonly LeafLowProfile[], Acc extends readonly LeafLowProfile[] = []> = R extends readonly [infer First, ...infer Rest] ? First extends LeafLowProfile ? AugmentLeaves<P, Param, Rest extends readonly LeafLowProfile[] ? Rest : [], Append<Acc, LeafLowProfile<First['method'], JoinPath<P, First['path']>, AugmentedCfg<First['cfg'], Param>>>> : never : Acc;
151
+ type NodeQueryExtension<Node> = Node extends {
152
+ queryExtensionSchema?: infer QE;
153
+ } ? QE extends ZodType ? QE : undefined : undefined;
154
+ type NodeOutputMeta<Node> = Node extends {
155
+ outputMetaSchema?: infer OM;
156
+ } ? OM extends ZodType ? OM : undefined : undefined;
157
+ type EffectiveQueryExtensionSchema<Method extends ZodType | undefined, Node extends NodeCfg | undefined> = Method extends ZodType ? Method : NodeQueryExtension<Node>;
158
+ type EffectiveOutputMetaSchema<Method extends ZodType | undefined, Node extends NodeCfg | undefined> = Method extends ZodType ? Method : NodeOutputMeta<Node>;
136
159
  type SegmentParams<S extends string> = S extends `:${infer P}` ? P : never;
137
160
  type Split<S extends string> = S extends '' ? [] : S extends `${infer A}/${infer B}` ? [A, ...Split<B>] : [S];
138
161
  type ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>;
@@ -153,41 +176,42 @@ export declare function buildCacheKey<L extends AnyLeafLowProfile>(args: {
153
176
  /** Definition-time method config (for clarity). */
154
177
  export type MethodCfgDef = MethodCfg;
155
178
  /** Low-profile method config where schemas carry a phantom __out like SocketSchema. */
156
- export type MethodCfgLowProfile = Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feedOutputMetaSchema' | 'feedQuerySchema'> & {
179
+ export type MethodCfgLowProfile = Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'outputMetaSchema' | 'queryExtensionSchema'> & {
157
180
  bodySchema?: RouteSchema;
158
181
  querySchema?: RouteSchema;
159
182
  paramsSchema?: RouteSchema;
160
183
  outputSchema?: RouteSchema;
161
- feedOutputMetaSchema?: RouteSchema;
162
- feedQuerySchema?: RouteSchema;
184
+ outputMetaSchema?: RouteSchema;
185
+ queryExtensionSchema?: RouteSchema;
163
186
  };
164
187
  export type AnyLeafLowProfile = LeafLowProfile<HttpMethod, string, MethodCfgLowProfile>;
165
- export declare function buildLowProfileLeaf<const M extends HttpMethod, const Path extends string, const O extends ZodType | undefined = undefined, const P extends ZodType | undefined = undefined, const Q extends ZodType | undefined = undefined, const B extends ZodType | undefined = undefined, const FO extends ZodType | undefined = undefined, const FQ extends ZodType | undefined = undefined, const Feed extends boolean = false>(leaf: {
188
+ export declare function buildLowProfileLeaf<const M extends HttpMethod, const Path extends string, const Node extends NodeCfg | undefined = undefined, const O extends ZodType | undefined = undefined, const P extends ZodType | undefined = undefined, const Q extends ZodType | undefined = undefined, const B extends ZodType | undefined = undefined, const FO extends ZodType | undefined = undefined, const FQ extends ZodType | undefined = undefined, const Feed extends boolean = false>(leaf: {
166
189
  method: M;
167
190
  path: Path;
168
- cfg: Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed' | 'feedOutputMetaSchema' | 'feedQuerySchema'> & {
191
+ node?: Node;
192
+ cfg: Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed' | 'outputMetaSchema' | 'queryExtensionSchema'> & {
169
193
  feed?: Feed;
170
194
  bodySchema?: B;
171
195
  querySchema?: Q;
172
196
  paramsSchema?: P;
173
197
  outputSchema?: O;
174
- feedOutputMetaSchema?: FO;
175
- feedQuerySchema?: FQ;
198
+ outputMetaSchema?: FO;
199
+ queryExtensionSchema?: FQ;
176
200
  };
177
- }): LeafLowProfile<M, Path, Prettify<Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed' | 'feedOutputMetaSchema' | 'feedQuerySchema'> & {
201
+ }): LeafLowProfile<M, Path, Prettify<Omit<MethodCfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed' | 'outputMetaSchema' | 'queryExtensionSchema'> & {
178
202
  feed: Feed;
179
- bodySchema: B extends ZodType<infer BOut> ? RouteSchema<BOut> : undefined;
203
+ bodySchema: B extends ZodType<infer BOut, infer BIn> ? RouteSchema<BOut, BIn> : undefined;
180
204
  querySchema: Feed extends true ? FeedQueryField<{
181
205
  querySchema: Q;
182
- feedQuerySchema: FQ;
183
- }>['querySchema'] extends ZodType<infer QOut> ? RouteSchema<QOut> : never : Q extends ZodType<infer QOut> ? RouteSchema<QOut> : undefined;
184
- paramsSchema: P extends ZodType<infer POut> ? RouteSchema<POut> : undefined;
185
- outputSchema: RouteSchema<OutputField<{
206
+ queryExtensionSchema: EffectiveQueryExtensionSchema<FQ, Node>;
207
+ }>['querySchema'] extends ZodType<infer QOut, infer QIn> ? RouteSchema<QOut, QIn> : never : Q extends ZodType<infer QOut, infer QIn> ? RouteSchema<QOut, QIn> : undefined;
208
+ paramsSchema: P extends ZodType<infer POut, infer PIn> ? RouteSchema<POut, PIn> : undefined;
209
+ outputSchema: OutputField<{
186
210
  outputSchema: O;
187
- feedOutputMetaSchema: FO;
188
- }>['outputSchema'] extends ZodObject<infer OOut> ? OOut : never>;
189
- feedOutputMetaSchema: FO extends ZodType<infer OOut> ? RouteSchema<OOut> : undefined;
190
- feedQuerySchema: FQ extends ZodType<infer QOut> ? RouteSchema<QOut> : undefined;
211
+ outputMetaSchema: EffectiveOutputMetaSchema<FO, Node>;
212
+ }>['outputSchema'] extends ZodType<infer OOut, infer OIn> ? RouteSchema<OOut, OIn> : never;
213
+ outputMetaSchema: EffectiveOutputMetaSchema<FO, Node> extends ZodType<infer OOut, infer OIn> ? RouteSchema<OOut, OIn> : undefined;
214
+ queryExtensionSchema: EffectiveQueryExtensionSchema<FQ, Node> extends ZodType<infer QOut, infer QIn> ? RouteSchema<QOut, QIn> : undefined;
191
215
  }>>;
192
216
  export type LeafLowProfile<M extends HttpMethod = HttpMethod, P extends string = string, C extends MethodCfgLowProfile = MethodCfgLowProfile> = {
193
217
  /** Lowercase HTTP method (get/post/...). */
@@ -198,15 +222,24 @@ export type LeafLowProfile<M extends HttpMethod = HttpMethod, P extends string =
198
222
  readonly cfg: Readonly<C>;
199
223
  };
200
224
  /** Infer params either from the explicit params schema or from the path literal. */
201
- export type InferParams<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['paramsSchema'] extends RouteSchema<infer P> ? P : Fallback;
225
+ export type InferParams<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['paramsSchema'] extends RouteSchema<any, infer PIn> ? PIn : L['cfg']['paramsSchema'] extends ZodType<any, infer PIn> ? PIn : Fallback;
202
226
  /** Infer query shape from a Zod schema when present. */
203
- export type InferQuery<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['querySchema'] extends RouteSchema<infer Q> ? Q : Fallback;
227
+ export type InferQuery<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['querySchema'] extends RouteSchema<any, infer QIn> ? QIn : L['cfg']['querySchema'] extends ZodType<any, infer QIn> ? QIn : Fallback;
204
228
  /** Infer request body shape from a Zod schema when present. */
205
- export type InferBody<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['bodySchema'] extends RouteSchema<infer B> ? B : Fallback;
229
+ export type InferBody<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['bodySchema'] extends RouteSchema<any, infer BIn> ? BIn : L['cfg']['bodySchema'] extends ZodType<any, infer BIn> ? BIn : Fallback;
206
230
  /** Infer handler output shape from a Zod schema. Defaults to unknown. */
207
- export type InferOutput<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['outputSchema'] extends RouteSchema<infer O> ? O : Fallback;
208
- export type InferFeedOutputMeta<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['feedOutputMetaSchema'] extends RouteSchema<infer O> ? O : Fallback;
209
- export type InferFeedQuery<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['feedQuerySchema'] extends RouteSchema<infer Q> ? Q : Fallback;
231
+ type InferMetaFromCfg<L extends AnyLeafLowProfile> = L['cfg']['outputMetaSchema'] extends RouteSchema<infer O> ? O : L['cfg']['outputMetaSchema'] extends ZodType<infer O> ? O : string | undefined;
232
+ type ResolvedMeta<L extends AnyLeafLowProfile, M> = [M] extends [never] | [unknown] ? InferMetaFromCfg<L> : M;
233
+ type ApplyMetaFallback<L extends AnyLeafLowProfile, O> = O extends {
234
+ meta: infer M;
235
+ } ? (undefined extends ResolvedMeta<L, M> ? {
236
+ meta?: ResolvedMeta<L, M>;
237
+ } : {
238
+ meta: ResolvedMeta<L, M>;
239
+ }) & Omit<O, 'meta'> : O;
240
+ export type InferOutput<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['outputSchema'] extends RouteSchema<infer O> ? ApplyMetaFallback<L, O> : L['cfg']['outputSchema'] extends ZodType<infer O> ? ApplyMetaFallback<L, O> : Fallback;
241
+ export type InferFeedOutputMeta<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['outputMetaSchema'] extends RouteSchema<infer O> ? O : L['cfg']['outputMetaSchema'] extends ZodType<infer O> ? O : Fallback;
242
+ export type InferFeedQuery<L extends AnyLeafLowProfile, Fallback = never> = L['cfg']['queryExtensionSchema'] extends RouteSchema<any, infer QIn> ? QIn : L['cfg']['queryExtensionSchema'] extends ZodType<any, infer QIn> ? QIn : Fallback;
210
243
  /** Render a type as if it were a simple object literal. */
211
244
  export type Prettify<T> = {
212
245
  [K in keyof T]: T[K];