@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 +9 -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 +22 -59
- package/dist/procedure/types.d.ts +39 -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/dist/rest/adapter.js
CHANGED
|
@@ -9,10 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { ConfigurationError } from '@veloxts/core';
|
|
11
11
|
import { executeProcedure } from '../procedure/builder.js';
|
|
12
|
-
import { buildNestedRestPath, buildRestPath, parseNamingConvention } from './naming.js';
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
12
|
+
import { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, parseNamingConvention, } from './naming.js';
|
|
13
|
+
/** Default nesting depth threshold for warnings */
|
|
14
|
+
const NESTING_DEPTH_WARNING_THRESHOLD = 3;
|
|
16
15
|
/**
|
|
17
16
|
* Generate REST routes from a procedure collection
|
|
18
17
|
*
|
|
@@ -22,23 +21,90 @@ import { buildNestedRestPath, buildRestPath, parseNamingConvention } from './nam
|
|
|
22
21
|
* 3. Skipping if neither applies (tRPC-only procedure)
|
|
23
22
|
*
|
|
24
23
|
* @param collection - Procedure collection to generate routes from
|
|
24
|
+
* @param options - Optional route generation options
|
|
25
25
|
* @returns Array of REST route definitions
|
|
26
26
|
*/
|
|
27
|
-
export function generateRestRoutes(collection) {
|
|
27
|
+
export function generateRestRoutes(collection, options = {}) {
|
|
28
28
|
const routes = [];
|
|
29
|
+
const { shortcuts = false, nestingWarnings = true } = options;
|
|
29
30
|
for (const [name, procedure] of Object.entries(collection.procedures)) {
|
|
30
31
|
const route = generateRouteForProcedure(name, procedure, collection.namespace);
|
|
31
32
|
if (route) {
|
|
32
33
|
routes.push(route);
|
|
34
|
+
// Check nesting depth and warn if too deep
|
|
35
|
+
if (nestingWarnings) {
|
|
36
|
+
const depth = calculateNestingDepth(procedure.parentResource, procedure.parentResources);
|
|
37
|
+
if (depth >= NESTING_DEPTH_WARNING_THRESHOLD) {
|
|
38
|
+
console.warn(`⚠️ Resource '${collection.namespace}/${name}' has ${depth} levels of nesting. ` +
|
|
39
|
+
`Consider using shortcuts: true or restructuring your API.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Generate shortcut route if enabled and route is nested with ID parameter
|
|
43
|
+
if (shortcuts && isNestedRoute(procedure) && route.path.endsWith('/:id')) {
|
|
44
|
+
const shortcutRoute = generateFlatRoute(route, collection.namespace);
|
|
45
|
+
if (shortcutRoute) {
|
|
46
|
+
routes.push(shortcutRoute);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
51
|
return routes;
|
|
36
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Check if a procedure has parent resources (is nested)
|
|
55
|
+
*/
|
|
56
|
+
function isNestedRoute(procedure) {
|
|
57
|
+
return (procedure.parentResource !== undefined ||
|
|
58
|
+
(procedure.parentResources !== undefined && procedure.parentResources.length > 0));
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate a shortcut route for a nested route
|
|
62
|
+
*
|
|
63
|
+
* Only generates shortcuts for single-resource operations (with :id).
|
|
64
|
+
* Collection operations require parent context and are not suitable for shortcuts.
|
|
65
|
+
*/
|
|
66
|
+
function generateFlatRoute(nestedRoute, namespace) {
|
|
67
|
+
// Only generate shortcuts for operations with :id (single resource)
|
|
68
|
+
if (!nestedRoute.path.endsWith('/:id')) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
// Build shortcut path: /{namespace}/:id
|
|
72
|
+
const shortcutPath = `/${namespace}/:id`;
|
|
73
|
+
return {
|
|
74
|
+
method: nestedRoute.method,
|
|
75
|
+
path: shortcutPath,
|
|
76
|
+
procedureName: `${nestedRoute.procedureName}Shortcut`,
|
|
77
|
+
procedure: nestedRoute.procedure,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the REST path for a procedure based on its nesting configuration
|
|
82
|
+
*
|
|
83
|
+
* Handles:
|
|
84
|
+
* - Flat routes: /users/:id
|
|
85
|
+
* - Single-level nested: /posts/:postId/comments/:id
|
|
86
|
+
* - Multi-level nested: /organizations/:orgId/projects/:projectId/tasks/:id
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
function buildProcedurePath(procedure, namespace, mapping) {
|
|
91
|
+
// Multi-level nesting takes precedence
|
|
92
|
+
if (procedure.parentResources && procedure.parentResources.length > 0) {
|
|
93
|
+
return buildMultiLevelNestedPath(procedure.parentResources, namespace, mapping);
|
|
94
|
+
}
|
|
95
|
+
// Single-level nesting
|
|
96
|
+
if (procedure.parentResource) {
|
|
97
|
+
return buildNestedRestPath(procedure.parentResource, namespace, mapping);
|
|
98
|
+
}
|
|
99
|
+
// Flat route
|
|
100
|
+
return buildRestPath(namespace, mapping);
|
|
101
|
+
}
|
|
37
102
|
/**
|
|
38
103
|
* Generate a REST route for a single procedure
|
|
39
104
|
*
|
|
40
|
-
* Handles
|
|
41
|
-
* (e.g., /posts/:postId/comments/:id)
|
|
105
|
+
* Handles flat routes (e.g., /users/:id), single-level nested routes
|
|
106
|
+
* (e.g., /posts/:postId/comments/:id), and multi-level nested routes
|
|
107
|
+
* (e.g., /organizations/:orgId/projects/:projectId/tasks/:id).
|
|
42
108
|
*
|
|
43
109
|
* @internal
|
|
44
110
|
*/
|
|
@@ -60,11 +126,8 @@ function generateRouteForProcedure(name, procedure, namespace) {
|
|
|
60
126
|
// Partial override - try to fill in missing parts from convention
|
|
61
127
|
const convention = parseNamingConvention(name, procedure.type);
|
|
62
128
|
if (convention) {
|
|
63
|
-
// Build path based on
|
|
64
|
-
const path = override.path ??
|
|
65
|
-
(procedure.parentResource
|
|
66
|
-
? buildNestedRestPath(procedure.parentResource, namespace, convention)
|
|
67
|
-
: buildRestPath(namespace, convention));
|
|
129
|
+
// Build path based on nesting configuration
|
|
130
|
+
const path = override.path ?? buildProcedurePath(procedure, namespace, convention);
|
|
68
131
|
return {
|
|
69
132
|
method: override.method ?? convention.method,
|
|
70
133
|
path,
|
|
@@ -78,10 +141,8 @@ function generateRouteForProcedure(name, procedure, namespace) {
|
|
|
78
141
|
// Try to infer from naming convention
|
|
79
142
|
const mapping = parseNamingConvention(name, procedure.type);
|
|
80
143
|
if (mapping) {
|
|
81
|
-
// Build path based on
|
|
82
|
-
const path = procedure
|
|
83
|
-
? buildNestedRestPath(procedure.parentResource, namespace, mapping)
|
|
84
|
-
: buildRestPath(namespace, mapping);
|
|
144
|
+
// Build path based on nesting configuration
|
|
145
|
+
const path = buildProcedurePath(procedure, namespace, mapping);
|
|
85
146
|
return {
|
|
86
147
|
method: mapping.method,
|
|
87
148
|
path,
|
|
@@ -143,32 +204,34 @@ function isPlainObject(value) {
|
|
|
143
204
|
* Gather input data from the request based on HTTP method
|
|
144
205
|
*
|
|
145
206
|
* - GET: Merge params and query
|
|
146
|
-
* - POST: Use body, but merge params for nested routes (parent
|
|
207
|
+
* - POST: Use body, but merge params for nested routes (parent IDs in URL)
|
|
147
208
|
* - PUT/PATCH: Merge params (for ID) and body (for data)
|
|
148
209
|
* - DELETE: Merge params and query (no body per REST conventions)
|
|
149
210
|
*
|
|
150
|
-
* For nested routes (e.g., /posts/:postId/comments
|
|
151
|
-
*
|
|
211
|
+
* For nested routes (e.g., /posts/:postId/comments or
|
|
212
|
+
* /organizations/:orgId/projects/:projectId/tasks), all parent params
|
|
213
|
+
* are extracted from the URL and merged with the body/query as appropriate.
|
|
152
214
|
*/
|
|
153
215
|
function gatherInput(request, route) {
|
|
154
216
|
const params = isPlainObject(request.params) ? request.params : {};
|
|
155
217
|
const query = isPlainObject(request.query) ? request.query : {};
|
|
156
218
|
const body = isPlainObject(request.body) ? request.body : {};
|
|
157
|
-
// Check if this is a nested route (has parent
|
|
158
|
-
const hasParentResource = route.procedure.parentResource !== undefined
|
|
219
|
+
// Check if this is a nested route (has single parent or multiple parents)
|
|
220
|
+
const hasParentResource = route.procedure.parentResource !== undefined ||
|
|
221
|
+
(route.procedure.parentResources !== undefined && route.procedure.parentResources.length > 0);
|
|
159
222
|
switch (route.method) {
|
|
160
223
|
case 'GET':
|
|
161
|
-
// GET: params (for :id and parent params) + query (for filters/pagination)
|
|
224
|
+
// GET: params (for :id and all parent params) + query (for filters/pagination)
|
|
162
225
|
return { ...params, ...query };
|
|
163
226
|
case 'DELETE':
|
|
164
|
-
// DELETE: params (for :id and parent params) + query (for options), no body per REST conventions
|
|
227
|
+
// DELETE: params (for :id and all parent params) + query (for options), no body per REST conventions
|
|
165
228
|
return { ...params, ...query };
|
|
166
229
|
case 'PUT':
|
|
167
230
|
case 'PATCH':
|
|
168
|
-
// PUT/PATCH: params (for :id and parent params) + body (for data)
|
|
231
|
+
// PUT/PATCH: params (for :id and all parent params) + body (for data)
|
|
169
232
|
return { ...params, ...body };
|
|
170
233
|
case 'POST':
|
|
171
|
-
// POST: For nested routes, merge params (for parent
|
|
234
|
+
// POST: For nested routes, merge params (for all parent IDs) with body
|
|
172
235
|
// For flat routes, use body only (no ID in params for creates)
|
|
173
236
|
if (hasParentResource) {
|
|
174
237
|
return { ...params, ...body };
|
|
@@ -244,9 +307,13 @@ function getContextFromRequest(request) {
|
|
|
244
307
|
* ```
|
|
245
308
|
*/
|
|
246
309
|
export function registerRestRoutes(server, collections, options = {}) {
|
|
247
|
-
const { prefix = '/api', _prefixHandledByFastify = false } = options;
|
|
310
|
+
const { prefix = '/api', _prefixHandledByFastify = false, shortcuts = false, nestingWarnings = true, } = options;
|
|
311
|
+
const routeGenOptions = {
|
|
312
|
+
shortcuts,
|
|
313
|
+
nestingWarnings,
|
|
314
|
+
};
|
|
248
315
|
for (const collection of collections) {
|
|
249
|
-
const routes = generateRestRoutes(collection);
|
|
316
|
+
const routes = generateRestRoutes(collection, routeGenOptions);
|
|
250
317
|
for (const route of routes) {
|
|
251
318
|
// When used with server.register(), Fastify handles the prefix automatically.
|
|
252
319
|
// When used in legacy mode, we prepend the prefix manually.
|
package/dist/rest/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module rest
|
|
5
5
|
*/
|
|
6
|
-
export type { RestAdapterOptions, RestPlugin, RestRoute } from './adapter.js';
|
|
6
|
+
export type { GenerateRestRoutesOptions, RestAdapterOptions, RestPlugin, RestRoute, } from './adapter.js';
|
|
7
7
|
export { generateRestRoutes, getRouteSummary, registerRestRoutes, rest, } from './adapter.js';
|
|
8
8
|
export type { RestMapping } from './naming.js';
|
|
9
|
-
export { buildNestedRestPath, buildRestPath, followsNamingConvention, inferResourceName, parseNamingConvention, } from './naming.js';
|
|
9
|
+
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, followsNamingConvention, inferResourceName, parseNamingConvention, } from './naming.js';
|
|
10
10
|
export type { ExtractRoutesType, RouteEntry, RouteMap } from './routes.js';
|
|
11
11
|
export { extractRoutes } from './routes.js';
|
package/dist/rest/index.js
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
* @module rest
|
|
5
5
|
*/
|
|
6
6
|
export { generateRestRoutes, getRouteSummary, registerRestRoutes, rest, } from './adapter.js';
|
|
7
|
-
export { buildNestedRestPath, buildRestPath, followsNamingConvention, inferResourceName, parseNamingConvention, } from './naming.js';
|
|
7
|
+
export { buildMultiLevelNestedPath, buildNestedRestPath, buildRestPath, calculateNestingDepth, followsNamingConvention, inferResourceName, parseNamingConvention, } from './naming.js';
|
|
8
8
|
export { extractRoutes } from './routes.js';
|
package/dist/rest/naming.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export declare function parseNamingConvention(name: string, type: ProcedureType)
|
|
|
59
59
|
*/
|
|
60
60
|
export declare function buildRestPath(namespace: string, mapping: RestMapping): string;
|
|
61
61
|
/**
|
|
62
|
-
* Build a nested REST path with parent resource prefix
|
|
62
|
+
* Build a nested REST path with parent resource prefix (single level)
|
|
63
63
|
*
|
|
64
64
|
* Creates paths like `/posts/:postId/comments/:id` for nested resources.
|
|
65
65
|
*
|
|
@@ -70,7 +70,7 @@ export declare function buildRestPath(namespace: string, mapping: RestMapping):
|
|
|
70
70
|
*
|
|
71
71
|
* @example
|
|
72
72
|
* ```typescript
|
|
73
|
-
* const parent = {
|
|
73
|
+
* const parent = { resource: 'posts', param: 'postId' };
|
|
74
74
|
*
|
|
75
75
|
* buildNestedRestPath(parent, 'comments', { method: 'GET', path: '/:id', hasIdParam: true })
|
|
76
76
|
* // Returns: '/posts/:postId/comments/:id'
|
|
@@ -83,6 +83,42 @@ export declare function buildRestPath(namespace: string, mapping: RestMapping):
|
|
|
83
83
|
* ```
|
|
84
84
|
*/
|
|
85
85
|
export declare function buildNestedRestPath(parentResource: ParentResourceConfig, childNamespace: string, mapping: RestMapping): string;
|
|
86
|
+
/**
|
|
87
|
+
* Build a deeply nested REST path with multiple parent resources
|
|
88
|
+
*
|
|
89
|
+
* Creates paths like `/organizations/:orgId/projects/:projectId/tasks/:id`
|
|
90
|
+
* for deeply nested resources.
|
|
91
|
+
*
|
|
92
|
+
* @param parentResources - Array of parent resource configurations (outermost to innermost)
|
|
93
|
+
* @param childNamespace - Child resource namespace (e.g., 'tasks')
|
|
94
|
+
* @param mapping - REST mapping from parseNamingConvention
|
|
95
|
+
* @returns Full deeply nested path
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const parents = [
|
|
100
|
+
* { resource: 'organizations', param: 'orgId' },
|
|
101
|
+
* { resource: 'projects', param: 'projectId' },
|
|
102
|
+
* ];
|
|
103
|
+
*
|
|
104
|
+
* buildMultiLevelNestedPath(parents, 'tasks', { method: 'GET', path: '/:id', hasIdParam: true })
|
|
105
|
+
* // Returns: '/organizations/:orgId/projects/:projectId/tasks/:id'
|
|
106
|
+
*
|
|
107
|
+
* buildMultiLevelNestedPath(parents, 'tasks', { method: 'GET', path: '/', hasIdParam: false })
|
|
108
|
+
* // Returns: '/organizations/:orgId/projects/:projectId/tasks'
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
export declare function buildMultiLevelNestedPath(parentResources: readonly ParentResourceConfig[], childNamespace: string, mapping: RestMapping): string;
|
|
112
|
+
/**
|
|
113
|
+
* Calculate the nesting depth of a route
|
|
114
|
+
*
|
|
115
|
+
* Returns the number of parent levels plus 1 for the resource itself.
|
|
116
|
+
*
|
|
117
|
+
* @param parentResource - Single parent resource (optional)
|
|
118
|
+
* @param parentResources - Multiple parent resources (optional)
|
|
119
|
+
* @returns The total nesting depth (1 = flat, 2 = one parent, 3+ = deeply nested)
|
|
120
|
+
*/
|
|
121
|
+
export declare function calculateNestingDepth(parentResource?: ParentResourceConfig, parentResources?: readonly ParentResourceConfig[]): number;
|
|
86
122
|
/**
|
|
87
123
|
* Infer the resource name from a procedure name
|
|
88
124
|
*
|
package/dist/rest/naming.js
CHANGED
|
@@ -140,6 +140,16 @@ export function parseNamingConvention(name, type) {
|
|
|
140
140
|
// No convention matched
|
|
141
141
|
return undefined;
|
|
142
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Append the mapping suffix to a base path
|
|
145
|
+
*
|
|
146
|
+
* @param basePath - The base path (e.g., '/users', '/posts/:postId/comments')
|
|
147
|
+
* @param mapping - REST mapping containing the path suffix
|
|
148
|
+
* @returns The complete path with suffix appended (if not just '/')
|
|
149
|
+
*/
|
|
150
|
+
function appendMappingSuffix(basePath, mapping) {
|
|
151
|
+
return mapping.path === '/' ? basePath : `${basePath}${mapping.path}`;
|
|
152
|
+
}
|
|
143
153
|
/**
|
|
144
154
|
* Build the full REST path from namespace and mapping
|
|
145
155
|
*
|
|
@@ -157,16 +167,10 @@ export function parseNamingConvention(name, type) {
|
|
|
157
167
|
* ```
|
|
158
168
|
*/
|
|
159
169
|
export function buildRestPath(namespace, mapping) {
|
|
160
|
-
|
|
161
|
-
// If path is just '/', return the base path without trailing slash
|
|
162
|
-
if (mapping.path === '/') {
|
|
163
|
-
return basePath;
|
|
164
|
-
}
|
|
165
|
-
// Otherwise append the path (e.g., '/:id')
|
|
166
|
-
return `${basePath}${mapping.path}`;
|
|
170
|
+
return appendMappingSuffix(`/${namespace}`, mapping);
|
|
167
171
|
}
|
|
168
172
|
/**
|
|
169
|
-
* Build a nested REST path with parent resource prefix
|
|
173
|
+
* Build a nested REST path with parent resource prefix (single level)
|
|
170
174
|
*
|
|
171
175
|
* Creates paths like `/posts/:postId/comments/:id` for nested resources.
|
|
172
176
|
*
|
|
@@ -177,7 +181,7 @@ export function buildRestPath(namespace, mapping) {
|
|
|
177
181
|
*
|
|
178
182
|
* @example
|
|
179
183
|
* ```typescript
|
|
180
|
-
* const parent = {
|
|
184
|
+
* const parent = { resource: 'posts', param: 'postId' };
|
|
181
185
|
*
|
|
182
186
|
* buildNestedRestPath(parent, 'comments', { method: 'GET', path: '/:id', hasIdParam: true })
|
|
183
187
|
* // Returns: '/posts/:postId/comments/:id'
|
|
@@ -190,16 +194,59 @@ export function buildRestPath(namespace, mapping) {
|
|
|
190
194
|
* ```
|
|
191
195
|
*/
|
|
192
196
|
export function buildNestedRestPath(parentResource, childNamespace, mapping) {
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
const parentPath = `/${parentResource.resource}/:${parentResource.param}`;
|
|
198
|
+
const basePath = `${parentPath}/${childNamespace}`;
|
|
199
|
+
return appendMappingSuffix(basePath, mapping);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Build a deeply nested REST path with multiple parent resources
|
|
203
|
+
*
|
|
204
|
+
* Creates paths like `/organizations/:orgId/projects/:projectId/tasks/:id`
|
|
205
|
+
* for deeply nested resources.
|
|
206
|
+
*
|
|
207
|
+
* @param parentResources - Array of parent resource configurations (outermost to innermost)
|
|
208
|
+
* @param childNamespace - Child resource namespace (e.g., 'tasks')
|
|
209
|
+
* @param mapping - REST mapping from parseNamingConvention
|
|
210
|
+
* @returns Full deeply nested path
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```typescript
|
|
214
|
+
* const parents = [
|
|
215
|
+
* { resource: 'organizations', param: 'orgId' },
|
|
216
|
+
* { resource: 'projects', param: 'projectId' },
|
|
217
|
+
* ];
|
|
218
|
+
*
|
|
219
|
+
* buildMultiLevelNestedPath(parents, 'tasks', { method: 'GET', path: '/:id', hasIdParam: true })
|
|
220
|
+
* // Returns: '/organizations/:orgId/projects/:projectId/tasks/:id'
|
|
221
|
+
*
|
|
222
|
+
* buildMultiLevelNestedPath(parents, 'tasks', { method: 'GET', path: '/', hasIdParam: false })
|
|
223
|
+
* // Returns: '/organizations/:orgId/projects/:projectId/tasks'
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
export function buildMultiLevelNestedPath(parentResources, childNamespace, mapping) {
|
|
227
|
+
const parentSegments = parentResources
|
|
228
|
+
.map((parent) => `/${parent.resource}/:${parent.param}`)
|
|
229
|
+
.join('');
|
|
230
|
+
const basePath = `${parentSegments}/${childNamespace}`;
|
|
231
|
+
return appendMappingSuffix(basePath, mapping);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Calculate the nesting depth of a route
|
|
235
|
+
*
|
|
236
|
+
* Returns the number of parent levels plus 1 for the resource itself.
|
|
237
|
+
*
|
|
238
|
+
* @param parentResource - Single parent resource (optional)
|
|
239
|
+
* @param parentResources - Multiple parent resources (optional)
|
|
240
|
+
* @returns The total nesting depth (1 = flat, 2 = one parent, 3+ = deeply nested)
|
|
241
|
+
*/
|
|
242
|
+
export function calculateNestingDepth(parentResource, parentResources) {
|
|
243
|
+
if (parentResources && parentResources.length > 0) {
|
|
244
|
+
return parentResources.length + 1;
|
|
245
|
+
}
|
|
246
|
+
if (parentResource) {
|
|
247
|
+
return 2;
|
|
200
248
|
}
|
|
201
|
-
|
|
202
|
-
return `${parentPath}${childBasePath}${mapping.path}`;
|
|
249
|
+
return 1;
|
|
203
250
|
}
|
|
204
251
|
/**
|
|
205
252
|
* Infer the resource name from a procedure name
|
package/dist/rpc.d.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC helper for type-safe tRPC registration
|
|
3
|
+
*
|
|
4
|
+
* Provides a symmetric API to `rest()` for registering tRPC endpoints
|
|
5
|
+
* with full type preservation for client inference.
|
|
6
|
+
*
|
|
7
|
+
* @module rpc
|
|
8
|
+
*/
|
|
9
|
+
import { type VeloxApp } from '@veloxts/core';
|
|
10
|
+
import type { FastifyInstance } from 'fastify';
|
|
11
|
+
import { type AnyRouter, type InferRouterFromCollections } from './trpc/index.js';
|
|
12
|
+
import type { ProcedureCollection } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Options for RPC registration
|
|
15
|
+
*/
|
|
16
|
+
export interface RpcOptions {
|
|
17
|
+
/**
|
|
18
|
+
* tRPC endpoint prefix
|
|
19
|
+
*
|
|
20
|
+
* @default '/trpc'
|
|
21
|
+
*/
|
|
22
|
+
prefix?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Result of rpc() for type-safe router access
|
|
26
|
+
*
|
|
27
|
+
* Contains both the typed router (for type export) and an async
|
|
28
|
+
* registration function (for registering with Fastify).
|
|
29
|
+
*/
|
|
30
|
+
export interface RpcResult<T extends readonly ProcedureCollection[]> {
|
|
31
|
+
/**
|
|
32
|
+
* The typed tRPC router
|
|
33
|
+
*
|
|
34
|
+
* Use `typeof router` to export the AppRouter type for clients.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* const { router } = rpc([userProcedures, postProcedures] as const);
|
|
39
|
+
* export type AppRouter = typeof router;
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
readonly router: AnyRouter & InferRouterFromCollections<T>;
|
|
43
|
+
/**
|
|
44
|
+
* Register the tRPC routes with a Fastify instance
|
|
45
|
+
*
|
|
46
|
+
* This is an async function that registers the tRPC plugin.
|
|
47
|
+
*
|
|
48
|
+
* @param server - Fastify instance or VeloxApp
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const { register } = rpc([userProcedures] as const);
|
|
53
|
+
* await register(app.server);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
readonly register: (server: FastifyInstance) => Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Create a type-safe tRPC router from procedure collections
|
|
60
|
+
*
|
|
61
|
+
* This is the RPC equivalent of `rest()`. It returns a typed router
|
|
62
|
+
* and a registration function, enabling proper type inference for
|
|
63
|
+
* client-side usage.
|
|
64
|
+
*
|
|
65
|
+
* **IMPORTANT**: Use `as const` on the collections array to preserve
|
|
66
|
+
* literal types for full type inference.
|
|
67
|
+
*
|
|
68
|
+
* @param collections - Array of procedure collections (use `as const`)
|
|
69
|
+
* @param options - Optional RPC configuration
|
|
70
|
+
* @returns RpcResult with typed router and register function
|
|
71
|
+
*
|
|
72
|
+
* @example Basic usage with VeloxApp
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import { veloxApp } from '@veloxts/core';
|
|
75
|
+
* import { rpc } from '@veloxts/router';
|
|
76
|
+
*
|
|
77
|
+
* const app = await veloxApp({ port: 3030 });
|
|
78
|
+
*
|
|
79
|
+
* const { router, register } = rpc([
|
|
80
|
+
* userProcedures,
|
|
81
|
+
* postProcedures,
|
|
82
|
+
* ] as const);
|
|
83
|
+
*
|
|
84
|
+
* // Register tRPC routes
|
|
85
|
+
* await register(app.server);
|
|
86
|
+
*
|
|
87
|
+
* // Export type for clients - fully typed!
|
|
88
|
+
* export type AppRouter = typeof router;
|
|
89
|
+
*
|
|
90
|
+
* await app.start();
|
|
91
|
+
* ```
|
|
92
|
+
*
|
|
93
|
+
* @example With custom prefix
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const { router, register } = rpc([userProcedures] as const, {
|
|
96
|
+
* prefix: '/api/trpc',
|
|
97
|
+
* });
|
|
98
|
+
*
|
|
99
|
+
* await register(app.server);
|
|
100
|
+
* export type AppRouter = typeof router;
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @example Combined with REST
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // Serve same procedures via both REST and tRPC
|
|
106
|
+
* const collections = [userProcedures, postProcedures] as const;
|
|
107
|
+
*
|
|
108
|
+
* // REST endpoints at /api/*
|
|
109
|
+
* app.routes(rest([...collections], { prefix: '/api' }));
|
|
110
|
+
*
|
|
111
|
+
* // tRPC endpoints at /trpc/*
|
|
112
|
+
* const { router, register } = rpc(collections);
|
|
113
|
+
* await register(app.server);
|
|
114
|
+
*
|
|
115
|
+
* export type AppRouter = typeof router;
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function rpc<const T extends readonly ProcedureCollection[]>(collections: T, options?: RpcOptions): RpcResult<T>;
|
|
119
|
+
/**
|
|
120
|
+
* Register tRPC routes and return the typed router
|
|
121
|
+
*
|
|
122
|
+
* This is a convenience function that combines router creation and
|
|
123
|
+
* registration in a single async call. Use this when you don't need
|
|
124
|
+
* to separate router creation from registration.
|
|
125
|
+
*
|
|
126
|
+
* **IMPORTANT**: Use `as const` on the collections array to preserve
|
|
127
|
+
* literal types for full type inference.
|
|
128
|
+
*
|
|
129
|
+
* @param app - VeloxApp instance
|
|
130
|
+
* @param collections - Array of procedure collections (use `as const`)
|
|
131
|
+
* @param options - Optional RPC configuration
|
|
132
|
+
* @returns The typed tRPC router
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* ```typescript
|
|
136
|
+
* const router = await registerRpc(app, [
|
|
137
|
+
* userProcedures,
|
|
138
|
+
* postProcedures,
|
|
139
|
+
* ] as const);
|
|
140
|
+
*
|
|
141
|
+
* export type AppRouter = typeof router;
|
|
142
|
+
* ```
|
|
143
|
+
*/
|
|
144
|
+
export declare function registerRpc<const T extends readonly ProcedureCollection[]>(app: VeloxApp, collections: T, options?: RpcOptions): Promise<AnyRouter & InferRouterFromCollections<T>>;
|
package/dist/rpc.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RPC helper for type-safe tRPC registration
|
|
3
|
+
*
|
|
4
|
+
* Provides a symmetric API to `rest()` for registering tRPC endpoints
|
|
5
|
+
* with full type preservation for client inference.
|
|
6
|
+
*
|
|
7
|
+
* @module rpc
|
|
8
|
+
*/
|
|
9
|
+
import { fail } from '@veloxts/core';
|
|
10
|
+
import { appRouter, registerTRPCPlugin, trpc, } from './trpc/index.js';
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Main Function
|
|
13
|
+
// ============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Create a type-safe tRPC router from procedure collections
|
|
16
|
+
*
|
|
17
|
+
* This is the RPC equivalent of `rest()`. It returns a typed router
|
|
18
|
+
* and a registration function, enabling proper type inference for
|
|
19
|
+
* client-side usage.
|
|
20
|
+
*
|
|
21
|
+
* **IMPORTANT**: Use `as const` on the collections array to preserve
|
|
22
|
+
* literal types for full type inference.
|
|
23
|
+
*
|
|
24
|
+
* @param collections - Array of procedure collections (use `as const`)
|
|
25
|
+
* @param options - Optional RPC configuration
|
|
26
|
+
* @returns RpcResult with typed router and register function
|
|
27
|
+
*
|
|
28
|
+
* @example Basic usage with VeloxApp
|
|
29
|
+
* ```typescript
|
|
30
|
+
* import { veloxApp } from '@veloxts/core';
|
|
31
|
+
* import { rpc } from '@veloxts/router';
|
|
32
|
+
*
|
|
33
|
+
* const app = await veloxApp({ port: 3030 });
|
|
34
|
+
*
|
|
35
|
+
* const { router, register } = rpc([
|
|
36
|
+
* userProcedures,
|
|
37
|
+
* postProcedures,
|
|
38
|
+
* ] as const);
|
|
39
|
+
*
|
|
40
|
+
* // Register tRPC routes
|
|
41
|
+
* await register(app.server);
|
|
42
|
+
*
|
|
43
|
+
* // Export type for clients - fully typed!
|
|
44
|
+
* export type AppRouter = typeof router;
|
|
45
|
+
*
|
|
46
|
+
* await app.start();
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @example With custom prefix
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const { router, register } = rpc([userProcedures] as const, {
|
|
52
|
+
* prefix: '/api/trpc',
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* await register(app.server);
|
|
56
|
+
* export type AppRouter = typeof router;
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example Combined with REST
|
|
60
|
+
* ```typescript
|
|
61
|
+
* // Serve same procedures via both REST and tRPC
|
|
62
|
+
* const collections = [userProcedures, postProcedures] as const;
|
|
63
|
+
*
|
|
64
|
+
* // REST endpoints at /api/*
|
|
65
|
+
* app.routes(rest([...collections], { prefix: '/api' }));
|
|
66
|
+
*
|
|
67
|
+
* // tRPC endpoints at /trpc/*
|
|
68
|
+
* const { router, register } = rpc(collections);
|
|
69
|
+
* await register(app.server);
|
|
70
|
+
*
|
|
71
|
+
* export type AppRouter = typeof router;
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function rpc(collections, options = {}) {
|
|
75
|
+
const { prefix = '/trpc' } = options;
|
|
76
|
+
// Validate inputs
|
|
77
|
+
if (collections.length === 0) {
|
|
78
|
+
throw fail('VELOX-2006');
|
|
79
|
+
}
|
|
80
|
+
// Create the typed router immediately
|
|
81
|
+
const t = trpc();
|
|
82
|
+
const router = appRouter(t, collections);
|
|
83
|
+
// Create the registration function
|
|
84
|
+
const register = async (server) => {
|
|
85
|
+
await registerTRPCPlugin(server, {
|
|
86
|
+
prefix,
|
|
87
|
+
router,
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
router,
|
|
92
|
+
register,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Async Helper (Alternative API)
|
|
97
|
+
// ============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Register tRPC routes and return the typed router
|
|
100
|
+
*
|
|
101
|
+
* This is a convenience function that combines router creation and
|
|
102
|
+
* registration in a single async call. Use this when you don't need
|
|
103
|
+
* to separate router creation from registration.
|
|
104
|
+
*
|
|
105
|
+
* **IMPORTANT**: Use `as const` on the collections array to preserve
|
|
106
|
+
* literal types for full type inference.
|
|
107
|
+
*
|
|
108
|
+
* @param app - VeloxApp instance
|
|
109
|
+
* @param collections - Array of procedure collections (use `as const`)
|
|
110
|
+
* @param options - Optional RPC configuration
|
|
111
|
+
* @returns The typed tRPC router
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const router = await registerRpc(app, [
|
|
116
|
+
* userProcedures,
|
|
117
|
+
* postProcedures,
|
|
118
|
+
* ] as const);
|
|
119
|
+
*
|
|
120
|
+
* export type AppRouter = typeof router;
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
export async function registerRpc(app, collections, options = {}) {
|
|
124
|
+
const { router, register } = rpc(collections, options);
|
|
125
|
+
await register(app.server);
|
|
126
|
+
return router;
|
|
127
|
+
}
|