@veloxts/router 0.6.84 → 0.6.86

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.
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { initTRPC, TRPCError } from '@trpc/server';
10
10
  import { isGuardError } from '../errors.js';
11
+ import { executeMiddlewareChain } from '../middleware/chain.js';
11
12
  // ============================================================================
12
13
  // tRPC Initialization
13
14
  // ============================================================================
@@ -74,33 +75,23 @@ export function buildTRPCRouter(t, collection) {
74
75
  * @internal
75
76
  */
76
77
  function buildTRPCProcedure(t, procedure) {
77
- // Start with base procedure builder
78
- const baseProcedure = t.procedure;
79
- // Build the procedure chain based on configuration
80
- if (procedure.inputSchema && procedure.outputSchema) {
81
- // Both input and output schemas
82
- const withInput = baseProcedure.input(procedure.inputSchema);
83
- const withOutput = withInput.output(procedure.outputSchema);
84
- const handler = createHandler(procedure);
85
- return procedure.type === 'query' ? withOutput.query(handler) : withOutput.mutation(handler);
86
- }
78
+ // Build the procedure chain incrementally
79
+ // biome-ignore lint/suspicious/noExplicitAny: tRPC procedure builder has complex types that vary by chain state
80
+ let builder = t.procedure;
81
+ // Add input schema if present
87
82
  if (procedure.inputSchema) {
88
- // Only input schema
89
- const withInput = baseProcedure.input(procedure.inputSchema);
90
- const handler = createHandler(procedure);
91
- return procedure.type === 'query' ? withInput.query(handler) : withInput.mutation(handler);
83
+ builder = builder.input(procedure.inputSchema);
92
84
  }
85
+ // Add output schema if present
93
86
  if (procedure.outputSchema) {
94
- // Only output schema
95
- const withOutput = baseProcedure.output(procedure.outputSchema);
96
- const handler = createNoInputHandler(procedure);
97
- return procedure.type === 'query' ? withOutput.query(handler) : withOutput.mutation(handler);
87
+ builder = builder.output(procedure.outputSchema);
98
88
  }
99
- // No schemas - use base procedure
100
- const handler = createNoInputHandler(procedure);
101
- return procedure.type === 'query'
102
- ? baseProcedure.query(handler)
103
- : baseProcedure.mutation(handler);
89
+ // Select handler based on whether input is expected
90
+ const handler = procedure.inputSchema
91
+ ? createHandler(procedure)
92
+ : createNoInputHandler(procedure);
93
+ // Finalize as query or mutation
94
+ return procedure.type === 'query' ? builder.query(handler) : builder.mutation(handler);
104
95
  }
105
96
  /**
106
97
  * Create a handler function for a procedure with input
@@ -141,31 +132,7 @@ function createNoInputHandler(procedure) {
141
132
  * @internal
142
133
  */
143
134
  async function executeWithMiddleware(procedure, input, ctx) {
144
- // Build middleware chain from end to start
145
- let next = async () => {
146
- const output = await procedure.handler({ input, ctx });
147
- return { output };
148
- };
149
- // Wrap each middleware from last to first
150
- for (let i = procedure.middlewares.length - 1; i >= 0; i--) {
151
- const middleware = procedure.middlewares[i];
152
- const currentNext = next;
153
- next = async () => {
154
- return middleware({
155
- input,
156
- ctx,
157
- next: async (opts) => {
158
- // Allow middleware to extend context
159
- if (opts?.ctx) {
160
- Object.assign(ctx, opts.ctx);
161
- }
162
- return currentNext();
163
- },
164
- });
165
- };
166
- }
167
- const result = await next();
168
- return result.output;
135
+ return executeMiddlewareChain(procedure.middlewares, input, ctx, async () => procedure.handler({ input, ctx }));
169
136
  }
170
137
  // ============================================================================
171
138
  // App Router Creation
@@ -174,10 +141,11 @@ async function executeWithMiddleware(procedure, input, ctx) {
174
141
  * Create a namespaced app router from multiple procedure collections
175
142
  *
176
143
  * Each collection becomes a nested router under its namespace.
144
+ * Use `as const` on the collections array to preserve literal types.
177
145
  *
178
146
  * @param t - tRPC instance
179
- * @param collections - Array of procedure collections
180
- * @returns Merged app router
147
+ * @param collections - Array of procedure collections (use `as const` for best type inference)
148
+ * @returns Merged app router with preserved types
181
149
  *
182
150
  * @example
183
151
  * ```typescript
@@ -185,13 +153,13 @@ async function executeWithMiddleware(procedure, input, ctx) {
185
153
  * const router = appRouter(t, [
186
154
  * userProcedures, // namespace: 'users'
187
155
  * postProcedures, // namespace: 'posts'
188
- * ]);
156
+ * ] as const);
189
157
  *
190
158
  * // Usage:
191
159
  * // router.users.getUser({ id: '123' })
192
160
  * // router.posts.listPosts({ page: 1 })
193
161
  *
194
- * // Export type for client
162
+ * // Export type for client - fully typed!
195
163
  * export type AppRouter = typeof router;
196
164
  * ```
197
165
  */
@@ -239,9 +207,6 @@ export function createTRPCContextFactory() {
239
207
  return req.context;
240
208
  };
241
209
  }
242
- // ============================================================================
243
- // Error Utilities
244
- // ============================================================================
245
210
  /**
246
211
  * Convert a VeloxTS error to a tRPC error
247
212
  *
@@ -268,19 +233,25 @@ export function veloxErrorToTRPCError(error, defaultCode = 'INTERNAL_SERVER_ERRO
268
233
  const trpcCode = error.statusCode ? (statusToTRPC[error.statusCode] ?? defaultCode) : defaultCode;
269
234
  // Handle GuardError specifically to preserve guard metadata
270
235
  if (isGuardError(error)) {
236
+ const cause = {
237
+ source: 'velox',
238
+ code: error.code,
239
+ guardName: error.guardName,
240
+ };
271
241
  return new TRPCError({
272
242
  code: trpcCode,
273
243
  message: error.message,
274
- cause: {
275
- code: error.code,
276
- guardName: error.guardName,
277
- },
244
+ cause,
278
245
  });
279
246
  }
247
+ const cause = {
248
+ source: 'velox',
249
+ code: error.code,
250
+ };
280
251
  return new TRPCError({
281
252
  code: trpcCode,
282
253
  message: error.message,
283
- cause: error.code,
254
+ cause,
284
255
  });
285
256
  }
286
257
  /**
@@ -290,7 +261,8 @@ export function veloxErrorToTRPCError(error, defaultCode = 'INTERNAL_SERVER_ERRO
290
261
  * using veloxErrorToTRPCError().
291
262
  */
292
263
  export function isVeloxTRPCError(error) {
293
- return typeof error.cause === 'string';
264
+ const cause = error.cause;
265
+ return (cause != null && typeof cause === 'object' && 'source' in cause && cause.source === 'velox');
294
266
  }
295
267
  /**
296
268
  * Register tRPC plugin with Fastify server
@@ -3,5 +3,7 @@
3
3
  *
4
4
  * @module trpc
5
5
  */
6
- export type { AnyRouter, InferAppRouter, TRPCInstance, TRPCPluginOptions } from './adapter.js';
6
+ export type { AnyRouter,
7
+ /** @deprecated Use `TRPCRouter` instead */
8
+ AsTRPCRouter, CollectionsToRouterRecord, ExtractNamespace, ExtractProcedures, InferAppRouter, InferRouterFromCollections, MapProcedureRecordToTRPC, MapProcedureToTRPC, TRPCInstance, TRPCPluginOptions, TRPCRouter, } from './adapter.js';
7
9
  export { appRouter, buildTRPCRouter, createTRPCContextFactory, isVeloxTRPCError, registerTRPCPlugin, trpc, veloxErrorToTRPCError, } from './adapter.js';
package/dist/types.d.ts CHANGED
@@ -193,7 +193,7 @@ export interface RestRouteOverride {
193
193
  /**
194
194
  * Parent resource configuration for nested routes
195
195
  *
196
- * Defines the parent resource relationship for generating nested REST routes
196
+ * Defines a single parent resource relationship for generating nested REST routes
197
197
  * like `/posts/:postId/comments/:id`.
198
198
  *
199
199
  * @example
@@ -207,16 +207,39 @@ export interface RestRouteOverride {
207
207
  */
208
208
  export interface ParentResourceConfig {
209
209
  /**
210
- * Parent resource namespace (e.g., 'posts', 'users')
211
- * Used to build the parent path segment: `/${namespace}/:${paramName}`
210
+ * Parent resource name (e.g., 'posts', 'users')
211
+ * Used to build the parent path segment: `/${resource}/:${param}`
212
212
  */
213
- readonly namespace: string;
213
+ readonly resource: string;
214
214
  /**
215
215
  * Parent resource parameter name in the path
216
- * Defaults to `${singularNamespace}Id` if not specified
216
+ * Defaults to `${singularResource}Id` if not specified
217
217
  * (e.g., 'posts' -> 'postId', 'users' -> 'userId')
218
218
  */
219
- readonly paramName: string;
219
+ readonly param: string;
220
+ }
221
+ /**
222
+ * Multi-level parent resource configuration for deeply nested routes
223
+ *
224
+ * Defines multiple parent resources for generating deeply nested REST routes
225
+ * like `/organizations/:orgId/projects/:projectId/tasks/:id`.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * // Multi-level nesting
230
+ * procedure().parents([
231
+ * { resource: 'organizations', param: 'orgId' },
232
+ * { resource: 'projects', param: 'projectId' },
233
+ * ])
234
+ * // Generates: /organizations/:orgId/projects/:projectId/tasks/:id
235
+ * ```
236
+ */
237
+ export interface ParentResourceChain {
238
+ /**
239
+ * Array of parent resources from outermost to innermost
240
+ * E.g., [organizations, projects] for /organizations/:orgId/projects/:projectId/tasks
241
+ */
242
+ readonly parents: readonly ParentResourceConfig[];
220
243
  }
221
244
  /**
222
245
  * Compiled procedure with all metadata and handlers
@@ -248,8 +271,12 @@ export interface CompiledProcedure<TInput = unknown, TOutput = unknown, TContext
248
271
  readonly guards: ReadonlyArray<GuardLike<TContext>>;
249
272
  /** REST route override (if specified) */
250
273
  readonly restOverride?: RestRouteOverride;
274
+ /** Whether this procedure is deprecated */
275
+ readonly deprecated?: boolean;
276
+ /** Deprecation message explaining why and what to use instead */
277
+ readonly deprecationMessage?: string;
251
278
  /**
252
- * Parent resource configuration for nested routes
279
+ * Parent resource configuration for nested routes (single level)
253
280
  *
254
281
  * When specified, the REST path will be prefixed with the parent resource:
255
282
  * `/${parent.namespace}/:${parent.paramName}/${childNamespace}/:id`
@@ -262,6 +289,23 @@ export interface CompiledProcedure<TInput = unknown, TOutput = unknown, TContext
262
289
  * ```
263
290
  */
264
291
  readonly parentResource?: ParentResourceConfig;
292
+ /**
293
+ * Multi-level parent resource configuration for deeply nested routes
294
+ *
295
+ * When specified, the REST path will be prefixed with all parent resources:
296
+ * `/${parent1.namespace}/:${parent1.paramName}/${parent2.namespace}/:${parent2.paramName}/...`
297
+ *
298
+ * @example
299
+ * ```typescript
300
+ * // With parentResources: [
301
+ * // { namespace: 'organizations', paramName: 'orgId' },
302
+ * // { namespace: 'projects', paramName: 'projectId' }
303
+ * // ]
304
+ * // and namespace: 'tasks'
305
+ * // Generates: /organizations/:orgId/projects/:projectId/tasks/:id
306
+ * ```
307
+ */
308
+ readonly parentResources?: readonly ParentResourceConfig[];
265
309
  /**
266
310
  * Pre-compiled middleware chain executor
267
311
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/router",
3
- "version": "0.6.84",
3
+ "version": "0.6.86",
4
4
  "description": "Procedure definitions with tRPC and REST routing for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -40,8 +40,8 @@
40
40
  "@trpc/server": "11.8.0",
41
41
  "fastify": "5.6.2",
42
42
  "zod-to-json-schema": "3.24.5",
43
- "@veloxts/core": "0.6.84",
44
- "@veloxts/validation": "0.6.84"
43
+ "@veloxts/validation": "0.6.86",
44
+ "@veloxts/core": "0.6.86"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@vitest/coverage-v8": "4.0.16",