@emeryld/rrroutes-contract 2.3.9 → 2.4.1
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 +77 -38
- package/dist/core/routesV3.builder.d.ts +32 -47
- package/dist/core/routesV3.core.d.ts +16 -2
- package/dist/index.cjs +76 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +76 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,8 +32,8 @@ import {
|
|
|
32
32
|
InferParams,
|
|
33
33
|
InferQuery,
|
|
34
34
|
resource,
|
|
35
|
-
} from '@emeryld/rrroutes-contract'
|
|
36
|
-
import { z } from 'zod'
|
|
35
|
+
} from '@emeryld/rrroutes-contract'
|
|
36
|
+
import { z } from 'zod'
|
|
37
37
|
|
|
38
38
|
// 1) Describe your API
|
|
39
39
|
const leaves = resource('/v1')
|
|
@@ -44,7 +44,9 @@ const leaves = resource('/v1')
|
|
|
44
44
|
search: z.string().optional(),
|
|
45
45
|
limit: z.coerce.number().min(1).max(50).default(20),
|
|
46
46
|
}),
|
|
47
|
-
outputSchema: z.array(
|
|
47
|
+
outputSchema: z.array(
|
|
48
|
+
z.object({ id: z.string().uuid(), email: z.string().email() }),
|
|
49
|
+
),
|
|
48
50
|
description: 'Find users',
|
|
49
51
|
})
|
|
50
52
|
.routeParameter('userId', z.string().uuid(), (user) =>
|
|
@@ -55,20 +57,25 @@ const leaves = resource('/v1')
|
|
|
55
57
|
)
|
|
56
58
|
.done(),
|
|
57
59
|
)
|
|
58
|
-
.done()
|
|
60
|
+
.done()
|
|
59
61
|
|
|
60
62
|
// 2) Freeze it into a registry for typed lookups
|
|
61
|
-
export const registry = finalize(leaves)
|
|
63
|
+
export const registry = finalize(leaves)
|
|
62
64
|
|
|
63
65
|
// 3) Consume a leaf with full types
|
|
64
|
-
const leaf = registry.byKey['PATCH /v1/users/:userId']
|
|
65
|
-
type Params = InferParams<typeof leaf
|
|
66
|
-
type Query = InferQuery<typeof leaf
|
|
67
|
-
type Output = InferOutput<typeof leaf
|
|
66
|
+
const leaf = registry.byKey['PATCH /v1/users/:userId']
|
|
67
|
+
type Params = InferParams<typeof leaf> // { userId: string }
|
|
68
|
+
type Query = InferQuery<typeof leaf> // never (no query)
|
|
69
|
+
type Output = InferOutput<typeof leaf> // { ok: true }
|
|
68
70
|
|
|
69
71
|
// 4) Build URLs + cache keys (React Query friendly)
|
|
70
|
-
const url = compilePath(leaf.path, {
|
|
71
|
-
|
|
72
|
+
const url = compilePath(leaf.path, {
|
|
73
|
+
userId: 'f2b2e72a-7f6d-4c3f-9c6f-7f0d8f3ac9e2',
|
|
74
|
+
})
|
|
75
|
+
const key = buildCacheKey({
|
|
76
|
+
leaf,
|
|
77
|
+
params: { userId: 'f2b2e72a-7f6d-4c3f-9c6f-7f0d8f3ac9e2' },
|
|
78
|
+
})
|
|
72
79
|
// key => ['patch', 'v1', 'users', 'f2b2e72a-7f6d-4c3f-9c6f-7f0d8f3ac9e2', { userId: 'f2b2e72a-7f6d-4c3f-9c6f-7f0d8f3ac9e2' }]
|
|
73
80
|
```
|
|
74
81
|
|
|
@@ -77,8 +84,8 @@ const key = buildCacheKey({ leaf, params: { userId: 'f2b2e72a-7f6d-4c3f-9c6f-7f0
|
|
|
77
84
|
### Fluent route builder
|
|
78
85
|
|
|
79
86
|
```ts
|
|
80
|
-
import { resource } from '@emeryld/rrroutes-contract'
|
|
81
|
-
import { z } from 'zod'
|
|
87
|
+
import { resource } from '@emeryld/rrroutes-contract'
|
|
88
|
+
import { z } from 'zod'
|
|
82
89
|
|
|
83
90
|
const leaves = resource('/api') // base path, optional inherited cfg
|
|
84
91
|
.with({ feed: false }) // merges flags into descendants (extend NodeCfg via declaration merging if you add your own)
|
|
@@ -86,7 +93,10 @@ const leaves = resource('/api') // base path, optional inherited cfg
|
|
|
86
93
|
projects
|
|
87
94
|
.get({
|
|
88
95
|
feed: true, // infinite/feed for clients
|
|
89
|
-
querySchema: z.object({
|
|
96
|
+
querySchema: z.object({
|
|
97
|
+
cursor: z.string().optional(),
|
|
98
|
+
limit: z.coerce.number().default(25),
|
|
99
|
+
}),
|
|
90
100
|
outputSchema: z.object({
|
|
91
101
|
items: z.array(z.object({ id: z.string(), name: z.string() })),
|
|
92
102
|
nextCursor: z.string().optional(),
|
|
@@ -117,7 +127,7 @@ const leaves = resource('/api') // base path, optional inherited cfg
|
|
|
117
127
|
)
|
|
118
128
|
.done(),
|
|
119
129
|
)
|
|
120
|
-
.done()
|
|
130
|
+
.done()
|
|
121
131
|
```
|
|
122
132
|
|
|
123
133
|
- `sub(name, [cfg], builder?)` nests paths (`/api/projects`).
|
|
@@ -135,21 +145,21 @@ import {
|
|
|
135
145
|
InferBody,
|
|
136
146
|
InferOutput,
|
|
137
147
|
SubsetRoutes,
|
|
138
|
-
} from '@emeryld/rrroutes-contract'
|
|
148
|
+
} from '@emeryld/rrroutes-contract'
|
|
139
149
|
|
|
140
|
-
const registry = finalize(leaves)
|
|
141
|
-
const leaf = registry.byKey['PATCH /api/projects/:projectId']
|
|
150
|
+
const registry = finalize(leaves)
|
|
151
|
+
const leaf = registry.byKey['PATCH /api/projects/:projectId']
|
|
142
152
|
|
|
143
153
|
// TypeScript helpers
|
|
144
|
-
type Body = InferBody<typeof leaf
|
|
145
|
-
type Output = InferOutput<typeof leaf
|
|
154
|
+
type Body = InferBody<typeof leaf> // { name: string }
|
|
155
|
+
type Output = InferOutput<typeof leaf> // { id: string; name: string }
|
|
146
156
|
|
|
147
157
|
// Runtime helpers
|
|
148
|
-
const url = compilePath(leaf.path, { projectId: '123' })
|
|
149
|
-
const cacheKey = buildCacheKey({ leaf, params: { projectId: '123' } })
|
|
158
|
+
const url = compilePath(leaf.path, { projectId: '123' }) // "/api/projects/123"
|
|
159
|
+
const cacheKey = buildCacheKey({ leaf, params: { projectId: '123' } })
|
|
150
160
|
|
|
151
161
|
// Typed subsets for routers/microfrontends
|
|
152
|
-
type ProjectRoutes = SubsetRoutes<typeof registry.all, '/api/projects'
|
|
162
|
+
type ProjectRoutes = SubsetRoutes<typeof registry.all, '/api/projects'>
|
|
153
163
|
```
|
|
154
164
|
|
|
155
165
|
- `finalize(leaves)` freezes the tuple and provides `byKey['METHOD /path']`, `all`, and `log(logger)`.
|
|
@@ -159,20 +169,34 @@ type ProjectRoutes = SubsetRoutes<typeof registry.all, '/api/projects'>;
|
|
|
159
169
|
### CRUD helper (`withCrud` / `resourceWithCrud`)
|
|
160
170
|
|
|
161
171
|
```ts
|
|
162
|
-
import {
|
|
163
|
-
|
|
172
|
+
import {
|
|
173
|
+
CrudDefaultPagination,
|
|
174
|
+
finalize,
|
|
175
|
+
resource,
|
|
176
|
+
withCrud,
|
|
177
|
+
} from '@emeryld/rrroutes-contract'
|
|
178
|
+
import { z } from 'zod'
|
|
164
179
|
|
|
165
|
-
const r = withCrud(resource('/v1'))
|
|
180
|
+
const r = withCrud(resource('/v1'))
|
|
166
181
|
|
|
167
182
|
const leaves = r
|
|
168
183
|
.crud(
|
|
169
184
|
'articles',
|
|
170
185
|
{
|
|
171
186
|
paramSchema: z.string().uuid(), // value schema; becomes :articlesId
|
|
172
|
-
itemOutputSchema: z.object({
|
|
187
|
+
itemOutputSchema: z.object({
|
|
188
|
+
id: z.string().uuid(),
|
|
189
|
+
title: z.string(),
|
|
190
|
+
body: z.string(),
|
|
191
|
+
}),
|
|
173
192
|
list: { querySchema: CrudDefaultPagination },
|
|
174
193
|
create: { bodySchema: z.object({ title: z.string(), body: z.string() }) },
|
|
175
|
-
update: {
|
|
194
|
+
update: {
|
|
195
|
+
bodySchema: z.object({
|
|
196
|
+
title: z.string().optional(),
|
|
197
|
+
body: z.string().optional(),
|
|
198
|
+
}),
|
|
199
|
+
},
|
|
176
200
|
enable: { remove: false }, // opt out of DELETE
|
|
177
201
|
},
|
|
178
202
|
({ collection }) =>
|
|
@@ -187,9 +211,9 @@ const leaves = r
|
|
|
187
211
|
)
|
|
188
212
|
.done(),
|
|
189
213
|
)
|
|
190
|
-
.done()
|
|
214
|
+
.done()
|
|
191
215
|
|
|
192
|
-
const registry = finalize(leaves)
|
|
216
|
+
const registry = finalize(leaves)
|
|
193
217
|
// registry.byKey now includes the CRUD + extras routes with full types
|
|
194
218
|
```
|
|
195
219
|
|
|
@@ -203,29 +227,44 @@ const registry = finalize(leaves);
|
|
|
203
227
|
Share a typed event map between client and server.
|
|
204
228
|
|
|
205
229
|
```ts
|
|
206
|
-
import { defineSocketEvents, Payload } from '@emeryld/rrroutes-contract'
|
|
207
|
-
import { z } from 'zod'
|
|
230
|
+
import { defineSocketEvents, Payload } from '@emeryld/rrroutes-contract'
|
|
231
|
+
import { z } from 'zod'
|
|
208
232
|
|
|
209
233
|
const { config, events } = defineSocketEvents(
|
|
210
234
|
{
|
|
211
235
|
joinMetaMessage: z.object({ room: z.string() }),
|
|
212
236
|
leaveMetaMessage: z.object({ room: z.string() }),
|
|
213
237
|
pingPayload: z.object({ clientEcho: z.object({ sentAt: z.string() }) }),
|
|
214
|
-
pongPayload: z.object({
|
|
238
|
+
pongPayload: z.object({
|
|
239
|
+
clientEcho: z.object({ sentAt: z.string() }).optional(),
|
|
240
|
+
sinceMs: z.number().optional(),
|
|
241
|
+
}),
|
|
215
242
|
},
|
|
216
243
|
{
|
|
217
|
-
'chat:message': {
|
|
218
|
-
|
|
244
|
+
'chat:message': {
|
|
245
|
+
message: z.object({
|
|
246
|
+
roomId: z.string(),
|
|
247
|
+
text: z.string(),
|
|
248
|
+
userId: z.string(),
|
|
249
|
+
}),
|
|
250
|
+
},
|
|
251
|
+
'typing:update': {
|
|
252
|
+
message: z.object({
|
|
253
|
+
roomId: z.string(),
|
|
254
|
+
userId: z.string(),
|
|
255
|
+
typing: z.boolean(),
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
219
258
|
},
|
|
220
|
-
)
|
|
259
|
+
)
|
|
221
260
|
|
|
222
261
|
// Typed payload extraction
|
|
223
|
-
type ChatPayload = Payload<typeof events, 'chat:message'
|
|
262
|
+
type ChatPayload = Payload<typeof events, 'chat:message'>
|
|
224
263
|
// ChatPayload -> { roomId: string; text: string; userId: string }
|
|
225
264
|
|
|
226
265
|
// Server-side guard example
|
|
227
266
|
function onChatMessage(raw: unknown) {
|
|
228
|
-
const parsed = events['chat:message'].message.parse(raw)
|
|
267
|
+
const parsed = events['chat:message'].message.parse(raw)
|
|
229
268
|
// parsed is strongly typed; safe to broadcast
|
|
230
269
|
}
|
|
231
270
|
```
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { AnyLeafLowProfile, Append, HttpMethod, Leaf, Merge, MergeArray, MethodCfg, NodeCfg, Prettify
|
|
2
|
+
import { AnyLeafLowProfile, Append, AugmentLeaves, HttpMethod, Leaf, LowProfileCfg, Merge, MergeArray, MethodCfg, NodeCfg, Prettify } from './routesV3.core';
|
|
3
3
|
declare const paginationQueryShape: {
|
|
4
4
|
pagination_cursor: z.ZodOptional<z.ZodString>;
|
|
5
5
|
pagination_limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
@@ -53,71 +53,56 @@ type ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny
|
|
|
53
53
|
};
|
|
54
54
|
type EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true ? FeedField<C> & FeedQueryField<C> & FeedOutputField<C> & ParamsField<C, PS> : FeedField<C> & NonFeedQueryField<C> & NonFeedOutputField<C> & ParamsField<C, PS>;
|
|
55
55
|
type EffectiveCfg<C extends MethodCfg, PS> = Prettify<Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>>;
|
|
56
|
-
type ToRouteSchema<S> = S extends ZodTypeAny ? RouteSchema<z.output<S>> : S;
|
|
57
|
-
type LowProfileCfg<Cfg extends MethodCfg> = Prettify<Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {
|
|
58
|
-
bodySchema: ToRouteSchema<Cfg['bodySchema']>;
|
|
59
|
-
querySchema: ToRouteSchema<Cfg['querySchema']>;
|
|
60
|
-
paramsSchema: ToRouteSchema<Cfg['paramsSchema']>;
|
|
61
|
-
outputSchema: ToRouteSchema<Cfg['outputSchema']>;
|
|
62
|
-
}>;
|
|
63
56
|
type BuiltLeaf<M extends HttpMethod, Base extends string, I extends NodeCfg, C extends MethodCfg, PS> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>;
|
|
64
|
-
|
|
65
|
-
export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined> {
|
|
66
|
-
/**
|
|
67
|
-
* Enter or define a static child segment.
|
|
68
|
-
* Optionally accepts extra config and/or a builder to populate nested routes.
|
|
69
|
-
* @param name Child segment literal (no leading slash).
|
|
70
|
-
* @param cfg Optional node configuration to merge for descendants.
|
|
71
|
-
* @param builder Callback to produce leaves for the child branch.
|
|
72
|
-
*/
|
|
73
|
-
sub<Name extends string, J extends NodeCfg>(name: Name, cfg?: J): Branch<`${Base}/${Name}`, Acc, Merge<I, NonNullable<J>>, PS>;
|
|
74
|
-
sub<Name extends string, J extends NodeCfg | undefined, R extends readonly AnyLeafLowProfile[]>(name: Name, cfg: J, builder: (r: Branch<`${Base}/${Name}`, readonly [], Merge<I, NonNullable<J>>, PS>) => R): Branch<Base, Append<Acc, R[number]>, Merge<I, NonNullable<J>>, PS>;
|
|
75
|
-
sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(name: Name, builder: (r: Branch<`${Base}/${Name}`, readonly [], I, PS>) => R): Branch<Base, MergeArray<Acc, R>, I, PS>;
|
|
76
|
-
/**
|
|
77
|
-
* Introduce a `:param` segment and merge its schema into downstream leaves.
|
|
78
|
-
* @param name Parameter key (without leading colon).
|
|
79
|
-
* @param paramsSchema Zod schema for the parameter value.
|
|
80
|
-
* @param builder Callback that produces leaves beneath the parameterized segment.
|
|
81
|
-
*/
|
|
82
|
-
routeParameter<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeafLowProfile[]>(name: Name, paramsSchema: P, builder: (r: Branch<`${Base}/:${Name}`, readonly [], I, MergedParamsResult<PS, Name, P>>) => R): Branch<Base, MergeArray<Acc, R>, I, MergedParamsResult<PS, Name, P>>;
|
|
83
|
-
/**
|
|
84
|
-
* Merge additional node configuration that subsequent leaves will inherit.
|
|
85
|
-
* @param cfg Partial node configuration.
|
|
86
|
-
*/
|
|
87
|
-
with<J extends NodeCfg>(cfg: J): Branch<Base, Acc, Merge<I, J>, PS>;
|
|
57
|
+
type MethodFns<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod> = {
|
|
88
58
|
/**
|
|
89
59
|
* Register a GET leaf at the current path.
|
|
90
|
-
* @param cfg Method configuration (schemas, flags, descriptions, etc).
|
|
91
60
|
*/
|
|
92
|
-
get<C extends MethodCfg>(cfg: C)
|
|
61
|
+
get: 'get' extends Used ? never : <C extends MethodCfg>(cfg: C) => Branch<Base, Append<Acc, Prettify<BuiltLeaf<'get', Base, I, C, PS>>>, I, PS, Used | 'get'>;
|
|
93
62
|
/**
|
|
94
63
|
* Register a POST leaf at the current path.
|
|
95
|
-
* @param cfg Method configuration (schemas, flags, descriptions, etc).
|
|
96
64
|
*/
|
|
97
|
-
post<C extends Omit<MethodCfg, 'feed'>>(cfg: C)
|
|
65
|
+
post: 'post' extends Used ? never : <C extends Omit<MethodCfg, 'feed'>>(cfg: C) => Branch<Base, Append<Acc, Prettify<BuiltLeaf<'post', Base, I, Merge<C, {
|
|
98
66
|
feed: false;
|
|
99
|
-
}>, PS>>>, I, PS>;
|
|
67
|
+
}>, PS>>>, I, PS, Used | 'post'>;
|
|
100
68
|
/**
|
|
101
69
|
* Register a PUT leaf at the current path.
|
|
102
|
-
* @param cfg Method configuration (schemas, flags, descriptions, etc).
|
|
103
70
|
*/
|
|
104
|
-
put<C extends Omit<MethodCfg, 'feed'>>(cfg: C)
|
|
71
|
+
put: 'put' extends Used ? never : <C extends Omit<MethodCfg, 'feed'>>(cfg: C) => Branch<Base, Append<Acc, Prettify<BuiltLeaf<'put', Base, I, Merge<C, {
|
|
105
72
|
feed: false;
|
|
106
|
-
}>, PS>>>, I, PS>;
|
|
73
|
+
}>, PS>>>, I, PS, Used | 'put'>;
|
|
107
74
|
/**
|
|
108
75
|
* Register a PATCH leaf at the current path.
|
|
109
|
-
* @param cfg Method configuration (schemas, flags, descriptions, etc).
|
|
110
76
|
*/
|
|
111
|
-
patch<C extends Omit<MethodCfg, 'feed'>>(cfg: C)
|
|
77
|
+
patch: 'patch' extends Used ? never : <C extends Omit<MethodCfg, 'feed'>>(cfg: C) => Branch<Base, Append<Acc, Prettify<BuiltLeaf<'patch', Base, I, Merge<C, {
|
|
112
78
|
feed: false;
|
|
113
|
-
}>, PS>>>, I, PS>;
|
|
79
|
+
}>, PS>>>, I, PS, Used | 'patch'>;
|
|
114
80
|
/**
|
|
115
81
|
* Register a DELETE leaf at the current path.
|
|
116
|
-
* @param cfg Method configuration (schemas, flags, descriptions, etc).
|
|
117
82
|
*/
|
|
118
|
-
delete<C extends Omit<MethodCfg, 'feed'>>(cfg: C)
|
|
83
|
+
delete: 'delete' extends Used ? never : <C extends Omit<MethodCfg, 'feed'>>(cfg: C) => Branch<Base, Append<Acc, Prettify<BuiltLeaf<'delete', Base, I, Merge<C, {
|
|
119
84
|
feed: false;
|
|
120
|
-
}>, PS>>>, I, PS>;
|
|
85
|
+
}>, PS>>>, I, PS, Used | 'delete'>;
|
|
86
|
+
};
|
|
87
|
+
/** Builder surface used by `resource(...)` to accumulate leaves. */
|
|
88
|
+
export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProfile[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Used extends HttpMethod = never> extends MethodFns<Base, Acc, I, PS, Used> {
|
|
89
|
+
/**
|
|
90
|
+
* Mount a static subtree under `name`.
|
|
91
|
+
* The `leaves` are built externally via `resource(...)` and will be
|
|
92
|
+
* rebased so that their paths become `${Base}/${name}${leaf.path}` and their
|
|
93
|
+
* paramsSchemas are merged with the parameters already accumulated on this branch.
|
|
94
|
+
*/
|
|
95
|
+
sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(name: Name, leaves: R): Branch<Base, MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>, I, PS, Used>;
|
|
96
|
+
/**
|
|
97
|
+
* Mount a static subtree under `name` and merge extra node-level config.
|
|
98
|
+
*/
|
|
99
|
+
sub<Name extends string, J extends NodeCfg, R extends readonly AnyLeafLowProfile[]>(name: Name, cfg: J, leaves: R): Branch<Base, MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>, Merge<I, J>, PS, Used>;
|
|
100
|
+
/**
|
|
101
|
+
* Introduce a `:param` segment and mount a pre-built subtree beneath it.
|
|
102
|
+
* The subtree paths are rebased to `${Base}/:${Name}${leaf.path}` and
|
|
103
|
+
* their paramsSchemas are intersected with the accumulated params plus this new param.
|
|
104
|
+
*/
|
|
105
|
+
routeParameter<Name extends string, P extends ZodTypeAny, 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>;
|
|
121
106
|
/**
|
|
122
107
|
* Finish the branch and return the collected leaves.
|
|
123
108
|
* @returns Readonly tuple of accumulated leaves.
|
|
@@ -130,7 +115,7 @@ export interface Branch<Base extends string, Acc extends readonly AnyLeafLowProf
|
|
|
130
115
|
* @param inherited Optional node configuration applied to all descendants.
|
|
131
116
|
* @returns Root `Branch` instance used to compose the route tree.
|
|
132
117
|
*/
|
|
133
|
-
export declare function resource<Base extends string, I extends NodeCfg = {}>(base
|
|
118
|
+
export declare function resource<Base extends string = '', I extends NodeCfg = {}>(base?: Base, inherited?: I): Branch<Base, readonly [], I, undefined>;
|
|
134
119
|
/**
|
|
135
120
|
* Merge two readonly tuples (preserves literal tuple information).
|
|
136
121
|
* @param arr1 First tuple.
|
|
@@ -20,13 +20,20 @@ export type RouteSchemaOutput<Schema extends ZodType> = Schema extends {
|
|
|
20
20
|
__out: infer Out;
|
|
21
21
|
} ? Out : z.output<Schema>;
|
|
22
22
|
export declare const lowProfileParse: <T extends RouteSchema>(schema: T, data: unknown) => RouteSchemaOutput<T>;
|
|
23
|
+
export type ToRouteSchema<S> = S extends ZodType ? RouteSchema<z.output<S>> : S;
|
|
24
|
+
export type LowProfileCfg<Cfg extends MethodCfg> = Prettify<Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {
|
|
25
|
+
bodySchema: ToRouteSchema<Cfg['bodySchema']>;
|
|
26
|
+
querySchema: ToRouteSchema<Cfg['querySchema']>;
|
|
27
|
+
paramsSchema: ToRouteSchema<Cfg['paramsSchema']>;
|
|
28
|
+
outputSchema: ToRouteSchema<Cfg['outputSchema']>;
|
|
29
|
+
}>;
|
|
23
30
|
/** Per-method configuration merged with inherited node config. */
|
|
24
31
|
export type MethodCfg = {
|
|
25
32
|
/** Zod schema describing the request body. */
|
|
26
33
|
bodySchema?: ZodType;
|
|
27
34
|
/** Zod schema describing the query string. */
|
|
28
35
|
querySchema?: ZodType;
|
|
29
|
-
/** Zod schema describing path params (
|
|
36
|
+
/** Zod schema describing path params (Internal only, set through sub and routeParameter). */
|
|
30
37
|
paramsSchema?: ZodType;
|
|
31
38
|
/** Zod schema describing the response payload. */
|
|
32
39
|
outputSchema?: ZodType;
|
|
@@ -88,6 +95,13 @@ export type Merge<A, B> = Prettify<Omit<A, keyof B> & B>;
|
|
|
88
95
|
export type Append<T extends readonly unknown[], X> = [...T, X];
|
|
89
96
|
/** Concatenate two readonly tuple types. */
|
|
90
97
|
export type MergeArray<A extends readonly unknown[], B extends readonly unknown[]> = [...A, ...B];
|
|
98
|
+
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;
|
|
99
|
+
type RouteSchemaFromZod<T extends ZodType | undefined> = T extends ZodType ? RouteSchema<z.output<T>> : undefined;
|
|
100
|
+
type MergeRouteSchemas<Existing extends RouteSchema | undefined, Parent extends ZodType | undefined> = Existing extends RouteSchema<infer ExistingOut> ? Parent extends ZodType ? RouteSchema<ExistingOut & z.output<Parent>> : Existing : Parent extends ZodType ? RouteSchemaFromZod<Parent> : undefined;
|
|
101
|
+
type AugmentedCfg<Cfg extends MethodCfgLowProfile, Param extends ZodType | undefined> = Prettify<Omit<Cfg, 'paramsSchema'> & {
|
|
102
|
+
paramsSchema: MergeRouteSchemas<Cfg['paramsSchema'], Param>;
|
|
103
|
+
}>;
|
|
104
|
+
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;
|
|
91
105
|
type SegmentParams<S extends string> = S extends `:${infer P}` ? P : never;
|
|
92
106
|
type Split<S extends string> = S extends '' ? [] : S extends `${infer A}/${infer B}` ? [A, ...Split<B>] : [S];
|
|
93
107
|
type ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>;
|
|
@@ -132,7 +146,7 @@ export declare function buildLowProfileLeaf<const M extends HttpMethod, const Pa
|
|
|
132
146
|
paramsSchema: P extends ZodType ? RouteSchema<z.infer<P>> : undefined;
|
|
133
147
|
outputSchema: O extends ZodType ? RouteSchema<z.infer<O>> : undefined;
|
|
134
148
|
}>>;
|
|
135
|
-
export type LeafLowProfile<M extends HttpMethod, P extends string, C extends MethodCfgLowProfile> = {
|
|
149
|
+
export type LeafLowProfile<M extends HttpMethod = HttpMethod, P extends string = string, C extends MethodCfgLowProfile = MethodCfgLowProfile> = {
|
|
136
150
|
/** Lowercase HTTP method (get/post/...). */
|
|
137
151
|
readonly method: M;
|
|
138
152
|
/** Concrete path for the route (e.g. `/v1/users/:userId`). */
|
package/dist/index.cjs
CHANGED
|
@@ -114,7 +114,7 @@ function mergeSchemas(a, b) {
|
|
|
114
114
|
return a ?? b;
|
|
115
115
|
}
|
|
116
116
|
function resource(base, inherited) {
|
|
117
|
-
const rootBase = base;
|
|
117
|
+
const rootBase = base ?? "";
|
|
118
118
|
const rootInherited = { ...inherited };
|
|
119
119
|
function makeBranch(base2, inherited2, mergedParamsSchema) {
|
|
120
120
|
const stack = [];
|
|
@@ -146,47 +146,90 @@ function resource(base, inherited) {
|
|
|
146
146
|
return api;
|
|
147
147
|
}
|
|
148
148
|
const api = {
|
|
149
|
-
|
|
150
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Mount a subtree built elsewhere.
|
|
151
|
+
*
|
|
152
|
+
* Usage:
|
|
153
|
+
* const users = resource('').get(...).done()
|
|
154
|
+
* resource('/api').sub('users', users).done()
|
|
155
|
+
*/
|
|
156
|
+
sub(name, cfgOrLeaves, maybeLeaves) {
|
|
151
157
|
let cfg;
|
|
152
|
-
let
|
|
153
|
-
if (
|
|
154
|
-
|
|
158
|
+
let leaves;
|
|
159
|
+
if (Array.isArray(cfgOrLeaves)) {
|
|
160
|
+
leaves = cfgOrLeaves;
|
|
155
161
|
} else {
|
|
156
|
-
cfg =
|
|
157
|
-
|
|
162
|
+
cfg = cfgOrLeaves;
|
|
163
|
+
leaves = maybeLeaves;
|
|
158
164
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
165
|
+
if (!leaves) {
|
|
166
|
+
throw new Error("sub() expects a leaves array as the last argument");
|
|
167
|
+
}
|
|
168
|
+
const childInherited = {
|
|
169
|
+
...inheritedCfg,
|
|
170
|
+
...cfg ?? {}
|
|
171
|
+
};
|
|
172
|
+
const baseForChildren = `${currentBase}/${name}`;
|
|
173
|
+
for (const leafLow of leaves) {
|
|
174
|
+
const leaf = leafLow;
|
|
175
|
+
const leafCfg = leaf.cfg;
|
|
176
|
+
const leafParams = leafCfg.paramsSchema;
|
|
177
|
+
const effectiveParams = mergeSchemas(
|
|
178
|
+
currentParamsSchema,
|
|
179
|
+
leafParams
|
|
166
180
|
);
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
const newCfg = {
|
|
182
|
+
...childInherited,
|
|
183
|
+
...leafCfg
|
|
184
|
+
};
|
|
185
|
+
if (effectiveParams) {
|
|
186
|
+
newCfg.paramsSchema = effectiveParams;
|
|
187
|
+
} else if ("paramsSchema" in newCfg) {
|
|
188
|
+
delete newCfg.paramsSchema;
|
|
189
|
+
}
|
|
190
|
+
const newLeaf = {
|
|
191
|
+
method: leaf.method,
|
|
192
|
+
path: `${baseForChildren}${leaf.path}`,
|
|
193
|
+
cfg: newCfg
|
|
194
|
+
};
|
|
195
|
+
stack.push(newLeaf);
|
|
174
196
|
}
|
|
197
|
+
return api;
|
|
175
198
|
},
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Introduce a :param segment and mount a subtree under it.
|
|
201
|
+
*
|
|
202
|
+
* The subtree is built independently (e.g. resource('').get(...).done())
|
|
203
|
+
* and its paths become `${currentBase}/:${name}${leaf.path}`.
|
|
204
|
+
* Params schemas are intersected with the accumulated params plus the new param.
|
|
205
|
+
*/
|
|
206
|
+
routeParameter(name, paramsSchema, leaves) {
|
|
179
207
|
const paramObj = import_zod.z.object({
|
|
180
208
|
[name]: paramsSchema
|
|
181
209
|
});
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
210
|
+
const mergedParams = currentParamsSchema ? mergeSchemas(currentParamsSchema, paramObj) : paramObj;
|
|
211
|
+
const baseForChildren = `${currentBase}/:${name}`;
|
|
212
|
+
for (const leafLow of leaves) {
|
|
213
|
+
const leaf = leafLow;
|
|
214
|
+
const leafCfg = leaf.cfg;
|
|
215
|
+
const leafParams = leafCfg.paramsSchema;
|
|
216
|
+
const effectiveParams = mergeSchemas(mergedParams, leafParams);
|
|
217
|
+
const newCfg = {
|
|
218
|
+
...inheritedCfg,
|
|
219
|
+
...leafCfg
|
|
220
|
+
};
|
|
221
|
+
if (effectiveParams) {
|
|
222
|
+
newCfg.paramsSchema = effectiveParams;
|
|
223
|
+
} else if ("paramsSchema" in newCfg) {
|
|
224
|
+
delete newCfg.paramsSchema;
|
|
225
|
+
}
|
|
226
|
+
const newLeaf = {
|
|
227
|
+
method: leaf.method,
|
|
228
|
+
path: `${baseForChildren}${leaf.path}`,
|
|
229
|
+
cfg: newCfg
|
|
230
|
+
};
|
|
231
|
+
stack.push(newLeaf);
|
|
232
|
+
}
|
|
190
233
|
return api;
|
|
191
234
|
},
|
|
192
235
|
// methods (inject current params schema if missing)
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/sockets/socket.index.ts"],"sourcesContent":["export * from './core/routesV3.builder'\nexport * from './core/routesV3.core'\nexport * from './core/routesV3.finalize'\nexport * from './sockets/socket.index'\n","import { z } from 'zod'\nimport {\n AnyLeaf,\n AnyLeafLowProfile,\n Append,\n HttpMethod,\n Leaf,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n RouteSchema,\n} from './routesV3.core'\n\nconst paginationQueryShape = {\n pagination_cursor: z.string().optional(),\n pagination_limit: z.coerce.number().min(1).max(100).default(20),\n}\n\nconst defaultFeedQuerySchema = z.object(paginationQueryShape)\ntype PaginationShape = typeof paginationQueryShape\n\ntype ZodTypeAny = z.ZodTypeAny\ntype ParamZod<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S\n}>\ntype ZodArrayAny = z.ZodArray<ZodTypeAny>\ntype ZodObjectAny = z.ZodObject<any>\ntype AnyZodObject = z.ZodObject<any>\ntype MergedParamsResult<\n PS,\n Name extends string,\n P extends ZodTypeAny,\n> = PS extends ZodTypeAny\n ? z.ZodIntersection<PS, ParamZod<Name, P>>\n : ParamZod<Name, P>\n\nfunction getZodShape(schema: ZodObjectAny) {\n const shapeOrGetter = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n if (!shapeOrGetter) return {}\n return typeof shapeOrGetter === 'function'\n ? shapeOrGetter.call(schema)\n : shapeOrGetter\n}\n\nfunction collectNestedFieldSuggestions(\n shape: Record<string, ZodTypeAny> | undefined,\n prefix: string[] = [],\n): string[] {\n if (!shape) return []\n const suggestions: string[] = []\n for (const [key, value] of Object.entries(shape)) {\n if (value instanceof z.ZodObject) {\n const nestedShape = getZodShape(value as ZodObjectAny)\n const nestedSuggestions = collectNestedFieldSuggestions(nestedShape, [\n ...prefix,\n key,\n ])\n suggestions.push(\n ...(nestedSuggestions.length\n ? nestedSuggestions\n : [[...prefix, key].join('_')]),\n )\n } else if (prefix.length > 0) {\n suggestions.push([...prefix, key].join('_'))\n }\n }\n return suggestions\n}\n\nconst defaultFeedOutputSchema = z.object({\n items: z.array(z.unknown()),\n nextCursor: z.string().optional(),\n})\n\nfunction augmentFeedQuerySchema<Q extends ZodTypeAny | undefined>(schema: Q) {\n if (schema && !(schema instanceof z.ZodObject)) {\n console.warn(\n 'Feed queries must be a ZodObject; default pagination applied.',\n )\n return defaultFeedQuerySchema\n }\n\n const base = (schema as ZodObjectAny) ?? z.object({})\n const shape = getZodShape(base)\n const nestedSuggestions = collectNestedFieldSuggestions(shape)\n if (nestedSuggestions.length) {\n console.warn(\n `Feed query schemas should avoid nested objects; consider flattening fields like: ${nestedSuggestions.join(\n ', ',\n )}`,\n )\n }\n return base.extend(paginationQueryShape)\n}\n\nfunction augmentFeedOutputSchema<O extends ZodTypeAny | undefined>(schema: O) {\n if (!schema) return defaultFeedOutputSchema\n if (schema instanceof z.ZodArray) {\n return z.object({\n items: schema,\n nextCursor: z.string().optional(),\n })\n }\n if (schema instanceof z.ZodObject) {\n const shape = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n const hasItems = Boolean(shape?.items)\n if (hasItems) {\n return schema.extend({ nextCursor: z.string().optional() })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n}\n\n/**\n * Runtime helper that mirrors the typed merge used by the builder.\n * @param a Previously merged params schema inherited from parent segments.\n * @param b Newly introduced params schema.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nfunction mergeSchemas<A extends ZodTypeAny, B extends ZodTypeAny>(\n a: A,\n b: B,\n): ZodTypeAny\nfunction mergeSchemas<A extends ZodTypeAny>(a: A, b: undefined): A\nfunction mergeSchemas<B extends ZodTypeAny>(a: undefined, b: B): B\nfunction mergeSchemas(\n a: ZodTypeAny | undefined,\n b: ZodTypeAny | undefined,\n): ZodTypeAny | undefined\nfunction mergeSchemas(a: ZodTypeAny | undefined, b: ZodTypeAny | undefined) {\n if (a && b) return z.intersection(a as any, b as any)\n return (a ?? b) as ZodTypeAny | undefined\n}\n\ntype FeedOutputSchema<C extends MethodCfg> =\n C['outputSchema'] extends ZodArrayAny\n ? z.ZodObject<{\n items: C['outputSchema']\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : C['outputSchema'] extends ZodTypeAny\n ? z.ZodObject<{\n items: z.ZodArray<C['outputSchema']>\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : typeof defaultFeedOutputSchema\n\ntype BaseMethodCfg<C extends MethodCfg> = Merge<\n Omit<MethodCfg, 'querySchema' | 'outputSchema' | 'feed'>,\n Omit<C, 'querySchema' | 'outputSchema' | 'feed'>\n>\n\ntype FeedField<C extends MethodCfg> = C['feed'] extends true\n ? { feed: true }\n : { feed?: boolean }\n\ntype AddPaginationToQuery<Q extends AnyZodObject | undefined> =\n Q extends z.ZodObject<infer Shape>\n ? z.ZodObject<Shape & PaginationShape>\n : z.ZodObject<PaginationShape>\n\ntype FeedQueryField<C extends MethodCfg> = {\n querySchema: AddPaginationToQuery<\n C['querySchema'] extends AnyZodObject ? C['querySchema'] : undefined\n >\n}\n\ntype NonFeedQueryField<C extends MethodCfg> =\n C['querySchema'] extends ZodTypeAny\n ? { querySchema: C['querySchema'] }\n : { querySchema?: undefined }\n\ntype FeedOutputField<C extends MethodCfg> = {\n outputSchema: FeedOutputSchema<C>\n}\n\ntype NonFeedOutputField<C extends MethodCfg> =\n C['outputSchema'] extends ZodTypeAny\n ? { outputSchema: C['outputSchema'] }\n : { outputSchema?: undefined }\n\ntype ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny\n ? { paramsSchema: C['paramsSchema'] }\n : { paramsSchema: PS }\n\ntype EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true\n ? FeedField<C> & FeedQueryField<C> & FeedOutputField<C> & ParamsField<C, PS>\n : FeedField<C> &\n NonFeedQueryField<C> &\n NonFeedOutputField<C> &\n ParamsField<C, PS>\n\ntype EffectiveCfg<C extends MethodCfg, PS> = Prettify<\n Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>\n>\n\ntype ToRouteSchema<S> = S extends ZodTypeAny ? RouteSchema<z.output<S>> : S\n\ntype LowProfileCfg<Cfg extends MethodCfg> = Prettify<\n Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {\n bodySchema: ToRouteSchema<Cfg['bodySchema']>\n querySchema: ToRouteSchema<Cfg['querySchema']>\n paramsSchema: ToRouteSchema<Cfg['paramsSchema']>\n outputSchema: ToRouteSchema<Cfg['outputSchema']>\n }\n>\n\ntype BuiltLeaf<\n M extends HttpMethod,\n Base extends string,\n I extends NodeCfg,\n C extends MethodCfg,\n PS,\n> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n> {\n // --- structure ---\n /**\n * Enter or define a static child segment.\n * Optionally accepts extra config and/or a builder to populate nested routes.\n * @param name Child segment literal (no leading slash).\n * @param cfg Optional node configuration to merge for descendants.\n * @param builder Callback to produce leaves for the child branch.\n */\n sub<Name extends string, J extends NodeCfg>(\n name: Name,\n cfg?: J,\n ): Branch<`${Base}/${Name}`, Acc, Merge<I, NonNullable<J>>, PS>\n\n sub<\n Name extends string,\n J extends NodeCfg | undefined,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n cfg: J,\n builder: (\n r: Branch<`${Base}/${Name}`, readonly [], Merge<I, NonNullable<J>>, PS>,\n ) => R,\n ): Branch<Base, Append<Acc, R[number]>, Merge<I, NonNullable<J>>, PS>\n\n sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(\n name: Name,\n builder: (r: Branch<`${Base}/${Name}`, readonly [], I, PS>) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, PS>\n\n // --- parameterized segment (single signature) ---\n /**\n * Introduce a `:param` segment and merge its schema into downstream leaves.\n * @param name Parameter key (without leading colon).\n * @param paramsSchema Zod schema for the parameter value.\n * @param builder Callback that produces leaves beneath the parameterized segment.\n */\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<\n `${Base}/:${Name}`,\n readonly [],\n I,\n MergedParamsResult<PS, Name, P>\n >,\n ) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, MergedParamsResult<PS, Name, P>>\n\n // --- flags inheritance ---\n /**\n * Merge additional node configuration that subsequent leaves will inherit.\n * @param cfg Partial node configuration.\n */\n with<J extends NodeCfg>(cfg: J): Branch<Base, Acc, Merge<I, J>, PS>\n\n // --- methods (return Branch to keep chaining) ---\n /**\n * Register a GET leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n get<C extends MethodCfg>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Prettify<BuiltLeaf<'get', Base, I, C, PS>>>,\n I,\n PS\n >\n\n /**\n * Register a POST leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n post<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'post', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a PUT leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n put<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'put', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a PATCH leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n patch<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'patch', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a DELETE leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n delete<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'delete', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n // --- finalize this subtree ---\n /**\n * Finish the branch and return the collected leaves.\n * @returns Readonly tuple of accumulated leaves.\n */\n done(): Readonly<Acc>\n}\n\n/**\n * Start building a resource at the given base path.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Optional node configuration applied to all descendants.\n * @returns Root `Branch` instance used to compose the route tree.\n */\nexport function resource<Base extends string, I extends NodeCfg = {}>(\n base: Base,\n inherited?: I,\n): Branch<Base, readonly [], I, undefined> {\n const rootBase = base\n const rootInherited: NodeCfg = { ...(inherited as NodeCfg) }\n\n function makeBranch<\n Base2 extends string,\n I2 extends NodeCfg,\n PS2 extends ZodTypeAny | undefined,\n >(base2: Base2, inherited2: I2, mergedParamsSchema?: PS2) {\n const stack: AnyLeaf[] = []\n let currentBase: string = base2\n let inheritedCfg: NodeCfg = { ...(inherited2 as NodeCfg) }\n let currentParamsSchema: PS2 = mergedParamsSchema as PS2\n\n function add<M extends HttpMethod, C extends MethodCfg>(method: M, cfg: C) {\n // If the method didn’t provide a paramsSchema, inject the active merged one.\n const effectiveParamsSchema = (cfg.paramsSchema ??\n currentParamsSchema) as PS2 | undefined\n const effectiveQuerySchema =\n cfg.feed === true\n ? augmentFeedQuerySchema(cfg.querySchema)\n : cfg.querySchema\n const effectiveOutputSchema =\n cfg.feed === true\n ? augmentFeedOutputSchema(cfg.outputSchema)\n : cfg.outputSchema\n\n const fullCfg = (\n effectiveParamsSchema\n ? {\n ...inheritedCfg,\n ...cfg,\n paramsSchema: effectiveParamsSchema,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n : {\n ...inheritedCfg,\n ...cfg,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n ) as any\n\n const leaf = {\n method,\n path: currentBase as Base2,\n cfg: fullCfg,\n } as const\n\n stack.push(leaf as unknown as AnyLeaf)\n\n // Return same runtime obj, but with Acc including this new leaf\n return api as unknown as Branch<\n Base2,\n Append<readonly [], typeof leaf>,\n I2,\n PS2\n >\n }\n\n const api: any = {\n // compose a plain subpath (optional cfg) with optional builder\n sub<Name extends string, J extends NodeCfg | undefined = undefined>(\n name: Name,\n cfgOrBuilder?: J | ((r: any) => readonly AnyLeafLowProfile[]),\n maybeBuilder?: (r: any) => readonly AnyLeafLowProfile[],\n ) {\n let cfg: NodeCfg | undefined\n let builder: ((r: any) => readonly AnyLeafLowProfile[]) | undefined\n\n if (typeof cfgOrBuilder === 'function') {\n builder = cfgOrBuilder as any\n } else {\n cfg = cfgOrBuilder as NodeCfg | undefined\n builder = maybeBuilder\n }\n\n const childBase = `${currentBase}/${name}` as const\n const childInherited = { ...inheritedCfg, ...(cfg ?? {}) } as Merge<\n I2,\n NonNullable<J>\n >\n\n if (builder) {\n // params schema PS2 flows through unchanged on plain sub\n const child = makeBranch(\n childBase,\n childInherited,\n currentParamsSchema,\n )\n const leaves = builder(child) //as readonly AnyLeafLowProfile[]\n for (const l of leaves) stack.push(l)\n return api as Branch<\n Base2,\n Append<readonly [], (typeof leaves)[number]>,\n typeof childInherited,\n PS2\n >\n } else {\n currentBase = childBase\n inheritedCfg = childInherited\n return api as Branch<\n `${Base2}/${Name}`,\n readonly [],\n typeof childInherited,\n PS2\n >\n }\n },\n\n // the single param function: name + schema + builder\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<\n `${Base2}/:${Name}`,\n readonly [],\n I2,\n MergedParamsResult<PS2, Name, P>\n >,\n ) => R,\n ) {\n const childBase = `${currentBase}/:${name}` as const\n // Wrap the value schema under the param name before merging with inherited params schema\n const paramObj: ParamZod<Name, P> = z.object({\n [name]: paramsSchema,\n } as Record<Name, P>)\n const childParams = (\n currentParamsSchema\n ? mergeSchemas(currentParamsSchema, paramObj)\n : paramObj\n ) as MergedParamsResult<PS2, Name, P>\n const child = makeBranch(childBase, inheritedCfg as I2, childParams)\n const leaves = builder(child)\n for (const l of leaves) stack.push(l)\n return api as Branch<\n Base2,\n Append<readonly [], (typeof leaves)[number]>,\n I2,\n MergedParamsResult<PS2, Name, P>\n >\n },\n\n with<J extends NodeCfg>(cfg: J) {\n inheritedCfg = { ...inheritedCfg, ...cfg }\n return api as Branch<Base2, readonly [], Merge<I2, J>, PS2>\n },\n\n // methods (inject current params schema if missing)\n get<C extends MethodCfg>(cfg: C) {\n return add('get', cfg)\n },\n post<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('post', { ...cfg, feed: false })\n },\n put<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('put', { ...cfg, feed: false })\n },\n patch<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('patch', { ...cfg, feed: false })\n },\n delete<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('delete', { ...cfg, feed: false })\n },\n\n done() {\n return stack\n },\n }\n\n return api as Branch<Base2, readonly [], I2, PS2>\n }\n\n // Root branch starts with no params schema (PS = undefined)\n return makeBranch(rootBase, rootInherited, undefined)\n}\n\n/**\n * Merge two readonly tuples (preserves literal tuple information).\n * @param arr1 First tuple.\n * @param arr2 Second tuple.\n * @returns New tuple containing values from both inputs.\n */\nexport const mergeArrays = <T extends readonly any[], S extends readonly any[]>(\n arr1: T,\n arr2: S,\n) => {\n return [...arr1, ...arr2] as [...T, ...S]\n}\n","import { z, ZodType } from 'zod'\n\n/** Supported HTTP verbs for the routes DSL. */\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\n/** Declarative description of a multipart upload field. */\nexport type FileField = {\n /** Form field name used for uploads. */\n name: string\n /** Maximum number of files accepted for this field. */\n maxCount: number\n}\n\n/** Configuration that applies to an entire branch of the route tree. */\nexport type NodeCfg = {\n /** @deprecated. Does nothing. */\n authenticated?: boolean\n}\n\nexport type RouteSchema<Output = unknown> = ZodType & {\n __out: Output\n}\n\nexport type RouteSchemaOutput<Schema extends ZodType> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport const lowProfileParse = <T extends RouteSchema>(\n schema: T,\n data: unknown,\n): RouteSchemaOutput<T> => {\n return schema.parse(data) as RouteSchemaOutput<T>\n}\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = {\n /** Zod schema describing the request body. */\n bodySchema?: ZodType\n /** Zod schema describing the query string. */\n querySchema?: ZodType\n /** Zod schema describing path params (overrides inferred params). */\n paramsSchema?: ZodType\n /** Zod schema describing the response payload. */\n outputSchema?: ZodType\n /** Multipart upload definitions for the route. */\n bodyFiles?: FileField[]\n /** Marks the route as an infinite feed (enables cursor helpers). */\n feed?: boolean\n\n /** Optional human-readable description for docs/debugging. */\n description?: string\n /**\n * Short one-line summary for docs list views.\n * Shown in navigation / tables instead of the full description.\n */\n summary?: string\n /**\n * Group name used for navigation sections in docs UIs.\n * e.g. \"Users\", \"Billing\", \"Auth\".\n */\n docsGroup?: string\n /**\n * Tags that can be used to filter / facet in interactive docs.\n * e.g. [\"users\", \"admin\", \"internal\"].\n */\n tags?: string[]\n /**\n * Mark the route as deprecated in docs.\n * Renderers can badge this and/or hide by default.\n */\n deprecated?: boolean\n /**\n * Optional stability information for the route.\n * Renderers can surface this prominently.\n */\n stability?: 'experimental' | 'beta' | 'stable' | 'deprecated'\n /**\n * Hide this route from public docs while keeping it usable in code.\n */\n docsHidden?: boolean\n /**\n * Arbitrary extra metadata for docs renderers.\n * Can be used for auth requirements, rate limits, feature flags, etc.\n */\n docsMeta?: Record<string, unknown>\n}\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfg,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n\n/** Convenience union covering all generated leaves. */\nexport type AnyLeaf = Leaf<HttpMethod, string, MethodCfg>\n\n/** Merge two object types while keeping nice IntelliSense output. */\nexport type Merge<A, B> = Prettify<Omit<A, keyof B> & B>\n\n/** Append a new element to a readonly tuple type. */\nexport type Append<T extends readonly unknown[], X> = [...T, X]\n\n/** Concatenate two readonly tuple types. */\nexport type MergeArray<\n A extends readonly unknown[],\n B extends readonly unknown[],\n> = [...A, ...B]\n\n// helpers (optional)\ntype SegmentParams<S extends string> = S extends `:${infer P}` ? P : never\ntype Split<S extends string> = S extends ''\n ? []\n : S extends `${infer A}/${infer B}`\n ? [A, ...Split<B>]\n : [S]\ntype ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>\n\n/** Derive a params object type from a literal route string. */\nexport type ExtractParamsFromPath<Path extends string> =\n ExtractParamNames<Path> extends never\n ? never\n : Record<ExtractParamNames<Path>, string | number>\n\n/**\n * Interpolate `:params` in a path using the given values.\n * @param path Literal route string containing `:param` segments.\n * @param params Object of parameter values to interpolate.\n * @returns Path string with parameters substituted.\n */\nexport function compilePath<Path extends string>(\n path: Path,\n params: ExtractParamsFromPath<Path>,\n) {\n if (!params) return path\n return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {\n const v = (params as any)[k]\n if (v === undefined || v === null) throw new Error(`Missing param :${k}`)\n return String(v)\n })\n}\n\n/**\n * Build a deterministic cache key for the given leaf.\n * The key matches the shape consumed by React Query helpers.\n * @param args.leaf Leaf describing the endpoint.\n * @param args.params Optional params used to build the path.\n * @param args.query Optional query payload.\n * @returns Tuple suitable for React Query cache keys.\n */\ntype SplitPath<P extends string> = P extends ''\n ? []\n : P extends `${infer A}/${infer B}`\n ? [A, ...SplitPath<B>]\n : [P]\nexport function buildCacheKey<L extends AnyLeaf>(args: {\n leaf: L\n params?: ExtractParamsFromPath<L['path']>\n query?: InferQuery<L>\n}) {\n let p = args.leaf.path\n if (args.params) {\n p = compilePath<L['path']>(p, args.params)\n }\n return [\n args.leaf.method,\n ...(p.split('/').filter(Boolean) as SplitPath<typeof p>),\n args.query ?? {},\n ] as const\n}\n\n/** Definition-time method config (for clarity). */\nexport type MethodCfgDef = MethodCfg\n\n/** Low-profile method config where schemas carry a phantom __out like SocketSchema. */\nexport type MethodCfgLowProfile = Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n> & {\n bodySchema?: RouteSchema\n querySchema?: RouteSchema\n paramsSchema?: RouteSchema\n outputSchema?: RouteSchema\n}\nexport type AnyLeafLowProfile = LeafLowProfile<\n HttpMethod,\n string,\n MethodCfgLowProfile\n>\n\nexport function buildLowProfileLeaf<\n const M extends HttpMethod,\n const Path extends string,\n const O extends ZodType | undefined = undefined,\n const P extends ZodType | undefined = undefined,\n const Q extends ZodType | undefined = undefined,\n const B extends ZodType | undefined = undefined,\n const Feed extends boolean = false,\n>(leaf: {\n method: M\n path: Path\n cfg: Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n > & {\n feed?: Feed\n bodySchema?: B\n querySchema?: Q\n paramsSchema?: P\n outputSchema?: O\n }\n}): LeafLowProfile<\n M,\n Path,\n Prettify<\n Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed'\n > & {\n feed: Feed\n bodySchema: B extends ZodType ? RouteSchema<z.infer<B>> : undefined\n querySchema: Q extends ZodType ? RouteSchema<z.infer<Q>> : undefined\n paramsSchema: P extends ZodType ? RouteSchema<z.infer<P>> : undefined\n outputSchema: O extends ZodType ? RouteSchema<z.infer<O>> : undefined\n }\n >\n>\nexport function buildLowProfileLeaf(leaf: any): any {\n return {\n ...leaf,\n cfg: {\n ...leaf.cfg,\n bodySchema: leaf.cfg.bodySchema as RouteSchema<\n z.infer<typeof leaf.cfg.bodySchema>\n >,\n querySchema: leaf.cfg.querySchema as RouteSchema<\n z.infer<typeof leaf.cfg.querySchema>\n >,\n paramsSchema: leaf.cfg.paramsSchema as RouteSchema<\n z.infer<typeof leaf.cfg.paramsSchema>\n >,\n outputSchema: leaf.cfg.outputSchema as RouteSchema<\n z.infer<typeof leaf.cfg.outputSchema>\n >,\n },\n }\n}\n\nexport type LeafLowProfile<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfgLowProfile,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<\n L extends AnyLeafLowProfile,\n Fallback = never,\n> = L['cfg']['paramsSchema'] extends RouteSchema<infer P> ? P : Fallback\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['querySchema'] extends RouteSchema<infer Q> ? Q : Fallback\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['bodySchema'] extends RouteSchema<infer B> ? B : Fallback\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['outputSchema'] extends RouteSchema<infer O> ? O : Fallback\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {}\n","// TODO:\n// * use this as a transform that infers the types from Zod, to only pass the types around instead of the full schemas -> make converters that go both ways (data to schema, schema to data)\n// * consider moving to core package\n// * update server and client side to use this type instead of raw zod schemas\nimport { AnyLeafLowProfile, HttpMethod, Prettify } from './routesV3.core'\n\n/** Build the key type for a leaf — distributive so method/path stay paired. */\nexport type KeyOf<L extends AnyLeafLowProfile> = L extends AnyLeafLowProfile\n ? `${Uppercase<L['method']>} ${L['path']}`\n : never\n\n// From a tuple of leaves, get the union of keys (pairwise, no cartesian blow-up)\ntype KeysOf<Leaves extends readonly AnyLeafLowProfile[]> = KeyOf<Leaves[number]>\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}`\n ? Lowercase<M>\n : never\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}`\n ? P\n : never\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<\n Leaves extends readonly AnyLeafLowProfile[],\n K extends string,\n> = Extract<\n Leaves[number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>\n\n/**\n * Freeze a leaf tuple into a registry with typed key lookups.\n * @param leaves Readonly tuple of leaves produced by the builder DSL.\n * @returns Registry containing the leaves array and a `byKey` lookup map.\n */\nexport function finalize<const L extends readonly AnyLeafLowProfile[]>(\n leaves: L,\n) {\n type Keys = KeysOf<L>\n type MapByKey = { [K in Keys]: Prettify<LeafForKey<L, K>> }\n\n const byKey = Object.fromEntries(\n leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l] as const),\n ) as unknown as MapByKey\n\n const log = (logger: { system: (...args: unknown[]) => void }) => {\n logger.system('Finalized routes:')\n ;(Object.keys(byKey) as Keys[]).forEach((k) => {\n const leaf = byKey[k]\n logger.system(`- ${k}`)\n })\n }\n\n return { all: leaves, byKey, log }\n}\n\n/** Nominal type alias for a finalized registry. */\nexport type Registry<R extends ReturnType<typeof finalize>> = R\n\ntype FilterRoute<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n Acc extends readonly AnyLeafLowProfile[] = [],\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? First extends { path: `${string}${F}${string}` }\n ? FilterRoute<Rest, F, [...Acc, First]>\n : FilterRoute<Rest, F, Acc>\n : Acc\n\ntype UpperCase<S extends string> = S extends `${infer First}${infer Rest}`\n ? `${Uppercase<First>}${UpperCase<Rest>}`\n : S\n\ntype Routes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = FilterRoute<T, F>\ntype ByKey<\n T extends readonly AnyLeafLowProfile[],\n Acc extends Record<string, AnyLeafLowProfile> = {},\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? ByKey<\n Rest,\n Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }\n >\n : Acc\n\n/**\n * Convenience helper for extracting a subset of routes by path fragment.\n * @param T Tuple of leaves produced by the DSL.\n * @param F String fragment to match against the route path.\n */\nexport type SubsetRoutes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = {\n byKey: ByKey<Routes<T, F>>\n all: Routes<T, F>\n}\n","// socket.index.ts (shared client/server)\n\nimport { z } from 'zod'\n\nexport type SocketSchema<Output = unknown> = z.ZodType & {\n __out: Output\n}\n\nexport type SocketSchemaOutput<Schema extends z.ZodTypeAny> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport type SocketEvent<Out = unknown> = {\n message: z.ZodTypeAny\n /** phantom field, only for typing; not meant to be used at runtime */\n __out: Out\n}\n\nexport type EventMap = Record<string, SocketEvent<any>>\n\n/**\n * System event names – shared so server and client\n * don't drift on naming.\n */\nexport type SysEventName =\n | 'sys:connect'\n | 'sys:disconnect'\n | 'sys:reconnect'\n | 'sys:connect_error'\n | 'sys:ping'\n | 'sys:pong'\n | 'sys:room_join'\n | 'sys:room_leave'\nexport function defineSocketEvents<\n const C extends SocketConnectionConfig,\n const Schemas extends Record<\n string,\n {\n message: z.ZodTypeAny\n }\n >,\n>(\n config: C,\n events: Schemas,\n): {\n config: {\n [K in keyof C]: SocketSchema<z.output<C[K]>>\n }\n events: {\n [K in keyof Schemas]: SocketEvent<z.output<Schemas[K]['message']>>\n }\n}\n\nexport function defineSocketEvents(config: any, events: any) {\n return { config, events }\n}\n\nexport type Payload<\n T extends EventMap,\n K extends keyof T,\n> = T[K] extends SocketEvent<infer Out> ? Out : never\nexport type SocketConnectionConfig = {\n joinMetaMessage: z.ZodTypeAny\n leaveMetaMessage: z.ZodTypeAny\n pingPayload: z.ZodTypeAny\n pongPayload: z.ZodTypeAny\n}\n\nexport type SocketConnectionConfigOutput = {\n joinMetaMessage: SocketSchema<any>\n leaveMetaMessage: SocketSchema<any>\n pingPayload: SocketSchema<any>\n pongPayload: SocketSchema<any>\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/sockets/socket.index.ts"],"sourcesContent":["export * from './core/routesV3.builder'\nexport * from './core/routesV3.core'\nexport * from './core/routesV3.finalize'\nexport * from './sockets/socket.index'\n","import { z } from 'zod'\nimport {\n AnyLeaf,\n AnyLeafLowProfile,\n Append,\n AugmentLeaves,\n HttpMethod,\n Leaf,\n LowProfileCfg,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n} from './routesV3.core'\n\nconst paginationQueryShape = {\n pagination_cursor: z.string().optional(),\n pagination_limit: z.coerce.number().min(1).max(100).default(20),\n}\n\nconst defaultFeedQuerySchema = z.object(paginationQueryShape)\ntype PaginationShape = typeof paginationQueryShape\n\ntype ZodTypeAny = z.ZodTypeAny\ntype ParamZod<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S\n}>\ntype ZodArrayAny = z.ZodArray<ZodTypeAny>\ntype ZodObjectAny = z.ZodObject<any>\ntype AnyZodObject = z.ZodObject<any>\ntype MergedParamsResult<\n PS,\n Name extends string,\n P extends ZodTypeAny,\n> = PS extends ZodTypeAny\n ? z.ZodIntersection<PS, ParamZod<Name, P>>\n : ParamZod<Name, P>\n\nfunction getZodShape(schema: ZodObjectAny) {\n const shapeOrGetter = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n if (!shapeOrGetter) return {}\n return typeof shapeOrGetter === 'function'\n ? shapeOrGetter.call(schema)\n : shapeOrGetter\n}\n\nfunction collectNestedFieldSuggestions(\n shape: Record<string, ZodTypeAny> | undefined,\n prefix: string[] = [],\n): string[] {\n if (!shape) return []\n const suggestions: string[] = []\n for (const [key, value] of Object.entries(shape)) {\n if (value instanceof z.ZodObject) {\n const nestedShape = getZodShape(value as ZodObjectAny)\n const nestedSuggestions = collectNestedFieldSuggestions(nestedShape, [\n ...prefix,\n key,\n ])\n suggestions.push(\n ...(nestedSuggestions.length\n ? nestedSuggestions\n : [[...prefix, key].join('_')]),\n )\n } else if (prefix.length > 0) {\n suggestions.push([...prefix, key].join('_'))\n }\n }\n return suggestions\n}\n\nconst defaultFeedOutputSchema = z.object({\n items: z.array(z.unknown()),\n nextCursor: z.string().optional(),\n})\n\nfunction augmentFeedQuerySchema<Q extends ZodTypeAny | undefined>(schema: Q) {\n if (schema && !(schema instanceof z.ZodObject)) {\n console.warn(\n 'Feed queries must be a ZodObject; default pagination applied.',\n )\n return defaultFeedQuerySchema\n }\n\n const base = (schema as ZodObjectAny) ?? z.object({})\n const shape = getZodShape(base)\n const nestedSuggestions = collectNestedFieldSuggestions(shape)\n if (nestedSuggestions.length) {\n console.warn(\n `Feed query schemas should avoid nested objects; consider flattening fields like: ${nestedSuggestions.join(\n ', ',\n )}`,\n )\n }\n return base.extend(paginationQueryShape)\n}\n\nfunction augmentFeedOutputSchema<O extends ZodTypeAny | undefined>(schema: O) {\n if (!schema) return defaultFeedOutputSchema\n if (schema instanceof z.ZodArray) {\n return z.object({\n items: schema,\n nextCursor: z.string().optional(),\n })\n }\n if (schema instanceof z.ZodObject) {\n const shape = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n const hasItems = Boolean(shape?.items)\n if (hasItems) {\n return schema.extend({ nextCursor: z.string().optional() })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n}\n\n/**\n * Runtime helper that mirrors the typed merge used by the builder.\n * @param a Previously merged params schema inherited from parent segments.\n * @param b Newly introduced params schema.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nfunction mergeSchemas<A extends ZodTypeAny, B extends ZodTypeAny>(\n a: A,\n b: B,\n): ZodTypeAny\nfunction mergeSchemas<A extends ZodTypeAny>(a: A, b: undefined): A\nfunction mergeSchemas<B extends ZodTypeAny>(a: undefined, b: B): B\nfunction mergeSchemas(\n a: ZodTypeAny | undefined,\n b: ZodTypeAny | undefined,\n): ZodTypeAny | undefined\nfunction mergeSchemas(a: ZodTypeAny | undefined, b: ZodTypeAny | undefined) {\n if (a && b) return z.intersection(a as any, b as any)\n return (a ?? b) as ZodTypeAny | undefined\n}\n\ntype FeedOutputSchema<C extends MethodCfg> =\n C['outputSchema'] extends ZodArrayAny\n ? z.ZodObject<{\n items: C['outputSchema']\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : C['outputSchema'] extends ZodTypeAny\n ? z.ZodObject<{\n items: z.ZodArray<C['outputSchema']>\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : typeof defaultFeedOutputSchema\n\ntype BaseMethodCfg<C extends MethodCfg> = Merge<\n Omit<MethodCfg, 'querySchema' | 'outputSchema' | 'feed'>,\n Omit<C, 'querySchema' | 'outputSchema' | 'feed'>\n>\n\ntype FeedField<C extends MethodCfg> = C['feed'] extends true\n ? { feed: true }\n : { feed?: boolean }\n\ntype AddPaginationToQuery<Q extends AnyZodObject | undefined> =\n Q extends z.ZodObject<infer Shape>\n ? z.ZodObject<Shape & PaginationShape>\n : z.ZodObject<PaginationShape>\n\ntype FeedQueryField<C extends MethodCfg> = {\n querySchema: AddPaginationToQuery<\n C['querySchema'] extends AnyZodObject ? C['querySchema'] : undefined\n >\n}\n\ntype NonFeedQueryField<C extends MethodCfg> =\n C['querySchema'] extends ZodTypeAny\n ? { querySchema: C['querySchema'] }\n : { querySchema?: undefined }\n\ntype FeedOutputField<C extends MethodCfg> = {\n outputSchema: FeedOutputSchema<C>\n}\n\ntype NonFeedOutputField<C extends MethodCfg> =\n C['outputSchema'] extends ZodTypeAny\n ? { outputSchema: C['outputSchema'] }\n : { outputSchema?: undefined }\n\ntype ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny\n ? { paramsSchema: C['paramsSchema'] }\n : { paramsSchema: PS }\n\ntype EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true\n ? FeedField<C> & FeedQueryField<C> & FeedOutputField<C> & ParamsField<C, PS>\n : FeedField<C> &\n NonFeedQueryField<C> &\n NonFeedOutputField<C> &\n ParamsField<C, PS>\n\ntype EffectiveCfg<C extends MethodCfg, PS> = Prettify<\n Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>\n>\n\ntype BuiltLeaf<\n M extends HttpMethod,\n Base extends string,\n I extends NodeCfg,\n C extends MethodCfg,\n PS,\n> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>\n\ntype MethodFns<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Used extends HttpMethod,\n> = {\n /**\n * Register a GET leaf at the current path.\n */\n get: 'get' extends Used\n ? never\n : <C extends MethodCfg>(\n cfg: C,\n ) => Branch<\n Base,\n Append<Acc, Prettify<BuiltLeaf<'get', Base, I, C, PS>>>,\n I,\n PS,\n Used | 'get'\n >\n\n /**\n * Register a POST leaf at the current path.\n */\n post: 'post' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'post', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'post'\n >\n\n /**\n * Register a PUT leaf at the current path.\n */\n put: 'put' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'put', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'put'\n >\n\n /**\n * Register a PATCH leaf at the current path.\n */\n patch: 'patch' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'patch', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'patch'\n >\n\n /**\n * Register a DELETE leaf at the current path.\n */\n delete: 'delete' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'delete', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'delete'\n >\n}\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Used extends HttpMethod = never,\n> extends MethodFns<Base, Acc, I, PS, Used> {\n /**\n * Mount a static subtree under `name`.\n * The `leaves` are built externally via `resource(...)` and will be\n * rebased so that their paths become `${Base}/${name}${leaf.path}` and their\n * paramsSchemas are merged with the parameters already accumulated on this branch.\n */\n sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(\n name: Name,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>,\n I,\n PS,\n Used\n >\n\n /**\n * Mount a static subtree under `name` and merge extra node-level config.\n */\n sub<\n Name extends string,\n J extends NodeCfg,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n cfg: J,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>,\n Merge<I, J>,\n PS,\n Used\n >\n\n /**\n * Introduce a `:param` segment and mount a pre-built subtree beneath it.\n * The subtree paths are rebased to `${Base}/:${Name}${leaf.path}` and\n * their paramsSchemas are intersected with the accumulated params plus this new param.\n */\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<\n Acc,\n AugmentLeaves<`${Base}/:${Name}`, MergedParamsResult<PS, Name, P>, R>\n >,\n I,\n PS,\n Used\n >\n\n /**\n * Finish the branch and return the collected leaves.\n * @returns Readonly tuple of accumulated leaves.\n */\n done(): Readonly<Acc>\n}\n\n/**\n * Start building a resource at the given base path.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Optional node configuration applied to all descendants.\n * @returns Root `Branch` instance used to compose the route tree.\n */\nexport function resource<Base extends string = '', I extends NodeCfg = {}>(\n base?: Base,\n inherited?: I,\n): Branch<Base, readonly [], I, undefined> {\n const rootBase = (base ?? '') as Base\n const rootInherited: NodeCfg = { ...(inherited as NodeCfg) }\n\n function makeBranch<\n Base2 extends string,\n I2 extends NodeCfg,\n PS2 extends ZodTypeAny | undefined,\n >(base2: Base2, inherited2: I2, mergedParamsSchema?: PS2) {\n const stack: AnyLeaf[] = []\n let currentBase: string = base2\n let inheritedCfg: NodeCfg = { ...(inherited2 as NodeCfg) }\n let currentParamsSchema: PS2 = mergedParamsSchema as PS2\n\n function add<M extends HttpMethod, C extends MethodCfg>(method: M, cfg: C) {\n const effectiveParamsSchema = (cfg.paramsSchema ??\n currentParamsSchema) as PS2 | undefined\n const effectiveQuerySchema =\n cfg.feed === true\n ? augmentFeedQuerySchema(cfg.querySchema)\n : cfg.querySchema\n const effectiveOutputSchema =\n cfg.feed === true\n ? augmentFeedOutputSchema(cfg.outputSchema)\n : cfg.outputSchema\n\n const fullCfg = (\n effectiveParamsSchema\n ? {\n ...inheritedCfg,\n ...cfg,\n paramsSchema: effectiveParamsSchema,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n : {\n ...inheritedCfg,\n ...cfg,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n ) as any\n\n const leaf = {\n method,\n path: currentBase as Base2,\n cfg: fullCfg,\n } as const\n\n stack.push(leaf as unknown as AnyLeaf)\n\n return api\n }\n\n const api: any = {\n /**\n * Mount a subtree built elsewhere.\n *\n * Usage:\n * const users = resource('').get(...).done()\n * resource('/api').sub('users', users).done()\n */\n sub<Name extends string>(\n name: Name,\n cfgOrLeaves?: NodeCfg | readonly AnyLeafLowProfile[],\n maybeLeaves?: readonly AnyLeafLowProfile[],\n ) {\n let cfg: NodeCfg | undefined\n let leaves: readonly AnyLeafLowProfile[] | undefined\n\n if (Array.isArray(cfgOrLeaves)) {\n leaves = cfgOrLeaves\n } else {\n cfg = cfgOrLeaves as NodeCfg | undefined\n leaves = maybeLeaves\n }\n\n if (!leaves) {\n throw new Error('sub() expects a leaves array as the last argument')\n }\n\n const childInherited: NodeCfg = {\n ...inheritedCfg,\n ...(cfg ?? {}),\n }\n\n const baseForChildren = `${currentBase}/${name}`\n\n for (const leafLow of leaves) {\n const leaf = leafLow as unknown as AnyLeaf\n const leafCfg = leaf.cfg as MethodCfg\n const leafParams = leafCfg.paramsSchema as ZodTypeAny | undefined\n\n const effectiveParams = mergeSchemas(\n currentParamsSchema as any,\n leafParams,\n )\n\n const newCfg: MethodCfg = {\n ...childInherited,\n ...leafCfg,\n }\n\n if (effectiveParams) {\n newCfg.paramsSchema = effectiveParams\n } else if ('paramsSchema' in newCfg) {\n delete newCfg.paramsSchema\n }\n\n const newLeaf: AnyLeaf = {\n method: leaf.method,\n path: `${baseForChildren}${leaf.path}` as string,\n cfg: newCfg,\n }\n\n stack.push(newLeaf)\n }\n\n return api\n },\n\n /**\n * Introduce a :param segment and mount a subtree under it.\n *\n * The subtree is built independently (e.g. resource('').get(...).done())\n * and its paths become `${currentBase}/:${name}${leaf.path}`.\n * Params schemas are intersected with the accumulated params plus the new param.\n */\n routeParameter<Name extends string, P extends ZodTypeAny>(\n name: Name,\n paramsSchema: P,\n leaves: readonly AnyLeafLowProfile[],\n ) {\n const paramObj: ParamZod<Name, P> = z.object({\n [name]: paramsSchema,\n } as Record<Name, P>)\n\n const mergedParams = (\n currentParamsSchema\n ? mergeSchemas(currentParamsSchema as any, paramObj)\n : paramObj\n ) as PS2\n\n const baseForChildren = `${currentBase}/:${name}`\n\n for (const leafLow of leaves) {\n const leaf = leafLow as unknown as AnyLeaf\n const leafCfg = leaf.cfg as MethodCfg\n const leafParams = leafCfg.paramsSchema as ZodTypeAny | undefined\n\n const effectiveParams = mergeSchemas(mergedParams as any, leafParams)\n\n const newCfg: MethodCfg = {\n ...inheritedCfg,\n ...leafCfg,\n }\n\n if (effectiveParams) {\n newCfg.paramsSchema = effectiveParams\n } else if ('paramsSchema' in newCfg) {\n delete newCfg.paramsSchema\n }\n\n const newLeaf: AnyLeaf = {\n method: leaf.method,\n path: `${baseForChildren}${leaf.path}` as string,\n cfg: newCfg,\n }\n\n stack.push(newLeaf)\n }\n\n return api\n },\n\n // methods (inject current params schema if missing)\n get<C extends MethodCfg>(cfg: C) {\n return add('get', cfg)\n },\n post<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('post', { ...cfg, feed: false })\n },\n put<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('put', { ...cfg, feed: false })\n },\n patch<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('patch', { ...cfg, feed: false })\n },\n delete<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('delete', { ...cfg, feed: false })\n },\n\n done() {\n return stack as readonly AnyLeafLowProfile[]\n },\n }\n\n return api as Branch<Base2, readonly [], I2, PS2>\n }\n\n // Root branch starts with no params schema (PS = undefined)\n return makeBranch(rootBase, rootInherited, undefined)\n}\n\n/**\n * Merge two readonly tuples (preserves literal tuple information).\n * @param arr1 First tuple.\n * @param arr2 Second tuple.\n * @returns New tuple containing values from both inputs.\n */\nexport const mergeArrays = <T extends readonly any[], S extends readonly any[]>(\n arr1: T,\n arr2: S,\n) => {\n return [...arr1, ...arr2] as [...T, ...S]\n}\n","import { z, ZodType } from 'zod'\n\n/** Supported HTTP verbs for the routes DSL. */\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\n/** Declarative description of a multipart upload field. */\nexport type FileField = {\n /** Form field name used for uploads. */\n name: string\n /** Maximum number of files accepted for this field. */\n maxCount: number\n}\n\n/** Configuration that applies to an entire branch of the route tree. */\nexport type NodeCfg = {\n /** @deprecated. Does nothing. */\n authenticated?: boolean\n}\n\nexport type RouteSchema<Output = unknown> = ZodType & {\n __out: Output\n}\n\nexport type RouteSchemaOutput<Schema extends ZodType> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport const lowProfileParse = <T extends RouteSchema>(\n schema: T,\n data: unknown,\n): RouteSchemaOutput<T> => {\n return schema.parse(data) as RouteSchemaOutput<T>\n}\n\nexport type ToRouteSchema<S> = S extends ZodType ? RouteSchema<z.output<S>> : S\n\nexport type LowProfileCfg<Cfg extends MethodCfg> = Prettify<\n Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {\n bodySchema: ToRouteSchema<Cfg['bodySchema']>\n querySchema: ToRouteSchema<Cfg['querySchema']>\n paramsSchema: ToRouteSchema<Cfg['paramsSchema']>\n outputSchema: ToRouteSchema<Cfg['outputSchema']>\n }\n>\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = {\n /** Zod schema describing the request body. */\n bodySchema?: ZodType\n /** Zod schema describing the query string. */\n querySchema?: ZodType\n /** Zod schema describing path params (Internal only, set through sub and routeParameter). */\n paramsSchema?: ZodType\n /** Zod schema describing the response payload. */\n outputSchema?: ZodType\n /** Multipart upload definitions for the route. */\n bodyFiles?: FileField[]\n /** Marks the route as an infinite feed (enables cursor helpers). */\n feed?: boolean\n\n /** Optional human-readable description for docs/debugging. */\n description?: string\n /**\n * Short one-line summary for docs list views.\n * Shown in navigation / tables instead of the full description.\n */\n summary?: string\n /**\n * Group name used for navigation sections in docs UIs.\n * e.g. \"Users\", \"Billing\", \"Auth\".\n */\n docsGroup?: string\n /**\n * Tags that can be used to filter / facet in interactive docs.\n * e.g. [\"users\", \"admin\", \"internal\"].\n */\n tags?: string[]\n /**\n * Mark the route as deprecated in docs.\n * Renderers can badge this and/or hide by default.\n */\n deprecated?: boolean\n /**\n * Optional stability information for the route.\n * Renderers can surface this prominently.\n */\n stability?: 'experimental' | 'beta' | 'stable' | 'deprecated'\n /**\n * Hide this route from public docs while keeping it usable in code.\n */\n docsHidden?: boolean\n /**\n * Arbitrary extra metadata for docs renderers.\n * Can be used for auth requirements, rate limits, feature flags, etc.\n */\n docsMeta?: Record<string, unknown>\n}\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfg,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n\n/** Convenience union covering all generated leaves. */\nexport type AnyLeaf = Leaf<HttpMethod, string, MethodCfg>\n\n/** Merge two object types while keeping nice IntelliSense output. */\nexport type Merge<A, B> = Prettify<Omit<A, keyof B> & B>\n\n/** Append a new element to a readonly tuple type. */\nexport type Append<T extends readonly unknown[], X> = [...T, X]\n\n/** Concatenate two readonly tuple types. */\nexport type MergeArray<\n A extends readonly unknown[],\n B extends readonly unknown[],\n> = [...A, ...B]\nexport type IntersectZod<\n A extends ZodType | undefined,\n B extends ZodType | undefined,\n> = B extends ZodType\n ? A extends ZodType\n ? z.ZodIntersection<A, B>\n : B\n : A extends ZodType\n ? A\n : undefined\n\ntype RouteSchemaFromZod<T extends ZodType | undefined> = T extends ZodType\n ? RouteSchema<z.output<T>>\n : undefined\n\ntype MergeRouteSchemas<\n Existing extends RouteSchema | undefined,\n Parent extends ZodType | undefined,\n> =\n Existing extends RouteSchema<infer ExistingOut>\n ? Parent extends ZodType\n ? RouteSchema<ExistingOut & z.output<Parent>>\n : Existing\n : Parent extends ZodType\n ? RouteSchemaFromZod<Parent>\n : undefined\n\ntype AugmentedCfg<\n Cfg extends MethodCfgLowProfile,\n Param extends ZodType | undefined,\n> = Prettify<\n Omit<Cfg, 'paramsSchema'> & {\n paramsSchema: MergeRouteSchemas<Cfg['paramsSchema'], Param>\n }\n>\n\nexport type AugmentLeaves<\n P extends string,\n Param extends ZodType | undefined,\n R extends readonly LeafLowProfile[],\n Acc extends readonly LeafLowProfile[] = [],\n> = R extends readonly [infer First, ...infer Rest]\n ? First extends LeafLowProfile\n ? AugmentLeaves<\n P,\n Param,\n Rest extends readonly LeafLowProfile[] ? Rest : [],\n Append<\n Acc,\n LeafLowProfile<\n First['method'],\n `${P}${First['path']}`,\n AugmentedCfg<First['cfg'], Param>\n >\n >\n >\n : never\n : Acc\n// helpers (optional)\ntype SegmentParams<S extends string> = S extends `:${infer P}` ? P : never\ntype Split<S extends string> = S extends ''\n ? []\n : S extends `${infer A}/${infer B}`\n ? [A, ...Split<B>]\n : [S]\ntype ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>\n\n/** Derive a params object type from a literal route string. */\nexport type ExtractParamsFromPath<Path extends string> =\n ExtractParamNames<Path> extends never\n ? never\n : Record<ExtractParamNames<Path>, string | number>\n\n/**\n * Interpolate `:params` in a path using the given values.\n * @param path Literal route string containing `:param` segments.\n * @param params Object of parameter values to interpolate.\n * @returns Path string with parameters substituted.\n */\nexport function compilePath<Path extends string>(\n path: Path,\n params: ExtractParamsFromPath<Path>,\n) {\n if (!params) return path\n return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {\n const v = (params as any)[k]\n if (v === undefined || v === null) throw new Error(`Missing param :${k}`)\n return String(v)\n })\n}\n\n/**\n * Build a deterministic cache key for the given leaf.\n * The key matches the shape consumed by React Query helpers.\n * @param args.leaf Leaf describing the endpoint.\n * @param args.params Optional params used to build the path.\n * @param args.query Optional query payload.\n * @returns Tuple suitable for React Query cache keys.\n */\ntype SplitPath<P extends string> = P extends ''\n ? []\n : P extends `${infer A}/${infer B}`\n ? [A, ...SplitPath<B>]\n : [P]\nexport function buildCacheKey<L extends AnyLeaf>(args: {\n leaf: L\n params?: ExtractParamsFromPath<L['path']>\n query?: InferQuery<L>\n}) {\n let p = args.leaf.path\n if (args.params) {\n p = compilePath<L['path']>(p, args.params)\n }\n return [\n args.leaf.method,\n ...(p.split('/').filter(Boolean) as SplitPath<typeof p>),\n args.query ?? {},\n ] as const\n}\n\n/** Definition-time method config (for clarity). */\nexport type MethodCfgDef = MethodCfg\n\n/** Low-profile method config where schemas carry a phantom __out like SocketSchema. */\nexport type MethodCfgLowProfile = Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n> & {\n bodySchema?: RouteSchema\n querySchema?: RouteSchema\n paramsSchema?: RouteSchema\n outputSchema?: RouteSchema\n}\nexport type AnyLeafLowProfile = LeafLowProfile<\n HttpMethod,\n string,\n MethodCfgLowProfile\n>\n\nexport function buildLowProfileLeaf<\n const M extends HttpMethod,\n const Path extends string,\n const O extends ZodType | undefined = undefined,\n const P extends ZodType | undefined = undefined,\n const Q extends ZodType | undefined = undefined,\n const B extends ZodType | undefined = undefined,\n const Feed extends boolean = false,\n>(leaf: {\n method: M\n path: Path\n cfg: Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n > & {\n feed?: Feed\n bodySchema?: B\n querySchema?: Q\n paramsSchema?: P\n outputSchema?: O\n }\n}): LeafLowProfile<\n M,\n Path,\n Prettify<\n Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed'\n > & {\n feed: Feed\n bodySchema: B extends ZodType ? RouteSchema<z.infer<B>> : undefined\n querySchema: Q extends ZodType ? RouteSchema<z.infer<Q>> : undefined\n paramsSchema: P extends ZodType ? RouteSchema<z.infer<P>> : undefined\n outputSchema: O extends ZodType ? RouteSchema<z.infer<O>> : undefined\n }\n >\n>\nexport function buildLowProfileLeaf(leaf: any): any {\n return {\n ...leaf,\n cfg: {\n ...leaf.cfg,\n bodySchema: leaf.cfg.bodySchema as RouteSchema<\n z.infer<typeof leaf.cfg.bodySchema>\n >,\n querySchema: leaf.cfg.querySchema as RouteSchema<\n z.infer<typeof leaf.cfg.querySchema>\n >,\n paramsSchema: leaf.cfg.paramsSchema as RouteSchema<\n z.infer<typeof leaf.cfg.paramsSchema>\n >,\n outputSchema: leaf.cfg.outputSchema as RouteSchema<\n z.infer<typeof leaf.cfg.outputSchema>\n >,\n },\n }\n}\n\nexport type LeafLowProfile<\n M extends HttpMethod = HttpMethod,\n P extends string = string,\n C extends MethodCfgLowProfile = MethodCfgLowProfile,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<L extends AnyLeafLowProfile, Fallback = never> =\n L['cfg']['paramsSchema'] extends RouteSchema<infer P> ? P : Fallback\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<L extends AnyLeaf, Fallback = never> =\n L['cfg']['querySchema'] extends RouteSchema<infer Q> ? Q : Fallback\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<L extends AnyLeaf, Fallback = never> =\n L['cfg']['bodySchema'] extends RouteSchema<infer B> ? B : Fallback\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<L extends AnyLeaf, Fallback = never> =\n L['cfg']['outputSchema'] extends RouteSchema<infer O> ? O : Fallback\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {}\n","// TODO:\n// * use this as a transform that infers the types from Zod, to only pass the types around instead of the full schemas -> make converters that go both ways (data to schema, schema to data)\n// * consider moving to core package\n// * update server and client side to use this type instead of raw zod schemas\nimport { AnyLeafLowProfile, HttpMethod, Prettify } from './routesV3.core'\n\n/** Build the key type for a leaf — distributive so method/path stay paired. */\nexport type KeyOf<L extends AnyLeafLowProfile> = L extends AnyLeafLowProfile\n ? `${Uppercase<L['method']>} ${L['path']}`\n : never\n\n// From a tuple of leaves, get the union of keys (pairwise, no cartesian blow-up)\ntype KeysOf<Leaves extends readonly AnyLeafLowProfile[]> = KeyOf<Leaves[number]>\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}`\n ? Lowercase<M>\n : never\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}`\n ? P\n : never\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<\n Leaves extends readonly AnyLeafLowProfile[],\n K extends string,\n> = Extract<\n Leaves[number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>\n\n/**\n * Freeze a leaf tuple into a registry with typed key lookups.\n * @param leaves Readonly tuple of leaves produced by the builder DSL.\n * @returns Registry containing the leaves array and a `byKey` lookup map.\n */\nexport function finalize<const L extends readonly AnyLeafLowProfile[]>(\n leaves: L,\n) {\n type Keys = KeysOf<L>\n type MapByKey = { [K in Keys]: Prettify<LeafForKey<L, K>> }\n\n const byKey = Object.fromEntries(\n leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l] as const),\n ) as unknown as MapByKey\n\n const log = (logger: { system: (...args: unknown[]) => void }) => {\n logger.system('Finalized routes:')\n ;(Object.keys(byKey) as Keys[]).forEach((k) => {\n const leaf = byKey[k]\n logger.system(`- ${k}`)\n })\n }\n\n return { all: leaves, byKey, log }\n}\n\n/** Nominal type alias for a finalized registry. */\nexport type Registry<R extends ReturnType<typeof finalize>> = R\n\ntype FilterRoute<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n Acc extends readonly AnyLeafLowProfile[] = [],\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? First extends { path: `${string}${F}${string}` }\n ? FilterRoute<Rest, F, [...Acc, First]>\n : FilterRoute<Rest, F, Acc>\n : Acc\n\ntype UpperCase<S extends string> = S extends `${infer First}${infer Rest}`\n ? `${Uppercase<First>}${UpperCase<Rest>}`\n : S\n\ntype Routes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = FilterRoute<T, F>\ntype ByKey<\n T extends readonly AnyLeafLowProfile[],\n Acc extends Record<string, AnyLeafLowProfile> = {},\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? ByKey<\n Rest,\n Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }\n >\n : Acc\n\n/**\n * Convenience helper for extracting a subset of routes by path fragment.\n * @param T Tuple of leaves produced by the DSL.\n * @param F String fragment to match against the route path.\n */\nexport type SubsetRoutes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = {\n byKey: ByKey<Routes<T, F>>\n all: Routes<T, F>\n}\n","// socket.index.ts (shared client/server)\n\nimport { z } from 'zod'\n\nexport type SocketSchema<Output = unknown> = z.ZodType & {\n __out: Output\n}\n\nexport type SocketSchemaOutput<Schema extends z.ZodTypeAny> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport type SocketEvent<Out = unknown> = {\n message: z.ZodTypeAny\n /** phantom field, only for typing; not meant to be used at runtime */\n __out: Out\n}\n\nexport type EventMap = Record<string, SocketEvent<any>>\n\n/**\n * System event names – shared so server and client\n * don't drift on naming.\n */\nexport type SysEventName =\n | 'sys:connect'\n | 'sys:disconnect'\n | 'sys:reconnect'\n | 'sys:connect_error'\n | 'sys:ping'\n | 'sys:pong'\n | 'sys:room_join'\n | 'sys:room_leave'\nexport function defineSocketEvents<\n const C extends SocketConnectionConfig,\n const Schemas extends Record<\n string,\n {\n message: z.ZodTypeAny\n }\n >,\n>(\n config: C,\n events: Schemas,\n): {\n config: {\n [K in keyof C]: SocketSchema<z.output<C[K]>>\n }\n events: {\n [K in keyof Schemas]: SocketEvent<z.output<Schemas[K]['message']>>\n }\n}\n\nexport function defineSocketEvents(config: any, events: any) {\n return { config, events }\n}\n\nexport type Payload<T extends EventMap, K extends keyof T> =\n T[K] extends SocketEvent<infer Out> ? Out : never\nexport type SocketConnectionConfig = {\n joinMetaMessage: z.ZodTypeAny\n leaveMetaMessage: z.ZodTypeAny\n pingPayload: z.ZodTypeAny\n pongPayload: z.ZodTypeAny\n}\n\nexport type SocketConnectionConfigOutput = {\n joinMetaMessage: SocketSchema<any>\n leaveMetaMessage: SocketSchema<any>\n pingPayload: SocketSchema<any>\n pongPayload: SocketSchema<any>\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
CHANGED
|
@@ -81,7 +81,7 @@ function mergeSchemas(a, b) {
|
|
|
81
81
|
return a ?? b;
|
|
82
82
|
}
|
|
83
83
|
function resource(base, inherited) {
|
|
84
|
-
const rootBase = base;
|
|
84
|
+
const rootBase = base ?? "";
|
|
85
85
|
const rootInherited = { ...inherited };
|
|
86
86
|
function makeBranch(base2, inherited2, mergedParamsSchema) {
|
|
87
87
|
const stack = [];
|
|
@@ -113,47 +113,90 @@ function resource(base, inherited) {
|
|
|
113
113
|
return api;
|
|
114
114
|
}
|
|
115
115
|
const api = {
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Mount a subtree built elsewhere.
|
|
118
|
+
*
|
|
119
|
+
* Usage:
|
|
120
|
+
* const users = resource('').get(...).done()
|
|
121
|
+
* resource('/api').sub('users', users).done()
|
|
122
|
+
*/
|
|
123
|
+
sub(name, cfgOrLeaves, maybeLeaves) {
|
|
118
124
|
let cfg;
|
|
119
|
-
let
|
|
120
|
-
if (
|
|
121
|
-
|
|
125
|
+
let leaves;
|
|
126
|
+
if (Array.isArray(cfgOrLeaves)) {
|
|
127
|
+
leaves = cfgOrLeaves;
|
|
122
128
|
} else {
|
|
123
|
-
cfg =
|
|
124
|
-
|
|
129
|
+
cfg = cfgOrLeaves;
|
|
130
|
+
leaves = maybeLeaves;
|
|
125
131
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
if (!leaves) {
|
|
133
|
+
throw new Error("sub() expects a leaves array as the last argument");
|
|
134
|
+
}
|
|
135
|
+
const childInherited = {
|
|
136
|
+
...inheritedCfg,
|
|
137
|
+
...cfg ?? {}
|
|
138
|
+
};
|
|
139
|
+
const baseForChildren = `${currentBase}/${name}`;
|
|
140
|
+
for (const leafLow of leaves) {
|
|
141
|
+
const leaf = leafLow;
|
|
142
|
+
const leafCfg = leaf.cfg;
|
|
143
|
+
const leafParams = leafCfg.paramsSchema;
|
|
144
|
+
const effectiveParams = mergeSchemas(
|
|
145
|
+
currentParamsSchema,
|
|
146
|
+
leafParams
|
|
133
147
|
);
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
148
|
+
const newCfg = {
|
|
149
|
+
...childInherited,
|
|
150
|
+
...leafCfg
|
|
151
|
+
};
|
|
152
|
+
if (effectiveParams) {
|
|
153
|
+
newCfg.paramsSchema = effectiveParams;
|
|
154
|
+
} else if ("paramsSchema" in newCfg) {
|
|
155
|
+
delete newCfg.paramsSchema;
|
|
156
|
+
}
|
|
157
|
+
const newLeaf = {
|
|
158
|
+
method: leaf.method,
|
|
159
|
+
path: `${baseForChildren}${leaf.path}`,
|
|
160
|
+
cfg: newCfg
|
|
161
|
+
};
|
|
162
|
+
stack.push(newLeaf);
|
|
141
163
|
}
|
|
164
|
+
return api;
|
|
142
165
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Introduce a :param segment and mount a subtree under it.
|
|
168
|
+
*
|
|
169
|
+
* The subtree is built independently (e.g. resource('').get(...).done())
|
|
170
|
+
* and its paths become `${currentBase}/:${name}${leaf.path}`.
|
|
171
|
+
* Params schemas are intersected with the accumulated params plus the new param.
|
|
172
|
+
*/
|
|
173
|
+
routeParameter(name, paramsSchema, leaves) {
|
|
146
174
|
const paramObj = z.object({
|
|
147
175
|
[name]: paramsSchema
|
|
148
176
|
});
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
177
|
+
const mergedParams = currentParamsSchema ? mergeSchemas(currentParamsSchema, paramObj) : paramObj;
|
|
178
|
+
const baseForChildren = `${currentBase}/:${name}`;
|
|
179
|
+
for (const leafLow of leaves) {
|
|
180
|
+
const leaf = leafLow;
|
|
181
|
+
const leafCfg = leaf.cfg;
|
|
182
|
+
const leafParams = leafCfg.paramsSchema;
|
|
183
|
+
const effectiveParams = mergeSchemas(mergedParams, leafParams);
|
|
184
|
+
const newCfg = {
|
|
185
|
+
...inheritedCfg,
|
|
186
|
+
...leafCfg
|
|
187
|
+
};
|
|
188
|
+
if (effectiveParams) {
|
|
189
|
+
newCfg.paramsSchema = effectiveParams;
|
|
190
|
+
} else if ("paramsSchema" in newCfg) {
|
|
191
|
+
delete newCfg.paramsSchema;
|
|
192
|
+
}
|
|
193
|
+
const newLeaf = {
|
|
194
|
+
method: leaf.method,
|
|
195
|
+
path: `${baseForChildren}${leaf.path}`,
|
|
196
|
+
cfg: newCfg
|
|
197
|
+
};
|
|
198
|
+
stack.push(newLeaf);
|
|
199
|
+
}
|
|
157
200
|
return api;
|
|
158
201
|
},
|
|
159
202
|
// methods (inject current params schema if missing)
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/sockets/socket.index.ts"],"sourcesContent":["import { z } from 'zod'\nimport {\n AnyLeaf,\n AnyLeafLowProfile,\n Append,\n HttpMethod,\n Leaf,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n RouteSchema,\n} from './routesV3.core'\n\nconst paginationQueryShape = {\n pagination_cursor: z.string().optional(),\n pagination_limit: z.coerce.number().min(1).max(100).default(20),\n}\n\nconst defaultFeedQuerySchema = z.object(paginationQueryShape)\ntype PaginationShape = typeof paginationQueryShape\n\ntype ZodTypeAny = z.ZodTypeAny\ntype ParamZod<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S\n}>\ntype ZodArrayAny = z.ZodArray<ZodTypeAny>\ntype ZodObjectAny = z.ZodObject<any>\ntype AnyZodObject = z.ZodObject<any>\ntype MergedParamsResult<\n PS,\n Name extends string,\n P extends ZodTypeAny,\n> = PS extends ZodTypeAny\n ? z.ZodIntersection<PS, ParamZod<Name, P>>\n : ParamZod<Name, P>\n\nfunction getZodShape(schema: ZodObjectAny) {\n const shapeOrGetter = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n if (!shapeOrGetter) return {}\n return typeof shapeOrGetter === 'function'\n ? shapeOrGetter.call(schema)\n : shapeOrGetter\n}\n\nfunction collectNestedFieldSuggestions(\n shape: Record<string, ZodTypeAny> | undefined,\n prefix: string[] = [],\n): string[] {\n if (!shape) return []\n const suggestions: string[] = []\n for (const [key, value] of Object.entries(shape)) {\n if (value instanceof z.ZodObject) {\n const nestedShape = getZodShape(value as ZodObjectAny)\n const nestedSuggestions = collectNestedFieldSuggestions(nestedShape, [\n ...prefix,\n key,\n ])\n suggestions.push(\n ...(nestedSuggestions.length\n ? nestedSuggestions\n : [[...prefix, key].join('_')]),\n )\n } else if (prefix.length > 0) {\n suggestions.push([...prefix, key].join('_'))\n }\n }\n return suggestions\n}\n\nconst defaultFeedOutputSchema = z.object({\n items: z.array(z.unknown()),\n nextCursor: z.string().optional(),\n})\n\nfunction augmentFeedQuerySchema<Q extends ZodTypeAny | undefined>(schema: Q) {\n if (schema && !(schema instanceof z.ZodObject)) {\n console.warn(\n 'Feed queries must be a ZodObject; default pagination applied.',\n )\n return defaultFeedQuerySchema\n }\n\n const base = (schema as ZodObjectAny) ?? z.object({})\n const shape = getZodShape(base)\n const nestedSuggestions = collectNestedFieldSuggestions(shape)\n if (nestedSuggestions.length) {\n console.warn(\n `Feed query schemas should avoid nested objects; consider flattening fields like: ${nestedSuggestions.join(\n ', ',\n )}`,\n )\n }\n return base.extend(paginationQueryShape)\n}\n\nfunction augmentFeedOutputSchema<O extends ZodTypeAny | undefined>(schema: O) {\n if (!schema) return defaultFeedOutputSchema\n if (schema instanceof z.ZodArray) {\n return z.object({\n items: schema,\n nextCursor: z.string().optional(),\n })\n }\n if (schema instanceof z.ZodObject) {\n const shape = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n const hasItems = Boolean(shape?.items)\n if (hasItems) {\n return schema.extend({ nextCursor: z.string().optional() })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n}\n\n/**\n * Runtime helper that mirrors the typed merge used by the builder.\n * @param a Previously merged params schema inherited from parent segments.\n * @param b Newly introduced params schema.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nfunction mergeSchemas<A extends ZodTypeAny, B extends ZodTypeAny>(\n a: A,\n b: B,\n): ZodTypeAny\nfunction mergeSchemas<A extends ZodTypeAny>(a: A, b: undefined): A\nfunction mergeSchemas<B extends ZodTypeAny>(a: undefined, b: B): B\nfunction mergeSchemas(\n a: ZodTypeAny | undefined,\n b: ZodTypeAny | undefined,\n): ZodTypeAny | undefined\nfunction mergeSchemas(a: ZodTypeAny | undefined, b: ZodTypeAny | undefined) {\n if (a && b) return z.intersection(a as any, b as any)\n return (a ?? b) as ZodTypeAny | undefined\n}\n\ntype FeedOutputSchema<C extends MethodCfg> =\n C['outputSchema'] extends ZodArrayAny\n ? z.ZodObject<{\n items: C['outputSchema']\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : C['outputSchema'] extends ZodTypeAny\n ? z.ZodObject<{\n items: z.ZodArray<C['outputSchema']>\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : typeof defaultFeedOutputSchema\n\ntype BaseMethodCfg<C extends MethodCfg> = Merge<\n Omit<MethodCfg, 'querySchema' | 'outputSchema' | 'feed'>,\n Omit<C, 'querySchema' | 'outputSchema' | 'feed'>\n>\n\ntype FeedField<C extends MethodCfg> = C['feed'] extends true\n ? { feed: true }\n : { feed?: boolean }\n\ntype AddPaginationToQuery<Q extends AnyZodObject | undefined> =\n Q extends z.ZodObject<infer Shape>\n ? z.ZodObject<Shape & PaginationShape>\n : z.ZodObject<PaginationShape>\n\ntype FeedQueryField<C extends MethodCfg> = {\n querySchema: AddPaginationToQuery<\n C['querySchema'] extends AnyZodObject ? C['querySchema'] : undefined\n >\n}\n\ntype NonFeedQueryField<C extends MethodCfg> =\n C['querySchema'] extends ZodTypeAny\n ? { querySchema: C['querySchema'] }\n : { querySchema?: undefined }\n\ntype FeedOutputField<C extends MethodCfg> = {\n outputSchema: FeedOutputSchema<C>\n}\n\ntype NonFeedOutputField<C extends MethodCfg> =\n C['outputSchema'] extends ZodTypeAny\n ? { outputSchema: C['outputSchema'] }\n : { outputSchema?: undefined }\n\ntype ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny\n ? { paramsSchema: C['paramsSchema'] }\n : { paramsSchema: PS }\n\ntype EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true\n ? FeedField<C> & FeedQueryField<C> & FeedOutputField<C> & ParamsField<C, PS>\n : FeedField<C> &\n NonFeedQueryField<C> &\n NonFeedOutputField<C> &\n ParamsField<C, PS>\n\ntype EffectiveCfg<C extends MethodCfg, PS> = Prettify<\n Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>\n>\n\ntype ToRouteSchema<S> = S extends ZodTypeAny ? RouteSchema<z.output<S>> : S\n\ntype LowProfileCfg<Cfg extends MethodCfg> = Prettify<\n Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {\n bodySchema: ToRouteSchema<Cfg['bodySchema']>\n querySchema: ToRouteSchema<Cfg['querySchema']>\n paramsSchema: ToRouteSchema<Cfg['paramsSchema']>\n outputSchema: ToRouteSchema<Cfg['outputSchema']>\n }\n>\n\ntype BuiltLeaf<\n M extends HttpMethod,\n Base extends string,\n I extends NodeCfg,\n C extends MethodCfg,\n PS,\n> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n> {\n // --- structure ---\n /**\n * Enter or define a static child segment.\n * Optionally accepts extra config and/or a builder to populate nested routes.\n * @param name Child segment literal (no leading slash).\n * @param cfg Optional node configuration to merge for descendants.\n * @param builder Callback to produce leaves for the child branch.\n */\n sub<Name extends string, J extends NodeCfg>(\n name: Name,\n cfg?: J,\n ): Branch<`${Base}/${Name}`, Acc, Merge<I, NonNullable<J>>, PS>\n\n sub<\n Name extends string,\n J extends NodeCfg | undefined,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n cfg: J,\n builder: (\n r: Branch<`${Base}/${Name}`, readonly [], Merge<I, NonNullable<J>>, PS>,\n ) => R,\n ): Branch<Base, Append<Acc, R[number]>, Merge<I, NonNullable<J>>, PS>\n\n sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(\n name: Name,\n builder: (r: Branch<`${Base}/${Name}`, readonly [], I, PS>) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, PS>\n\n // --- parameterized segment (single signature) ---\n /**\n * Introduce a `:param` segment and merge its schema into downstream leaves.\n * @param name Parameter key (without leading colon).\n * @param paramsSchema Zod schema for the parameter value.\n * @param builder Callback that produces leaves beneath the parameterized segment.\n */\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<\n `${Base}/:${Name}`,\n readonly [],\n I,\n MergedParamsResult<PS, Name, P>\n >,\n ) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, MergedParamsResult<PS, Name, P>>\n\n // --- flags inheritance ---\n /**\n * Merge additional node configuration that subsequent leaves will inherit.\n * @param cfg Partial node configuration.\n */\n with<J extends NodeCfg>(cfg: J): Branch<Base, Acc, Merge<I, J>, PS>\n\n // --- methods (return Branch to keep chaining) ---\n /**\n * Register a GET leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n get<C extends MethodCfg>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Prettify<BuiltLeaf<'get', Base, I, C, PS>>>,\n I,\n PS\n >\n\n /**\n * Register a POST leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n post<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'post', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a PUT leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n put<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'put', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a PATCH leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n patch<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'patch', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n /**\n * Register a DELETE leaf at the current path.\n * @param cfg Method configuration (schemas, flags, descriptions, etc).\n */\n delete<C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'delete', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS\n >\n\n // --- finalize this subtree ---\n /**\n * Finish the branch and return the collected leaves.\n * @returns Readonly tuple of accumulated leaves.\n */\n done(): Readonly<Acc>\n}\n\n/**\n * Start building a resource at the given base path.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Optional node configuration applied to all descendants.\n * @returns Root `Branch` instance used to compose the route tree.\n */\nexport function resource<Base extends string, I extends NodeCfg = {}>(\n base: Base,\n inherited?: I,\n): Branch<Base, readonly [], I, undefined> {\n const rootBase = base\n const rootInherited: NodeCfg = { ...(inherited as NodeCfg) }\n\n function makeBranch<\n Base2 extends string,\n I2 extends NodeCfg,\n PS2 extends ZodTypeAny | undefined,\n >(base2: Base2, inherited2: I2, mergedParamsSchema?: PS2) {\n const stack: AnyLeaf[] = []\n let currentBase: string = base2\n let inheritedCfg: NodeCfg = { ...(inherited2 as NodeCfg) }\n let currentParamsSchema: PS2 = mergedParamsSchema as PS2\n\n function add<M extends HttpMethod, C extends MethodCfg>(method: M, cfg: C) {\n // If the method didn’t provide a paramsSchema, inject the active merged one.\n const effectiveParamsSchema = (cfg.paramsSchema ??\n currentParamsSchema) as PS2 | undefined\n const effectiveQuerySchema =\n cfg.feed === true\n ? augmentFeedQuerySchema(cfg.querySchema)\n : cfg.querySchema\n const effectiveOutputSchema =\n cfg.feed === true\n ? augmentFeedOutputSchema(cfg.outputSchema)\n : cfg.outputSchema\n\n const fullCfg = (\n effectiveParamsSchema\n ? {\n ...inheritedCfg,\n ...cfg,\n paramsSchema: effectiveParamsSchema,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n : {\n ...inheritedCfg,\n ...cfg,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n ) as any\n\n const leaf = {\n method,\n path: currentBase as Base2,\n cfg: fullCfg,\n } as const\n\n stack.push(leaf as unknown as AnyLeaf)\n\n // Return same runtime obj, but with Acc including this new leaf\n return api as unknown as Branch<\n Base2,\n Append<readonly [], typeof leaf>,\n I2,\n PS2\n >\n }\n\n const api: any = {\n // compose a plain subpath (optional cfg) with optional builder\n sub<Name extends string, J extends NodeCfg | undefined = undefined>(\n name: Name,\n cfgOrBuilder?: J | ((r: any) => readonly AnyLeafLowProfile[]),\n maybeBuilder?: (r: any) => readonly AnyLeafLowProfile[],\n ) {\n let cfg: NodeCfg | undefined\n let builder: ((r: any) => readonly AnyLeafLowProfile[]) | undefined\n\n if (typeof cfgOrBuilder === 'function') {\n builder = cfgOrBuilder as any\n } else {\n cfg = cfgOrBuilder as NodeCfg | undefined\n builder = maybeBuilder\n }\n\n const childBase = `${currentBase}/${name}` as const\n const childInherited = { ...inheritedCfg, ...(cfg ?? {}) } as Merge<\n I2,\n NonNullable<J>\n >\n\n if (builder) {\n // params schema PS2 flows through unchanged on plain sub\n const child = makeBranch(\n childBase,\n childInherited,\n currentParamsSchema,\n )\n const leaves = builder(child) //as readonly AnyLeafLowProfile[]\n for (const l of leaves) stack.push(l)\n return api as Branch<\n Base2,\n Append<readonly [], (typeof leaves)[number]>,\n typeof childInherited,\n PS2\n >\n } else {\n currentBase = childBase\n inheritedCfg = childInherited\n return api as Branch<\n `${Base2}/${Name}`,\n readonly [],\n typeof childInherited,\n PS2\n >\n }\n },\n\n // the single param function: name + schema + builder\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<\n `${Base2}/:${Name}`,\n readonly [],\n I2,\n MergedParamsResult<PS2, Name, P>\n >,\n ) => R,\n ) {\n const childBase = `${currentBase}/:${name}` as const\n // Wrap the value schema under the param name before merging with inherited params schema\n const paramObj: ParamZod<Name, P> = z.object({\n [name]: paramsSchema,\n } as Record<Name, P>)\n const childParams = (\n currentParamsSchema\n ? mergeSchemas(currentParamsSchema, paramObj)\n : paramObj\n ) as MergedParamsResult<PS2, Name, P>\n const child = makeBranch(childBase, inheritedCfg as I2, childParams)\n const leaves = builder(child)\n for (const l of leaves) stack.push(l)\n return api as Branch<\n Base2,\n Append<readonly [], (typeof leaves)[number]>,\n I2,\n MergedParamsResult<PS2, Name, P>\n >\n },\n\n with<J extends NodeCfg>(cfg: J) {\n inheritedCfg = { ...inheritedCfg, ...cfg }\n return api as Branch<Base2, readonly [], Merge<I2, J>, PS2>\n },\n\n // methods (inject current params schema if missing)\n get<C extends MethodCfg>(cfg: C) {\n return add('get', cfg)\n },\n post<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('post', { ...cfg, feed: false })\n },\n put<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('put', { ...cfg, feed: false })\n },\n patch<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('patch', { ...cfg, feed: false })\n },\n delete<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('delete', { ...cfg, feed: false })\n },\n\n done() {\n return stack\n },\n }\n\n return api as Branch<Base2, readonly [], I2, PS2>\n }\n\n // Root branch starts with no params schema (PS = undefined)\n return makeBranch(rootBase, rootInherited, undefined)\n}\n\n/**\n * Merge two readonly tuples (preserves literal tuple information).\n * @param arr1 First tuple.\n * @param arr2 Second tuple.\n * @returns New tuple containing values from both inputs.\n */\nexport const mergeArrays = <T extends readonly any[], S extends readonly any[]>(\n arr1: T,\n arr2: S,\n) => {\n return [...arr1, ...arr2] as [...T, ...S]\n}\n","import { z, ZodType } from 'zod'\n\n/** Supported HTTP verbs for the routes DSL. */\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\n/** Declarative description of a multipart upload field. */\nexport type FileField = {\n /** Form field name used for uploads. */\n name: string\n /** Maximum number of files accepted for this field. */\n maxCount: number\n}\n\n/** Configuration that applies to an entire branch of the route tree. */\nexport type NodeCfg = {\n /** @deprecated. Does nothing. */\n authenticated?: boolean\n}\n\nexport type RouteSchema<Output = unknown> = ZodType & {\n __out: Output\n}\n\nexport type RouteSchemaOutput<Schema extends ZodType> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport const lowProfileParse = <T extends RouteSchema>(\n schema: T,\n data: unknown,\n): RouteSchemaOutput<T> => {\n return schema.parse(data) as RouteSchemaOutput<T>\n}\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = {\n /** Zod schema describing the request body. */\n bodySchema?: ZodType\n /** Zod schema describing the query string. */\n querySchema?: ZodType\n /** Zod schema describing path params (overrides inferred params). */\n paramsSchema?: ZodType\n /** Zod schema describing the response payload. */\n outputSchema?: ZodType\n /** Multipart upload definitions for the route. */\n bodyFiles?: FileField[]\n /** Marks the route as an infinite feed (enables cursor helpers). */\n feed?: boolean\n\n /** Optional human-readable description for docs/debugging. */\n description?: string\n /**\n * Short one-line summary for docs list views.\n * Shown in navigation / tables instead of the full description.\n */\n summary?: string\n /**\n * Group name used for navigation sections in docs UIs.\n * e.g. \"Users\", \"Billing\", \"Auth\".\n */\n docsGroup?: string\n /**\n * Tags that can be used to filter / facet in interactive docs.\n * e.g. [\"users\", \"admin\", \"internal\"].\n */\n tags?: string[]\n /**\n * Mark the route as deprecated in docs.\n * Renderers can badge this and/or hide by default.\n */\n deprecated?: boolean\n /**\n * Optional stability information for the route.\n * Renderers can surface this prominently.\n */\n stability?: 'experimental' | 'beta' | 'stable' | 'deprecated'\n /**\n * Hide this route from public docs while keeping it usable in code.\n */\n docsHidden?: boolean\n /**\n * Arbitrary extra metadata for docs renderers.\n * Can be used for auth requirements, rate limits, feature flags, etc.\n */\n docsMeta?: Record<string, unknown>\n}\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfg,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n\n/** Convenience union covering all generated leaves. */\nexport type AnyLeaf = Leaf<HttpMethod, string, MethodCfg>\n\n/** Merge two object types while keeping nice IntelliSense output. */\nexport type Merge<A, B> = Prettify<Omit<A, keyof B> & B>\n\n/** Append a new element to a readonly tuple type. */\nexport type Append<T extends readonly unknown[], X> = [...T, X]\n\n/** Concatenate two readonly tuple types. */\nexport type MergeArray<\n A extends readonly unknown[],\n B extends readonly unknown[],\n> = [...A, ...B]\n\n// helpers (optional)\ntype SegmentParams<S extends string> = S extends `:${infer P}` ? P : never\ntype Split<S extends string> = S extends ''\n ? []\n : S extends `${infer A}/${infer B}`\n ? [A, ...Split<B>]\n : [S]\ntype ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>\n\n/** Derive a params object type from a literal route string. */\nexport type ExtractParamsFromPath<Path extends string> =\n ExtractParamNames<Path> extends never\n ? never\n : Record<ExtractParamNames<Path>, string | number>\n\n/**\n * Interpolate `:params` in a path using the given values.\n * @param path Literal route string containing `:param` segments.\n * @param params Object of parameter values to interpolate.\n * @returns Path string with parameters substituted.\n */\nexport function compilePath<Path extends string>(\n path: Path,\n params: ExtractParamsFromPath<Path>,\n) {\n if (!params) return path\n return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {\n const v = (params as any)[k]\n if (v === undefined || v === null) throw new Error(`Missing param :${k}`)\n return String(v)\n })\n}\n\n/**\n * Build a deterministic cache key for the given leaf.\n * The key matches the shape consumed by React Query helpers.\n * @param args.leaf Leaf describing the endpoint.\n * @param args.params Optional params used to build the path.\n * @param args.query Optional query payload.\n * @returns Tuple suitable for React Query cache keys.\n */\ntype SplitPath<P extends string> = P extends ''\n ? []\n : P extends `${infer A}/${infer B}`\n ? [A, ...SplitPath<B>]\n : [P]\nexport function buildCacheKey<L extends AnyLeaf>(args: {\n leaf: L\n params?: ExtractParamsFromPath<L['path']>\n query?: InferQuery<L>\n}) {\n let p = args.leaf.path\n if (args.params) {\n p = compilePath<L['path']>(p, args.params)\n }\n return [\n args.leaf.method,\n ...(p.split('/').filter(Boolean) as SplitPath<typeof p>),\n args.query ?? {},\n ] as const\n}\n\n/** Definition-time method config (for clarity). */\nexport type MethodCfgDef = MethodCfg\n\n/** Low-profile method config where schemas carry a phantom __out like SocketSchema. */\nexport type MethodCfgLowProfile = Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n> & {\n bodySchema?: RouteSchema\n querySchema?: RouteSchema\n paramsSchema?: RouteSchema\n outputSchema?: RouteSchema\n}\nexport type AnyLeafLowProfile = LeafLowProfile<\n HttpMethod,\n string,\n MethodCfgLowProfile\n>\n\nexport function buildLowProfileLeaf<\n const M extends HttpMethod,\n const Path extends string,\n const O extends ZodType | undefined = undefined,\n const P extends ZodType | undefined = undefined,\n const Q extends ZodType | undefined = undefined,\n const B extends ZodType | undefined = undefined,\n const Feed extends boolean = false,\n>(leaf: {\n method: M\n path: Path\n cfg: Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n > & {\n feed?: Feed\n bodySchema?: B\n querySchema?: Q\n paramsSchema?: P\n outputSchema?: O\n }\n}): LeafLowProfile<\n M,\n Path,\n Prettify<\n Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed'\n > & {\n feed: Feed\n bodySchema: B extends ZodType ? RouteSchema<z.infer<B>> : undefined\n querySchema: Q extends ZodType ? RouteSchema<z.infer<Q>> : undefined\n paramsSchema: P extends ZodType ? RouteSchema<z.infer<P>> : undefined\n outputSchema: O extends ZodType ? RouteSchema<z.infer<O>> : undefined\n }\n >\n>\nexport function buildLowProfileLeaf(leaf: any): any {\n return {\n ...leaf,\n cfg: {\n ...leaf.cfg,\n bodySchema: leaf.cfg.bodySchema as RouteSchema<\n z.infer<typeof leaf.cfg.bodySchema>\n >,\n querySchema: leaf.cfg.querySchema as RouteSchema<\n z.infer<typeof leaf.cfg.querySchema>\n >,\n paramsSchema: leaf.cfg.paramsSchema as RouteSchema<\n z.infer<typeof leaf.cfg.paramsSchema>\n >,\n outputSchema: leaf.cfg.outputSchema as RouteSchema<\n z.infer<typeof leaf.cfg.outputSchema>\n >,\n },\n }\n}\n\nexport type LeafLowProfile<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfgLowProfile,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<\n L extends AnyLeafLowProfile,\n Fallback = never,\n> = L['cfg']['paramsSchema'] extends RouteSchema<infer P> ? P : Fallback\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['querySchema'] extends RouteSchema<infer Q> ? Q : Fallback\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['bodySchema'] extends RouteSchema<infer B> ? B : Fallback\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<\n L extends AnyLeaf,\n Fallback = never,\n> = L['cfg']['outputSchema'] extends RouteSchema<infer O> ? O : Fallback\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {}\n","// TODO:\n// * use this as a transform that infers the types from Zod, to only pass the types around instead of the full schemas -> make converters that go both ways (data to schema, schema to data)\n// * consider moving to core package\n// * update server and client side to use this type instead of raw zod schemas\nimport { AnyLeafLowProfile, HttpMethod, Prettify } from './routesV3.core'\n\n/** Build the key type for a leaf — distributive so method/path stay paired. */\nexport type KeyOf<L extends AnyLeafLowProfile> = L extends AnyLeafLowProfile\n ? `${Uppercase<L['method']>} ${L['path']}`\n : never\n\n// From a tuple of leaves, get the union of keys (pairwise, no cartesian blow-up)\ntype KeysOf<Leaves extends readonly AnyLeafLowProfile[]> = KeyOf<Leaves[number]>\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}`\n ? Lowercase<M>\n : never\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}`\n ? P\n : never\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<\n Leaves extends readonly AnyLeafLowProfile[],\n K extends string,\n> = Extract<\n Leaves[number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>\n\n/**\n * Freeze a leaf tuple into a registry with typed key lookups.\n * @param leaves Readonly tuple of leaves produced by the builder DSL.\n * @returns Registry containing the leaves array and a `byKey` lookup map.\n */\nexport function finalize<const L extends readonly AnyLeafLowProfile[]>(\n leaves: L,\n) {\n type Keys = KeysOf<L>\n type MapByKey = { [K in Keys]: Prettify<LeafForKey<L, K>> }\n\n const byKey = Object.fromEntries(\n leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l] as const),\n ) as unknown as MapByKey\n\n const log = (logger: { system: (...args: unknown[]) => void }) => {\n logger.system('Finalized routes:')\n ;(Object.keys(byKey) as Keys[]).forEach((k) => {\n const leaf = byKey[k]\n logger.system(`- ${k}`)\n })\n }\n\n return { all: leaves, byKey, log }\n}\n\n/** Nominal type alias for a finalized registry. */\nexport type Registry<R extends ReturnType<typeof finalize>> = R\n\ntype FilterRoute<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n Acc extends readonly AnyLeafLowProfile[] = [],\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? First extends { path: `${string}${F}${string}` }\n ? FilterRoute<Rest, F, [...Acc, First]>\n : FilterRoute<Rest, F, Acc>\n : Acc\n\ntype UpperCase<S extends string> = S extends `${infer First}${infer Rest}`\n ? `${Uppercase<First>}${UpperCase<Rest>}`\n : S\n\ntype Routes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = FilterRoute<T, F>\ntype ByKey<\n T extends readonly AnyLeafLowProfile[],\n Acc extends Record<string, AnyLeafLowProfile> = {},\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? ByKey<\n Rest,\n Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }\n >\n : Acc\n\n/**\n * Convenience helper for extracting a subset of routes by path fragment.\n * @param T Tuple of leaves produced by the DSL.\n * @param F String fragment to match against the route path.\n */\nexport type SubsetRoutes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = {\n byKey: ByKey<Routes<T, F>>\n all: Routes<T, F>\n}\n","// socket.index.ts (shared client/server)\n\nimport { z } from 'zod'\n\nexport type SocketSchema<Output = unknown> = z.ZodType & {\n __out: Output\n}\n\nexport type SocketSchemaOutput<Schema extends z.ZodTypeAny> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport type SocketEvent<Out = unknown> = {\n message: z.ZodTypeAny\n /** phantom field, only for typing; not meant to be used at runtime */\n __out: Out\n}\n\nexport type EventMap = Record<string, SocketEvent<any>>\n\n/**\n * System event names – shared so server and client\n * don't drift on naming.\n */\nexport type SysEventName =\n | 'sys:connect'\n | 'sys:disconnect'\n | 'sys:reconnect'\n | 'sys:connect_error'\n | 'sys:ping'\n | 'sys:pong'\n | 'sys:room_join'\n | 'sys:room_leave'\nexport function defineSocketEvents<\n const C extends SocketConnectionConfig,\n const Schemas extends Record<\n string,\n {\n message: z.ZodTypeAny\n }\n >,\n>(\n config: C,\n events: Schemas,\n): {\n config: {\n [K in keyof C]: SocketSchema<z.output<C[K]>>\n }\n events: {\n [K in keyof Schemas]: SocketEvent<z.output<Schemas[K]['message']>>\n }\n}\n\nexport function defineSocketEvents(config: any, events: any) {\n return { config, events }\n}\n\nexport type Payload<\n T extends EventMap,\n K extends keyof T,\n> = T[K] extends SocketEvent<infer Out> ? Out : never\nexport type SocketConnectionConfig = {\n joinMetaMessage: z.ZodTypeAny\n leaveMetaMessage: z.ZodTypeAny\n pingPayload: z.ZodTypeAny\n pongPayload: z.ZodTypeAny\n}\n\nexport type SocketConnectionConfigOutput = {\n joinMetaMessage: SocketSchema<any>\n leaveMetaMessage: SocketSchema<any>\n pingPayload: SocketSchema<any>\n pongPayload: SocketSchema<any>\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/sockets/socket.index.ts"],"sourcesContent":["import { z } from 'zod'\nimport {\n AnyLeaf,\n AnyLeafLowProfile,\n Append,\n AugmentLeaves,\n HttpMethod,\n Leaf,\n LowProfileCfg,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n} from './routesV3.core'\n\nconst paginationQueryShape = {\n pagination_cursor: z.string().optional(),\n pagination_limit: z.coerce.number().min(1).max(100).default(20),\n}\n\nconst defaultFeedQuerySchema = z.object(paginationQueryShape)\ntype PaginationShape = typeof paginationQueryShape\n\ntype ZodTypeAny = z.ZodTypeAny\ntype ParamZod<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S\n}>\ntype ZodArrayAny = z.ZodArray<ZodTypeAny>\ntype ZodObjectAny = z.ZodObject<any>\ntype AnyZodObject = z.ZodObject<any>\ntype MergedParamsResult<\n PS,\n Name extends string,\n P extends ZodTypeAny,\n> = PS extends ZodTypeAny\n ? z.ZodIntersection<PS, ParamZod<Name, P>>\n : ParamZod<Name, P>\n\nfunction getZodShape(schema: ZodObjectAny) {\n const shapeOrGetter = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n if (!shapeOrGetter) return {}\n return typeof shapeOrGetter === 'function'\n ? shapeOrGetter.call(schema)\n : shapeOrGetter\n}\n\nfunction collectNestedFieldSuggestions(\n shape: Record<string, ZodTypeAny> | undefined,\n prefix: string[] = [],\n): string[] {\n if (!shape) return []\n const suggestions: string[] = []\n for (const [key, value] of Object.entries(shape)) {\n if (value instanceof z.ZodObject) {\n const nestedShape = getZodShape(value as ZodObjectAny)\n const nestedSuggestions = collectNestedFieldSuggestions(nestedShape, [\n ...prefix,\n key,\n ])\n suggestions.push(\n ...(nestedSuggestions.length\n ? nestedSuggestions\n : [[...prefix, key].join('_')]),\n )\n } else if (prefix.length > 0) {\n suggestions.push([...prefix, key].join('_'))\n }\n }\n return suggestions\n}\n\nconst defaultFeedOutputSchema = z.object({\n items: z.array(z.unknown()),\n nextCursor: z.string().optional(),\n})\n\nfunction augmentFeedQuerySchema<Q extends ZodTypeAny | undefined>(schema: Q) {\n if (schema && !(schema instanceof z.ZodObject)) {\n console.warn(\n 'Feed queries must be a ZodObject; default pagination applied.',\n )\n return defaultFeedQuerySchema\n }\n\n const base = (schema as ZodObjectAny) ?? z.object({})\n const shape = getZodShape(base)\n const nestedSuggestions = collectNestedFieldSuggestions(shape)\n if (nestedSuggestions.length) {\n console.warn(\n `Feed query schemas should avoid nested objects; consider flattening fields like: ${nestedSuggestions.join(\n ', ',\n )}`,\n )\n }\n return base.extend(paginationQueryShape)\n}\n\nfunction augmentFeedOutputSchema<O extends ZodTypeAny | undefined>(schema: O) {\n if (!schema) return defaultFeedOutputSchema\n if (schema instanceof z.ZodArray) {\n return z.object({\n items: schema,\n nextCursor: z.string().optional(),\n })\n }\n if (schema instanceof z.ZodObject) {\n const shape = (schema as any).shape\n ? (schema as any).shape\n : (schema as any)._def?.shape?.()\n const hasItems = Boolean(shape?.items)\n if (hasItems) {\n return schema.extend({ nextCursor: z.string().optional() })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n }\n return z.object({\n items: z.array(schema as ZodTypeAny),\n nextCursor: z.string().optional(),\n })\n}\n\n/**\n * Runtime helper that mirrors the typed merge used by the builder.\n * @param a Previously merged params schema inherited from parent segments.\n * @param b Newly introduced params schema.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nfunction mergeSchemas<A extends ZodTypeAny, B extends ZodTypeAny>(\n a: A,\n b: B,\n): ZodTypeAny\nfunction mergeSchemas<A extends ZodTypeAny>(a: A, b: undefined): A\nfunction mergeSchemas<B extends ZodTypeAny>(a: undefined, b: B): B\nfunction mergeSchemas(\n a: ZodTypeAny | undefined,\n b: ZodTypeAny | undefined,\n): ZodTypeAny | undefined\nfunction mergeSchemas(a: ZodTypeAny | undefined, b: ZodTypeAny | undefined) {\n if (a && b) return z.intersection(a as any, b as any)\n return (a ?? b) as ZodTypeAny | undefined\n}\n\ntype FeedOutputSchema<C extends MethodCfg> =\n C['outputSchema'] extends ZodArrayAny\n ? z.ZodObject<{\n items: C['outputSchema']\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : C['outputSchema'] extends ZodTypeAny\n ? z.ZodObject<{\n items: z.ZodArray<C['outputSchema']>\n nextCursor: z.ZodOptional<z.ZodString>\n }>\n : typeof defaultFeedOutputSchema\n\ntype BaseMethodCfg<C extends MethodCfg> = Merge<\n Omit<MethodCfg, 'querySchema' | 'outputSchema' | 'feed'>,\n Omit<C, 'querySchema' | 'outputSchema' | 'feed'>\n>\n\ntype FeedField<C extends MethodCfg> = C['feed'] extends true\n ? { feed: true }\n : { feed?: boolean }\n\ntype AddPaginationToQuery<Q extends AnyZodObject | undefined> =\n Q extends z.ZodObject<infer Shape>\n ? z.ZodObject<Shape & PaginationShape>\n : z.ZodObject<PaginationShape>\n\ntype FeedQueryField<C extends MethodCfg> = {\n querySchema: AddPaginationToQuery<\n C['querySchema'] extends AnyZodObject ? C['querySchema'] : undefined\n >\n}\n\ntype NonFeedQueryField<C extends MethodCfg> =\n C['querySchema'] extends ZodTypeAny\n ? { querySchema: C['querySchema'] }\n : { querySchema?: undefined }\n\ntype FeedOutputField<C extends MethodCfg> = {\n outputSchema: FeedOutputSchema<C>\n}\n\ntype NonFeedOutputField<C extends MethodCfg> =\n C['outputSchema'] extends ZodTypeAny\n ? { outputSchema: C['outputSchema'] }\n : { outputSchema?: undefined }\n\ntype ParamsField<C extends MethodCfg, PS> = C['paramsSchema'] extends ZodTypeAny\n ? { paramsSchema: C['paramsSchema'] }\n : { paramsSchema: PS }\n\ntype EffectiveFeedFields<C extends MethodCfg, PS> = C['feed'] extends true\n ? FeedField<C> & FeedQueryField<C> & FeedOutputField<C> & ParamsField<C, PS>\n : FeedField<C> &\n NonFeedQueryField<C> &\n NonFeedOutputField<C> &\n ParamsField<C, PS>\n\ntype EffectiveCfg<C extends MethodCfg, PS> = Prettify<\n Merge<MethodCfg, BaseMethodCfg<C> & EffectiveFeedFields<C, PS>>\n>\n\ntype BuiltLeaf<\n M extends HttpMethod,\n Base extends string,\n I extends NodeCfg,\n C extends MethodCfg,\n PS,\n> = Leaf<M, Base, Merge<I, LowProfileCfg<EffectiveCfg<C, PS>>>>\n\ntype MethodFns<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Used extends HttpMethod,\n> = {\n /**\n * Register a GET leaf at the current path.\n */\n get: 'get' extends Used\n ? never\n : <C extends MethodCfg>(\n cfg: C,\n ) => Branch<\n Base,\n Append<Acc, Prettify<BuiltLeaf<'get', Base, I, C, PS>>>,\n I,\n PS,\n Used | 'get'\n >\n\n /**\n * Register a POST leaf at the current path.\n */\n post: 'post' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'post', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'post'\n >\n\n /**\n * Register a PUT leaf at the current path.\n */\n put: 'put' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'put', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'put'\n >\n\n /**\n * Register a PATCH leaf at the current path.\n */\n patch: 'patch' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'patch', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'patch'\n >\n\n /**\n * Register a DELETE leaf at the current path.\n */\n delete: 'delete' extends Used\n ? never\n : <C extends Omit<MethodCfg, 'feed'>>(\n cfg: C,\n ) => Branch<\n Base,\n Append<\n Acc,\n Prettify<BuiltLeaf<'delete', Base, I, Merge<C, { feed: false }>, PS>>\n >,\n I,\n PS,\n Used | 'delete'\n >\n}\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeafLowProfile[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Used extends HttpMethod = never,\n> extends MethodFns<Base, Acc, I, PS, Used> {\n /**\n * Mount a static subtree under `name`.\n * The `leaves` are built externally via `resource(...)` and will be\n * rebased so that their paths become `${Base}/${name}${leaf.path}` and their\n * paramsSchemas are merged with the parameters already accumulated on this branch.\n */\n sub<Name extends string, R extends readonly AnyLeafLowProfile[]>(\n name: Name,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>,\n I,\n PS,\n Used\n >\n\n /**\n * Mount a static subtree under `name` and merge extra node-level config.\n */\n sub<\n Name extends string,\n J extends NodeCfg,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n cfg: J,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<Acc, AugmentLeaves<`${Base}/${Name}`, PS, R>>,\n Merge<I, J>,\n PS,\n Used\n >\n\n /**\n * Introduce a `:param` segment and mount a pre-built subtree beneath it.\n * The subtree paths are rebased to `${Base}/:${Name}${leaf.path}` and\n * their paramsSchemas are intersected with the accumulated params plus this new param.\n */\n routeParameter<\n Name extends string,\n P extends ZodTypeAny,\n R extends readonly AnyLeafLowProfile[],\n >(\n name: Name,\n paramsSchema: P,\n leaves: R,\n ): Branch<\n Base,\n MergeArray<\n Acc,\n AugmentLeaves<`${Base}/:${Name}`, MergedParamsResult<PS, Name, P>, R>\n >,\n I,\n PS,\n Used\n >\n\n /**\n * Finish the branch and return the collected leaves.\n * @returns Readonly tuple of accumulated leaves.\n */\n done(): Readonly<Acc>\n}\n\n/**\n * Start building a resource at the given base path.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Optional node configuration applied to all descendants.\n * @returns Root `Branch` instance used to compose the route tree.\n */\nexport function resource<Base extends string = '', I extends NodeCfg = {}>(\n base?: Base,\n inherited?: I,\n): Branch<Base, readonly [], I, undefined> {\n const rootBase = (base ?? '') as Base\n const rootInherited: NodeCfg = { ...(inherited as NodeCfg) }\n\n function makeBranch<\n Base2 extends string,\n I2 extends NodeCfg,\n PS2 extends ZodTypeAny | undefined,\n >(base2: Base2, inherited2: I2, mergedParamsSchema?: PS2) {\n const stack: AnyLeaf[] = []\n let currentBase: string = base2\n let inheritedCfg: NodeCfg = { ...(inherited2 as NodeCfg) }\n let currentParamsSchema: PS2 = mergedParamsSchema as PS2\n\n function add<M extends HttpMethod, C extends MethodCfg>(method: M, cfg: C) {\n const effectiveParamsSchema = (cfg.paramsSchema ??\n currentParamsSchema) as PS2 | undefined\n const effectiveQuerySchema =\n cfg.feed === true\n ? augmentFeedQuerySchema(cfg.querySchema)\n : cfg.querySchema\n const effectiveOutputSchema =\n cfg.feed === true\n ? augmentFeedOutputSchema(cfg.outputSchema)\n : cfg.outputSchema\n\n const fullCfg = (\n effectiveParamsSchema\n ? {\n ...inheritedCfg,\n ...cfg,\n paramsSchema: effectiveParamsSchema,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n : {\n ...inheritedCfg,\n ...cfg,\n ...(effectiveQuerySchema\n ? { querySchema: effectiveQuerySchema }\n : {}),\n ...(effectiveOutputSchema\n ? { outputSchema: effectiveOutputSchema }\n : {}),\n }\n ) as any\n\n const leaf = {\n method,\n path: currentBase as Base2,\n cfg: fullCfg,\n } as const\n\n stack.push(leaf as unknown as AnyLeaf)\n\n return api\n }\n\n const api: any = {\n /**\n * Mount a subtree built elsewhere.\n *\n * Usage:\n * const users = resource('').get(...).done()\n * resource('/api').sub('users', users).done()\n */\n sub<Name extends string>(\n name: Name,\n cfgOrLeaves?: NodeCfg | readonly AnyLeafLowProfile[],\n maybeLeaves?: readonly AnyLeafLowProfile[],\n ) {\n let cfg: NodeCfg | undefined\n let leaves: readonly AnyLeafLowProfile[] | undefined\n\n if (Array.isArray(cfgOrLeaves)) {\n leaves = cfgOrLeaves\n } else {\n cfg = cfgOrLeaves as NodeCfg | undefined\n leaves = maybeLeaves\n }\n\n if (!leaves) {\n throw new Error('sub() expects a leaves array as the last argument')\n }\n\n const childInherited: NodeCfg = {\n ...inheritedCfg,\n ...(cfg ?? {}),\n }\n\n const baseForChildren = `${currentBase}/${name}`\n\n for (const leafLow of leaves) {\n const leaf = leafLow as unknown as AnyLeaf\n const leafCfg = leaf.cfg as MethodCfg\n const leafParams = leafCfg.paramsSchema as ZodTypeAny | undefined\n\n const effectiveParams = mergeSchemas(\n currentParamsSchema as any,\n leafParams,\n )\n\n const newCfg: MethodCfg = {\n ...childInherited,\n ...leafCfg,\n }\n\n if (effectiveParams) {\n newCfg.paramsSchema = effectiveParams\n } else if ('paramsSchema' in newCfg) {\n delete newCfg.paramsSchema\n }\n\n const newLeaf: AnyLeaf = {\n method: leaf.method,\n path: `${baseForChildren}${leaf.path}` as string,\n cfg: newCfg,\n }\n\n stack.push(newLeaf)\n }\n\n return api\n },\n\n /**\n * Introduce a :param segment and mount a subtree under it.\n *\n * The subtree is built independently (e.g. resource('').get(...).done())\n * and its paths become `${currentBase}/:${name}${leaf.path}`.\n * Params schemas are intersected with the accumulated params plus the new param.\n */\n routeParameter<Name extends string, P extends ZodTypeAny>(\n name: Name,\n paramsSchema: P,\n leaves: readonly AnyLeafLowProfile[],\n ) {\n const paramObj: ParamZod<Name, P> = z.object({\n [name]: paramsSchema,\n } as Record<Name, P>)\n\n const mergedParams = (\n currentParamsSchema\n ? mergeSchemas(currentParamsSchema as any, paramObj)\n : paramObj\n ) as PS2\n\n const baseForChildren = `${currentBase}/:${name}`\n\n for (const leafLow of leaves) {\n const leaf = leafLow as unknown as AnyLeaf\n const leafCfg = leaf.cfg as MethodCfg\n const leafParams = leafCfg.paramsSchema as ZodTypeAny | undefined\n\n const effectiveParams = mergeSchemas(mergedParams as any, leafParams)\n\n const newCfg: MethodCfg = {\n ...inheritedCfg,\n ...leafCfg,\n }\n\n if (effectiveParams) {\n newCfg.paramsSchema = effectiveParams\n } else if ('paramsSchema' in newCfg) {\n delete newCfg.paramsSchema\n }\n\n const newLeaf: AnyLeaf = {\n method: leaf.method,\n path: `${baseForChildren}${leaf.path}` as string,\n cfg: newCfg,\n }\n\n stack.push(newLeaf)\n }\n\n return api\n },\n\n // methods (inject current params schema if missing)\n get<C extends MethodCfg>(cfg: C) {\n return add('get', cfg)\n },\n post<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('post', { ...cfg, feed: false })\n },\n put<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('put', { ...cfg, feed: false })\n },\n patch<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('patch', { ...cfg, feed: false })\n },\n delete<C extends Omit<MethodCfg, 'feed'>>(cfg: C) {\n return add('delete', { ...cfg, feed: false })\n },\n\n done() {\n return stack as readonly AnyLeafLowProfile[]\n },\n }\n\n return api as Branch<Base2, readonly [], I2, PS2>\n }\n\n // Root branch starts with no params schema (PS = undefined)\n return makeBranch(rootBase, rootInherited, undefined)\n}\n\n/**\n * Merge two readonly tuples (preserves literal tuple information).\n * @param arr1 First tuple.\n * @param arr2 Second tuple.\n * @returns New tuple containing values from both inputs.\n */\nexport const mergeArrays = <T extends readonly any[], S extends readonly any[]>(\n arr1: T,\n arr2: S,\n) => {\n return [...arr1, ...arr2] as [...T, ...S]\n}\n","import { z, ZodType } from 'zod'\n\n/** Supported HTTP verbs for the routes DSL. */\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\n/** Declarative description of a multipart upload field. */\nexport type FileField = {\n /** Form field name used for uploads. */\n name: string\n /** Maximum number of files accepted for this field. */\n maxCount: number\n}\n\n/** Configuration that applies to an entire branch of the route tree. */\nexport type NodeCfg = {\n /** @deprecated. Does nothing. */\n authenticated?: boolean\n}\n\nexport type RouteSchema<Output = unknown> = ZodType & {\n __out: Output\n}\n\nexport type RouteSchemaOutput<Schema extends ZodType> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport const lowProfileParse = <T extends RouteSchema>(\n schema: T,\n data: unknown,\n): RouteSchemaOutput<T> => {\n return schema.parse(data) as RouteSchemaOutput<T>\n}\n\nexport type ToRouteSchema<S> = S extends ZodType ? RouteSchema<z.output<S>> : S\n\nexport type LowProfileCfg<Cfg extends MethodCfg> = Prettify<\n Omit<Cfg, 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'> & {\n bodySchema: ToRouteSchema<Cfg['bodySchema']>\n querySchema: ToRouteSchema<Cfg['querySchema']>\n paramsSchema: ToRouteSchema<Cfg['paramsSchema']>\n outputSchema: ToRouteSchema<Cfg['outputSchema']>\n }\n>\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = {\n /** Zod schema describing the request body. */\n bodySchema?: ZodType\n /** Zod schema describing the query string. */\n querySchema?: ZodType\n /** Zod schema describing path params (Internal only, set through sub and routeParameter). */\n paramsSchema?: ZodType\n /** Zod schema describing the response payload. */\n outputSchema?: ZodType\n /** Multipart upload definitions for the route. */\n bodyFiles?: FileField[]\n /** Marks the route as an infinite feed (enables cursor helpers). */\n feed?: boolean\n\n /** Optional human-readable description for docs/debugging. */\n description?: string\n /**\n * Short one-line summary for docs list views.\n * Shown in navigation / tables instead of the full description.\n */\n summary?: string\n /**\n * Group name used for navigation sections in docs UIs.\n * e.g. \"Users\", \"Billing\", \"Auth\".\n */\n docsGroup?: string\n /**\n * Tags that can be used to filter / facet in interactive docs.\n * e.g. [\"users\", \"admin\", \"internal\"].\n */\n tags?: string[]\n /**\n * Mark the route as deprecated in docs.\n * Renderers can badge this and/or hide by default.\n */\n deprecated?: boolean\n /**\n * Optional stability information for the route.\n * Renderers can surface this prominently.\n */\n stability?: 'experimental' | 'beta' | 'stable' | 'deprecated'\n /**\n * Hide this route from public docs while keeping it usable in code.\n */\n docsHidden?: boolean\n /**\n * Arbitrary extra metadata for docs renderers.\n * Can be used for auth requirements, rate limits, feature flags, etc.\n */\n docsMeta?: Record<string, unknown>\n}\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<\n M extends HttpMethod,\n P extends string,\n C extends MethodCfg,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n\n/** Convenience union covering all generated leaves. */\nexport type AnyLeaf = Leaf<HttpMethod, string, MethodCfg>\n\n/** Merge two object types while keeping nice IntelliSense output. */\nexport type Merge<A, B> = Prettify<Omit<A, keyof B> & B>\n\n/** Append a new element to a readonly tuple type. */\nexport type Append<T extends readonly unknown[], X> = [...T, X]\n\n/** Concatenate two readonly tuple types. */\nexport type MergeArray<\n A extends readonly unknown[],\n B extends readonly unknown[],\n> = [...A, ...B]\nexport type IntersectZod<\n A extends ZodType | undefined,\n B extends ZodType | undefined,\n> = B extends ZodType\n ? A extends ZodType\n ? z.ZodIntersection<A, B>\n : B\n : A extends ZodType\n ? A\n : undefined\n\ntype RouteSchemaFromZod<T extends ZodType | undefined> = T extends ZodType\n ? RouteSchema<z.output<T>>\n : undefined\n\ntype MergeRouteSchemas<\n Existing extends RouteSchema | undefined,\n Parent extends ZodType | undefined,\n> =\n Existing extends RouteSchema<infer ExistingOut>\n ? Parent extends ZodType\n ? RouteSchema<ExistingOut & z.output<Parent>>\n : Existing\n : Parent extends ZodType\n ? RouteSchemaFromZod<Parent>\n : undefined\n\ntype AugmentedCfg<\n Cfg extends MethodCfgLowProfile,\n Param extends ZodType | undefined,\n> = Prettify<\n Omit<Cfg, 'paramsSchema'> & {\n paramsSchema: MergeRouteSchemas<Cfg['paramsSchema'], Param>\n }\n>\n\nexport type AugmentLeaves<\n P extends string,\n Param extends ZodType | undefined,\n R extends readonly LeafLowProfile[],\n Acc extends readonly LeafLowProfile[] = [],\n> = R extends readonly [infer First, ...infer Rest]\n ? First extends LeafLowProfile\n ? AugmentLeaves<\n P,\n Param,\n Rest extends readonly LeafLowProfile[] ? Rest : [],\n Append<\n Acc,\n LeafLowProfile<\n First['method'],\n `${P}${First['path']}`,\n AugmentedCfg<First['cfg'], Param>\n >\n >\n >\n : never\n : Acc\n// helpers (optional)\ntype SegmentParams<S extends string> = S extends `:${infer P}` ? P : never\ntype Split<S extends string> = S extends ''\n ? []\n : S extends `${infer A}/${infer B}`\n ? [A, ...Split<B>]\n : [S]\ntype ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>\n\n/** Derive a params object type from a literal route string. */\nexport type ExtractParamsFromPath<Path extends string> =\n ExtractParamNames<Path> extends never\n ? never\n : Record<ExtractParamNames<Path>, string | number>\n\n/**\n * Interpolate `:params` in a path using the given values.\n * @param path Literal route string containing `:param` segments.\n * @param params Object of parameter values to interpolate.\n * @returns Path string with parameters substituted.\n */\nexport function compilePath<Path extends string>(\n path: Path,\n params: ExtractParamsFromPath<Path>,\n) {\n if (!params) return path\n return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {\n const v = (params as any)[k]\n if (v === undefined || v === null) throw new Error(`Missing param :${k}`)\n return String(v)\n })\n}\n\n/**\n * Build a deterministic cache key for the given leaf.\n * The key matches the shape consumed by React Query helpers.\n * @param args.leaf Leaf describing the endpoint.\n * @param args.params Optional params used to build the path.\n * @param args.query Optional query payload.\n * @returns Tuple suitable for React Query cache keys.\n */\ntype SplitPath<P extends string> = P extends ''\n ? []\n : P extends `${infer A}/${infer B}`\n ? [A, ...SplitPath<B>]\n : [P]\nexport function buildCacheKey<L extends AnyLeaf>(args: {\n leaf: L\n params?: ExtractParamsFromPath<L['path']>\n query?: InferQuery<L>\n}) {\n let p = args.leaf.path\n if (args.params) {\n p = compilePath<L['path']>(p, args.params)\n }\n return [\n args.leaf.method,\n ...(p.split('/').filter(Boolean) as SplitPath<typeof p>),\n args.query ?? {},\n ] as const\n}\n\n/** Definition-time method config (for clarity). */\nexport type MethodCfgDef = MethodCfg\n\n/** Low-profile method config where schemas carry a phantom __out like SocketSchema. */\nexport type MethodCfgLowProfile = Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n> & {\n bodySchema?: RouteSchema\n querySchema?: RouteSchema\n paramsSchema?: RouteSchema\n outputSchema?: RouteSchema\n}\nexport type AnyLeafLowProfile = LeafLowProfile<\n HttpMethod,\n string,\n MethodCfgLowProfile\n>\n\nexport function buildLowProfileLeaf<\n const M extends HttpMethod,\n const Path extends string,\n const O extends ZodType | undefined = undefined,\n const P extends ZodType | undefined = undefined,\n const Q extends ZodType | undefined = undefined,\n const B extends ZodType | undefined = undefined,\n const Feed extends boolean = false,\n>(leaf: {\n method: M\n path: Path\n cfg: Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema'\n > & {\n feed?: Feed\n bodySchema?: B\n querySchema?: Q\n paramsSchema?: P\n outputSchema?: O\n }\n}): LeafLowProfile<\n M,\n Path,\n Prettify<\n Omit<\n MethodCfg,\n 'bodySchema' | 'querySchema' | 'paramsSchema' | 'outputSchema' | 'feed'\n > & {\n feed: Feed\n bodySchema: B extends ZodType ? RouteSchema<z.infer<B>> : undefined\n querySchema: Q extends ZodType ? RouteSchema<z.infer<Q>> : undefined\n paramsSchema: P extends ZodType ? RouteSchema<z.infer<P>> : undefined\n outputSchema: O extends ZodType ? RouteSchema<z.infer<O>> : undefined\n }\n >\n>\nexport function buildLowProfileLeaf(leaf: any): any {\n return {\n ...leaf,\n cfg: {\n ...leaf.cfg,\n bodySchema: leaf.cfg.bodySchema as RouteSchema<\n z.infer<typeof leaf.cfg.bodySchema>\n >,\n querySchema: leaf.cfg.querySchema as RouteSchema<\n z.infer<typeof leaf.cfg.querySchema>\n >,\n paramsSchema: leaf.cfg.paramsSchema as RouteSchema<\n z.infer<typeof leaf.cfg.paramsSchema>\n >,\n outputSchema: leaf.cfg.outputSchema as RouteSchema<\n z.infer<typeof leaf.cfg.outputSchema>\n >,\n },\n }\n}\n\nexport type LeafLowProfile<\n M extends HttpMethod = HttpMethod,\n P extends string = string,\n C extends MethodCfgLowProfile = MethodCfgLowProfile,\n> = {\n /** Lowercase HTTP method (get/post/...). */\n readonly method: M\n /** Concrete path for the route (e.g. `/v1/users/:userId`). */\n readonly path: P\n /** Readonly snapshot of route configuration. */\n readonly cfg: Readonly<C>\n}\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<L extends AnyLeafLowProfile, Fallback = never> =\n L['cfg']['paramsSchema'] extends RouteSchema<infer P> ? P : Fallback\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<L extends AnyLeaf, Fallback = never> =\n L['cfg']['querySchema'] extends RouteSchema<infer Q> ? Q : Fallback\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<L extends AnyLeaf, Fallback = never> =\n L['cfg']['bodySchema'] extends RouteSchema<infer B> ? B : Fallback\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<L extends AnyLeaf, Fallback = never> =\n L['cfg']['outputSchema'] extends RouteSchema<infer O> ? O : Fallback\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {}\n","// TODO:\n// * use this as a transform that infers the types from Zod, to only pass the types around instead of the full schemas -> make converters that go both ways (data to schema, schema to data)\n// * consider moving to core package\n// * update server and client side to use this type instead of raw zod schemas\nimport { AnyLeafLowProfile, HttpMethod, Prettify } from './routesV3.core'\n\n/** Build the key type for a leaf — distributive so method/path stay paired. */\nexport type KeyOf<L extends AnyLeafLowProfile> = L extends AnyLeafLowProfile\n ? `${Uppercase<L['method']>} ${L['path']}`\n : never\n\n// From a tuple of leaves, get the union of keys (pairwise, no cartesian blow-up)\ntype KeysOf<Leaves extends readonly AnyLeafLowProfile[]> = KeyOf<Leaves[number]>\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}`\n ? Lowercase<M>\n : never\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}`\n ? P\n : never\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<\n Leaves extends readonly AnyLeafLowProfile[],\n K extends string,\n> = Extract<\n Leaves[number],\n { method: MethodFromKey<K> & HttpMethod; path: PathFromKey<K> }\n>\n\n/**\n * Freeze a leaf tuple into a registry with typed key lookups.\n * @param leaves Readonly tuple of leaves produced by the builder DSL.\n * @returns Registry containing the leaves array and a `byKey` lookup map.\n */\nexport function finalize<const L extends readonly AnyLeafLowProfile[]>(\n leaves: L,\n) {\n type Keys = KeysOf<L>\n type MapByKey = { [K in Keys]: Prettify<LeafForKey<L, K>> }\n\n const byKey = Object.fromEntries(\n leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l] as const),\n ) as unknown as MapByKey\n\n const log = (logger: { system: (...args: unknown[]) => void }) => {\n logger.system('Finalized routes:')\n ;(Object.keys(byKey) as Keys[]).forEach((k) => {\n const leaf = byKey[k]\n logger.system(`- ${k}`)\n })\n }\n\n return { all: leaves, byKey, log }\n}\n\n/** Nominal type alias for a finalized registry. */\nexport type Registry<R extends ReturnType<typeof finalize>> = R\n\ntype FilterRoute<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n Acc extends readonly AnyLeafLowProfile[] = [],\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? First extends { path: `${string}${F}${string}` }\n ? FilterRoute<Rest, F, [...Acc, First]>\n : FilterRoute<Rest, F, Acc>\n : Acc\n\ntype UpperCase<S extends string> = S extends `${infer First}${infer Rest}`\n ? `${Uppercase<First>}${UpperCase<Rest>}`\n : S\n\ntype Routes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = FilterRoute<T, F>\ntype ByKey<\n T extends readonly AnyLeafLowProfile[],\n Acc extends Record<string, AnyLeafLowProfile> = {},\n> = T extends readonly [\n infer First extends AnyLeafLowProfile,\n ...infer Rest extends AnyLeafLowProfile[],\n]\n ? ByKey<\n Rest,\n Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }\n >\n : Acc\n\n/**\n * Convenience helper for extracting a subset of routes by path fragment.\n * @param T Tuple of leaves produced by the DSL.\n * @param F String fragment to match against the route path.\n */\nexport type SubsetRoutes<\n T extends readonly AnyLeafLowProfile[],\n F extends string,\n> = {\n byKey: ByKey<Routes<T, F>>\n all: Routes<T, F>\n}\n","// socket.index.ts (shared client/server)\n\nimport { z } from 'zod'\n\nexport type SocketSchema<Output = unknown> = z.ZodType & {\n __out: Output\n}\n\nexport type SocketSchemaOutput<Schema extends z.ZodTypeAny> = Schema extends {\n __out: infer Out\n}\n ? Out\n : z.output<Schema>\n\nexport type SocketEvent<Out = unknown> = {\n message: z.ZodTypeAny\n /** phantom field, only for typing; not meant to be used at runtime */\n __out: Out\n}\n\nexport type EventMap = Record<string, SocketEvent<any>>\n\n/**\n * System event names – shared so server and client\n * don't drift on naming.\n */\nexport type SysEventName =\n | 'sys:connect'\n | 'sys:disconnect'\n | 'sys:reconnect'\n | 'sys:connect_error'\n | 'sys:ping'\n | 'sys:pong'\n | 'sys:room_join'\n | 'sys:room_leave'\nexport function defineSocketEvents<\n const C extends SocketConnectionConfig,\n const Schemas extends Record<\n string,\n {\n message: z.ZodTypeAny\n }\n >,\n>(\n config: C,\n events: Schemas,\n): {\n config: {\n [K in keyof C]: SocketSchema<z.output<C[K]>>\n }\n events: {\n [K in keyof Schemas]: SocketEvent<z.output<Schemas[K]['message']>>\n }\n}\n\nexport function defineSocketEvents(config: any, events: any) {\n return { config, events }\n}\n\nexport type Payload<T extends EventMap, K extends keyof T> =\n T[K] extends SocketEvent<infer Out> ? Out : never\nexport type SocketConnectionConfig = {\n joinMetaMessage: z.ZodTypeAny\n leaveMetaMessage: z.ZodTypeAny\n pingPayload: z.ZodTypeAny\n pongPayload: z.ZodTypeAny\n}\n\nexport type SocketConnectionConfigOutput = {\n joinMetaMessage: SocketSchema<any>\n leaveMetaMessage: SocketSchema<any>\n pingPayload: SocketSchema<any>\n pongPayload: SocketSchema<any>\n}\n"],"mappings":"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","names":[]}
|