@veloxts/router 0.4.1 → 0.4.2

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.
Files changed (2) hide show
  1. package/README.md +21 -674
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -1,705 +1,52 @@
1
1
  # @veloxts/router
2
2
 
3
- > **Alpha Release** - This framework is in early development. APIs may change between versions. Not recommended for production use yet.
3
+ **Pre-Alpha Notice:** This framework is in early development (v0.4.x). APIs are subject to change. Not recommended for production use.
4
4
 
5
- Procedure-based routing with hybrid tRPC and REST adapters for the VeloxTS framework.
5
+ ## What is this?
6
6
 
7
- ## Installation
7
+ Procedure-based routing package for the VeloxTS Framework, with hybrid tRPC and REST adapters for type-safe APIs.
8
8
 
9
- ```bash
10
- npm install @veloxts/router @veloxts/validation
11
- # or
12
- pnpm add @veloxts/router @veloxts/validation
13
- ```
14
-
15
- ## Quick Start
16
-
17
- ```typescript
18
- import { procedure, defineProcedures } from '@veloxts/router';
19
- import { z } from '@veloxts/validation';
20
-
21
- // Define procedures
22
- export const userProcedures = defineProcedures('users', {
23
- // GET /users/:id
24
- getUser: procedure()
25
- .input(z.object({ id: z.string().uuid() }))
26
- .output(UserSchema)
27
- .query(async ({ input, ctx }) => {
28
- return ctx.db.user.findUnique({ where: { id: input.id } });
29
- }),
30
-
31
- // POST /users
32
- createUser: procedure()
33
- .input(CreateUserSchema)
34
- .output(UserSchema)
35
- .mutation(async ({ input, ctx }) => {
36
- return ctx.db.user.create({ data: input });
37
- }),
38
- });
39
- ```
40
-
41
- ## Core Concepts
42
-
43
- ### Procedures
44
-
45
- A **procedure** is the fundamental abstraction in VeloxTS. It defines:
46
-
47
- 1. **Input schema** - What data the endpoint expects (validated with Zod)
48
- 2. **Output schema** - What data the endpoint returns (validated with Zod)
49
- 3. **Handler** - The business logic (query for reads, mutation for writes)
50
- 4. **Middleware** - Optional request processing (auth, logging, etc.)
51
-
52
- Procedures are type-safe from backend to frontend with zero code generation.
53
-
54
- ### Procedure Builder API
55
-
56
- The fluent builder pattern enables composable, type-safe endpoint definitions:
57
-
58
- ```typescript
59
- procedure()
60
- .input(schema) // Define input validation
61
- .output(schema) // Define output validation
62
- .use(middleware) // Add middleware (optional)
63
- .rest(config) // Override REST mapping (optional)
64
- .query(handler) // Define read handler (GET)
65
- // OR
66
- .mutation(handler) // Define write handler (POST/PUT/DELETE)
67
- ```
68
-
69
- ## Defining Procedures
70
-
71
- ### Query Procedures (Read Operations)
72
-
73
- Use `.query()` for operations that read data:
74
-
75
- ```typescript
76
- const userProcedures = defineProcedures('users', {
77
- // Get single user
78
- getUser: procedure()
79
- .input(z.object({ id: z.string().uuid() }))
80
- .output(UserSchema)
81
- .query(async ({ input, ctx }) => {
82
- const user = await ctx.db.user.findUnique({
83
- where: { id: input.id },
84
- });
85
-
86
- if (!user) {
87
- throw new NotFoundError('User', input.id);
88
- }
89
-
90
- return user;
91
- }),
92
-
93
- // List users with pagination
94
- listUsers: procedure()
95
- .input(paginationInputSchema.optional())
96
- .output(z.object({
97
- data: z.array(UserSchema),
98
- meta: z.object({
99
- page: z.number(),
100
- limit: z.number(),
101
- total: z.number(),
102
- }),
103
- }))
104
- .query(async ({ input, ctx }) => {
105
- const page = input?.page ?? 1;
106
- const limit = input?.limit ?? 10;
107
- const skip = (page - 1) * limit;
108
-
109
- const [data, total] = await Promise.all([
110
- ctx.db.user.findMany({ skip, take: limit }),
111
- ctx.db.user.count(),
112
- ]);
113
-
114
- return { data, meta: { page, limit, total } };
115
- }),
116
- });
117
- ```
118
-
119
- ### Mutation Procedures (Write Operations)
120
-
121
- Use `.mutation()` for operations that modify data:
122
-
123
- ```typescript
124
- const userProcedures = defineProcedures('users', {
125
- // Create user
126
- createUser: procedure()
127
- .input(z.object({
128
- name: z.string().min(1),
129
- email: z.string().email(),
130
- }))
131
- .output(UserSchema)
132
- .mutation(async ({ input, ctx }) => {
133
- return ctx.db.user.create({ data: input });
134
- }),
135
-
136
- // PUT /api/users/:id - Full update
137
- updateUser: procedure()
138
- .input(z.object({
139
- id: z.string().uuid(),
140
- name: z.string().min(1),
141
- email: z.string().email(),
142
- }))
143
- .output(UserSchema)
144
- .mutation(async ({ input, ctx }) => {
145
- const { id, ...data } = input;
146
- return ctx.db.user.update({
147
- where: { id },
148
- data,
149
- });
150
- }),
151
-
152
- // PATCH /api/users/:id - Partial update
153
- patchUser: procedure()
154
- .input(z.object({
155
- id: z.string().uuid(),
156
- name: z.string().min(1).optional(),
157
- email: z.string().email().optional(),
158
- }))
159
- .output(UserSchema)
160
- .mutation(async ({ input, ctx }) => {
161
- const { id, ...data } = input;
162
- return ctx.db.user.update({
163
- where: { id },
164
- data,
165
- });
166
- }),
167
-
168
- // DELETE /api/users/:id
169
- deleteUser: procedure()
170
- .input(z.object({ id: z.string().uuid() }))
171
- .output(z.object({ success: z.boolean() }))
172
- .mutation(async ({ input, ctx }) => {
173
- await ctx.db.user.delete({ where: { id: input.id } });
174
- return { success: true };
175
- }),
176
- });
177
- ```
178
-
179
- ## REST Endpoint Generation
180
-
181
- VeloxTS automatically generates REST endpoints from procedure names using **naming conventions**:
182
-
183
- ### Supported Naming Patterns
184
-
185
- | Procedure Name | HTTP Method | REST Path | Status Code | Notes |
186
- |---------------|-------------|-----------|-------------|-------|
187
- | `getUser` | GET | `/users/:id` | 200 | Single resource |
188
- | `listUsers` | GET | `/users` | 200 | Collection |
189
- | `findUsers` | GET | `/users` | 200 | Search/filter |
190
- | `createUser` | POST | `/users` | 201 | Create resource |
191
- | `addUser` | POST | `/users` | 201 | Create resource |
192
- | `updateUser` | PUT | `/users/:id` | 200 | Full update |
193
- | `editUser` | PUT | `/users/:id` | 200 | Full update |
194
- | `patchUser` | PATCH | `/users/:id` | 200 | Partial update |
195
- | `deleteUser` | DELETE | `/users/:id` | 200/204 | Delete resource |
196
- | `removeUser` | DELETE | `/users/:id` | 200/204 | Delete resource |
197
-
198
- ### Input Gathering
199
-
200
- Different HTTP methods gather inputs from different request parts:
201
-
202
- - **GET/DELETE**: `params` (route parameters) + `query` (query string)
203
- - **POST**: `body` only
204
- - **PUT/PATCH**: `params` (route parameters) + `body`
205
-
206
- ### Status Codes
207
-
208
- - **GET**: 200 OK
209
- - **POST**: 201 Created
210
- - **PUT/PATCH**: 200 OK
211
- - **DELETE**: 200 OK (with response body) or 204 No Content (if handler returns null)
212
-
213
- ### Custom REST Paths
214
-
215
- Override naming conventions when needed:
216
-
217
- ```typescript
218
- const userProcedures = defineProcedures('users', {
219
- // Custom path for search endpoint
220
- searchUsers: procedure()
221
- .rest({ method: 'GET', path: '/users/search' })
222
- .input(z.object({ q: z.string() }))
223
- .output(z.array(UserSchema))
224
- .query(async ({ input, ctx }) => {
225
- return ctx.db.user.findMany({
226
- where: {
227
- OR: [
228
- { name: { contains: input.q } },
229
- { email: { contains: input.q } },
230
- ],
231
- },
232
- });
233
- }),
234
-
235
- // Custom path for password reset
236
- resetPassword: procedure()
237
- .rest({ method: 'POST', path: '/auth/password-reset' })
238
- .input(z.object({ email: z.string().email() }))
239
- .output(z.object({ success: z.boolean() }))
240
- .mutation(async ({ input, ctx }) => {
241
- // Send password reset email
242
- return { success: true };
243
- }),
244
- });
245
- ```
246
-
247
- ## Registering Routes
248
-
249
- ### REST Adapter
250
-
251
- Register REST routes with your VeloxTS app:
252
-
253
- ```typescript
254
- import { createVeloxApp } from '@veloxts/core';
255
- import { registerRestRoutes } from '@veloxts/router';
256
- import { userProcedures } from './procedures/users';
257
-
258
- const app = await createVeloxApp({ port: 3210 });
259
-
260
- // Register REST routes
261
- await registerRestRoutes(app.server, {
262
- prefix: '/api',
263
- procedures: {
264
- users: userProcedures,
265
- },
266
- });
267
-
268
- await app.start();
269
- ```
270
-
271
- This generates the following endpoints:
272
-
273
- ```
274
- GET /api/users/:id (getUser)
275
- GET /api/users (listUsers)
276
- POST /api/users (createUser)
277
- GET /api/users/search (searchUsers)
278
- ```
279
-
280
- ### tRPC Adapter
281
-
282
- For type-safe internal API calls from your frontend:
283
-
284
- ```typescript
285
- import { createAppRouter, registerTRPCPlugin } from '@veloxts/router';
286
- import { userProcedures, postProcedures } from './procedures';
287
-
288
- // Create tRPC router
289
- const appRouter = createAppRouter({
290
- users: userProcedures,
291
- posts: postProcedures,
292
- });
293
-
294
- // Register tRPC plugin
295
- await registerTRPCPlugin(app.server, {
296
- router: appRouter,
297
- prefix: '/trpc',
298
- });
299
-
300
- // Export type for frontend
301
- export type AppRouter = typeof appRouter;
302
- ```
303
-
304
- Frontend usage:
305
-
306
- ```typescript
307
- import { createClient } from '@veloxts/client';
308
- import type { AppRouter } from '../server';
309
-
310
- const api = createClient<AppRouter>({ baseUrl: '/api' });
311
-
312
- // Fully typed calls
313
- const user = await api.users.getUser({ id: '123' });
314
- // ^? User (inferred from UserSchema)
315
- ```
316
-
317
- ## Middleware
318
-
319
- Add cross-cutting concerns with middleware:
320
-
321
- ```typescript
322
- const loggerMiddleware: MiddlewareFunction = async ({ ctx, next }) => {
323
- const start = Date.now();
324
- console.log(`[${ctx.request.method}] ${ctx.request.url}`);
325
-
326
- const result = await next();
327
-
328
- const duration = Date.now() - start;
329
- console.log(`[${ctx.request.method}] ${ctx.request.url} - ${duration}ms`);
9
+ ## Part of @veloxts/velox
330
10
 
331
- return result;
332
- };
11
+ This package is part of the VeloxTS Framework. For the complete framework experience, install:
333
12
 
334
- const userProcedures = defineProcedures('users', {
335
- getUser: procedure()
336
- .use(loggerMiddleware)
337
- .input(z.object({ id: z.string().uuid() }))
338
- .output(UserSchema)
339
- .query(async ({ input, ctx }) => {
340
- return ctx.db.user.findUnique({ where: { id: input.id } });
341
- }),
342
- });
343
- ```
344
-
345
- ## Type Inference
346
-
347
- VeloxTS automatically infers types throughout the procedure chain:
348
-
349
- ```typescript
350
- const userProcedures = defineProcedures('users', {
351
- getUser: procedure()
352
- .input(z.object({ id: z.string().uuid() }))
353
- .output(UserSchema)
354
- .query(async ({ input, ctx }) => {
355
- // input is typed as { id: string }
356
- // ^? { id: string }
357
-
358
- // ctx is typed as BaseContext (with extensions)
359
- // ^? BaseContext & { db: PrismaClient }
360
-
361
- // Return type must match UserSchema
362
- return ctx.db.user.findUnique({ where: { id: input.id } });
363
- // ^? User | null
364
- }),
365
- });
366
-
367
- // Frontend infers the complete type
368
- const user = await api.users.getUser({ id: '123' });
369
- // ^? User | null
370
- ```
371
-
372
- ### Type Helpers
373
-
374
- Extract types from procedures:
375
-
376
- ```typescript
377
- import type {
378
- InferProcedureInput,
379
- InferProcedureOutput,
380
- InferProcedures,
381
- } from '@veloxts/router';
382
-
383
- // Infer input type
384
- type GetUserInput = InferProcedureInput<typeof userProcedures.getUser>;
385
- // { id: string }
386
-
387
- // Infer output type
388
- type GetUserOutput = InferProcedureOutput<typeof userProcedures.getUser>;
389
- // User | null
390
-
391
- // Infer all procedures
392
- type UserProcedures = InferProcedures<typeof userProcedures>;
13
+ ```bash
14
+ npm install @veloxts/velox
393
15
  ```
394
16
 
395
- ## Context System
17
+ Visit [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox) for the complete framework documentation.
396
18
 
397
- Procedures receive a context object with request-scoped state:
19
+ ## Standalone Installation
398
20
 
399
- ### Base Context
400
-
401
- ```typescript
402
- interface BaseContext {
403
- request: FastifyRequest;
404
- reply: FastifyReply;
405
- }
21
+ ```bash
22
+ npm install @veloxts/router @veloxts/validation
406
23
  ```
407
24
 
408
- ### Extending Context
25
+ ## Documentation
409
26
 
410
- Plugins can extend context via declaration merging:
27
+ For detailed documentation, usage examples, and API reference, see [GUIDE.md](./GUIDE.md).
411
28
 
412
- ```typescript
413
- import type { PrismaClient } from '@prisma/client';
414
-
415
- declare module '@veloxts/core' {
416
- interface BaseContext {
417
- db: PrismaClient; // Added by @veloxts/orm
418
- user?: User; // Added by @veloxts/auth (v1.1+)
419
- }
420
- }
421
-
422
- // Now all procedures have ctx.db and ctx.user
423
- const userProcedures = defineProcedures('users', {
424
- getProfile: procedure()
425
- .output(UserSchema)
426
- .query(async ({ ctx }) => {
427
- // ctx.db is available (typed as PrismaClient)
428
- // ctx.user is available (typed as User | undefined)
429
- if (!ctx.user) {
430
- throw new UnauthorizedError('Must be logged in');
431
- }
432
-
433
- return ctx.db.user.findUnique({
434
- where: { id: ctx.user.id },
435
- });
436
- }),
437
- });
438
- ```
439
-
440
- ## Practical Examples
441
-
442
- ### Complete CRUD API
29
+ ## Quick Example
443
30
 
444
31
  ```typescript
445
- import { defineProcedures, procedure } from '@veloxts/router';
446
- import { z, paginationInputSchema } from '@veloxts/validation';
447
- import { NotFoundError } from '@veloxts/core';
448
-
449
- const UserSchema = z.object({
450
- id: z.string().uuid(),
451
- name: z.string(),
452
- email: z.string().email(),
453
- createdAt: z.string().datetime(),
454
- updatedAt: z.string().datetime(),
455
- });
456
-
457
- const CreateUserInput = z.object({
458
- name: z.string().min(1).max(100),
459
- email: z.string().email(),
460
- });
32
+ import { procedure, defineProcedures } from '@veloxts/router';
33
+ import { z } from '@veloxts/validation';
461
34
 
462
35
  export const userProcedures = defineProcedures('users', {
463
- // GET /users/:id
464
- getUser: procedure()
465
- .input(z.object({ id: z.string().uuid() }))
466
- .output(UserSchema.nullable())
467
- .query(async ({ input, ctx }) => {
468
- return ctx.db.user.findUnique({ where: { id: input.id } });
469
- }),
470
-
471
- // GET /users
472
- listUsers: procedure()
473
- .input(paginationInputSchema.optional())
474
- .output(z.object({
475
- data: z.array(UserSchema),
476
- meta: z.object({
477
- page: z.number(),
478
- limit: z.number(),
479
- total: z.number(),
480
- }),
481
- }))
482
- .query(async ({ input, ctx }) => {
483
- const page = input?.page ?? 1;
484
- const limit = input?.limit ?? 10;
485
- const skip = (page - 1) * limit;
486
-
487
- const [data, total] = await Promise.all([
488
- ctx.db.user.findMany({ skip, take: limit }),
489
- ctx.db.user.count(),
490
- ]);
491
-
492
- return { data, meta: { page, limit, total } };
493
- }),
494
-
495
- // POST /users
496
- createUser: procedure()
497
- .input(CreateUserInput)
498
- .output(UserSchema)
499
- .mutation(async ({ input, ctx }) => {
500
- return ctx.db.user.create({ data: input });
501
- }),
502
-
503
- // PUT /users/:id
504
- updateUser: procedure()
505
- .input(z.object({
506
- id: z.string().uuid(),
507
- name: z.string().min(1).max(100),
508
- email: z.string().email(),
509
- }))
510
- .output(UserSchema)
511
- .mutation(async ({ input, ctx }) => {
512
- const { id, ...data } = input;
513
- return ctx.db.user.update({ where: { id }, data });
514
- }),
515
-
516
- // DELETE /users/:id
517
- deleteUser: procedure()
518
- .input(z.object({ id: z.string().uuid() }))
519
- .output(z.object({ success: z.boolean() }))
520
- .mutation(async ({ input, ctx }) => {
521
- await ctx.db.user.delete({ where: { id: input.id } });
522
- return { success: true };
523
- }),
524
- });
525
- ```
526
-
527
- ### Search with Custom Path
528
-
529
- ```typescript
530
- const postProcedures = defineProcedures('posts', {
531
- // GET /posts/search?q=keyword&published=true
532
- searchPosts: procedure()
533
- .rest({ method: 'GET', path: '/posts/search' })
534
- .input(z.object({
535
- q: z.string().min(1),
536
- published: z.boolean().optional(),
537
- }))
538
- .output(z.array(PostSchema))
539
- .query(async ({ input, ctx }) => {
540
- return ctx.db.post.findMany({
541
- where: {
542
- AND: [
543
- {
544
- OR: [
545
- { title: { contains: input.q } },
546
- { content: { contains: input.q } },
547
- ],
548
- },
549
- input.published !== undefined
550
- ? { published: input.published }
551
- : {},
552
- ],
553
- },
554
- });
555
- }),
556
- });
557
- ```
558
-
559
- ### Authentication Middleware
560
-
561
- ```typescript
562
- const authMiddleware: MiddlewareFunction = async ({ ctx, next }) => {
563
- const token = ctx.request.headers.authorization?.replace('Bearer ', '');
564
-
565
- if (!token) {
566
- throw new UnauthorizedError('Missing authentication token');
567
- }
568
-
569
- // Verify token and attach user to context
570
- const user = await verifyToken(token);
571
- (ctx as ExtendedContext).user = user;
572
-
573
- return next();
574
- };
575
-
576
- const protectedProcedures = defineProcedures('protected', {
577
- getProfile: procedure()
578
- .use(authMiddleware)
579
- .output(UserSchema)
580
- .query(async ({ ctx }) => {
581
- // ctx.user is guaranteed to exist after authMiddleware
582
- const user = (ctx as ExtendedContext).user;
583
- return ctx.db.user.findUnique({ where: { id: user.id } });
584
- }),
585
- });
586
- ```
587
-
588
- ## Error Handling
589
-
590
- Procedures integrate with VeloxTS error classes:
591
-
592
- ```typescript
593
- import {
594
- VeloxError,
595
- NotFoundError,
596
- ValidationError,
597
- UnauthorizedError,
598
- } from '@veloxts/core';
599
-
600
- const userProcedures = defineProcedures('users', {
601
36
  getUser: procedure()
602
37
  .input(z.object({ id: z.string().uuid() }))
603
38
  .output(UserSchema)
604
39
  .query(async ({ input, ctx }) => {
605
- const user = await ctx.db.user.findUnique({
606
- where: { id: input.id },
607
- });
608
-
609
- if (!user) {
610
- // Returns 404 with structured error
611
- throw new NotFoundError('User', input.id);
612
- }
613
-
614
- return user;
615
- }),
616
-
617
- deleteUser: procedure()
618
- .input(z.object({ id: z.string().uuid() }))
619
- .output(z.object({ success: z.boolean() }))
620
- .mutation(async ({ input, ctx }) => {
621
- try {
622
- await ctx.db.user.delete({ where: { id: input.id } });
623
- return { success: true };
624
- } catch (error) {
625
- // Handle Prisma errors
626
- if (error.code === 'P2025') {
627
- throw new NotFoundError('User', input.id);
628
- }
629
- throw new VeloxError('Failed to delete user', 500);
630
- }
40
+ return ctx.db.user.findUnique({ where: { id: input.id } });
631
41
  }),
632
42
  });
633
43
  ```
634
44
 
635
- ## Configuration
636
-
637
- ### Route Summary
638
-
639
- Get a summary of generated routes:
640
-
641
- ```typescript
642
- import { getRouteSummary } from '@veloxts/router';
643
-
644
- const summary = getRouteSummary({
645
- users: userProcedures,
646
- posts: postProcedures,
647
- });
648
-
649
- console.log(summary);
650
- // [
651
- // { method: 'GET', path: '/users/:id', procedure: 'users.getUser' },
652
- // { method: 'GET', path: '/users', procedure: 'users.listUsers' },
653
- // { method: 'POST', path: '/users', procedure: 'users.createUser' },
654
- // { method: 'GET', path: '/posts/:id', procedure: 'posts.getPost' },
655
- // ...
656
- // ]
657
- ```
658
-
659
- ### Route Prefix
660
-
661
- Apply a common prefix to all routes:
662
-
663
- ```typescript
664
- await registerRestRoutes(app.server, {
665
- prefix: '/api/v1', // All routes start with /api/v1
666
- procedures: {
667
- users: userProcedures,
668
- },
669
- });
670
-
671
- // Generates: GET /api/v1/users/:id
672
- ```
673
-
674
- ## Current Features
675
-
676
- **Included:**
677
- - Query procedures (GET)
678
- - Mutation procedures (POST, PUT, PATCH, DELETE)
679
- - Full REST verb support with smart input gathering
680
- - Proper HTTP status codes (201, 204, etc.)
681
- - Input/output validation with Zod
682
- - Naming convention-based REST mapping
683
- - Custom REST path overrides
684
- - tRPC adapter for type-safe internal calls
685
- - Middleware support
686
-
687
- **Planned for Future Releases:**
688
- - Nested resource routing
689
- - OpenAPI/Swagger documentation generation
690
- - Rate limiting middleware
691
- - Request/response transformation hooks
692
-
693
- ## Related Packages
694
-
695
- - [@veloxts/core](/packages/core) - Core framework with context and errors
696
- - [@veloxts/validation](/packages/validation) - Zod schemas for input/output
697
- - [@veloxts/orm](/packages/orm) - Database integration (provides ctx.db)
698
- - [@veloxts/client](/packages/client) - Type-safe frontend API client
699
-
700
- ## TypeScript Support
45
+ ## Learn More
701
46
 
702
- All exports are fully typed with comprehensive JSDoc documentation. The package includes type definitions and declaration maps for excellent IDE support.
47
+ - [Full Documentation](./GUIDE.md)
48
+ - [VeloxTS Framework](https://www.npmjs.com/package/@veloxts/velox)
49
+ - [GitHub Repository](https://github.com/veloxts/velox-ts-framework)
703
50
 
704
51
  ## License
705
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veloxts/router",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Procedure definitions with tRPC and REST routing for VeloxTS framework",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -27,8 +27,8 @@
27
27
  "dependencies": {
28
28
  "@trpc/server": "11.7.2",
29
29
  "fastify": "5.6.2",
30
- "@veloxts/core": "0.4.1",
31
- "@veloxts/validation": "0.4.1"
30
+ "@veloxts/core": "0.4.2",
31
+ "@veloxts/validation": "0.4.2"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@vitest/coverage-v8": "4.0.15",