@veloxts/router 0.6.83 → 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 +18 -0
- package/dist/expose.d.ts +12 -9
- package/dist/expose.js +13 -9
- package/dist/index.d.ts +8 -4
- package/dist/index.js +2 -1
- package/dist/middleware/chain.d.ts +54 -0
- package/dist/middleware/chain.js +80 -0
- package/dist/middleware/index.d.ts +7 -0
- package/dist/middleware/index.js +6 -0
- package/dist/procedure/builder.js +34 -59
- package/dist/procedure/types.d.ts +66 -6
- package/dist/rest/adapter.d.ts +38 -1
- package/dist/rest/adapter.js +94 -27
- package/dist/rest/index.d.ts +2 -2
- package/dist/rest/index.js +1 -1
- package/dist/rest/naming.d.ts +38 -2
- package/dist/rest/naming.js +65 -18
- package/dist/rpc.d.ts +144 -0
- package/dist/rpc.js +127 -0
- package/dist/trpc/adapter.d.ts +139 -10
- package/dist/trpc/adapter.js +33 -61
- package/dist/trpc/index.d.ts +3 -1
- package/dist/types.d.ts +47 -7
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.6.84
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- - auth: add simplified guard() function with overloads + fluent builder
|
|
17
|
+
- Updated dependencies
|
|
18
|
+
- @veloxts/core@0.6.84
|
|
19
|
+
- @veloxts/validation@0.6.84
|
|
20
|
+
|
|
3
21
|
## 0.6.83
|
|
4
22
|
|
|
5
23
|
### 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:
|
|
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
|
-
|
|
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,
|
|
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
|
+
}
|
|
@@ -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
|
// ============================================================================
|
|
@@ -187,6 +189,18 @@ function createBuilder(state) {
|
|
|
187
189
|
guards: [...state.guards, guardDef],
|
|
188
190
|
});
|
|
189
191
|
},
|
|
192
|
+
/**
|
|
193
|
+
* Adds multiple authorization guards at once
|
|
194
|
+
*
|
|
195
|
+
* Convenience method equivalent to chaining multiple `.guard()` calls.
|
|
196
|
+
* Guards execute left-to-right. All must pass for the procedure to execute.
|
|
197
|
+
*/
|
|
198
|
+
guards(...guardDefs) {
|
|
199
|
+
return createBuilder({
|
|
200
|
+
...state,
|
|
201
|
+
guards: [...state.guards, ...guardDefs],
|
|
202
|
+
});
|
|
203
|
+
},
|
|
190
204
|
/**
|
|
191
205
|
* Sets REST route override
|
|
192
206
|
*/
|
|
@@ -197,18 +211,31 @@ function createBuilder(state) {
|
|
|
197
211
|
});
|
|
198
212
|
},
|
|
199
213
|
/**
|
|
200
|
-
* Declares a parent resource for nested routes
|
|
214
|
+
* Declares a parent resource for nested routes (single level)
|
|
201
215
|
*/
|
|
202
|
-
parent(
|
|
216
|
+
parent(resource, param) {
|
|
203
217
|
const parentConfig = {
|
|
204
|
-
|
|
205
|
-
|
|
218
|
+
resource,
|
|
219
|
+
param: param ?? deriveParentParamName(resource),
|
|
206
220
|
};
|
|
207
221
|
return createBuilder({
|
|
208
222
|
...state,
|
|
209
223
|
parentResource: parentConfig,
|
|
210
224
|
});
|
|
211
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
|
+
},
|
|
212
239
|
/**
|
|
213
240
|
* Finalizes as a query procedure
|
|
214
241
|
*/
|
|
@@ -249,6 +276,7 @@ function compileProcedure(type, handler, state) {
|
|
|
249
276
|
guards: state.guards,
|
|
250
277
|
restOverride: state.restOverride,
|
|
251
278
|
parentResource: state.parentResource,
|
|
279
|
+
parentResources: state.parentResources,
|
|
252
280
|
// Store pre-compiled executor for performance
|
|
253
281
|
_precompiledExecutor: precompiledExecutor,
|
|
254
282
|
};
|
|
@@ -263,36 +291,7 @@ function compileProcedure(type, handler, state) {
|
|
|
263
291
|
* @internal
|
|
264
292
|
*/
|
|
265
293
|
function createPrecompiledMiddlewareExecutor(middlewares, handler) {
|
|
266
|
-
|
|
267
|
-
return async (input, ctx) => {
|
|
268
|
-
// Create mutable context copy for middleware extensions
|
|
269
|
-
const mutableCtx = ctx;
|
|
270
|
-
// Build the handler wrapper
|
|
271
|
-
const executeHandler = async () => {
|
|
272
|
-
const output = await handler({ input, ctx: mutableCtx });
|
|
273
|
-
return { output };
|
|
274
|
-
};
|
|
275
|
-
// Build chain from end to start (only done once per request, not per middleware)
|
|
276
|
-
let next = executeHandler;
|
|
277
|
-
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
278
|
-
const middleware = middlewares[i];
|
|
279
|
-
const currentNext = next;
|
|
280
|
-
next = async () => {
|
|
281
|
-
return middleware({
|
|
282
|
-
input,
|
|
283
|
-
ctx: mutableCtx,
|
|
284
|
-
next: async (opts) => {
|
|
285
|
-
if (opts?.ctx) {
|
|
286
|
-
Object.assign(mutableCtx, opts.ctx);
|
|
287
|
-
}
|
|
288
|
-
return currentNext();
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
const result = await next();
|
|
294
|
-
return result.output;
|
|
295
|
-
};
|
|
294
|
+
return createMiddlewareExecutor(middlewares, handler);
|
|
296
295
|
}
|
|
297
296
|
/**
|
|
298
297
|
* Defines a collection of procedures under a namespace
|
|
@@ -490,31 +489,7 @@ export async function executeProcedure(procedure, rawInput, ctx) {
|
|
|
490
489
|
* @internal
|
|
491
490
|
*/
|
|
492
491
|
async function executeMiddlewareChainFallback(middlewares, input, ctx, handler) {
|
|
493
|
-
|
|
494
|
-
let next = async () => {
|
|
495
|
-
const output = await handler();
|
|
496
|
-
return { output };
|
|
497
|
-
};
|
|
498
|
-
// Wrap each middleware from last to first
|
|
499
|
-
for (let i = middlewares.length - 1; i >= 0; i--) {
|
|
500
|
-
const middleware = middlewares[i];
|
|
501
|
-
const currentNext = next;
|
|
502
|
-
next = async () => {
|
|
503
|
-
return middleware({
|
|
504
|
-
input,
|
|
505
|
-
ctx,
|
|
506
|
-
next: async (opts) => {
|
|
507
|
-
// Allow middleware to extend context
|
|
508
|
-
if (opts?.ctx) {
|
|
509
|
-
Object.assign(ctx, opts.ctx);
|
|
510
|
-
}
|
|
511
|
-
return currentNext();
|
|
512
|
-
},
|
|
513
|
-
});
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
const result = await next();
|
|
517
|
-
return result.output;
|
|
492
|
+
return executeMiddlewareChain(middlewares, input, ctx, handler);
|
|
518
493
|
}
|
|
519
494
|
// ============================================================================
|
|
520
495
|
// Type Utilities
|
|
@@ -205,6 +205,33 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
205
205
|
guardNarrow<TNarrowedContext>(guard: GuardLike<Partial<TContext>> & {
|
|
206
206
|
readonly _narrows: TNarrowedContext;
|
|
207
207
|
}): ProcedureBuilder<TInput, TOutput, TContext & TNarrowedContext>;
|
|
208
|
+
/**
|
|
209
|
+
* Adds multiple authorization guards at once
|
|
210
|
+
*
|
|
211
|
+
* This is a convenience method equivalent to chaining multiple `.guard()` calls.
|
|
212
|
+
* Guards execute left-to-right. All must pass for the procedure to execute.
|
|
213
|
+
*
|
|
214
|
+
* @param guards - Guard definitions to apply (spread)
|
|
215
|
+
* @returns Same builder (no type changes)
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* import { authenticated, hasRole, emailVerified } from '@veloxts/auth';
|
|
220
|
+
*
|
|
221
|
+
* // Multiple guards in one call
|
|
222
|
+
* procedure()
|
|
223
|
+
* .guards(authenticated, hasRole('admin'), emailVerified)
|
|
224
|
+
* .mutation(async ({ input, ctx }) => { ... });
|
|
225
|
+
*
|
|
226
|
+
* // Equivalent to:
|
|
227
|
+
* procedure()
|
|
228
|
+
* .guard(authenticated)
|
|
229
|
+
* .guard(hasRole('admin'))
|
|
230
|
+
* .guard(emailVerified)
|
|
231
|
+
* .mutation(async ({ input, ctx }) => { ... });
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
guards<TGuards extends GuardLike<Partial<TContext>>[]>(...guards: TGuards): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
208
235
|
/**
|
|
209
236
|
* Configures REST route override
|
|
210
237
|
*
|
|
@@ -222,16 +249,16 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
222
249
|
*/
|
|
223
250
|
rest(config: RestRouteOverride): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
224
251
|
/**
|
|
225
|
-
* Declares a parent resource for nested routes
|
|
252
|
+
* Declares a parent resource for nested routes (single level)
|
|
226
253
|
*
|
|
227
254
|
* When a procedure has a parent resource, the REST path will be nested
|
|
228
|
-
* under the parent: `/${
|
|
255
|
+
* under the parent: `/${parentResource}/:${parentParam}/${childResource}/:id`
|
|
229
256
|
*
|
|
230
257
|
* The input schema should include the parent parameter (e.g., `postId`) for
|
|
231
258
|
* proper type safety and runtime validation.
|
|
232
259
|
*
|
|
233
|
-
* @param
|
|
234
|
-
* @param
|
|
260
|
+
* @param resource - Parent resource name (e.g., 'posts', 'users')
|
|
261
|
+
* @param param - Optional custom parameter name (default: `${singularResource}Id`)
|
|
235
262
|
* @returns Same builder (no type changes)
|
|
236
263
|
*
|
|
237
264
|
* @example
|
|
@@ -249,7 +276,38 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
249
276
|
* .query(async ({ input }) => { ... });
|
|
250
277
|
* ```
|
|
251
278
|
*/
|
|
252
|
-
parent(
|
|
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>;
|
|
253
311
|
/**
|
|
254
312
|
* Finalizes the procedure as a query (read-only operation)
|
|
255
313
|
*
|
|
@@ -306,8 +364,10 @@ export interface BuilderRuntimeState {
|
|
|
306
364
|
guards: GuardLike<unknown>[];
|
|
307
365
|
/** REST route override */
|
|
308
366
|
restOverride?: RestRouteOverride;
|
|
309
|
-
/** Parent resource configuration for nested routes */
|
|
367
|
+
/** Parent resource configuration for nested routes (single level) */
|
|
310
368
|
parentResource?: ParentResourceConfig;
|
|
369
|
+
/** Multi-level parent resource configuration for deeply nested routes */
|
|
370
|
+
parentResources?: ParentResourceConfig[];
|
|
311
371
|
}
|
|
312
372
|
/**
|
|
313
373
|
* Type for the procedures object passed to defineProcedures
|
package/dist/rest/adapter.d.ts
CHANGED
|
@@ -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
|
*
|