@veloxts/router 0.6.84 → 0.6.85

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # @veloxts/router
2
2
 
3
+ ## 0.6.85
4
+
5
+ ### Patch Changes
6
+
7
+ - implement missing features from original requirements
8
+ - Updated dependencies
9
+ - @veloxts/core@0.6.85
10
+ - @veloxts/validation@0.6.85
11
+
3
12
  ## 0.6.84
4
13
 
5
14
  ### Patch Changes
package/dist/expose.d.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * @module serve
8
8
  */
9
9
  import { type VeloxApp } from '@veloxts/core';
10
- import { type AnyRouter } from './trpc/index.js';
10
+ import { type AnyRouter, type InferRouterFromCollections } from './trpc/index.js';
11
11
  import type { ProcedureCollection } from './types.js';
12
12
  /**
13
13
  * Options for serving procedures
@@ -38,38 +38,41 @@ export interface ServeOptions {
38
38
  * This is the recommended way to register your API.
39
39
  * Define your procedures once, serve them everywhere.
40
40
  *
41
+ * **IMPORTANT**: Use `as const` on the procedures array to preserve
42
+ * literal types for full type inference on the returned router.
43
+ *
41
44
  * @param app - VeloxTS application instance
42
- * @param procedures - Array of procedure collections to serve
45
+ * @param procedures - Array of procedure collections to serve (use `as const`)
43
46
  * @param options - Optional configuration for API and RPC prefixes
44
- * @returns The tRPC router for type exports
47
+ * @returns The typed tRPC router for type exports
45
48
  *
46
49
  * @example
47
50
  * ```typescript
48
51
  * // Both REST (/api) and tRPC (/trpc) with zero config
49
- * const router = await serve(app, [healthProcedures, userProcedures]);
50
- * export type AppRouter = typeof router;
52
+ * const router = await serve(app, [healthProcedures, userProcedures] as const);
53
+ * export type AppRouter = typeof router; // Fully typed!
51
54
  * ```
52
55
  *
53
56
  * @example
54
57
  * ```typescript
55
58
  * // REST only (external API)
56
- * await serve(app, [healthProcedures, userProcedures], { rpc: false });
59
+ * await serve(app, [healthProcedures, userProcedures] as const, { rpc: false });
57
60
  * ```
58
61
  *
59
62
  * @example
60
63
  * ```typescript
61
64
  * // tRPC only (internal app)
62
- * const router = await serve(app, [healthProcedures, userProcedures], { api: false });
65
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, { api: false });
63
66
  * export type AppRouter = typeof router;
64
67
  * ```
65
68
  *
66
69
  * @example
67
70
  * ```typescript
68
71
  * // Custom prefixes
69
- * const router = await serve(app, [healthProcedures, userProcedures], {
72
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, {
70
73
  * api: '/v1',
71
74
  * rpc: '/rpc',
72
75
  * });
73
76
  * ```
74
77
  */
75
- export declare function serve(app: VeloxApp, procedures: ProcedureCollection[], options?: ServeOptions): Promise<AnyRouter>;
78
+ export declare function serve<const T extends readonly ProcedureCollection[]>(app: VeloxApp, procedures: T, options?: ServeOptions): Promise<AnyRouter & InferRouterFromCollections<T>>;
package/dist/expose.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { fail } from '@veloxts/core';
10
10
  import { rest } from './rest/index.js';
11
- import { appRouter, registerTRPCPlugin, trpc } from './trpc/index.js';
11
+ import { appRouter, registerTRPCPlugin, trpc, } from './trpc/index.js';
12
12
  // ============================================================================
13
13
  // Main Function
14
14
  // ============================================================================
@@ -18,35 +18,38 @@ import { appRouter, registerTRPCPlugin, trpc } from './trpc/index.js';
18
18
  * This is the recommended way to register your API.
19
19
  * Define your procedures once, serve them everywhere.
20
20
  *
21
+ * **IMPORTANT**: Use `as const` on the procedures array to preserve
22
+ * literal types for full type inference on the returned router.
23
+ *
21
24
  * @param app - VeloxTS application instance
22
- * @param procedures - Array of procedure collections to serve
25
+ * @param procedures - Array of procedure collections to serve (use `as const`)
23
26
  * @param options - Optional configuration for API and RPC prefixes
24
- * @returns The tRPC router for type exports
27
+ * @returns The typed tRPC router for type exports
25
28
  *
26
29
  * @example
27
30
  * ```typescript
28
31
  * // Both REST (/api) and tRPC (/trpc) with zero config
29
- * const router = await serve(app, [healthProcedures, userProcedures]);
30
- * export type AppRouter = typeof router;
32
+ * const router = await serve(app, [healthProcedures, userProcedures] as const);
33
+ * export type AppRouter = typeof router; // Fully typed!
31
34
  * ```
32
35
  *
33
36
  * @example
34
37
  * ```typescript
35
38
  * // REST only (external API)
36
- * await serve(app, [healthProcedures, userProcedures], { rpc: false });
39
+ * await serve(app, [healthProcedures, userProcedures] as const, { rpc: false });
37
40
  * ```
38
41
  *
39
42
  * @example
40
43
  * ```typescript
41
44
  * // tRPC only (internal app)
42
- * const router = await serve(app, [healthProcedures, userProcedures], { api: false });
45
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, { api: false });
43
46
  * export type AppRouter = typeof router;
44
47
  * ```
45
48
  *
46
49
  * @example
47
50
  * ```typescript
48
51
  * // Custom prefixes
49
- * const router = await serve(app, [healthProcedures, userProcedures], {
52
+ * const router = await serve(app, [healthProcedures, userProcedures] as const, {
50
53
  * api: '/v1',
51
54
  * rpc: '/rpc',
52
55
  * });
@@ -73,7 +76,8 @@ export async function serve(app, procedures, options = {}) {
73
76
  }
74
77
  // Register REST routes if enabled
75
78
  if (apiPrefix !== false) {
76
- app.routes(rest(procedures, { prefix: apiPrefix }));
79
+ // Need to spread to convert readonly tuple to mutable array for rest()
80
+ app.routes(rest([...procedures], { prefix: apiPrefix }));
77
81
  }
78
82
  return router;
79
83
  }
package/dist/index.d.ts CHANGED
@@ -39,7 +39,7 @@
39
39
  */
40
40
  /** Router package version */
41
41
  export declare const ROUTER_VERSION: string;
42
- export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, InferProcedureTypes, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
42
+ export type { CompiledProcedure, ContextExtensions, ContextFactory, ExtendedContext, GuardLike, HttpMethod, InferProcedureContext, InferProcedureInput, InferProcedureOutput, InferProcedureTypes, MiddlewareArgs, MiddlewareFunction, MiddlewareNext, MiddlewareResult, ParentResourceChain, ParentResourceConfig, ProcedureCollection, ProcedureHandler, ProcedureHandlerArgs, ProcedureRecord, ProcedureType, RestRouteOverride, } from './types.js';
43
43
  export { PROCEDURE_METHOD_MAP, } from './types.js';
44
44
  export type { GuardErrorResponse, RouterErrorCode } from './errors.js';
45
45
  export { GuardError, isGuardError } from './errors.js';
@@ -51,10 +51,14 @@ export type { RouterResult } from './router-utils.js';
51
51
  export { createRouter, toRouter } from './router-utils.js';
52
52
  export type { NamingWarning, NamingWarningType, WarningConfig, WarningOption } from './warnings.js';
53
53
  export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
54
- export type { ExtractRoutesType, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
55
- export { buildNestedRestPath, buildRestPath, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
56
- export type { AnyRouter, InferAppRouter, TRPCInstance, TRPCPluginOptions, } from './trpc/index.js';
54
+ export type { ExtractRoutesType, GenerateRestRoutesOptions, RestAdapterOptions, RestMapping, RestRoute, RouteMap, } from './rest/index.js';
55
+ export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
56
+ export type { AnyRouter,
57
+ /** @deprecated Use `TRPCRouter` instead */
58
+ AsTRPCRouter, CollectionsToRouterRecord, ExtractNamespace, ExtractProcedures, InferAppRouter, InferRouterFromCollections, MapProcedureRecordToTRPC, MapProcedureToTRPC, TRPCInstance, TRPCPluginOptions, TRPCRouter, } from './trpc/index.js';
57
59
  export { appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
60
+ export type { RpcOptions, RpcResult } from './rpc.js';
61
+ export { registerRpc, rpc } from './rpc.js';
58
62
  export type { DiscoveryOptions, DiscoveryResult, DiscoveryWarning } from './discovery/index.js';
59
63
  export { DiscoveryError, DiscoveryErrorCode, directoryNotFound, discoverProcedures, discoverProceduresVerbose, fileLoadError, invalidExport, invalidFileType, isDiscoveryError, noProceduresFound, permissionDenied, } from './discovery/index.js';
60
64
  export type { ServeOptions } from './expose.js';
package/dist/index.js CHANGED
@@ -58,10 +58,11 @@ defineProcedures, executeProcedure, isCompiledProcedure, isProcedureCollection,
58
58
  export { createProcedure, typedProcedure } from './procedure/factory.js';
59
59
  export { createRouter, toRouter } from './router-utils.js';
60
60
  export { analyzeNamingConvention, isDevelopment, normalizeWarningOption } from './warnings.js';
61
- export { buildNestedRestPath, buildRestPath, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
61
+ export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, extractRoutes, followsNamingConvention, generateRestRoutes, getRouteSummary, inferResourceName, parseNamingConvention, registerRestRoutes, rest, } from './rest/index.js';
62
62
  export {
63
63
  // tRPC utilities
64
64
  appRouter, buildTRPCRouter, createTRPCContextFactory, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './trpc/index.js';
65
+ export { registerRpc, rpc } from './rpc.js';
65
66
  export { DiscoveryError, DiscoveryErrorCode, directoryNotFound, discoverProcedures, discoverProceduresVerbose, fileLoadError, invalidExport, invalidFileType, isDiscoveryError, noProceduresFound, permissionDenied, } from './discovery/index.js';
66
67
  export { serve } from './expose.js';
67
68
  // ============================================================================
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Shared middleware chain execution utility
3
+ *
4
+ * Provides a single implementation for executing middleware chains,
5
+ * used by both the procedure builder and tRPC adapter.
6
+ *
7
+ * @module middleware/chain
8
+ */
9
+ import type { BaseContext } from '@veloxts/core';
10
+ import type { MiddlewareFunction } from '../types.js';
11
+ /**
12
+ * Result wrapper for middleware chain execution
13
+ */
14
+ export interface MiddlewareResult<TOutput> {
15
+ output: TOutput;
16
+ }
17
+ /**
18
+ * Execute a middleware chain with the given handler
19
+ *
20
+ * Builds the chain from end to start and executes it, allowing each
21
+ * middleware to extend the context before calling the next middleware.
22
+ *
23
+ * @param middlewares - Array of middleware functions to execute
24
+ * @param input - The input to pass to each middleware
25
+ * @param ctx - The context object (will be mutated by middleware)
26
+ * @param handler - The final handler to execute after all middleware
27
+ * @returns The output from the handler
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const result = await executeMiddlewareChain(
32
+ * procedure.middlewares,
33
+ * input,
34
+ * ctx,
35
+ * async () => handler({ input, ctx })
36
+ * );
37
+ * ```
38
+ */
39
+ export declare function executeMiddlewareChain<TInput, TOutput, TContext extends BaseContext>(middlewares: ReadonlyArray<MiddlewareFunction<TInput, TContext, TContext, TOutput>>, input: TInput, ctx: TContext, handler: () => Promise<TOutput>): Promise<TOutput>;
40
+ /**
41
+ * Create a precompiled middleware executor for a fixed middleware chain
42
+ *
43
+ * This is an optimization that builds the chain structure once during
44
+ * procedure compilation, creating a reusable function that can execute
45
+ * the chain without rebuilding closures on every request.
46
+ *
47
+ * @param middlewares - Array of middleware functions
48
+ * @param handler - The procedure handler (can return sync or async)
49
+ * @returns A function that executes the full chain
50
+ */
51
+ export declare function createMiddlewareExecutor<TInput, TOutput, TContext extends BaseContext>(middlewares: ReadonlyArray<MiddlewareFunction<TInput, TContext, TContext, TOutput>>, handler: (params: {
52
+ input: TInput;
53
+ ctx: TContext;
54
+ }) => TOutput | Promise<TOutput>): (input: TInput, ctx: TContext) => Promise<TOutput>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared middleware chain execution utility
3
+ *
4
+ * Provides a single implementation for executing middleware chains,
5
+ * used by both the procedure builder and tRPC adapter.
6
+ *
7
+ * @module middleware/chain
8
+ */
9
+ /**
10
+ * Execute a middleware chain with the given handler
11
+ *
12
+ * Builds the chain from end to start and executes it, allowing each
13
+ * middleware to extend the context before calling the next middleware.
14
+ *
15
+ * @param middlewares - Array of middleware functions to execute
16
+ * @param input - The input to pass to each middleware
17
+ * @param ctx - The context object (will be mutated by middleware)
18
+ * @param handler - The final handler to execute after all middleware
19
+ * @returns The output from the handler
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const result = await executeMiddlewareChain(
24
+ * procedure.middlewares,
25
+ * input,
26
+ * ctx,
27
+ * async () => handler({ input, ctx })
28
+ * );
29
+ * ```
30
+ */
31
+ export async function executeMiddlewareChain(middlewares, input, ctx, handler) {
32
+ // Build the chain from the end (handler) back to the start
33
+ let next = async () => {
34
+ const output = await handler();
35
+ return { output };
36
+ };
37
+ // Wrap each middleware from last to first
38
+ for (let i = middlewares.length - 1; i >= 0; i--) {
39
+ const middleware = middlewares[i];
40
+ const currentNext = next;
41
+ next = async () => {
42
+ return middleware({
43
+ input,
44
+ ctx,
45
+ next: async (opts) => {
46
+ // Allow middleware to extend context
47
+ if (opts?.ctx) {
48
+ Object.assign(ctx, opts.ctx);
49
+ }
50
+ return currentNext();
51
+ },
52
+ });
53
+ };
54
+ }
55
+ const result = await next();
56
+ return result.output;
57
+ }
58
+ /**
59
+ * Create a precompiled middleware executor for a fixed middleware chain
60
+ *
61
+ * This is an optimization that builds the chain structure once during
62
+ * procedure compilation, creating a reusable function that can execute
63
+ * the chain without rebuilding closures on every request.
64
+ *
65
+ * @param middlewares - Array of middleware functions
66
+ * @param handler - The procedure handler (can return sync or async)
67
+ * @returns A function that executes the full chain
68
+ */
69
+ export function createMiddlewareExecutor(middlewares, handler) {
70
+ // If no middlewares, just return a direct handler call
71
+ if (middlewares.length === 0) {
72
+ return async (input, ctx) => {
73
+ return handler({ input, ctx });
74
+ };
75
+ }
76
+ // Return an executor that uses the shared chain execution
77
+ return async (input, ctx) => {
78
+ return executeMiddlewareChain(middlewares, input, ctx, async () => handler({ input, ctx }));
79
+ };
80
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Middleware utilities
3
+ *
4
+ * @module middleware
5
+ */
6
+ export type { MiddlewareResult } from './chain.js';
7
+ export { createMiddlewareExecutor, executeMiddlewareChain } from './chain.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Middleware utilities
3
+ *
4
+ * @module middleware
5
+ */
6
+ export { createMiddlewareExecutor, executeMiddlewareChain } from './chain.js';
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { ConfigurationError, logWarning } from '@veloxts/core';
11
11
  import { GuardError } from '../errors.js';
12
+ import { createMiddlewareExecutor, executeMiddlewareChain } from '../middleware/chain.js';
12
13
  import { analyzeNamingConvention, isDevelopment, normalizeWarningOption, } from '../warnings.js';
13
14
  // ============================================================================
14
15
  // Utility Functions
@@ -115,6 +116,7 @@ export function procedure() {
115
116
  guards: [],
116
117
  restOverride: undefined,
117
118
  parentResource: undefined,
119
+ parentResources: undefined,
118
120
  });
119
121
  }
120
122
  // ============================================================================
@@ -209,18 +211,31 @@ function createBuilder(state) {
209
211
  });
210
212
  },
211
213
  /**
212
- * Declares a parent resource for nested routes
214
+ * Declares a parent resource for nested routes (single level)
213
215
  */
214
- parent(namespace, paramName) {
216
+ parent(resource, param) {
215
217
  const parentConfig = {
216
- namespace,
217
- paramName: paramName ?? deriveParentParamName(namespace),
218
+ resource,
219
+ param: param ?? deriveParentParamName(resource),
218
220
  };
219
221
  return createBuilder({
220
222
  ...state,
221
223
  parentResource: parentConfig,
222
224
  });
223
225
  },
226
+ /**
227
+ * Declares multiple parent resources for deeply nested routes
228
+ */
229
+ parents(config) {
230
+ const parentConfigs = config.map((item) => ({
231
+ resource: item.resource,
232
+ param: item.param ?? deriveParentParamName(item.resource),
233
+ }));
234
+ return createBuilder({
235
+ ...state,
236
+ parentResources: parentConfigs,
237
+ });
238
+ },
224
239
  /**
225
240
  * Finalizes as a query procedure
226
241
  */
@@ -261,6 +276,7 @@ function compileProcedure(type, handler, state) {
261
276
  guards: state.guards,
262
277
  restOverride: state.restOverride,
263
278
  parentResource: state.parentResource,
279
+ parentResources: state.parentResources,
264
280
  // Store pre-compiled executor for performance
265
281
  _precompiledExecutor: precompiledExecutor,
266
282
  };
@@ -275,36 +291,7 @@ function compileProcedure(type, handler, state) {
275
291
  * @internal
276
292
  */
277
293
  function createPrecompiledMiddlewareExecutor(middlewares, handler) {
278
- // Pre-build the chain executor once
279
- return async (input, ctx) => {
280
- // Create mutable context copy for middleware extensions
281
- const mutableCtx = ctx;
282
- // Build the handler wrapper
283
- const executeHandler = async () => {
284
- const output = await handler({ input, ctx: mutableCtx });
285
- return { output };
286
- };
287
- // Build chain from end to start (only done once per request, not per middleware)
288
- let next = executeHandler;
289
- for (let i = middlewares.length - 1; i >= 0; i--) {
290
- const middleware = middlewares[i];
291
- const currentNext = next;
292
- next = async () => {
293
- return middleware({
294
- input,
295
- ctx: mutableCtx,
296
- next: async (opts) => {
297
- if (opts?.ctx) {
298
- Object.assign(mutableCtx, opts.ctx);
299
- }
300
- return currentNext();
301
- },
302
- });
303
- };
304
- }
305
- const result = await next();
306
- return result.output;
307
- };
294
+ return createMiddlewareExecutor(middlewares, handler);
308
295
  }
309
296
  /**
310
297
  * Defines a collection of procedures under a namespace
@@ -502,31 +489,7 @@ export async function executeProcedure(procedure, rawInput, ctx) {
502
489
  * @internal
503
490
  */
504
491
  async function executeMiddlewareChainFallback(middlewares, input, ctx, handler) {
505
- // Build the chain from the end (handler) back to the start
506
- let next = async () => {
507
- const output = await handler();
508
- return { output };
509
- };
510
- // Wrap each middleware from last to first
511
- for (let i = middlewares.length - 1; i >= 0; i--) {
512
- const middleware = middlewares[i];
513
- const currentNext = next;
514
- next = async () => {
515
- return middleware({
516
- input,
517
- ctx,
518
- next: async (opts) => {
519
- // Allow middleware to extend context
520
- if (opts?.ctx) {
521
- Object.assign(ctx, opts.ctx);
522
- }
523
- return currentNext();
524
- },
525
- });
526
- };
527
- }
528
- const result = await next();
529
- return result.output;
492
+ return executeMiddlewareChain(middlewares, input, ctx, handler);
530
493
  }
531
494
  // ============================================================================
532
495
  // Type Utilities
@@ -249,16 +249,16 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
249
249
  */
250
250
  rest(config: RestRouteOverride): ProcedureBuilder<TInput, TOutput, TContext>;
251
251
  /**
252
- * Declares a parent resource for nested routes
252
+ * Declares a parent resource for nested routes (single level)
253
253
  *
254
254
  * When a procedure has a parent resource, the REST path will be nested
255
- * under the parent: `/${parentNamespace}/:${parentParam}/${childNamespace}/:id`
255
+ * under the parent: `/${parentResource}/:${parentParam}/${childResource}/:id`
256
256
  *
257
257
  * The input schema should include the parent parameter (e.g., `postId`) for
258
258
  * proper type safety and runtime validation.
259
259
  *
260
- * @param namespace - Parent resource namespace (e.g., 'posts', 'users')
261
- * @param paramName - Optional custom parameter name (default: `${singularNamespace}Id`)
260
+ * @param resource - Parent resource name (e.g., 'posts', 'users')
261
+ * @param param - Optional custom parameter name (default: `${singularResource}Id`)
262
262
  * @returns Same builder (no type changes)
263
263
  *
264
264
  * @example
@@ -276,7 +276,38 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
276
276
  * .query(async ({ input }) => { ... });
277
277
  * ```
278
278
  */
279
- parent(namespace: string, paramName?: string): ProcedureBuilder<TInput, TOutput, TContext>;
279
+ parent(resource: string, param?: string): ProcedureBuilder<TInput, TOutput, TContext>;
280
+ /**
281
+ * Declares multiple parent resources for deeply nested routes
282
+ *
283
+ * When a procedure has multiple parent resources, the REST path will be
284
+ * deeply nested: `/${parent1}/:${param1}/${parent2}/:${param2}/.../${child}/:id`
285
+ *
286
+ * The input schema should include ALL parent parameters for proper type safety.
287
+ *
288
+ * @param config - Array of parent resource configurations from outermost to innermost
289
+ * @returns Same builder (no type changes)
290
+ *
291
+ * @example
292
+ * ```typescript
293
+ * // Generates: GET /organizations/:orgId/projects/:projectId/tasks/:id
294
+ * const getTask = procedure()
295
+ * .parents([
296
+ * { resource: 'organizations', param: 'orgId' },
297
+ * { resource: 'projects', param: 'projectId' },
298
+ * ])
299
+ * .input(z.object({
300
+ * orgId: z.string(),
301
+ * projectId: z.string(),
302
+ * id: z.string()
303
+ * }))
304
+ * .query(async ({ input }) => { ... });
305
+ * ```
306
+ */
307
+ parents(config: Array<{
308
+ resource: string;
309
+ param?: string;
310
+ }>): ProcedureBuilder<TInput, TOutput, TContext>;
280
311
  /**
281
312
  * Finalizes the procedure as a query (read-only operation)
282
313
  *
@@ -333,8 +364,10 @@ export interface BuilderRuntimeState {
333
364
  guards: GuardLike<unknown>[];
334
365
  /** REST route override */
335
366
  restOverride?: RestRouteOverride;
336
- /** Parent resource configuration for nested routes */
367
+ /** Parent resource configuration for nested routes (single level) */
337
368
  parentResource?: ParentResourceConfig;
369
+ /** Multi-level parent resource configuration for deeply nested routes */
370
+ parentResources?: ParentResourceConfig[];
338
371
  }
339
372
  /**
340
373
  * Type for the procedures object passed to defineProcedures
@@ -41,6 +41,35 @@ export interface RestAdapterOptions {
41
41
  prefix?: string;
42
42
  /** Custom error handler */
43
43
  onError?: (error: unknown, request: FastifyRequest, reply: FastifyReply) => void;
44
+ /**
45
+ * Generate flat shortcut routes alongside nested routes.
46
+ *
47
+ * When enabled, nested routes like `/organizations/:orgId/projects/:projectId/tasks/:id`
48
+ * will also generate a flat shortcut route like `/tasks/:id`.
49
+ *
50
+ * Note: Shortcuts only work for single-resource operations (GET, PUT, PATCH, DELETE with :id).
51
+ * Collection operations (list, create) require parent context and are NOT generated as shortcuts.
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * rest([tasks], { shortcuts: true })
56
+ * // Generates:
57
+ * // GET /organizations/:orgId/projects/:projectId/tasks/:id (nested)
58
+ * // GET /tasks/:id (shortcut)
59
+ * ```
60
+ *
61
+ * @default false
62
+ */
63
+ shortcuts?: boolean;
64
+ /**
65
+ * Enable warnings about deep nesting (3+ levels).
66
+ *
67
+ * When true (default), the router warns when nesting exceeds 3 levels.
68
+ * Set to false to silence these warnings.
69
+ *
70
+ * @default true
71
+ */
72
+ nestingWarnings?: boolean;
44
73
  }
45
74
  /**
46
75
  * Internal options used during route registration
@@ -53,6 +82,13 @@ interface InternalRegistrationOptions extends RestAdapterOptions {
53
82
  */
54
83
  _prefixHandledByFastify?: boolean;
55
84
  }
85
+ /** Options for generating REST routes */
86
+ export interface GenerateRestRoutesOptions {
87
+ /** Generate flat shortcut routes alongside nested routes */
88
+ shortcuts?: boolean;
89
+ /** Enable nesting depth warnings (default: true) */
90
+ nestingWarnings?: boolean;
91
+ }
56
92
  /**
57
93
  * Generate REST routes from a procedure collection
58
94
  *
@@ -62,9 +98,10 @@ interface InternalRegistrationOptions extends RestAdapterOptions {
62
98
  * 3. Skipping if neither applies (tRPC-only procedure)
63
99
  *
64
100
  * @param collection - Procedure collection to generate routes from
101
+ * @param options - Optional route generation options
65
102
  * @returns Array of REST route definitions
66
103
  */
67
- export declare function generateRestRoutes(collection: ProcedureCollection): RestRoute[];
104
+ export declare function generateRestRoutes(collection: ProcedureCollection, options?: GenerateRestRoutesOptions): RestRoute[];
68
105
  /**
69
106
  * Register REST routes from procedure collections onto a Fastify instance
70
107
  *