@emeryld/rrroutes-contract 1.1.0

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 ADDED
@@ -0,0 +1,61 @@
1
+ # @emeryld/rrroutes-contract
2
+
3
+ Core builder + registry utilities for RRRoutes. The contract package exposes the fluent DSL (`resource`, `withCrud`), the `finalize` helper, cache-key utilities, and all TypeScript inference helpers used by both the client and server packages.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ pnpm add @emeryld/rrroutes-contract
9
+ # or
10
+ npm install @emeryld/rrroutes-contract
11
+ ```
12
+
13
+ `zod` ships as a dependency so you do not need to install it separately.
14
+
15
+ ## Quick start
16
+
17
+ ```ts
18
+ import { resource, finalize } from '@emeryld/rrroutes-contract';
19
+ import { z } from 'zod';
20
+
21
+ const users = resource('/v1')
22
+ .sub('users', (users) =>
23
+ users
24
+ .get({
25
+ querySchema: z.object({ search: z.string().optional() }),
26
+ outputSchema: z.array(z.object({ id: z.string().uuid(), email: z.string().email() })),
27
+ })
28
+ .routeParameter('userId', z.string().uuid(), (user) =>
29
+ user.get({
30
+ outputSchema: z.object({ id: z.string().uuid(), email: z.string().email() }),
31
+ }),
32
+ )
33
+ .done(),
34
+ )
35
+ .done();
36
+
37
+ export const registry = finalize([...users]);
38
+ ```
39
+
40
+ `registry.all` retains the readonly tuple of leaves, while `registry.byKey['GET /v1/users']` gives you autocomplete-safe access to a single endpoint.
41
+
42
+ ## Scripts
43
+
44
+ Run everything from the repo root using pnpm:
45
+
46
+ ```sh
47
+ pnpm install
48
+ pnpm --filter @emeryld/rrroutes-contract build # tsup + d.ts
49
+ pnpm --filter @emeryld/rrroutes-contract test # optional, via Jest
50
+ ```
51
+
52
+ ## Publishing
53
+
54
+ Once the workspace is built, publish just this package from its folder:
55
+
56
+ ```sh
57
+ cd packages/contract
58
+ npm publish --access public
59
+ ```
60
+
61
+ Make sure the version in `packages/contract/package.json` matches what you intend to release.
@@ -0,0 +1,90 @@
1
+ import { z } from 'zod';
2
+ import { AnyLeaf, Append, Leaf, Merge, MergeArray, MethodCfg, NodeCfg, Prettify } from './routesV3.core';
3
+ type ZodTypeAny = z.ZodTypeAny;
4
+ /**
5
+ * Runtime helper that mirrors the typed merge used by the builder.
6
+ * @param a Previously merged params schema inherited from parent segments.
7
+ * @param b Newly introduced params schema.
8
+ * @returns Intersection of schemas when both exist, otherwise whichever is defined.
9
+ */
10
+ declare function mergeSchemas<A extends ZodTypeAny | undefined, B extends ZodTypeAny | undefined>(a: A, b: B): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B;
11
+ /** Builder surface used by `resource(...)` to accumulate leaves. */
12
+ export interface Branch<Base extends string, Acc extends readonly AnyLeaf[], I extends NodeCfg, PS extends ZodTypeAny | undefined> {
13
+ /**
14
+ * Enter or define a static child segment.
15
+ * Optionally accepts extra config and/or a builder to populate nested routes.
16
+ * @param name Child segment literal (no leading slash).
17
+ * @param cfg Optional node configuration to merge for descendants.
18
+ * @param builder Callback to produce leaves for the child branch.
19
+ */
20
+ sub<Name extends string, J extends NodeCfg>(name: Name, cfg?: J): Branch<`${Base}/${Name}`, Acc, Merge<I, NonNullable<J>>, PS>;
21
+ sub<Name extends string, J extends NodeCfg | undefined, R extends readonly AnyLeaf[]>(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>;
22
+ sub<Name extends string, R extends readonly AnyLeaf[]>(name: Name, builder: (r: Branch<`${Base}/${Name}`, readonly [], I, PS>) => R): Branch<Base, MergeArray<Acc, R>, I, PS>;
23
+ /**
24
+ * Introduce a `:param` segment and merge its schema into downstream leaves.
25
+ * @param name Parameter key (without leading colon).
26
+ * @param paramsSchema Zod schema for the parameter value.
27
+ * @param builder Callback that produces leaves beneath the parameterized segment.
28
+ */
29
+ routeParameter<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeaf[]>(name: Name, paramsSchema: P, builder: (r: Branch<`${Base}/:${Name}`, readonly [], I, ReturnType<typeof mergeSchemas<PS, P>>>) => R): Branch<Base, MergeArray<Acc, R>, I, ReturnType<typeof mergeSchemas<PS, P>>>;
30
+ /**
31
+ * Merge additional node configuration that subsequent leaves will inherit.
32
+ * @param cfg Partial node configuration (e.g. `{ authenticated: true }`).
33
+ */
34
+ with<J extends NodeCfg>(cfg: J): Branch<Base, Acc, Merge<I, J>, PS>;
35
+ /**
36
+ * Register a GET leaf at the current path.
37
+ * @param cfg Method configuration (schemas, flags, descriptions, etc).
38
+ */
39
+ get<C extends Omit<MethodCfg, 'paramsSchema'>>(cfg: C): Branch<Base, Append<Acc, Prettify<Leaf<'get', Base, Merge<Merge<I, C>, {
40
+ paramsSchema: PS;
41
+ }>>>>, I, PS>;
42
+ /**
43
+ * Register a POST leaf at the current path.
44
+ * @param cfg Method configuration (schemas, flags, descriptions, etc).
45
+ */
46
+ post<C extends Omit<MethodCfg, 'paramsSchema'>>(cfg: C): Branch<Base, Append<Acc, Leaf<'post', Base, Merge<Merge<I, C>, {
47
+ paramsSchema: PS;
48
+ }>>>, I, PS>;
49
+ /**
50
+ * Register a PUT leaf at the current path.
51
+ * @param cfg Method configuration (schemas, flags, descriptions, etc).
52
+ */
53
+ put<C extends Omit<MethodCfg, 'paramsSchema'>>(cfg: C): Branch<Base, Append<Acc, Leaf<'put', Base, Merge<Merge<I, C>, {
54
+ paramsSchema: PS;
55
+ }>>>, I, PS>;
56
+ /**
57
+ * Register a PATCH leaf at the current path.
58
+ * @param cfg Method configuration (schemas, flags, descriptions, etc).
59
+ */
60
+ patch<C extends Omit<MethodCfg, 'paramsSchema'>>(cfg: C): Branch<Base, Append<Acc, Leaf<'patch', Base, Merge<Merge<I, C>, {
61
+ paramsSchema: PS;
62
+ }>>>, I, PS>;
63
+ /**
64
+ * Register a DELETE leaf at the current path.
65
+ * @param cfg Method configuration (schemas, flags, descriptions, etc).
66
+ */
67
+ delete<C extends Omit<MethodCfg, 'paramsSchema'>>(cfg: C): Branch<Base, Append<Acc, Leaf<'delete', Base, Merge<Merge<I, C>, {
68
+ paramsSchema: PS;
69
+ }>>>, I, PS>;
70
+ /**
71
+ * Finish the branch and return the collected leaves.
72
+ * @returns Readonly tuple of accumulated leaves.
73
+ */
74
+ done(): Readonly<Acc>;
75
+ }
76
+ /**
77
+ * Start building a resource at the given base path.
78
+ * @param base Root path for the resource (e.g. `/v1`).
79
+ * @param inherited Optional node configuration applied to all descendants.
80
+ * @returns Root `Branch` instance used to compose the route tree.
81
+ */
82
+ export declare function resource<Base extends string, I extends NodeCfg = {}>(base: Base, inherited?: I): Branch<Base, readonly [], I, undefined>;
83
+ /**
84
+ * Merge two readonly tuples (preserves literal tuple information).
85
+ * @param arr1 First tuple.
86
+ * @param arr2 Second tuple.
87
+ * @returns New tuple containing values from both inputs.
88
+ */
89
+ export declare const mergeArrays: <T extends readonly any[], S extends readonly any[]>(arr1: T, arr2: S) => [...T, ...S];
90
+ export {};
@@ -0,0 +1,90 @@
1
+ import { z, ZodTypeAny } from 'zod';
2
+ /** Supported HTTP verbs for the routes DSL. */
3
+ export type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete';
4
+ /** Declarative description of a multipart upload field. */
5
+ export type FileField = {
6
+ /** Form field name used for uploads. */
7
+ name: string;
8
+ /** Maximum number of files accepted for this field. */
9
+ maxCount: number;
10
+ };
11
+ /** Configuration that applies to an entire branch of the route tree. */
12
+ export type NodeCfg = {
13
+ /** When true, descendant routes require authentication. */
14
+ authenticated?: boolean;
15
+ };
16
+ /** Per-method configuration merged with inherited node config. */
17
+ export type MethodCfg = NodeCfg & {
18
+ /** Zod schema describing the request body. */
19
+ bodySchema?: ZodTypeAny;
20
+ /** Zod schema describing the query string. */
21
+ querySchema?: ZodTypeAny;
22
+ /** Zod schema describing path params (overrides inferred params). */
23
+ paramsSchema?: ZodTypeAny;
24
+ /** Zod schema describing the response payload. */
25
+ outputSchema?: ZodTypeAny;
26
+ /** Multipart upload definitions for the route. */
27
+ bodyFiles?: FileField[];
28
+ /** Marks the route as an infinite feed (enables cursor helpers). */
29
+ feed?: boolean;
30
+ /** Optional human-readable description for docs/debugging. */
31
+ description?: string;
32
+ };
33
+ /** Immutable representation of a single HTTP route in the tree. */
34
+ export type Leaf<M extends HttpMethod, P extends string, C extends MethodCfg> = {
35
+ /** Lowercase HTTP method (get/post/...). */
36
+ readonly method: M;
37
+ /** Concrete path for the route (e.g. `/v1/users/:userId`). */
38
+ readonly path: P;
39
+ /** Readonly snapshot of route configuration. */
40
+ readonly cfg: Readonly<C>;
41
+ };
42
+ /** Convenience union covering all generated leaves. */
43
+ export type AnyLeaf = Leaf<HttpMethod, string, MethodCfg>;
44
+ /** Merge two object types while keeping nice IntelliSense output. */
45
+ export type Merge<A, B> = Prettify<Omit<A, keyof B> & B>;
46
+ /** Append a new element to a readonly tuple type. */
47
+ export type Append<T extends readonly unknown[], X> = [...T, X];
48
+ /** Concatenate two readonly tuple types. */
49
+ export type MergeArray<A extends readonly unknown[], B extends readonly unknown[]> = [
50
+ ...A,
51
+ ...B
52
+ ];
53
+ type SegmentParams<S extends string> = S extends `:${infer P}` ? P : never;
54
+ type Split<S extends string> = S extends '' ? [] : S extends `${infer A}/${infer B}` ? [A, ...Split<B>] : [S];
55
+ type ExtractParamNames<Path extends string> = SegmentParams<Split<Path>[number]>;
56
+ /** Derive a params object type from a literal route string. */
57
+ export type ExtractParamsFromPath<Path extends string> = ExtractParamNames<Path> extends never ? never : Record<ExtractParamNames<Path>, string | number>;
58
+ /**
59
+ * Interpolate `:params` in a path using the given values.
60
+ * @param path Literal route string containing `:param` segments.
61
+ * @param params Object of parameter values to interpolate.
62
+ * @returns Path string with parameters substituted.
63
+ */
64
+ export declare function compilePath<Path extends string>(path: Path, params: ExtractParamsFromPath<Path>): string;
65
+ /**
66
+ * Build a deterministic cache key for the given leaf.
67
+ * The key matches the shape consumed by React Query helpers.
68
+ * @param args.leaf Leaf describing the endpoint.
69
+ * @param args.params Optional params used to build the path.
70
+ * @param args.query Optional query payload.
71
+ * @returns Tuple suitable for React Query cache keys.
72
+ */
73
+ export declare function buildCacheKey<L extends AnyLeaf>(args: {
74
+ leaf: L;
75
+ params?: ExtractParamsFromPath<L['path']>;
76
+ query?: InferQuery<L>;
77
+ }): readonly [HttpMethod, ...string[], {}];
78
+ /** Infer params either from the explicit params schema or from the path literal. */
79
+ export type InferParams<L extends AnyLeaf> = L['cfg']['paramsSchema'] extends ZodTypeAny ? z.infer<L['cfg']['paramsSchema']> : ExtractParamsFromPath<L['path']>;
80
+ /** Infer query shape from a Zod schema when present. */
81
+ export type InferQuery<L extends AnyLeaf> = L['cfg']['querySchema'] extends ZodTypeAny ? z.infer<L['cfg']['querySchema']> : never;
82
+ /** Infer request body shape from a Zod schema when present. */
83
+ export type InferBody<L extends AnyLeaf> = L['cfg']['bodySchema'] extends ZodTypeAny ? z.infer<L['cfg']['bodySchema']> : never;
84
+ /** Infer handler output shape from a Zod schema. Defaults to unknown. */
85
+ export type InferOutput<L extends AnyLeaf> = L['cfg']['outputSchema'] extends ZodTypeAny ? z.infer<L['cfg']['outputSchema']> : unknown;
86
+ /** Render a type as if it were a simple object literal. */
87
+ export type Prettify<T> = {
88
+ [K in keyof T]: T[K];
89
+ } & {};
90
+ export {};
@@ -0,0 +1,40 @@
1
+ import { AnyLeaf, HttpMethod } from './routesV3.core';
2
+ /** Build the key type for a leaf — distributive so method/path stay paired. */
3
+ export type KeyOf<L extends AnyLeaf> = L extends AnyLeaf ? `${Uppercase<L['method']>} ${L['path']}` : never;
4
+ type MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;
5
+ type PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;
6
+ /**
7
+ * Freeze a leaf tuple into a registry with typed key lookups.
8
+ * @param leaves Readonly tuple of leaves produced by the builder DSL.
9
+ * @returns Registry containing the leaves array and a `byKey` lookup map.
10
+ */
11
+ export declare function finalize<const L extends readonly AnyLeaf[]>(leaves: L): {
12
+ all: L;
13
+ byKey: { [K in KeyOf<L[number]>]: Extract<L[number], {
14
+ method: MethodFromKey<K> & HttpMethod;
15
+ path: PathFromKey<K>;
16
+ }> extends infer T ? { [K_1 in keyof T]: T[K_1]; } : never; };
17
+ log: (logger: {
18
+ system: (...args: unknown[]) => void;
19
+ }) => void;
20
+ };
21
+ /** Nominal type alias for a finalized registry. */
22
+ export type Registry<R extends ReturnType<typeof finalize>> = R;
23
+ type FilterRoute<T extends readonly AnyLeaf[], F extends string, Acc extends readonly AnyLeaf[] = []> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]] ? First extends {
24
+ path: `${string}${F}${string}`;
25
+ } ? FilterRoute<Rest, F, [...Acc, First]> : FilterRoute<Rest, F, Acc> : Acc;
26
+ type UpperCase<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${UpperCase<Rest>}` : S;
27
+ type Routes<T extends readonly AnyLeaf[], F extends string> = FilterRoute<T, F>;
28
+ type ByKey<T extends readonly AnyLeaf[], Acc extends Record<string, AnyLeaf> = {}> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]] ? ByKey<Rest, Acc & {
29
+ [K in `${UpperCase<First['method']>} ${First['path']}`]: First;
30
+ }> : Acc;
31
+ /**
32
+ * Convenience helper for extracting a subset of routes by path fragment.
33
+ * @param T Tuple of leaves produced by the DSL.
34
+ * @param F String fragment to match against the route path.
35
+ */
36
+ export type SubsetRoutes<T extends readonly AnyLeaf[], F extends string> = {
37
+ byKey: ByKey<Routes<T, F>>;
38
+ all: Routes<T, F>;
39
+ };
40
+ export {};
@@ -0,0 +1,200 @@
1
+ import { z, type ZodTypeAny } from 'zod';
2
+ import { resource as baseResource, type Branch as _Branch } from '../core/routesV3.builder';
3
+ import { type AnyLeaf, type Leaf, type NodeCfg } from '../core/routesV3.core';
4
+ /** Default cursor pagination used by list (GET collection). */
5
+ export declare const CrudDefaultPagination: z.ZodObject<{
6
+ limit: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
7
+ cursor: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ /**
10
+ * Merge two Zod schemas at runtime; matches the typing pattern used in builder.
11
+ * @param a Existing params schema inherited from parent branches.
12
+ * @param b Schema introduced at the current branch.
13
+ * @returns Intersection of schemas when both exist, otherwise whichever is defined.
14
+ */
15
+ export declare function mergeSchemas<A extends ZodTypeAny | undefined, B extends ZodTypeAny | undefined>(a: A, b: B): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B;
16
+ /** Build { [Name]: S } Zod object at the type-level. */
17
+ export type ParamSchema<Name extends string, S extends ZodTypeAny> = z.ZodObject<{
18
+ [K in Name]: S;
19
+ }>;
20
+ /** Inject active params schema into a leaf cfg (to match your Leaf typing). */
21
+ type WithParams<I, P> = P extends ZodTypeAny ? Omit<I, 'paramsSchema'> & {
22
+ paramsSchema: P;
23
+ } : I;
24
+ /** Resolve boolean flag: default D unless explicitly false. */
25
+ type Flag<E, D extends boolean> = E extends false ? false : D;
26
+ /** Toggle individual CRUD methods; defaults enable all (create/update require body). */
27
+ export type CrudEnable = {
28
+ /** GET /collection (feed). Defaults to true. */ list?: boolean;
29
+ /** POST /collection. Defaults to true if `create` provided. */ create?: boolean;
30
+ /** GET /collection/:id. Defaults to true. */ read?: boolean;
31
+ /** PATCH /collection/:id. Defaults to true if `update` provided. */ update?: boolean;
32
+ /** DELETE /collection/:id. Defaults to true. */ remove?: boolean;
33
+ };
34
+ /** Collection GET config. */
35
+ export type CrudListCfg = {
36
+ /** Query schema (defaults to cursor pagination). */
37
+ querySchema?: ZodTypeAny;
38
+ /** Output schema (defaults to { items: Item[], nextCursor? }). */
39
+ outputSchema?: ZodTypeAny;
40
+ /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */
41
+ authenticated?: boolean;
42
+ /** Optional description string. */
43
+ description?: string;
44
+ };
45
+ /** Collection POST config. */
46
+ export type CrudCreateCfg = {
47
+ /** Body schema for creating an item. */
48
+ bodySchema: ZodTypeAny;
49
+ /** Output schema (defaults to itemOutputSchema). */
50
+ outputSchema?: ZodTypeAny;
51
+ /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */
52
+ authenticated?: boolean;
53
+ /** Optional description. */
54
+ description?: string;
55
+ };
56
+ /** Item GET config. */
57
+ export type CrudReadCfg = {
58
+ /** Output schema (defaults to itemOutputSchema). */
59
+ outputSchema?: ZodTypeAny;
60
+ /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */
61
+ authenticated?: boolean;
62
+ /** Optional description. */
63
+ description?: string;
64
+ };
65
+ /** Item PATCH config. */
66
+ export type CrudUpdateCfg = {
67
+ /** Body schema for partial updates. */
68
+ bodySchema: ZodTypeAny;
69
+ /** Output schema (defaults to itemOutputSchema). */
70
+ outputSchema?: ZodTypeAny;
71
+ /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */
72
+ authenticated?: boolean;
73
+ /** Optional description. */
74
+ description?: string;
75
+ };
76
+ /** Item DELETE config. */
77
+ export type CrudRemoveCfg = {
78
+ /** Output schema (defaults to { ok: true }). */
79
+ outputSchema?: ZodTypeAny;
80
+ /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */
81
+ authenticated?: boolean;
82
+ /** Optional description. */
83
+ description?: string;
84
+ };
85
+ /**
86
+ * CRUD config for `.crud("resource", cfg, extras?)`.
87
+ *
88
+ * It synthesizes `:[resourceName]Id` as the param key and uses `paramSchema` for the value.
89
+ * Prefer passing a **value schema** (e.g. `z.string().uuid()`).
90
+ * As a convenience, a ZodObject of shape `{ [resourceName]Id: schema }` is accepted at runtime too.
91
+ */
92
+ export type CrudCfg<Name extends string = string> = {
93
+ /**
94
+ * Zod schema for the ID *value* (e.g. `z.string().uuid()`).
95
+ * The parameter key is always `:${name}Id`.
96
+ * (If you pass `z.object({ [name]Id: ... })`, it's accepted at runtime.)
97
+ */
98
+ paramSchema: ZodTypeAny;
99
+ /**
100
+ * The single-item output shape used by read/create/update by default.
101
+ * List defaults to `{ items: z.array(itemOutputSchema), nextCursor? }`.
102
+ */
103
+ itemOutputSchema: ZodTypeAny;
104
+ /** Per-method toggles (all enabled by default; create/update require bodies). */
105
+ enable?: CrudEnable;
106
+ /** GET /collection configuration. */ list?: CrudListCfg;
107
+ /** POST /collection configuration (enables create). */ create?: CrudCreateCfg;
108
+ /** GET /collection/:id configuration. */ read?: CrudReadCfg;
109
+ /** PATCH /collection/:id configuration (enables update). */ update?: CrudUpdateCfg;
110
+ /** DELETE /collection/:id configuration. */ remove?: CrudRemoveCfg;
111
+ };
112
+ type CrudLeavesTuple<Base extends string, I extends NodeCfg, PS extends ZodTypeAny | undefined, Name extends string, C extends CrudCfg<Name>> = `${Name}Id` extends infer IdName extends string ? ParamSchema<IdName, C['paramSchema']> extends infer IdParamZ extends ZodTypeAny ? [
113
+ ...(Flag<C['enable'] extends CrudEnable ? C['enable']['list'] : undefined, true> extends true ? [
114
+ Leaf<'get', `${Base}/${Name}`, WithParams<Omit<I, never> & {
115
+ feed: true;
116
+ querySchema: C['list'] extends CrudListCfg ? C['list']['querySchema'] : typeof CrudDefaultPagination;
117
+ outputSchema: C['list'] extends CrudListCfg ? C['list']['outputSchema'] extends ZodTypeAny ? C['list']['outputSchema'] : z.ZodObject<{
118
+ items: z.ZodArray<C['itemOutputSchema']>;
119
+ nextCursor: z.ZodOptional<z.ZodString>;
120
+ }> : z.ZodObject<{
121
+ items: z.ZodArray<C['itemOutputSchema']>;
122
+ nextCursor: z.ZodOptional<z.ZodString>;
123
+ }>;
124
+ description?: string;
125
+ }, PS>>
126
+ ] : []),
127
+ ...((C['create'] extends CrudCreateCfg ? true : false) extends true ? Flag<C['enable'] extends CrudEnable ? C['enable']['create'] : undefined, true> extends true ? [
128
+ Leaf<'post', `${Base}/${Name}`, WithParams<Omit<I, never> & {
129
+ bodySchema: C['create'] extends CrudCreateCfg ? C['create']['bodySchema'] : never;
130
+ outputSchema: C['create'] extends CrudCreateCfg ? C['create']['outputSchema'] extends ZodTypeAny ? C['create']['outputSchema'] : C['itemOutputSchema'] : never;
131
+ description?: string;
132
+ }, PS>>
133
+ ] : [] : []),
134
+ ...(Flag<C['enable'] extends CrudEnable ? C['enable']['read'] : undefined, true> extends true ? [
135
+ Leaf<'get', `${Base}/${Name}/:${IdName}`, WithParams<Omit<I, never> & {
136
+ outputSchema: C['read'] extends CrudReadCfg ? C['read']['outputSchema'] extends ZodTypeAny ? C['read']['outputSchema'] : C['itemOutputSchema'] : C['itemOutputSchema'];
137
+ description?: string;
138
+ }, ReturnType<typeof mergeSchemas<PS, IdParamZ>>>>
139
+ ] : []),
140
+ ...((C['update'] extends CrudUpdateCfg ? true : false) extends true ? Flag<C['enable'] extends CrudEnable ? C['enable']['update'] : undefined, true> extends true ? [
141
+ Leaf<'patch', `${Base}/${Name}/:${IdName}`, WithParams<Omit<I, never> & {
142
+ bodySchema: C['update'] extends CrudUpdateCfg ? C['update']['bodySchema'] : never;
143
+ outputSchema: C['update'] extends CrudUpdateCfg ? C['update']['outputSchema'] extends ZodTypeAny ? C['update']['outputSchema'] : C['itemOutputSchema'] : never;
144
+ description?: string;
145
+ }, ReturnType<typeof mergeSchemas<PS, IdParamZ>>>>
146
+ ] : [] : []),
147
+ ...(Flag<C['enable'] extends CrudEnable ? C['enable']['remove'] : undefined, true> extends true ? [
148
+ Leaf<'delete', `${Base}/${Name}/:${IdName}`, WithParams<Omit<I, never> & {
149
+ outputSchema: C['remove'] extends CrudRemoveCfg ? C['remove']['outputSchema'] extends ZodTypeAny ? C['remove']['outputSchema'] : z.ZodObject<{
150
+ ok: z.ZodLiteral<true>;
151
+ }> : z.ZodObject<{
152
+ ok: z.ZodLiteral<true>;
153
+ }>;
154
+ description?: string;
155
+ }, ReturnType<typeof mergeSchemas<PS, IdParamZ>>>>
156
+ ] : [])
157
+ ] : never : never;
158
+ /** Merge generated leaves + extras into the branch accumulator. */
159
+ type CrudResultAcc<Base extends string, Acc extends readonly AnyLeaf[], I extends NodeCfg, PS extends ZodTypeAny | undefined, Name extends string, C extends CrudCfg<Name>, Extras extends readonly AnyLeaf[]> = [...Acc, ...CrudLeavesTuple<Base, I, PS, Name, C>, ...Extras];
160
+ declare module './../core/routesV3.builder' {
161
+ interface Branch<Base extends string, Acc extends readonly AnyLeaf[], I extends NodeCfg, PS extends ZodTypeAny | undefined> {
162
+ /**
163
+ * Generate opinionated CRUD endpoints for a collection at `/.../<name>`
164
+ * with an item at `/.../<name>/:<name>Id`.
165
+ *
166
+ * Creates (subject to `enable` + presence of create/update bodies):
167
+ * - GET /<name> (feed list)
168
+ * - POST /<name> (create)
169
+ * - GET /<name>/:<name>Id (read item)
170
+ * - PATCH /<name>/:<name>Id (update item)
171
+ * - DELETE /<name>/:<name>Id (remove item)
172
+ *
173
+ * The `extras` callback receives live builders at both collection and item
174
+ * levels so you can add custom subroutes; their leaves are included in the
175
+ * returned Branch type.
176
+ */
177
+ crud<Name extends string, C extends CrudCfg<Name>, Extras extends readonly AnyLeaf[] = readonly []>(name: Name, cfg: C, extras?: (ctx: {
178
+ /** Builder at `/.../<name>` (collection scope). */
179
+ collection: _Branch<`${Base}/${Name}`, readonly [], I, PS>;
180
+ /** Builder at `/.../<name>/:<name>Id` (item scope). */
181
+ item: _Branch<`${Base}/${Name}/:${`${Name}Id`}`, readonly [], I, ReturnType<typeof mergeSchemas<PS, ParamSchema<`${Name}Id`, C['paramSchema']>>>>;
182
+ }) => Extras): _Branch<Base, CrudResultAcc<Base, Acc, I, PS, Name, C, Extras>, I, PS>;
183
+ }
184
+ }
185
+ /**
186
+ * Decorate a Branch instance so `.crud(...)` is available at runtime.
187
+ * Tip: wrap the root: `withCrud(resource('/v1'))`.
188
+ * @param branch Branch returned by `resource(...)`.
189
+ * @returns Same branch with `.crud(...)` installed.
190
+ */
191
+ export declare function withCrud<Base extends string, Acc extends readonly AnyLeaf[], I extends NodeCfg, PS extends ZodTypeAny | undefined>(branch: _Branch<Base, Acc, I, PS>): _Branch<Base, Acc, I, PS>;
192
+ /**
193
+ * Drop-in replacement for `resource(...)` that returns a Branch with `.crud()` installed.
194
+ * You can either use this or call `withCrud(resource(...))`.
195
+ * @param base Root path for the resource (e.g. `/v1`).
196
+ * @param inherited Node configuration merged into every leaf.
197
+ * @returns Branch builder that already includes the CRUD helper.
198
+ */
199
+ export declare function resourceWithCrud<Base extends string, I extends NodeCfg = {}>(base: Base, inherited: I): _Branch<Base, readonly [], I, undefined>;
200
+ export { baseResource as resource };
package/dist/index.cjs ADDED
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CrudDefaultPagination: () => CrudDefaultPagination,
24
+ buildCacheKey: () => buildCacheKey,
25
+ compilePath: () => compilePath,
26
+ finalize: () => finalize,
27
+ mergeArrays: () => mergeArrays,
28
+ mergeSchemas: () => mergeSchemas2,
29
+ resource: () => resource,
30
+ resourceWithCrud: () => resourceWithCrud,
31
+ withCrud: () => withCrud
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/core/routesV3.builder.ts
36
+ var import_zod = require("zod");
37
+ function mergeSchemas(a, b) {
38
+ if (a && b) return import_zod.z.intersection(a, b);
39
+ return a ?? b;
40
+ }
41
+ function resource(base, inherited) {
42
+ const rootBase = base;
43
+ const rootInherited = { ...inherited };
44
+ function makeBranch(base2, inherited2, mergedParamsSchema) {
45
+ const stack = [];
46
+ let currentBase = base2;
47
+ let inheritedCfg = { ...inherited2 };
48
+ let currentParamsSchema = mergedParamsSchema;
49
+ function add(method, cfg) {
50
+ const effectiveParamsSchema = cfg.paramsSchema ?? currentParamsSchema;
51
+ const fullCfg = effectiveParamsSchema ? { ...inheritedCfg, ...cfg, paramsSchema: effectiveParamsSchema } : { ...inheritedCfg, ...cfg };
52
+ const leaf = {
53
+ method,
54
+ path: currentBase,
55
+ cfg: fullCfg
56
+ };
57
+ stack.push(leaf);
58
+ return api;
59
+ }
60
+ const api = {
61
+ // compose a plain subpath (optional cfg) with optional builder
62
+ sub(name, cfgOrBuilder, maybeBuilder) {
63
+ let cfg;
64
+ let builder;
65
+ if (typeof cfgOrBuilder === "function") {
66
+ builder = cfgOrBuilder;
67
+ } else {
68
+ cfg = cfgOrBuilder;
69
+ builder = maybeBuilder;
70
+ }
71
+ const childBase = `${currentBase}/${name}`;
72
+ const childInherited = { ...inheritedCfg, ...cfg ?? {} };
73
+ if (builder) {
74
+ const child = makeBranch(childBase, childInherited, currentParamsSchema);
75
+ const leaves = builder(child);
76
+ for (const l of leaves) stack.push(l);
77
+ return api;
78
+ } else {
79
+ currentBase = childBase;
80
+ inheritedCfg = childInherited;
81
+ return api;
82
+ }
83
+ },
84
+ // the single param function: name + schema + builder
85
+ routeParameter(name, paramsSchema, builder) {
86
+ const childBase = `${currentBase}/:${name}`;
87
+ const childParams = mergeSchemas(currentParamsSchema, paramsSchema);
88
+ const child = makeBranch(childBase, inheritedCfg, childParams);
89
+ const leaves = builder(child);
90
+ for (const l of leaves) stack.push(l);
91
+ return api;
92
+ },
93
+ with(cfg) {
94
+ inheritedCfg = { ...inheritedCfg, ...cfg };
95
+ return api;
96
+ },
97
+ // methods (inject current params schema if missing)
98
+ get(cfg) {
99
+ return add("get", cfg);
100
+ },
101
+ post(cfg) {
102
+ return add("post", { ...cfg, feed: false });
103
+ },
104
+ put(cfg) {
105
+ return add("put", { ...cfg, feed: false });
106
+ },
107
+ patch(cfg) {
108
+ return add("patch", { ...cfg, feed: false });
109
+ },
110
+ delete(cfg) {
111
+ return add("delete", { ...cfg, feed: false });
112
+ },
113
+ done() {
114
+ return stack;
115
+ }
116
+ };
117
+ return api;
118
+ }
119
+ return makeBranch(rootBase, rootInherited, void 0);
120
+ }
121
+ var mergeArrays = (arr1, arr2) => {
122
+ return [...arr1, ...arr2];
123
+ };
124
+
125
+ // src/core/routesV3.core.ts
126
+ function compilePath(path, params) {
127
+ if (!params) return path;
128
+ return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
129
+ const v = params[k];
130
+ if (v === void 0 || v === null) throw new Error(`Missing param :${k}`);
131
+ return String(v);
132
+ });
133
+ }
134
+ function buildCacheKey(args) {
135
+ let p = args.leaf.path;
136
+ if (args.params) {
137
+ p = compilePath(p, args.params);
138
+ }
139
+ return [args.leaf.method, ...p.split("/").filter(Boolean), args.query ?? {}];
140
+ }
141
+
142
+ // src/core/routesV3.finalize.ts
143
+ function finalize(leaves) {
144
+ const byKey = Object.fromEntries(
145
+ leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l])
146
+ );
147
+ const log = (logger) => {
148
+ logger.system("Finalized routes:");
149
+ Object.keys(byKey).forEach((k) => {
150
+ const leaf = byKey[k];
151
+ logger.system(`- ${k} (auth: ${leaf.cfg.authenticated ? "yes" : "no"})`);
152
+ });
153
+ };
154
+ return { all: leaves, byKey, log };
155
+ }
156
+
157
+ // src/crud/routesV3.crud.ts
158
+ var import_zod2 = require("zod");
159
+ var CrudDefaultPagination = import_zod2.z.object({
160
+ limit: import_zod2.z.coerce.number().min(1).max(100).default(20),
161
+ cursor: import_zod2.z.string().optional()
162
+ });
163
+ function mergeSchemas2(a, b) {
164
+ if (a && b) return import_zod2.z.intersection(a, b);
165
+ return a ?? b;
166
+ }
167
+ function withCrud(branch) {
168
+ const b = branch;
169
+ if (typeof b.crud === "function") return branch;
170
+ b.crud = function(name, cfg, extras) {
171
+ return this.sub(name, (collection) => {
172
+ const idKey = `${name}Id`;
173
+ const s = cfg.paramSchema;
174
+ const isObj = s && typeof s._def === "object" && s._def.typeName === "ZodObject" && typeof s.shape === "function";
175
+ const paramObj = isObj && s.shape()[idKey] ? s : import_zod2.z.object({ [idKey]: s });
176
+ const itemOut = cfg.itemOutputSchema;
177
+ const listOut = cfg.list?.outputSchema ?? import_zod2.z.object({
178
+ items: import_zod2.z.array(itemOut),
179
+ nextCursor: import_zod2.z.string().optional()
180
+ });
181
+ const want = {
182
+ list: cfg.enable?.list !== false,
183
+ create: cfg.enable?.create !== false && !!cfg.create?.bodySchema,
184
+ read: cfg.enable?.read !== false,
185
+ update: cfg.enable?.update !== false && !!cfg.update?.bodySchema,
186
+ remove: cfg.enable?.remove !== false
187
+ };
188
+ if (want.list) {
189
+ const c = cfg.list?.authenticated ? collection.with({ authenticated: true }) : collection;
190
+ c.get({
191
+ feed: true,
192
+ querySchema: cfg.list?.querySchema ?? CrudDefaultPagination,
193
+ outputSchema: listOut,
194
+ description: cfg.list?.description ?? "List"
195
+ });
196
+ }
197
+ if (want.create) {
198
+ const c = cfg.create?.authenticated ? collection.with({ authenticated: true }) : collection;
199
+ c.post({
200
+ bodySchema: cfg.create.bodySchema,
201
+ outputSchema: cfg.create?.outputSchema ?? itemOut,
202
+ description: cfg.create?.description ?? "Create"
203
+ });
204
+ }
205
+ collection.routeParameter(idKey, paramObj, (item) => {
206
+ if (want.read) {
207
+ const i = cfg.read?.authenticated ? item.with({ authenticated: true }) : item;
208
+ i.get({
209
+ outputSchema: cfg.read?.outputSchema ?? itemOut,
210
+ description: cfg.read?.description ?? "Read"
211
+ });
212
+ }
213
+ if (want.update) {
214
+ const i = cfg.update?.authenticated ? item.with({ authenticated: true }) : item;
215
+ i.patch({
216
+ bodySchema: cfg.update.bodySchema,
217
+ outputSchema: cfg.update?.outputSchema ?? itemOut,
218
+ description: cfg.update?.description ?? "Update"
219
+ });
220
+ }
221
+ if (want.remove) {
222
+ const i = cfg.remove?.authenticated ? item.with({ authenticated: true }) : item;
223
+ i.delete({
224
+ outputSchema: cfg.remove?.outputSchema ?? import_zod2.z.object({ ok: import_zod2.z.literal(true) }),
225
+ description: cfg.remove?.description ?? "Delete"
226
+ });
227
+ }
228
+ if (extras) extras({ collection: withCrud(collection), item: withCrud(item) });
229
+ return item.done();
230
+ });
231
+ return collection.done();
232
+ });
233
+ };
234
+ return branch;
235
+ }
236
+ function resourceWithCrud(base, inherited) {
237
+ return withCrud(resource(base, inherited));
238
+ }
239
+ // Annotate the CommonJS export names for ESM import in node:
240
+ 0 && (module.exports = {
241
+ CrudDefaultPagination,
242
+ buildCacheKey,
243
+ compilePath,
244
+ finalize,
245
+ mergeArrays,
246
+ mergeSchemas,
247
+ resource,
248
+ resourceWithCrud,
249
+ withCrud
250
+ });
251
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/crud/routesV3.crud.ts"],"sourcesContent":["export * from './core/routesV3.builder';\nexport * from './core/routesV3.core';\nexport * from './core/routesV3.finalize';\nexport * from './crud/routesV3.crud';","import { z } from 'zod';\nimport {\n AnyLeaf,\n Append,\n HttpMethod,\n Leaf,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n} from './routesV3.core';\n\ntype ZodTypeAny = z.ZodTypeAny;\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 | undefined, B extends ZodTypeAny | undefined>(\n a: A,\n b: B,\n): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B {\n if (a && b) return z.intersection(a as any, b as any) as any;\n return (a ?? b) as any;\n}\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeaf[],\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<Name extends string, J extends NodeCfg | undefined, R extends readonly AnyLeaf[]>(\n name: Name,\n cfg: J,\n builder: (r: Branch<`${Base}/${Name}`, readonly [], Merge<I, NonNullable<J>>, PS>) => R,\n ): Branch<Base, Append<Acc, R[number]>, Merge<I, NonNullable<J>>, PS>;\n\n sub<Name extends string, R extends readonly AnyLeaf[]>(\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<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeaf[]>(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<`${Base}/:${Name}`, readonly [], I, ReturnType<typeof mergeSchemas<PS, P>>>,\n ) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, ReturnType<typeof mergeSchemas<PS, P>>>;\n\n // --- flags inheritance ---\n /**\n * Merge additional node configuration that subsequent leaves will inherit.\n * @param cfg Partial node configuration (e.g. `{ authenticated: true }`).\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 Omit<MethodCfg, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Prettify<Leaf<'get', Base, Merge<Merge<I, C>, { paramsSchema: 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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<Base, Append<Acc, Leaf<'post', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>, I, PS>;\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<Base, Append<Acc, Leaf<'put', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>, I, PS>;\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Leaf<'patch', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>,\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Leaf<'delete', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>,\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<Base2 extends string, I2 extends NodeCfg, PS2 extends ZodTypeAny | undefined>(\n base2: Base2,\n inherited2: I2,\n mergedParamsSchema?: PS2,\n ) {\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 ?? currentParamsSchema) as PS2 | undefined;\n const fullCfg = (\n effectiveParamsSchema\n ? { ...inheritedCfg, ...cfg, paramsSchema: effectiveParamsSchema }\n : { ...inheritedCfg, ...cfg }\n ) as Merge<I2, C>;\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<Base2, Append<readonly [], typeof leaf>, I2, PS2>;\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 AnyLeaf[]),\n maybeBuilder?: (r: any) => readonly AnyLeaf[],\n ) {\n let cfg: NodeCfg | undefined;\n let builder: ((r: any) => readonly AnyLeaf[]) | 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<I2, NonNullable<J>>;\n\n if (builder) {\n // params schema PS2 flows through unchanged on plain sub\n const child = makeBranch(childBase, childInherited, currentParamsSchema);\n const leaves = builder(child) as readonly AnyLeaf[];\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<`${Base2}/${Name}`, readonly [], typeof childInherited, PS2>;\n }\n },\n\n // the single param function: name + schema + builder\n routeParameter<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeaf[]>(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<`${Base2}/:${Name}`, readonly [], I2, ReturnType<typeof mergeSchemas<PS2, P>>>,\n ) => R,\n ) {\n const childBase = `${currentBase}/:${name}` as const;\n // Merge existing PS2 with P to create the child’s active params schema\n const childParams = mergeSchemas(currentParamsSchema, paramsSchema);\n const child = makeBranch(childBase, inheritedCfg as I2, childParams);\n const leaves = builder(child) as readonly AnyLeaf[];\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 ReturnType<typeof mergeSchemas<PS2, 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, ZodTypeAny } 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 /** When true, descendant routes require authentication. */\n authenticated?: boolean;\n};\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = NodeCfg & {\n /** Zod schema describing the request body. */\n bodySchema?: ZodTypeAny;\n /** Zod schema describing the query string. */\n querySchema?: ZodTypeAny;\n /** Zod schema describing path params (overrides inferred params). */\n paramsSchema?: ZodTypeAny;\n /** Zod schema describing the response payload. */\n outputSchema?: ZodTypeAny;\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 /** Optional human-readable description for docs/debugging. */\n description?: string;\n};\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<M extends HttpMethod, P extends string, C extends MethodCfg> = {\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<A extends readonly unknown[], B extends readonly unknown[]> = [\n ...A,\n ...B,\n];\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 ? never : 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>(path: Path, params: ExtractParamsFromPath<Path>) {\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 */\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 [args.leaf.method, ...p.split('/').filter(Boolean), args.query ?? {}] as const;\n}\n\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<L extends AnyLeaf> = L['cfg']['paramsSchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['paramsSchema']>\n : ExtractParamsFromPath<L['path']>;\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<L extends AnyLeaf> = L['cfg']['querySchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['querySchema']>\n : never;\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<L extends AnyLeaf> = L['cfg']['bodySchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['bodySchema']>\n : never;\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<L extends AnyLeaf> = L['cfg']['outputSchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['outputSchema']>\n : unknown;\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n","import { AnyLeaf, 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 AnyLeaf> = L extends AnyLeaf\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 AnyLeaf[]> = KeyOf<Leaves[number]>;\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<Leaves extends readonly AnyLeaf[], K extends string> = 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 AnyLeaf[]>(leaves: L) {\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} (auth: ${leaf.cfg.authenticated ? 'yes' : 'no'})`);\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 AnyLeaf[],\n F extends string,\n Acc extends readonly AnyLeaf[] = [],\n> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]]\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<T extends readonly AnyLeaf[], F extends string> = FilterRoute<T, F>;\ntype ByKey<\n T extends readonly AnyLeaf[],\n Acc extends Record<string, AnyLeaf> = {},\n> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]]\n ? ByKey<Rest, Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }>\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<T extends readonly AnyLeaf[], F extends string> = {\n byKey: ByKey<Routes<T, F>>;\n all: Routes<T, F>;\n};\n","// routesV3.crud.ts\n// -----------------------------------------------------------------------------\n// Add a first-class .crud() helper to the routesV3 DSL, without touching the\n// core builder. This file:\n// 1) Declares types + module augmentation to extend Branch<...> with .crud\n// 2) Provides a runtime decorator `withCrud(...)` that installs the method\n// 3) Exports a convenience `resourceWithCrud(...)` wrapper (optional)\n// -----------------------------------------------------------------------------\n\nimport { z, type ZodTypeAny } from 'zod';\nimport {\n resource as baseResource,\n type Branch as _Branch, // import type so we can augment it\n} from '../core/routesV3.builder';\nimport { type AnyLeaf, type Leaf, type NodeCfg } from '../core/routesV3.core';\n\n// -----------------------------------------------------------------------------\n// Small utilities (runtime + types)\n// -----------------------------------------------------------------------------\n\n/** Default cursor pagination used by list (GET collection). */\nexport const CrudDefaultPagination = z.object({\n limit: z.coerce.number().min(1).max(100).default(20),\n cursor: z.string().optional(),\n});\n\n/**\n * Merge two Zod schemas at runtime; matches the typing pattern used in builder.\n * @param a Existing params schema inherited from parent branches.\n * @param b Schema introduced at the current branch.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nexport function mergeSchemas<A extends ZodTypeAny | undefined, B extends ZodTypeAny | undefined>(\n a: A,\n b: B,\n): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B {\n if (a && b) return z.intersection(a as any, b as any) as any;\n return (a ?? b) as any;\n}\n\n/** Build { [Name]: S } Zod object at the type-level. */\nexport type ParamSchema<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S;\n}>;\n\n/** Inject active params schema into a leaf cfg (to match your Leaf typing). */\ntype WithParams<I, P> = P extends ZodTypeAny ? Omit<I, 'paramsSchema'> & { paramsSchema: P } : I;\n\n/** Resolve boolean flag: default D unless explicitly false. */\ntype Flag<E, D extends boolean> = E extends false ? false : D;\n\n// -----------------------------------------------------------------------------\n// CRUD config (few knobs, with DX comments)\n// -----------------------------------------------------------------------------\n\n/** Toggle individual CRUD methods; defaults enable all (create/update require body). */\nexport type CrudEnable = {\n /** GET /collection (feed). Defaults to true. */ list?: boolean;\n /** POST /collection. Defaults to true if `create` provided. */ create?: boolean;\n /** GET /collection/:id. Defaults to true. */ read?: boolean;\n /** PATCH /collection/:id. Defaults to true if `update` provided. */ update?: boolean;\n /** DELETE /collection/:id. Defaults to true. */ remove?: boolean;\n};\n\n/** Collection GET config. */\nexport type CrudListCfg = {\n /** Query schema (defaults to cursor pagination). */\n querySchema?: ZodTypeAny;\n /** Output schema (defaults to { items: Item[], nextCursor? }). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description string. */\n description?: string;\n};\n\n/** Collection POST config. */\nexport type CrudCreateCfg = {\n /** Body schema for creating an item. */\n bodySchema: ZodTypeAny;\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item GET config. */\nexport type CrudReadCfg = {\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item PATCH config. */\nexport type CrudUpdateCfg = {\n /** Body schema for partial updates. */\n bodySchema: ZodTypeAny;\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item DELETE config. */\nexport type CrudRemoveCfg = {\n /** Output schema (defaults to { ok: true }). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/**\n * CRUD config for `.crud(\"resource\", cfg, extras?)`.\n *\n * It synthesizes `:[resourceName]Id` as the param key and uses `paramSchema` for the value.\n * Prefer passing a **value schema** (e.g. `z.string().uuid()`).\n * As a convenience, a ZodObject of shape `{ [resourceName]Id: schema }` is accepted at runtime too.\n */\nexport type CrudCfg<Name extends string = string> = {\n /**\n * Zod schema for the ID *value* (e.g. `z.string().uuid()`).\n * The parameter key is always `:${name}Id`.\n * (If you pass `z.object({ [name]Id: ... })`, it's accepted at runtime.)\n */\n paramSchema: ZodTypeAny;\n\n /**\n * The single-item output shape used by read/create/update by default.\n * List defaults to `{ items: z.array(itemOutputSchema), nextCursor? }`.\n */\n itemOutputSchema: ZodTypeAny;\n\n /** Per-method toggles (all enabled by default; create/update require bodies). */\n enable?: CrudEnable;\n\n /** GET /collection configuration. */ list?: CrudListCfg;\n /** POST /collection configuration (enables create). */ create?: CrudCreateCfg;\n /** GET /collection/:id configuration. */ read?: CrudReadCfg;\n /** PATCH /collection/:id configuration (enables update). */ update?: CrudUpdateCfg;\n /** DELETE /collection/:id configuration. */ remove?: CrudRemoveCfg;\n};\n\n// -----------------------------------------------------------------------------\n// Type plumbing to expose generated leaves (so finalize().byKey sees them)\n// -----------------------------------------------------------------------------\n\ntype CrudLeavesTuple<\n Base extends string,\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Name extends string,\n C extends CrudCfg<Name>,\n> = `${Name}Id` extends infer IdName extends string\n ? ParamSchema<IdName, C['paramSchema']> extends infer IdParamZ extends ZodTypeAny\n ? [\n // GET /collection\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['list'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'get',\n `${Base}/${Name}`,\n WithParams<\n Omit<I, never> & {\n feed: true;\n querySchema: C['list'] extends CrudListCfg\n ? C['list']['querySchema']\n : typeof CrudDefaultPagination;\n outputSchema: C['list'] extends CrudListCfg\n ? C['list']['outputSchema'] extends ZodTypeAny\n ? C['list']['outputSchema']\n : z.ZodObject<{\n items: z.ZodArray<C['itemOutputSchema']>;\n nextCursor: z.ZodOptional<z.ZodString>;\n }>\n : z.ZodObject<{\n items: z.ZodArray<C['itemOutputSchema']>;\n nextCursor: z.ZodOptional<z.ZodString>;\n }>;\n description?: string;\n },\n PS\n >\n >,\n ]\n : []),\n\n // POST /collection\n ...((C['create'] extends CrudCreateCfg ? true : false) extends true\n ? Flag<\n C['enable'] extends CrudEnable ? C['enable']['create'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'post',\n `${Base}/${Name}`,\n WithParams<\n Omit<I, never> & {\n bodySchema: C['create'] extends CrudCreateCfg\n ? C['create']['bodySchema']\n : never;\n outputSchema: C['create'] extends CrudCreateCfg\n ? C['create']['outputSchema'] extends ZodTypeAny\n ? C['create']['outputSchema']\n : C['itemOutputSchema']\n : never;\n description?: string;\n },\n PS\n >\n >,\n ]\n : []\n : []),\n\n // GET /collection/:id\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['read'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'get',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n outputSchema: C['read'] extends CrudReadCfg\n ? C['read']['outputSchema'] extends ZodTypeAny\n ? C['read']['outputSchema']\n : C['itemOutputSchema']\n : C['itemOutputSchema'];\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []),\n\n // PATCH /collection/:id\n ...((C['update'] extends CrudUpdateCfg ? true : false) extends true\n ? Flag<\n C['enable'] extends CrudEnable ? C['enable']['update'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'patch',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n bodySchema: C['update'] extends CrudUpdateCfg\n ? C['update']['bodySchema']\n : never;\n outputSchema: C['update'] extends CrudUpdateCfg\n ? C['update']['outputSchema'] extends ZodTypeAny\n ? C['update']['outputSchema']\n : C['itemOutputSchema']\n : never;\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []\n : []),\n\n // DELETE /collection/:id\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['remove'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'delete',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n outputSchema: C['remove'] extends CrudRemoveCfg\n ? C['remove']['outputSchema'] extends ZodTypeAny\n ? C['remove']['outputSchema']\n : z.ZodObject<{ ok: z.ZodLiteral<true> }>\n : z.ZodObject<{ ok: z.ZodLiteral<true> }>;\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []),\n ]\n : never\n : never;\n\n/** Merge generated leaves + extras into the branch accumulator. */\ntype CrudResultAcc<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[],\n> = [...Acc, ...CrudLeavesTuple<Base, I, PS, Name, C>, ...Extras];\n\n// -----------------------------------------------------------------------------\n// Module augmentation: add the typed `.crud(...)` to Branch<...>\n// -----------------------------------------------------------------------------\n\ndeclare module './../core/routesV3.builder' {\n interface Branch<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n > {\n /**\n * Generate opinionated CRUD endpoints for a collection at `/.../<name>`\n * with an item at `/.../<name>/:<name>Id`.\n *\n * Creates (subject to `enable` + presence of create/update bodies):\n * - GET /<name> (feed list)\n * - POST /<name> (create)\n * - GET /<name>/:<name>Id (read item)\n * - PATCH /<name>/:<name>Id (update item)\n * - DELETE /<name>/:<name>Id (remove item)\n *\n * The `extras` callback receives live builders at both collection and item\n * levels so you can add custom subroutes; their leaves are included in the\n * returned Branch type.\n */\n crud<\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[] = readonly [],\n >(\n name: Name,\n cfg: C,\n extras?: (ctx: {\n /** Builder at `/.../<name>` (collection scope). */\n collection: _Branch<`${Base}/${Name}`, readonly [], I, PS>;\n /** Builder at `/.../<name>/:<name>Id` (item scope). */\n item: _Branch<\n `${Base}/${Name}/:${`${Name}Id`}`,\n readonly [],\n I,\n ReturnType<typeof mergeSchemas<PS, ParamSchema<`${Name}Id`, C['paramSchema']>>>\n >;\n }) => Extras,\n ): _Branch<Base, CrudResultAcc<Base, Acc, I, PS, Name, C, Extras>, I, PS>;\n }\n}\n\n// -----------------------------------------------------------------------------\n// Runtime: install .crud on any Branch via decorator\n// -----------------------------------------------------------------------------\n\n/**\n * Decorate a Branch instance so `.crud(...)` is available at runtime.\n * Tip: wrap the root: `withCrud(resource('/v1'))`.\n * @param branch Branch returned by `resource(...)`.\n * @returns Same branch with `.crud(...)` installed.\n */\nexport function withCrud<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n>(branch: _Branch<Base, Acc, I, PS>): _Branch<Base, Acc, I, PS> {\n const b = branch as any;\n if (typeof b.crud === 'function') return branch;\n\n b.crud = function <\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[] = readonly [],\n >(this: any, name: Name, cfg: C, extras?: (ctx: { collection: any; item: any }) => Extras) {\n // Build inside a sub-branch at `/.../<name>` and harvest its leaves.\n return this.sub(name, (collection: any) => {\n const idKey = `${name}Id`;\n\n // Accept a value schema (preferred); but if a z.object({[name]Id: ...}) is provided, use it.\n const s = cfg.paramSchema;\n const isObj =\n s &&\n typeof (s as any)._def === 'object' &&\n (s as any)._def.typeName === 'ZodObject' &&\n typeof (s as any).shape === 'function';\n\n const paramObj =\n isObj && (s as any).shape()[idKey]\n ? (s as ZodTypeAny)\n : z.object({ [idKey]: s } as Record<string, ZodTypeAny>);\n\n const itemOut = cfg.itemOutputSchema;\n const listOut =\n cfg.list?.outputSchema ??\n z.object({\n items: z.array(itemOut),\n nextCursor: z.string().optional(),\n });\n\n const want = {\n list: cfg.enable?.list !== false,\n create: cfg.enable?.create !== false && !!cfg.create?.bodySchema,\n read: cfg.enable?.read !== false,\n update: cfg.enable?.update !== false && !!cfg.update?.bodySchema,\n remove: cfg.enable?.remove !== false,\n } as const;\n\n // Collection routes\n if (want.list) {\n const c = cfg.list?.authenticated ? collection.with({ authenticated: true }) : collection;\n c.get({\n feed: true,\n querySchema: cfg.list?.querySchema ?? CrudDefaultPagination,\n outputSchema: listOut,\n description: cfg.list?.description ?? 'List',\n });\n }\n\n if (want.create) {\n const c = cfg.create?.authenticated ? collection.with({ authenticated: true }) : collection;\n c.post({\n bodySchema: cfg.create!.bodySchema,\n outputSchema: cfg.create?.outputSchema ?? itemOut,\n description: cfg.create?.description ?? 'Create',\n });\n }\n\n // Item routes via :<name>Id\n collection.routeParameter(idKey as any, paramObj as any, (item: any) => {\n if (want.read) {\n const i = cfg.read?.authenticated ? item.with({ authenticated: true }) : item;\n i.get({\n outputSchema: cfg.read?.outputSchema ?? itemOut,\n description: cfg.read?.description ?? 'Read',\n });\n }\n if (want.update) {\n const i = cfg.update?.authenticated ? item.with({ authenticated: true }) : item;\n i.patch({\n bodySchema: cfg.update!.bodySchema,\n outputSchema: cfg.update?.outputSchema ?? itemOut,\n description: cfg.update?.description ?? 'Update',\n });\n }\n if (want.remove) {\n const i = cfg.remove?.authenticated ? item.with({ authenticated: true }) : item;\n i.delete({\n outputSchema: cfg.remove?.outputSchema ?? z.object({ ok: z.literal(true) }),\n description: cfg.remove?.description ?? 'Delete',\n });\n }\n\n // Give extras fully-capable builders (decorate so extras can call .crud again)\n if (extras) extras({ collection: withCrud(collection), item: withCrud(item) });\n\n return item.done();\n });\n\n return collection.done();\n });\n };\n\n return branch;\n}\n\n// -----------------------------------------------------------------------------\n// Optional convenience: re-export a resource() that already has .crud installed\n// -----------------------------------------------------------------------------\n\n/**\n * Drop-in replacement for `resource(...)` that returns a Branch with `.crud()` installed.\n * You can either use this or call `withCrud(resource(...))`.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Node configuration merged into every leaf.\n * @returns Branch builder that already includes the CRUD helper.\n */\nexport function resourceWithCrud<Base extends string, I extends NodeCfg = {}>(\n base: Base,\n inherited: I,\n) {\n return withCrud(baseResource(base, inherited));\n}\n\n// Re-export the original in case you want both styles from this module\nexport { baseResource as resource };\n"],"mappings":"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","names":["mergeSchemas","import_zod","mergeSchemas"]}
@@ -0,0 +1,4 @@
1
+ export * from './core/routesV3.builder';
2
+ export * from './core/routesV3.core';
3
+ export * from './core/routesV3.finalize';
4
+ export * from './crud/routesV3.crud';
package/dist/index.js ADDED
@@ -0,0 +1,216 @@
1
+ // src/core/routesV3.builder.ts
2
+ import { z } from "zod";
3
+ function mergeSchemas(a, b) {
4
+ if (a && b) return z.intersection(a, b);
5
+ return a ?? b;
6
+ }
7
+ function resource(base, inherited) {
8
+ const rootBase = base;
9
+ const rootInherited = { ...inherited };
10
+ function makeBranch(base2, inherited2, mergedParamsSchema) {
11
+ const stack = [];
12
+ let currentBase = base2;
13
+ let inheritedCfg = { ...inherited2 };
14
+ let currentParamsSchema = mergedParamsSchema;
15
+ function add(method, cfg) {
16
+ const effectiveParamsSchema = cfg.paramsSchema ?? currentParamsSchema;
17
+ const fullCfg = effectiveParamsSchema ? { ...inheritedCfg, ...cfg, paramsSchema: effectiveParamsSchema } : { ...inheritedCfg, ...cfg };
18
+ const leaf = {
19
+ method,
20
+ path: currentBase,
21
+ cfg: fullCfg
22
+ };
23
+ stack.push(leaf);
24
+ return api;
25
+ }
26
+ const api = {
27
+ // compose a plain subpath (optional cfg) with optional builder
28
+ sub(name, cfgOrBuilder, maybeBuilder) {
29
+ let cfg;
30
+ let builder;
31
+ if (typeof cfgOrBuilder === "function") {
32
+ builder = cfgOrBuilder;
33
+ } else {
34
+ cfg = cfgOrBuilder;
35
+ builder = maybeBuilder;
36
+ }
37
+ const childBase = `${currentBase}/${name}`;
38
+ const childInherited = { ...inheritedCfg, ...cfg ?? {} };
39
+ if (builder) {
40
+ const child = makeBranch(childBase, childInherited, currentParamsSchema);
41
+ const leaves = builder(child);
42
+ for (const l of leaves) stack.push(l);
43
+ return api;
44
+ } else {
45
+ currentBase = childBase;
46
+ inheritedCfg = childInherited;
47
+ return api;
48
+ }
49
+ },
50
+ // the single param function: name + schema + builder
51
+ routeParameter(name, paramsSchema, builder) {
52
+ const childBase = `${currentBase}/:${name}`;
53
+ const childParams = mergeSchemas(currentParamsSchema, paramsSchema);
54
+ const child = makeBranch(childBase, inheritedCfg, childParams);
55
+ const leaves = builder(child);
56
+ for (const l of leaves) stack.push(l);
57
+ return api;
58
+ },
59
+ with(cfg) {
60
+ inheritedCfg = { ...inheritedCfg, ...cfg };
61
+ return api;
62
+ },
63
+ // methods (inject current params schema if missing)
64
+ get(cfg) {
65
+ return add("get", cfg);
66
+ },
67
+ post(cfg) {
68
+ return add("post", { ...cfg, feed: false });
69
+ },
70
+ put(cfg) {
71
+ return add("put", { ...cfg, feed: false });
72
+ },
73
+ patch(cfg) {
74
+ return add("patch", { ...cfg, feed: false });
75
+ },
76
+ delete(cfg) {
77
+ return add("delete", { ...cfg, feed: false });
78
+ },
79
+ done() {
80
+ return stack;
81
+ }
82
+ };
83
+ return api;
84
+ }
85
+ return makeBranch(rootBase, rootInherited, void 0);
86
+ }
87
+ var mergeArrays = (arr1, arr2) => {
88
+ return [...arr1, ...arr2];
89
+ };
90
+
91
+ // src/core/routesV3.core.ts
92
+ function compilePath(path, params) {
93
+ if (!params) return path;
94
+ return path.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
95
+ const v = params[k];
96
+ if (v === void 0 || v === null) throw new Error(`Missing param :${k}`);
97
+ return String(v);
98
+ });
99
+ }
100
+ function buildCacheKey(args) {
101
+ let p = args.leaf.path;
102
+ if (args.params) {
103
+ p = compilePath(p, args.params);
104
+ }
105
+ return [args.leaf.method, ...p.split("/").filter(Boolean), args.query ?? {}];
106
+ }
107
+
108
+ // src/core/routesV3.finalize.ts
109
+ function finalize(leaves) {
110
+ const byKey = Object.fromEntries(
111
+ leaves.map((l) => [`${l.method.toUpperCase()} ${l.path}`, l])
112
+ );
113
+ const log = (logger) => {
114
+ logger.system("Finalized routes:");
115
+ Object.keys(byKey).forEach((k) => {
116
+ const leaf = byKey[k];
117
+ logger.system(`- ${k} (auth: ${leaf.cfg.authenticated ? "yes" : "no"})`);
118
+ });
119
+ };
120
+ return { all: leaves, byKey, log };
121
+ }
122
+
123
+ // src/crud/routesV3.crud.ts
124
+ import { z as z2 } from "zod";
125
+ var CrudDefaultPagination = z2.object({
126
+ limit: z2.coerce.number().min(1).max(100).default(20),
127
+ cursor: z2.string().optional()
128
+ });
129
+ function mergeSchemas2(a, b) {
130
+ if (a && b) return z2.intersection(a, b);
131
+ return a ?? b;
132
+ }
133
+ function withCrud(branch) {
134
+ const b = branch;
135
+ if (typeof b.crud === "function") return branch;
136
+ b.crud = function(name, cfg, extras) {
137
+ return this.sub(name, (collection) => {
138
+ const idKey = `${name}Id`;
139
+ const s = cfg.paramSchema;
140
+ const isObj = s && typeof s._def === "object" && s._def.typeName === "ZodObject" && typeof s.shape === "function";
141
+ const paramObj = isObj && s.shape()[idKey] ? s : z2.object({ [idKey]: s });
142
+ const itemOut = cfg.itemOutputSchema;
143
+ const listOut = cfg.list?.outputSchema ?? z2.object({
144
+ items: z2.array(itemOut),
145
+ nextCursor: z2.string().optional()
146
+ });
147
+ const want = {
148
+ list: cfg.enable?.list !== false,
149
+ create: cfg.enable?.create !== false && !!cfg.create?.bodySchema,
150
+ read: cfg.enable?.read !== false,
151
+ update: cfg.enable?.update !== false && !!cfg.update?.bodySchema,
152
+ remove: cfg.enable?.remove !== false
153
+ };
154
+ if (want.list) {
155
+ const c = cfg.list?.authenticated ? collection.with({ authenticated: true }) : collection;
156
+ c.get({
157
+ feed: true,
158
+ querySchema: cfg.list?.querySchema ?? CrudDefaultPagination,
159
+ outputSchema: listOut,
160
+ description: cfg.list?.description ?? "List"
161
+ });
162
+ }
163
+ if (want.create) {
164
+ const c = cfg.create?.authenticated ? collection.with({ authenticated: true }) : collection;
165
+ c.post({
166
+ bodySchema: cfg.create.bodySchema,
167
+ outputSchema: cfg.create?.outputSchema ?? itemOut,
168
+ description: cfg.create?.description ?? "Create"
169
+ });
170
+ }
171
+ collection.routeParameter(idKey, paramObj, (item) => {
172
+ if (want.read) {
173
+ const i = cfg.read?.authenticated ? item.with({ authenticated: true }) : item;
174
+ i.get({
175
+ outputSchema: cfg.read?.outputSchema ?? itemOut,
176
+ description: cfg.read?.description ?? "Read"
177
+ });
178
+ }
179
+ if (want.update) {
180
+ const i = cfg.update?.authenticated ? item.with({ authenticated: true }) : item;
181
+ i.patch({
182
+ bodySchema: cfg.update.bodySchema,
183
+ outputSchema: cfg.update?.outputSchema ?? itemOut,
184
+ description: cfg.update?.description ?? "Update"
185
+ });
186
+ }
187
+ if (want.remove) {
188
+ const i = cfg.remove?.authenticated ? item.with({ authenticated: true }) : item;
189
+ i.delete({
190
+ outputSchema: cfg.remove?.outputSchema ?? z2.object({ ok: z2.literal(true) }),
191
+ description: cfg.remove?.description ?? "Delete"
192
+ });
193
+ }
194
+ if (extras) extras({ collection: withCrud(collection), item: withCrud(item) });
195
+ return item.done();
196
+ });
197
+ return collection.done();
198
+ });
199
+ };
200
+ return branch;
201
+ }
202
+ function resourceWithCrud(base, inherited) {
203
+ return withCrud(resource(base, inherited));
204
+ }
205
+ export {
206
+ CrudDefaultPagination,
207
+ buildCacheKey,
208
+ compilePath,
209
+ finalize,
210
+ mergeArrays,
211
+ mergeSchemas2 as mergeSchemas,
212
+ resource,
213
+ resourceWithCrud,
214
+ withCrud
215
+ };
216
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/routesV3.builder.ts","../src/core/routesV3.core.ts","../src/core/routesV3.finalize.ts","../src/crud/routesV3.crud.ts"],"sourcesContent":["import { z } from 'zod';\nimport {\n AnyLeaf,\n Append,\n HttpMethod,\n Leaf,\n Merge,\n MergeArray,\n MethodCfg,\n NodeCfg,\n Prettify,\n} from './routesV3.core';\n\ntype ZodTypeAny = z.ZodTypeAny;\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 | undefined, B extends ZodTypeAny | undefined>(\n a: A,\n b: B,\n): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B {\n if (a && b) return z.intersection(a as any, b as any) as any;\n return (a ?? b) as any;\n}\n\n/** Builder surface used by `resource(...)` to accumulate leaves. */\nexport interface Branch<\n Base extends string,\n Acc extends readonly AnyLeaf[],\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<Name extends string, J extends NodeCfg | undefined, R extends readonly AnyLeaf[]>(\n name: Name,\n cfg: J,\n builder: (r: Branch<`${Base}/${Name}`, readonly [], Merge<I, NonNullable<J>>, PS>) => R,\n ): Branch<Base, Append<Acc, R[number]>, Merge<I, NonNullable<J>>, PS>;\n\n sub<Name extends string, R extends readonly AnyLeaf[]>(\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<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeaf[]>(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<`${Base}/:${Name}`, readonly [], I, ReturnType<typeof mergeSchemas<PS, P>>>,\n ) => R,\n ): Branch<Base, MergeArray<Acc, R>, I, ReturnType<typeof mergeSchemas<PS, P>>>;\n\n // --- flags inheritance ---\n /**\n * Merge additional node configuration that subsequent leaves will inherit.\n * @param cfg Partial node configuration (e.g. `{ authenticated: true }`).\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 Omit<MethodCfg, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Prettify<Leaf<'get', Base, Merge<Merge<I, C>, { paramsSchema: 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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<Base, Append<Acc, Leaf<'post', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>, I, PS>;\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<Base, Append<Acc, Leaf<'put', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>, I, PS>;\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Leaf<'patch', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>,\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, 'paramsSchema'>>(\n cfg: C,\n ): Branch<\n Base,\n Append<Acc, Leaf<'delete', Base, Merge<Merge<I, C>, { paramsSchema: PS }>>>,\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<Base2 extends string, I2 extends NodeCfg, PS2 extends ZodTypeAny | undefined>(\n base2: Base2,\n inherited2: I2,\n mergedParamsSchema?: PS2,\n ) {\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 ?? currentParamsSchema) as PS2 | undefined;\n const fullCfg = (\n effectiveParamsSchema\n ? { ...inheritedCfg, ...cfg, paramsSchema: effectiveParamsSchema }\n : { ...inheritedCfg, ...cfg }\n ) as Merge<I2, C>;\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<Base2, Append<readonly [], typeof leaf>, I2, PS2>;\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 AnyLeaf[]),\n maybeBuilder?: (r: any) => readonly AnyLeaf[],\n ) {\n let cfg: NodeCfg | undefined;\n let builder: ((r: any) => readonly AnyLeaf[]) | 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<I2, NonNullable<J>>;\n\n if (builder) {\n // params schema PS2 flows through unchanged on plain sub\n const child = makeBranch(childBase, childInherited, currentParamsSchema);\n const leaves = builder(child) as readonly AnyLeaf[];\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<`${Base2}/${Name}`, readonly [], typeof childInherited, PS2>;\n }\n },\n\n // the single param function: name + schema + builder\n routeParameter<Name extends string, P extends ZodTypeAny, R extends readonly AnyLeaf[]>(\n name: Name,\n paramsSchema: P,\n builder: (\n r: Branch<`${Base2}/:${Name}`, readonly [], I2, ReturnType<typeof mergeSchemas<PS2, P>>>,\n ) => R,\n ) {\n const childBase = `${currentBase}/:${name}` as const;\n // Merge existing PS2 with P to create the child’s active params schema\n const childParams = mergeSchemas(currentParamsSchema, paramsSchema);\n const child = makeBranch(childBase, inheritedCfg as I2, childParams);\n const leaves = builder(child) as readonly AnyLeaf[];\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 ReturnType<typeof mergeSchemas<PS2, 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, ZodTypeAny } 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 /** When true, descendant routes require authentication. */\n authenticated?: boolean;\n};\n\n/** Per-method configuration merged with inherited node config. */\nexport type MethodCfg = NodeCfg & {\n /** Zod schema describing the request body. */\n bodySchema?: ZodTypeAny;\n /** Zod schema describing the query string. */\n querySchema?: ZodTypeAny;\n /** Zod schema describing path params (overrides inferred params). */\n paramsSchema?: ZodTypeAny;\n /** Zod schema describing the response payload. */\n outputSchema?: ZodTypeAny;\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 /** Optional human-readable description for docs/debugging. */\n description?: string;\n};\n\n/** Immutable representation of a single HTTP route in the tree. */\nexport type Leaf<M extends HttpMethod, P extends string, C extends MethodCfg> = {\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<A extends readonly unknown[], B extends readonly unknown[]> = [\n ...A,\n ...B,\n];\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 ? never : 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>(path: Path, params: ExtractParamsFromPath<Path>) {\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 */\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 [args.leaf.method, ...p.split('/').filter(Boolean), args.query ?? {}] as const;\n}\n\n/** Infer params either from the explicit params schema or from the path literal. */\nexport type InferParams<L extends AnyLeaf> = L['cfg']['paramsSchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['paramsSchema']>\n : ExtractParamsFromPath<L['path']>;\n\n/** Infer query shape from a Zod schema when present. */\nexport type InferQuery<L extends AnyLeaf> = L['cfg']['querySchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['querySchema']>\n : never;\n\n/** Infer request body shape from a Zod schema when present. */\nexport type InferBody<L extends AnyLeaf> = L['cfg']['bodySchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['bodySchema']>\n : never;\n\n/** Infer handler output shape from a Zod schema. Defaults to unknown. */\nexport type InferOutput<L extends AnyLeaf> = L['cfg']['outputSchema'] extends ZodTypeAny\n ? z.infer<L['cfg']['outputSchema']>\n : unknown;\n\n/** Render a type as if it were a simple object literal. */\nexport type Prettify<T> = { [K in keyof T]: T[K] } & {};\n","import { AnyLeaf, 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 AnyLeaf> = L extends AnyLeaf\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 AnyLeaf[]> = KeyOf<Leaves[number]>;\n\n// Parse method & path out of a key\ntype MethodFromKey<K extends string> = K extends `${infer M} ${string}` ? Lowercase<M> : never;\ntype PathFromKey<K extends string> = K extends `${string} ${infer P}` ? P : never;\n\n// Given Leaves and a Key, pick the exact leaf that matches method+path\ntype LeafForKey<Leaves extends readonly AnyLeaf[], K extends string> = 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 AnyLeaf[]>(leaves: L) {\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} (auth: ${leaf.cfg.authenticated ? 'yes' : 'no'})`);\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 AnyLeaf[],\n F extends string,\n Acc extends readonly AnyLeaf[] = [],\n> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]]\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<T extends readonly AnyLeaf[], F extends string> = FilterRoute<T, F>;\ntype ByKey<\n T extends readonly AnyLeaf[],\n Acc extends Record<string, AnyLeaf> = {},\n> = T extends readonly [infer First extends AnyLeaf, ...infer Rest extends AnyLeaf[]]\n ? ByKey<Rest, Acc & { [K in `${UpperCase<First['method']>} ${First['path']}`]: First }>\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<T extends readonly AnyLeaf[], F extends string> = {\n byKey: ByKey<Routes<T, F>>;\n all: Routes<T, F>;\n};\n","// routesV3.crud.ts\n// -----------------------------------------------------------------------------\n// Add a first-class .crud() helper to the routesV3 DSL, without touching the\n// core builder. This file:\n// 1) Declares types + module augmentation to extend Branch<...> with .crud\n// 2) Provides a runtime decorator `withCrud(...)` that installs the method\n// 3) Exports a convenience `resourceWithCrud(...)` wrapper (optional)\n// -----------------------------------------------------------------------------\n\nimport { z, type ZodTypeAny } from 'zod';\nimport {\n resource as baseResource,\n type Branch as _Branch, // import type so we can augment it\n} from '../core/routesV3.builder';\nimport { type AnyLeaf, type Leaf, type NodeCfg } from '../core/routesV3.core';\n\n// -----------------------------------------------------------------------------\n// Small utilities (runtime + types)\n// -----------------------------------------------------------------------------\n\n/** Default cursor pagination used by list (GET collection). */\nexport const CrudDefaultPagination = z.object({\n limit: z.coerce.number().min(1).max(100).default(20),\n cursor: z.string().optional(),\n});\n\n/**\n * Merge two Zod schemas at runtime; matches the typing pattern used in builder.\n * @param a Existing params schema inherited from parent branches.\n * @param b Schema introduced at the current branch.\n * @returns Intersection of schemas when both exist, otherwise whichever is defined.\n */\nexport function mergeSchemas<A extends ZodTypeAny | undefined, B extends ZodTypeAny | undefined>(\n a: A,\n b: B,\n): A extends ZodTypeAny ? (B extends ZodTypeAny ? ReturnType<typeof z.intersection<A, B>> : A) : B {\n if (a && b) return z.intersection(a as any, b as any) as any;\n return (a ?? b) as any;\n}\n\n/** Build { [Name]: S } Zod object at the type-level. */\nexport type ParamSchema<Name extends string, S extends ZodTypeAny> = z.ZodObject<{\n [K in Name]: S;\n}>;\n\n/** Inject active params schema into a leaf cfg (to match your Leaf typing). */\ntype WithParams<I, P> = P extends ZodTypeAny ? Omit<I, 'paramsSchema'> & { paramsSchema: P } : I;\n\n/** Resolve boolean flag: default D unless explicitly false. */\ntype Flag<E, D extends boolean> = E extends false ? false : D;\n\n// -----------------------------------------------------------------------------\n// CRUD config (few knobs, with DX comments)\n// -----------------------------------------------------------------------------\n\n/** Toggle individual CRUD methods; defaults enable all (create/update require body). */\nexport type CrudEnable = {\n /** GET /collection (feed). Defaults to true. */ list?: boolean;\n /** POST /collection. Defaults to true if `create` provided. */ create?: boolean;\n /** GET /collection/:id. Defaults to true. */ read?: boolean;\n /** PATCH /collection/:id. Defaults to true if `update` provided. */ update?: boolean;\n /** DELETE /collection/:id. Defaults to true. */ remove?: boolean;\n};\n\n/** Collection GET config. */\nexport type CrudListCfg = {\n /** Query schema (defaults to cursor pagination). */\n querySchema?: ZodTypeAny;\n /** Output schema (defaults to { items: Item[], nextCursor? }). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description string. */\n description?: string;\n};\n\n/** Collection POST config. */\nexport type CrudCreateCfg = {\n /** Body schema for creating an item. */\n bodySchema: ZodTypeAny;\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item GET config. */\nexport type CrudReadCfg = {\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item PATCH config. */\nexport type CrudUpdateCfg = {\n /** Body schema for partial updates. */\n bodySchema: ZodTypeAny;\n /** Output schema (defaults to itemOutputSchema). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/** Item DELETE config. */\nexport type CrudRemoveCfg = {\n /** Output schema (defaults to { ok: true }). */\n outputSchema?: ZodTypeAny;\n /** Require auth for this leaf (put on node via `.with({ authenticated:true })`). */\n authenticated?: boolean;\n /** Optional description. */\n description?: string;\n};\n\n/**\n * CRUD config for `.crud(\"resource\", cfg, extras?)`.\n *\n * It synthesizes `:[resourceName]Id` as the param key and uses `paramSchema` for the value.\n * Prefer passing a **value schema** (e.g. `z.string().uuid()`).\n * As a convenience, a ZodObject of shape `{ [resourceName]Id: schema }` is accepted at runtime too.\n */\nexport type CrudCfg<Name extends string = string> = {\n /**\n * Zod schema for the ID *value* (e.g. `z.string().uuid()`).\n * The parameter key is always `:${name}Id`.\n * (If you pass `z.object({ [name]Id: ... })`, it's accepted at runtime.)\n */\n paramSchema: ZodTypeAny;\n\n /**\n * The single-item output shape used by read/create/update by default.\n * List defaults to `{ items: z.array(itemOutputSchema), nextCursor? }`.\n */\n itemOutputSchema: ZodTypeAny;\n\n /** Per-method toggles (all enabled by default; create/update require bodies). */\n enable?: CrudEnable;\n\n /** GET /collection configuration. */ list?: CrudListCfg;\n /** POST /collection configuration (enables create). */ create?: CrudCreateCfg;\n /** GET /collection/:id configuration. */ read?: CrudReadCfg;\n /** PATCH /collection/:id configuration (enables update). */ update?: CrudUpdateCfg;\n /** DELETE /collection/:id configuration. */ remove?: CrudRemoveCfg;\n};\n\n// -----------------------------------------------------------------------------\n// Type plumbing to expose generated leaves (so finalize().byKey sees them)\n// -----------------------------------------------------------------------------\n\ntype CrudLeavesTuple<\n Base extends string,\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Name extends string,\n C extends CrudCfg<Name>,\n> = `${Name}Id` extends infer IdName extends string\n ? ParamSchema<IdName, C['paramSchema']> extends infer IdParamZ extends ZodTypeAny\n ? [\n // GET /collection\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['list'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'get',\n `${Base}/${Name}`,\n WithParams<\n Omit<I, never> & {\n feed: true;\n querySchema: C['list'] extends CrudListCfg\n ? C['list']['querySchema']\n : typeof CrudDefaultPagination;\n outputSchema: C['list'] extends CrudListCfg\n ? C['list']['outputSchema'] extends ZodTypeAny\n ? C['list']['outputSchema']\n : z.ZodObject<{\n items: z.ZodArray<C['itemOutputSchema']>;\n nextCursor: z.ZodOptional<z.ZodString>;\n }>\n : z.ZodObject<{\n items: z.ZodArray<C['itemOutputSchema']>;\n nextCursor: z.ZodOptional<z.ZodString>;\n }>;\n description?: string;\n },\n PS\n >\n >,\n ]\n : []),\n\n // POST /collection\n ...((C['create'] extends CrudCreateCfg ? true : false) extends true\n ? Flag<\n C['enable'] extends CrudEnable ? C['enable']['create'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'post',\n `${Base}/${Name}`,\n WithParams<\n Omit<I, never> & {\n bodySchema: C['create'] extends CrudCreateCfg\n ? C['create']['bodySchema']\n : never;\n outputSchema: C['create'] extends CrudCreateCfg\n ? C['create']['outputSchema'] extends ZodTypeAny\n ? C['create']['outputSchema']\n : C['itemOutputSchema']\n : never;\n description?: string;\n },\n PS\n >\n >,\n ]\n : []\n : []),\n\n // GET /collection/:id\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['read'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'get',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n outputSchema: C['read'] extends CrudReadCfg\n ? C['read']['outputSchema'] extends ZodTypeAny\n ? C['read']['outputSchema']\n : C['itemOutputSchema']\n : C['itemOutputSchema'];\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []),\n\n // PATCH /collection/:id\n ...((C['update'] extends CrudUpdateCfg ? true : false) extends true\n ? Flag<\n C['enable'] extends CrudEnable ? C['enable']['update'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'patch',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n bodySchema: C['update'] extends CrudUpdateCfg\n ? C['update']['bodySchema']\n : never;\n outputSchema: C['update'] extends CrudUpdateCfg\n ? C['update']['outputSchema'] extends ZodTypeAny\n ? C['update']['outputSchema']\n : C['itemOutputSchema']\n : never;\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []\n : []),\n\n // DELETE /collection/:id\n ...(Flag<\n C['enable'] extends CrudEnable ? C['enable']['remove'] : undefined,\n true\n > extends true\n ? [\n Leaf<\n 'delete',\n `${Base}/${Name}/:${IdName}`,\n WithParams<\n Omit<I, never> & {\n outputSchema: C['remove'] extends CrudRemoveCfg\n ? C['remove']['outputSchema'] extends ZodTypeAny\n ? C['remove']['outputSchema']\n : z.ZodObject<{ ok: z.ZodLiteral<true> }>\n : z.ZodObject<{ ok: z.ZodLiteral<true> }>;\n description?: string;\n },\n ReturnType<typeof mergeSchemas<PS, IdParamZ>>\n >\n >,\n ]\n : []),\n ]\n : never\n : never;\n\n/** Merge generated leaves + extras into the branch accumulator. */\ntype CrudResultAcc<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[],\n> = [...Acc, ...CrudLeavesTuple<Base, I, PS, Name, C>, ...Extras];\n\n// -----------------------------------------------------------------------------\n// Module augmentation: add the typed `.crud(...)` to Branch<...>\n// -----------------------------------------------------------------------------\n\ndeclare module './../core/routesV3.builder' {\n interface Branch<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n > {\n /**\n * Generate opinionated CRUD endpoints for a collection at `/.../<name>`\n * with an item at `/.../<name>/:<name>Id`.\n *\n * Creates (subject to `enable` + presence of create/update bodies):\n * - GET /<name> (feed list)\n * - POST /<name> (create)\n * - GET /<name>/:<name>Id (read item)\n * - PATCH /<name>/:<name>Id (update item)\n * - DELETE /<name>/:<name>Id (remove item)\n *\n * The `extras` callback receives live builders at both collection and item\n * levels so you can add custom subroutes; their leaves are included in the\n * returned Branch type.\n */\n crud<\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[] = readonly [],\n >(\n name: Name,\n cfg: C,\n extras?: (ctx: {\n /** Builder at `/.../<name>` (collection scope). */\n collection: _Branch<`${Base}/${Name}`, readonly [], I, PS>;\n /** Builder at `/.../<name>/:<name>Id` (item scope). */\n item: _Branch<\n `${Base}/${Name}/:${`${Name}Id`}`,\n readonly [],\n I,\n ReturnType<typeof mergeSchemas<PS, ParamSchema<`${Name}Id`, C['paramSchema']>>>\n >;\n }) => Extras,\n ): _Branch<Base, CrudResultAcc<Base, Acc, I, PS, Name, C, Extras>, I, PS>;\n }\n}\n\n// -----------------------------------------------------------------------------\n// Runtime: install .crud on any Branch via decorator\n// -----------------------------------------------------------------------------\n\n/**\n * Decorate a Branch instance so `.crud(...)` is available at runtime.\n * Tip: wrap the root: `withCrud(resource('/v1'))`.\n * @param branch Branch returned by `resource(...)`.\n * @returns Same branch with `.crud(...)` installed.\n */\nexport function withCrud<\n Base extends string,\n Acc extends readonly AnyLeaf[],\n I extends NodeCfg,\n PS extends ZodTypeAny | undefined,\n>(branch: _Branch<Base, Acc, I, PS>): _Branch<Base, Acc, I, PS> {\n const b = branch as any;\n if (typeof b.crud === 'function') return branch;\n\n b.crud = function <\n Name extends string,\n C extends CrudCfg<Name>,\n Extras extends readonly AnyLeaf[] = readonly [],\n >(this: any, name: Name, cfg: C, extras?: (ctx: { collection: any; item: any }) => Extras) {\n // Build inside a sub-branch at `/.../<name>` and harvest its leaves.\n return this.sub(name, (collection: any) => {\n const idKey = `${name}Id`;\n\n // Accept a value schema (preferred); but if a z.object({[name]Id: ...}) is provided, use it.\n const s = cfg.paramSchema;\n const isObj =\n s &&\n typeof (s as any)._def === 'object' &&\n (s as any)._def.typeName === 'ZodObject' &&\n typeof (s as any).shape === 'function';\n\n const paramObj =\n isObj && (s as any).shape()[idKey]\n ? (s as ZodTypeAny)\n : z.object({ [idKey]: s } as Record<string, ZodTypeAny>);\n\n const itemOut = cfg.itemOutputSchema;\n const listOut =\n cfg.list?.outputSchema ??\n z.object({\n items: z.array(itemOut),\n nextCursor: z.string().optional(),\n });\n\n const want = {\n list: cfg.enable?.list !== false,\n create: cfg.enable?.create !== false && !!cfg.create?.bodySchema,\n read: cfg.enable?.read !== false,\n update: cfg.enable?.update !== false && !!cfg.update?.bodySchema,\n remove: cfg.enable?.remove !== false,\n } as const;\n\n // Collection routes\n if (want.list) {\n const c = cfg.list?.authenticated ? collection.with({ authenticated: true }) : collection;\n c.get({\n feed: true,\n querySchema: cfg.list?.querySchema ?? CrudDefaultPagination,\n outputSchema: listOut,\n description: cfg.list?.description ?? 'List',\n });\n }\n\n if (want.create) {\n const c = cfg.create?.authenticated ? collection.with({ authenticated: true }) : collection;\n c.post({\n bodySchema: cfg.create!.bodySchema,\n outputSchema: cfg.create?.outputSchema ?? itemOut,\n description: cfg.create?.description ?? 'Create',\n });\n }\n\n // Item routes via :<name>Id\n collection.routeParameter(idKey as any, paramObj as any, (item: any) => {\n if (want.read) {\n const i = cfg.read?.authenticated ? item.with({ authenticated: true }) : item;\n i.get({\n outputSchema: cfg.read?.outputSchema ?? itemOut,\n description: cfg.read?.description ?? 'Read',\n });\n }\n if (want.update) {\n const i = cfg.update?.authenticated ? item.with({ authenticated: true }) : item;\n i.patch({\n bodySchema: cfg.update!.bodySchema,\n outputSchema: cfg.update?.outputSchema ?? itemOut,\n description: cfg.update?.description ?? 'Update',\n });\n }\n if (want.remove) {\n const i = cfg.remove?.authenticated ? item.with({ authenticated: true }) : item;\n i.delete({\n outputSchema: cfg.remove?.outputSchema ?? z.object({ ok: z.literal(true) }),\n description: cfg.remove?.description ?? 'Delete',\n });\n }\n\n // Give extras fully-capable builders (decorate so extras can call .crud again)\n if (extras) extras({ collection: withCrud(collection), item: withCrud(item) });\n\n return item.done();\n });\n\n return collection.done();\n });\n };\n\n return branch;\n}\n\n// -----------------------------------------------------------------------------\n// Optional convenience: re-export a resource() that already has .crud installed\n// -----------------------------------------------------------------------------\n\n/**\n * Drop-in replacement for `resource(...)` that returns a Branch with `.crud()` installed.\n * You can either use this or call `withCrud(resource(...))`.\n * @param base Root path for the resource (e.g. `/v1`).\n * @param inherited Node configuration merged into every leaf.\n * @returns Branch builder that already includes the CRUD helper.\n */\nexport function resourceWithCrud<Base extends string, I extends NodeCfg = {}>(\n base: Base,\n inherited: I,\n) {\n return withCrud(baseResource(base, inherited));\n}\n\n// Re-export the original in case you want both styles from this module\nexport { baseResource as resource };\n"],"mappings":"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","names":["z","z","mergeSchemas"]}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@emeryld/rrroutes-contract",
3
+ "version": "1.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "dependencies": {
20
+ "zod": "^4.1.8"
21
+ },
22
+ "devDependencies": {
23
+ "@jest/globals": "^30.2.0"
24
+ },
25
+ "scripts": {
26
+ "clean": "rimraf dist",
27
+ "build": "pnpm run clean && pnpm run build:js && pnpm run build:types",
28
+ "build:js": "tsup src/index.ts --sourcemap --format esm,cjs --out-dir dist",
29
+ "build:types": "tsc -p tsconfig.build.json",
30
+ "typecheck": "tsc -p tsconfig.json --noEmit"
31
+ }
32
+ }