@veloxts/router 0.4.1 → 0.4.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/README.md +21 -674
- package/dist/discovery/errors.d.ts +65 -0
- package/dist/discovery/errors.d.ts.map +1 -0
- package/dist/discovery/errors.js +131 -0
- package/dist/discovery/errors.js.map +1 -0
- package/dist/discovery/index.d.ts +35 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +34 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/loader.d.ts +58 -0
- package/dist/discovery/loader.d.ts.map +1 -0
- package/dist/discovery/loader.js +377 -0
- package/dist/discovery/loader.js.map +1 -0
- package/dist/discovery/types.d.ts +102 -0
- package/dist/discovery/types.d.ts.map +1 -0
- package/dist/discovery/types.js +25 -0
- package/dist/discovery/types.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,705 +1,52 @@
|
|
|
1
1
|
# @veloxts/router
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
5
|
+
## What is this?
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Procedure-based routing package for the VeloxTS Framework, with hybrid tRPC and REST adapters for type-safe APIs.
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
332
|
-
};
|
|
11
|
+
This package is part of the VeloxTS Framework. For the complete framework experience, install:
|
|
333
12
|
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
17
|
+
Visit [@veloxts/velox](https://www.npmjs.com/package/@veloxts/velox) for the complete framework documentation.
|
|
396
18
|
|
|
397
|
-
|
|
19
|
+
## Standalone Installation
|
|
398
20
|
|
|
399
|
-
|
|
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
|
-
|
|
25
|
+
## Documentation
|
|
409
26
|
|
|
410
|
-
|
|
27
|
+
For detailed documentation, usage examples, and API reference, see [GUIDE.md](./GUIDE.md).
|
|
411
28
|
|
|
412
|
-
|
|
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 {
|
|
446
|
-
import { z
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|