@veloxts/router 0.7.1 → 0.7.3

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,22 @@
1
1
  # @veloxts/router
2
2
 
3
+ ## 0.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - feat(cli): auto-populate Zod schemas from Prisma model fields
8
+ - Updated dependencies
9
+ - @veloxts/core@0.7.3
10
+ - @veloxts/validation@0.7.3
11
+
12
+ ## 0.7.2
13
+
14
+ ### Patch Changes
15
+
16
+ - simplify code for clarity and maintainability
17
+ - Updated dependencies
18
+ - @veloxts/core@0.7.2
19
+
3
20
  ## 0.7.1
4
21
 
5
22
  ### Patch Changes
package/GUIDE.md CHANGED
@@ -503,7 +503,7 @@ export const userProcedures = procedures('users', {
503
503
  .input(z.object({ id: z.string().uuid() }))
504
504
  .query(async ({ input, ctx }) => {
505
505
  const user = await ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
506
- return resource(user, UserSchema).forAnonymous();
506
+ return resource(user, UserSchema.public);
507
507
  }),
508
508
 
509
509
  // Conditional projection based on ownership
@@ -514,9 +514,9 @@ export const userProcedures = procedures('users', {
514
514
  const user = await ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
515
515
  // Show more fields if viewing own profile
516
516
  if (user.id === ctx.user?.id) {
517
- return resource(user, UserSchema).forAuthenticated();
517
+ return resource(user, UserSchema.authenticated);
518
518
  }
519
- return resource(user, UserSchema).forAnonymous();
519
+ return resource(user, UserSchema.public);
520
520
  }),
521
521
 
522
522
  // Admin with explicit projection
@@ -525,7 +525,7 @@ export const userProcedures = procedures('users', {
525
525
  .input(z.object({ id: z.string().uuid() }))
526
526
  .query(async ({ input, ctx }) => {
527
527
  const user = await ctx.db.user.findUniqueOrThrow({ where: { id: input.id } });
528
- return resource(user, UserSchema).forAdmin();
528
+ return resource(user, UserSchema.admin);
529
529
  }),
530
530
  });
531
531
  ```
@@ -554,7 +554,7 @@ const listUsersManual = procedure()
554
554
  .guard(authenticated)
555
555
  .query(async ({ ctx }) => {
556
556
  const users = await ctx.db.user.findMany({ take: 50 });
557
- return resourceCollection(users, UserSchema).forAuthenticated();
557
+ return resourceCollection(users, UserSchema.authenticated);
558
558
  });
559
559
  ```
560
560
 
@@ -577,9 +577,9 @@ const getUser = procedure()
577
577
 
578
578
  | Method | Fields Included | Use Case |
579
579
  |--------|-----------------|----------|
580
- | `.forAnonymous()` | `public` only | Public APIs, unauthenticated users |
581
- | `.forAuthenticated()` | `public` + `authenticated` | Logged-in users |
582
- | `.forAdmin()` | All fields | Admin dashboards, internal tools |
580
+ | `UserSchema.public` | `public` only | Public APIs, unauthenticated users |
581
+ | `UserSchema.authenticated` | `public` + `authenticated` | Logged-in users |
582
+ | `UserSchema.admin` | All fields | Admin dashboards, internal tools |
583
583
  | `.for(ctx)` | Auto-detected from context | Dynamic access control |
584
584
 
585
585
  ### Type Safety
@@ -596,14 +596,14 @@ const getProfile = procedure()
596
596
  });
597
597
  // Return type: { id: string; name: string; email: string; createdAt: Date }
598
598
 
599
- // Manual projection - type inferred from method
600
- const result = resource(user, UserSchema).forAnonymous();
599
+ // Tagged view projection - type inferred from schema view
600
+ const result = resource(user, UserSchema.public);
601
601
  // Type: { id: string; name: string }
602
602
 
603
- const authResult = resource(user, UserSchema).forAuthenticated();
603
+ const authResult = resource(user, UserSchema.authenticated);
604
604
  // Type: { id: string; name: string; email: string; createdAt: Date }
605
605
 
606
- const adminResult = resource(user, UserSchema).forAdmin();
606
+ const adminResult = resource(user, UserSchema.admin);
607
607
  // Type: { id: string; name: string; email: string; createdAt: Date; internalNotes: string | null; lastLoginIp: string | null }
608
608
  ```
609
609
 
@@ -612,8 +612,8 @@ const adminResult = resource(user, UserSchema).forAdmin();
612
612
  | Scenario | Approach |
613
613
  |----------|----------|
614
614
  | Guard determines access level | **Automatic** (`.guardNarrow().resource()`) |
615
- | Public endpoints (no guard) | Manual (`.forAnonymous()`) |
616
- | Conditional/dynamic projection | Manual (`.forX()` in handler) |
615
+ | Public endpoints (no guard) | Tagged view (`UserSchema.public`) |
616
+ | Conditional/dynamic projection | Tagged view or `.for(ctx)` in handler |
617
617
  | Simple, declarative code | **Automatic** |
618
618
  | Complex access logic | Manual
619
619
 
@@ -201,6 +201,42 @@ function hasProperties(schema) {
201
201
  // ============================================================================
202
202
  // Response Generation
203
203
  // ============================================================================
204
+ /**
205
+ * Creates a standard error response schema with the given example code
206
+ *
207
+ * @param exampleCode - Example error code (e.g., 'VALIDATION_ERROR', 'NOT_FOUND')
208
+ * @param extraProperties - Additional properties on the error object (e.g., details)
209
+ */
210
+ function createErrorSchema(exampleCode, extraProperties) {
211
+ return {
212
+ type: 'object',
213
+ properties: {
214
+ error: {
215
+ type: 'object',
216
+ properties: {
217
+ code: { type: 'string', example: exampleCode },
218
+ message: { type: 'string' },
219
+ ...extraProperties,
220
+ },
221
+ required: ['code', 'message'],
222
+ },
223
+ },
224
+ required: ['error'],
225
+ };
226
+ }
227
+ /**
228
+ * Creates a standard error response with the given description and example code
229
+ */
230
+ function createErrorResponse(description, exampleCode) {
231
+ return {
232
+ description,
233
+ content: {
234
+ 'application/json': {
235
+ schema: createErrorSchema(exampleCode),
236
+ },
237
+ },
238
+ };
239
+ }
204
240
  /**
205
241
  * Builds response definitions for an operation
206
242
  */
@@ -211,7 +247,6 @@ function buildResponses(method, outputSchema, hasAuth) {
211
247
  const successDescription = getSuccessDescription(method);
212
248
  // Success response
213
249
  if (successCode === '204') {
214
- // No content
215
250
  responses['204'] = { description: successDescription };
216
251
  }
217
252
  else {
@@ -224,120 +259,26 @@ function buildResponses(method, outputSchema, hasAuth) {
224
259
  }),
225
260
  };
226
261
  }
227
- // Error responses
262
+ // Validation error
228
263
  responses['400'] = {
229
264
  description: 'Bad Request - Validation error',
230
265
  content: {
231
266
  'application/json': {
232
- schema: {
233
- type: 'object',
234
- properties: {
235
- error: {
236
- type: 'object',
237
- properties: {
238
- code: { type: 'string', example: 'VALIDATION_ERROR' },
239
- message: { type: 'string' },
240
- details: { type: 'object' },
241
- },
242
- required: ['code', 'message'],
243
- },
244
- },
245
- required: ['error'],
246
- },
267
+ schema: createErrorSchema('VALIDATION_ERROR', { details: { type: 'object' } }),
247
268
  },
248
269
  },
249
270
  };
250
271
  // Auth-related responses
251
272
  if (hasAuth) {
252
- responses['401'] = {
253
- description: 'Unauthorized - Authentication required',
254
- content: {
255
- 'application/json': {
256
- schema: {
257
- type: 'object',
258
- properties: {
259
- error: {
260
- type: 'object',
261
- properties: {
262
- code: { type: 'string', example: 'UNAUTHORIZED' },
263
- message: { type: 'string' },
264
- },
265
- required: ['code', 'message'],
266
- },
267
- },
268
- required: ['error'],
269
- },
270
- },
271
- },
272
- };
273
- responses['403'] = {
274
- description: 'Forbidden - Insufficient permissions',
275
- content: {
276
- 'application/json': {
277
- schema: {
278
- type: 'object',
279
- properties: {
280
- error: {
281
- type: 'object',
282
- properties: {
283
- code: { type: 'string', example: 'FORBIDDEN' },
284
- message: { type: 'string' },
285
- },
286
- required: ['code', 'message'],
287
- },
288
- },
289
- required: ['error'],
290
- },
291
- },
292
- },
293
- };
273
+ responses['401'] = createErrorResponse('Unauthorized - Authentication required', 'UNAUTHORIZED');
274
+ responses['403'] = createErrorResponse('Forbidden - Insufficient permissions', 'FORBIDDEN');
294
275
  }
295
276
  // Not found for single-resource operations
296
277
  if (['GET', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
297
- responses['404'] = {
298
- description: 'Not Found - Resource does not exist',
299
- content: {
300
- 'application/json': {
301
- schema: {
302
- type: 'object',
303
- properties: {
304
- error: {
305
- type: 'object',
306
- properties: {
307
- code: { type: 'string', example: 'NOT_FOUND' },
308
- message: { type: 'string' },
309
- },
310
- required: ['code', 'message'],
311
- },
312
- },
313
- required: ['error'],
314
- },
315
- },
316
- },
317
- };
278
+ responses['404'] = createErrorResponse('Not Found - Resource does not exist', 'NOT_FOUND');
318
279
  }
319
280
  // Internal server error
320
- responses['500'] = {
321
- description: 'Internal Server Error',
322
- content: {
323
- 'application/json': {
324
- schema: {
325
- type: 'object',
326
- properties: {
327
- error: {
328
- type: 'object',
329
- properties: {
330
- code: { type: 'string', example: 'INTERNAL_ERROR' },
331
- message: { type: 'string' },
332
- },
333
- required: ['code', 'message'],
334
- },
335
- },
336
- required: ['error'],
337
- },
338
- },
339
- },
340
- };
281
+ responses['500'] = createErrorResponse('Internal Server Error', 'INTERNAL_ERROR');
341
282
  return responses;
342
283
  }
343
284
  /**
@@ -97,7 +97,7 @@ export function parsePathParameters(path, schemas) {
97
97
  * @returns True if path contains parameters
98
98
  */
99
99
  export function hasPathParameters(path) {
100
- return PATH_PARAM_REGEX.test(path);
100
+ return path.includes(':');
101
101
  }
102
102
  /**
103
103
  * Extracts query parameters from a JSON Schema
@@ -199,26 +199,6 @@ function createBuilder(state) {
199
199
  mutation(handler) {
200
200
  return compileProcedure('mutation', handler, state);
201
201
  },
202
- /**
203
- * Sets the output type based on a resource schema
204
- *
205
- * Accepts either a plain `ResourceSchema` or a tagged schema
206
- * (e.g., `UserSchema.authenticated`) for declarative auto-projection.
207
- *
208
- * @example
209
- * ```typescript
210
- * // Tagged schema — auto-projects to authenticated fields
211
- * procedure()
212
- * .guard(authenticated)
213
- * .resource(UserSchema.authenticated)
214
- * .query(handler);
215
- *
216
- * // Plain schema — defaults to public, or derives from guardNarrow
217
- * procedure()
218
- * .resource(UserSchema)
219
- * .query(handler);
220
- * ```
221
- */
222
202
  /**
223
203
  * Sets the output type based on a resource schema
224
204
  *
@@ -248,9 +228,7 @@ function compileProcedure(type, handler, state) {
248
228
  const typedMiddlewares = state.middlewares;
249
229
  // Pre-compile the middleware chain executor if middlewares exist
250
230
  // This avoids rebuilding the chain on every request
251
- const precompiledExecutor = typedMiddlewares.length > 0
252
- ? createPrecompiledMiddlewareExecutor(typedMiddlewares, handler)
253
- : undefined;
231
+ const precompiledExecutor = typedMiddlewares.length > 0 ? createMiddlewareExecutor(typedMiddlewares, handler) : undefined;
254
232
  // Create the final procedure object
255
233
  return {
256
234
  type,
@@ -272,18 +250,6 @@ function compileProcedure(type, handler, state) {
272
250
  _resourceLevel: state.resourceLevel,
273
251
  };
274
252
  }
275
- /**
276
- * Creates a pre-compiled middleware chain executor
277
- *
278
- * PERFORMANCE: This function builds the middleware chain once during procedure
279
- * compilation, creating a single reusable function that executes the entire chain.
280
- * This eliminates the need to rebuild closures on every request.
281
- *
282
- * @internal
283
- */
284
- function createPrecompiledMiddlewareExecutor(middlewares, handler) {
285
- return createMiddlewareExecutor(middlewares, handler);
286
- }
287
253
  /**
288
254
  * Defines a collection of procedures under a namespace
289
255
  *
@@ -477,7 +443,7 @@ export async function executeProcedure(procedure, rawInput, ctx) {
477
443
  }
478
444
  else {
479
445
  // Fallback: Build middleware chain dynamically (should not normally happen)
480
- result = await executeMiddlewareChainFallback(procedure.middlewares, input, ctxWithLevel, async () => procedure.handler({ input, ctx: ctxWithLevel }));
446
+ result = await executeMiddlewareChain(procedure.middlewares, input, ctxWithLevel, async () => procedure.handler({ input, ctx: ctxWithLevel }));
481
447
  }
482
448
  // Step 4: Auto-project if resource schema is set
483
449
  if (procedure._resourceSchema) {
@@ -508,17 +474,6 @@ export async function executeProcedure(procedure, rawInput, ctx) {
508
474
  }
509
475
  return result;
510
476
  }
511
- /**
512
- * Fallback middleware chain executor for edge cases
513
- *
514
- * This function is only used when _precompiledExecutor is not available,
515
- * which should be rare in normal operation.
516
- *
517
- * @internal
518
- */
519
- async function executeMiddlewareChainFallback(middlewares, input, ctx, handler) {
520
- return executeMiddlewareChain(middlewares, input, ctx, handler);
521
- }
522
477
  // ============================================================================
523
478
  // Type Utilities
524
479
  // ============================================================================
@@ -222,10 +222,8 @@ function gatherInput(request, route) {
222
222
  (route.procedure.parentResources !== undefined && route.procedure.parentResources.length > 0);
223
223
  switch (route.method) {
224
224
  case 'GET':
225
- // GET: params (for :id and all parent params) + query (for filters/pagination)
226
- return { ...params, ...query };
227
225
  case 'DELETE':
228
- // DELETE: params (for :id and all parent params) + query (for options), no body per REST conventions
226
+ // GET/DELETE: params (for :id and all parent params) + query (for filters/pagination/options)
229
227
  return { ...params, ...query };
230
228
  case 'PUT':
231
229
  case 'PATCH':
@@ -320,24 +318,7 @@ export function registerRestRoutes(server, collections, options = {}) {
320
318
  // When used in legacy mode, we prepend the prefix manually.
321
319
  const fullPath = _prefixHandledByFastify ? route.path : `${prefix}${route.path}`;
322
320
  const handler = createRouteHandler(route);
323
- // Register route based on method
324
- switch (route.method) {
325
- case 'GET':
326
- server.get(fullPath, handler);
327
- break;
328
- case 'POST':
329
- server.post(fullPath, handler);
330
- break;
331
- case 'PUT':
332
- server.put(fullPath, handler);
333
- break;
334
- case 'PATCH':
335
- server.patch(fullPath, handler);
336
- break;
337
- case 'DELETE':
338
- server.delete(fullPath, handler);
339
- break;
340
- }
321
+ server.route({ method: route.method, url: fullPath, handler });
341
322
  }
342
323
  }
343
324
  }
@@ -86,38 +86,22 @@ function buildTRPCProcedure(t, procedure) {
86
86
  if (procedure.outputSchema) {
87
87
  builder = builder.output(procedure.outputSchema);
88
88
  }
89
- // Select handler based on whether input is expected
90
- const handler = procedure.inputSchema
91
- ? createHandler(procedure)
92
- : createNoInputHandler(procedure);
89
+ // Create handler that works with or without input
90
+ const handler = createProcedureHandler(procedure);
93
91
  // Finalize as query or mutation
94
92
  return procedure.type === 'query' ? builder.query(handler) : builder.mutation(handler);
95
93
  }
96
94
  /**
97
- * Create a handler function for a procedure with input
95
+ * Create a tRPC handler function from a compiled procedure
98
96
  *
99
- * @internal
100
- */
101
- function createHandler(procedure) {
102
- return async (opts) => {
103
- const { input, ctx } = opts;
104
- // Execute middleware chain if any
105
- if (procedure.middlewares.length > 0) {
106
- return executeWithMiddleware(procedure, input, ctx);
107
- }
108
- // Direct handler execution
109
- return procedure.handler({ input, ctx });
110
- };
111
- }
112
- /**
113
- * Create a handler function for a procedure without input
97
+ * Works for both procedures with and without input schemas.
114
98
  *
115
99
  * @internal
116
100
  */
117
- function createNoInputHandler(procedure) {
101
+ function createProcedureHandler(procedure) {
118
102
  return async (opts) => {
119
103
  const { ctx } = opts;
120
- const input = undefined;
104
+ const input = opts.input ?? undefined;
121
105
  // Execute middleware chain if any
122
106
  if (procedure.middlewares.length > 0) {
123
107
  return executeWithMiddleware(procedure, input, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/router",
3
- "version": "0.7.1",
3
+ "version": "0.7.3",
4
4
  "description": "Procedure definitions with tRPC and REST routing for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,8 +39,8 @@
39
39
  "dependencies": {
40
40
  "@trpc/server": "11.10.0",
41
41
  "fastify": "5.7.4",
42
- "@veloxts/core": "0.7.1",
43
- "@veloxts/validation": "0.7.1"
42
+ "@veloxts/core": "0.7.3",
43
+ "@veloxts/validation": "0.7.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@vitest/coverage-v8": "4.0.18",