create-velox-app 0.6.63 → 0.6.65

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 (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/templates/auth.js +0 -8
  3. package/dist/templates/shared/root.d.ts +7 -0
  4. package/dist/templates/shared/root.js +32 -0
  5. package/dist/templates/spa.js +0 -8
  6. package/dist/templates/trpc.js +0 -8
  7. package/package.json +1 -1
  8. package/src/templates/source/api/package.auth.json +0 -1
  9. package/src/templates/source/api/package.default.json +0 -1
  10. package/src/templates/source/root/.claude/skills/veloxts/GENERATORS.md +313 -0
  11. package/src/templates/source/root/.claude/skills/veloxts/PROCEDURES.md +466 -0
  12. package/src/templates/source/root/.claude/skills/veloxts/SKILL.md +225 -0
  13. package/src/templates/source/root/.claude/skills/veloxts/TROUBLESHOOTING.md +416 -0
  14. package/src/templates/source/root/CLAUDE.auth.md +33 -1
  15. package/src/templates/source/root/CLAUDE.default.md +33 -1
  16. package/src/templates/source/root/CLAUDE.trpc.md +20 -0
  17. package/src/templates/source/root/package.json +4 -1
  18. package/src/templates/source/rsc/CLAUDE.md +19 -0
  19. package/src/templates/source/rsc/package.json +1 -0
  20. package/src/templates/source/rsc-auth/CLAUDE.md +19 -0
  21. package/src/templates/source/rsc-auth/package.json +1 -0
  22. package/src/templates/source/web/api.ts +4 -5
  23. package/src/templates/source/web/main.tsx +2 -2
  24. package/src/templates/source/web/routes/__root.tsx +1 -1
  25. package/src/templates/source/api/router.types.auth.ts +0 -88
  26. package/src/templates/source/api/router.types.default.ts +0 -73
  27. package/src/templates/source/api/router.types.trpc.ts +0 -73
  28. package/src/templates/source/api/routes.auth.ts +0 -66
  29. package/src/templates/source/api/routes.default.ts +0 -53
@@ -0,0 +1,466 @@
1
+ # VeloxTS Procedures API Reference
2
+
3
+ Procedures are the core abstraction for defining type-safe API endpoints. They combine validation, handlers, and routing in a fluent builder pattern.
4
+
5
+ ## Basic Structure
6
+
7
+ ```typescript
8
+ import { procedure, procedures, z } from '@veloxts/velox';
9
+
10
+ export const userProcedures = procedures('users', {
11
+ getUser: procedure()
12
+ .input(z.object({ id: z.string().uuid() }))
13
+ .output(UserSchema)
14
+ .query(async ({ input, ctx }) => {
15
+ return ctx.db.user.findUnique({ where: { id: input.id } });
16
+ }),
17
+
18
+ createUser: procedure()
19
+ .input(CreateUserSchema)
20
+ .output(UserSchema)
21
+ .mutation(async ({ input, ctx }) => {
22
+ return ctx.db.user.create({ data: input });
23
+ }),
24
+ });
25
+ ```
26
+
27
+ ## Procedure Methods
28
+
29
+ ### `.input(schema)`
30
+
31
+ Validates incoming request data with Zod.
32
+
33
+ ```typescript
34
+ .input(z.object({
35
+ id: z.string().uuid(),
36
+ email: z.string().email(),
37
+ age: z.number().min(0).max(150).optional(),
38
+ }))
39
+ ```
40
+
41
+ **Important**: Input is validated before the handler runs. Invalid input returns 400.
42
+
43
+ ### `.output(schema)`
44
+
45
+ Validates and types the response.
46
+
47
+ ```typescript
48
+ .output(z.object({
49
+ id: z.string(),
50
+ name: z.string(),
51
+ createdAt: z.coerce.date(),
52
+ }))
53
+ ```
54
+
55
+ **Tip**: Use `.output()` for type inference and runtime validation of responses.
56
+
57
+ ### `.query(handler)` vs `.mutation(handler)`
58
+
59
+ - **Query**: Read operations (GET requests)
60
+ - **Mutation**: Write operations (POST, PUT, PATCH, DELETE)
61
+
62
+ ```typescript
63
+ // Query - for reading data
64
+ listUsers: procedure()
65
+ .output(z.array(UserSchema))
66
+ .query(async ({ ctx }) => {
67
+ return ctx.db.user.findMany();
68
+ }),
69
+
70
+ // Mutation - for writing data
71
+ createUser: procedure()
72
+ .input(CreateUserSchema)
73
+ .output(UserSchema)
74
+ .mutation(async ({ input, ctx }) => {
75
+ return ctx.db.user.create({ data: input });
76
+ }),
77
+ ```
78
+
79
+ ### `.guard(guardFn)`
80
+
81
+ Protects procedures with authentication/authorization.
82
+
83
+ ```typescript
84
+ import { authenticated, hasRole, hasPermission } from '@veloxts/auth';
85
+
86
+ // Require authentication
87
+ getProfile: procedure()
88
+ .guard(authenticated)
89
+ .query(({ ctx }) => ctx.user),
90
+
91
+ // Require specific role
92
+ adminPanel: procedure()
93
+ .guard(hasRole('admin'))
94
+ .query(() => ({ admin: true })),
95
+
96
+ // Require permission
97
+ deletePost: procedure()
98
+ .guard(hasPermission('posts.delete'))
99
+ .input(z.object({ id: z.string() }))
100
+ .mutation(async ({ ctx, input }) => {
101
+ await ctx.db.post.delete({ where: { id: input.id } });
102
+ }),
103
+
104
+ // Custom guard
105
+ isOwner: procedure()
106
+ .guard(async ({ ctx, input }) => {
107
+ const post = await ctx.db.post.findUnique({ where: { id: input.id } });
108
+ if (post?.authorId !== ctx.user?.id) {
109
+ throw new Error('Not authorized');
110
+ }
111
+ })
112
+ .input(z.object({ id: z.string() }))
113
+ .mutation(async ({ ctx, input }) => { /* ... */ }),
114
+ ```
115
+
116
+ ### `.rest(options)`
117
+
118
+ Override automatic REST route inference.
119
+
120
+ ```typescript
121
+ // Custom path
122
+ publishPost: procedure()
123
+ .input(z.object({ id: z.string() }))
124
+ .rest({ method: 'POST', path: '/posts/:id/publish' })
125
+ .mutation(/* ... */),
126
+
127
+ // Custom method
128
+ archivePost: procedure()
129
+ .rest({ method: 'PATCH', path: '/posts/:id/archive' })
130
+ .mutation(/* ... */),
131
+ ```
132
+
133
+ **Warning**: Don't include `/api` prefix - it's added automatically.
134
+
135
+ ### `.use(middleware)`
136
+
137
+ Add procedure-specific middleware.
138
+
139
+ ```typescript
140
+ import { rateLimit, cache } from '@veloxts/velox';
141
+
142
+ // Rate limiting
143
+ createComment: procedure()
144
+ .use(rateLimit({ max: 10, window: '1m' }))
145
+ .input(CommentSchema)
146
+ .mutation(/* ... */),
147
+
148
+ // Caching
149
+ getPopularPosts: procedure()
150
+ .use(cache({ ttl: '5m' }))
151
+ .output(z.array(PostSchema))
152
+ .query(/* ... */),
153
+ ```
154
+
155
+ ## REST Route Inference
156
+
157
+ VeloxTS automatically maps procedure names to HTTP routes:
158
+
159
+ ### Query Procedures (GET)
160
+
161
+ | Prefix | HTTP | Route Pattern | Example |
162
+ |--------|------|---------------|---------|
163
+ | `get*` | GET | `/{namespace}/:id` | `getUser` → `GET /users/:id` |
164
+ | `list*` | GET | `/{namespace}` | `listUsers` → `GET /users` |
165
+ | `find*` | GET | `/{namespace}` | `findUsers` → `GET /users` |
166
+
167
+ ### Mutation Procedures
168
+
169
+ | Prefix | HTTP | Route Pattern | Example |
170
+ |--------|------|---------------|---------|
171
+ | `create*` | POST | `/{namespace}` (201) | `createUser` → `POST /users` |
172
+ | `add*` | POST | `/{namespace}` (201) | `addUser` → `POST /users` |
173
+ | `update*` | PUT | `/{namespace}/:id` | `updateUser` → `PUT /users/:id` |
174
+ | `edit*` | PUT | `/{namespace}/:id` | `editUser` → `PUT /users/:id` |
175
+ | `patch*` | PATCH | `/{namespace}/:id` | `patchUser` → `PATCH /users/:id` |
176
+ | `delete*` | DELETE | `/{namespace}/:id` | `deleteUser` → `DELETE /users/:id` |
177
+ | `remove*` | DELETE | `/{namespace}/:id` | `removeUser` → `DELETE /users/:id` |
178
+
179
+ ### React Query Hook Mapping
180
+
181
+ Naming also determines which React Query hooks are available:
182
+
183
+ **Query prefixes** (`get*`, `list*`, `find*`):
184
+ ```typescript
185
+ api.users.getUser.useQuery({ id })
186
+ api.users.listUsers.useSuspenseQuery({})
187
+ api.posts.findPosts.useQuery({ search: 'hello' })
188
+ ```
189
+
190
+ **Mutation prefixes** (everything else):
191
+ ```typescript
192
+ api.users.createUser.useMutation()
193
+ api.posts.updatePost.useMutation()
194
+ api.comments.deleteComment.useMutation()
195
+ ```
196
+
197
+ **Warning**: Non-standard names like `fetchUsers` are treated as mutations!
198
+
199
+ ## Context Object
200
+
201
+ The `ctx` parameter provides request-scoped data:
202
+
203
+ ```typescript
204
+ .query(async ({ input, ctx }) => {
205
+ // Database client
206
+ const user = await ctx.db.user.findUnique({ where: { id: input.id } });
207
+
208
+ // Current authenticated user (if using auth)
209
+ const currentUser = ctx.user;
210
+
211
+ // Raw Fastify request/reply
212
+ const ip = ctx.request.ip;
213
+ ctx.reply.header('X-Custom', 'value');
214
+
215
+ // Cache (if configured)
216
+ const cached = await ctx.cache.get('key');
217
+
218
+ // Queue (if configured)
219
+ await ctx.queue.dispatch(SendEmailJob, { to: user.email });
220
+ })
221
+ ```
222
+
223
+ ### Available Context Properties
224
+
225
+ | Property | Type | Description |
226
+ |----------|------|-------------|
227
+ | `ctx.db` | `PrismaClient` | Database client |
228
+ | `ctx.user` | `User \| undefined` | Authenticated user |
229
+ | `ctx.request` | `FastifyRequest` | Raw HTTP request |
230
+ | `ctx.reply` | `FastifyReply` | Raw HTTP response |
231
+ | `ctx.cache` | `CacheManager` | Cache operations (if enabled) |
232
+ | `ctx.queue` | `QueueManager` | Job queue (if enabled) |
233
+ | `ctx.storage` | `StorageManager` | File storage (if enabled) |
234
+
235
+ ## Validation Patterns
236
+
237
+ ### Input Schemas
238
+
239
+ ```typescript
240
+ // Required fields
241
+ const CreateUserInput = z.object({
242
+ email: z.string().email(),
243
+ name: z.string().min(1).max(255),
244
+ password: z.string().min(8),
245
+ });
246
+
247
+ // Optional fields with defaults
248
+ const ListUsersInput = z.object({
249
+ page: z.number().min(1).default(1),
250
+ limit: z.number().min(1).max(100).default(10),
251
+ search: z.string().optional(),
252
+ });
253
+
254
+ // Enum fields
255
+ const UpdateStatusInput = z.object({
256
+ status: z.enum(['draft', 'published', 'archived']),
257
+ });
258
+
259
+ // Nested objects
260
+ const CreateOrderInput = z.object({
261
+ items: z.array(z.object({
262
+ productId: z.string().uuid(),
263
+ quantity: z.number().min(1),
264
+ })).min(1),
265
+ shippingAddress: AddressSchema,
266
+ });
267
+ ```
268
+
269
+ ### Output Schemas
270
+
271
+ ```typescript
272
+ // Handle Prisma types
273
+ const UserSchema = z.object({
274
+ id: z.string().uuid(),
275
+ email: z.string(),
276
+ name: z.string(),
277
+ // Prisma returns Date objects
278
+ createdAt: z.coerce.date(),
279
+ updatedAt: z.coerce.date(),
280
+ // Prisma Decimal → number
281
+ balance: z.any().transform((val) => Number(val)),
282
+ });
283
+
284
+ // Nullable response
285
+ .output(UserSchema.nullable())
286
+
287
+ // Array response
288
+ .output(z.array(UserSchema))
289
+
290
+ // Paginated response
291
+ .output(z.object({
292
+ data: z.array(UserSchema),
293
+ meta: z.object({
294
+ page: z.number(),
295
+ limit: z.number(),
296
+ total: z.number(),
297
+ totalPages: z.number(),
298
+ }),
299
+ }))
300
+ ```
301
+
302
+ ## Error Handling
303
+
304
+ ### VeloxError
305
+
306
+ ```typescript
307
+ import { VeloxError } from '@veloxts/core';
308
+
309
+ getUser: procedure()
310
+ .input(z.object({ id: z.string().uuid() }))
311
+ .query(async ({ input, ctx }) => {
312
+ const user = await ctx.db.user.findUnique({ where: { id: input.id } });
313
+
314
+ if (!user) {
315
+ throw VeloxError.notFound('User', input.id);
316
+ }
317
+
318
+ return user;
319
+ }),
320
+ ```
321
+
322
+ ### Error Types
323
+
324
+ | Method | HTTP | Use Case |
325
+ |--------|------|----------|
326
+ | `VeloxError.notFound(entity, id)` | 404 | Resource not found |
327
+ | `VeloxError.unauthorized(message)` | 401 | Not authenticated |
328
+ | `VeloxError.forbidden(message)` | 403 | Not authorized |
329
+ | `VeloxError.badRequest(message)` | 400 | Invalid input |
330
+ | `VeloxError.conflict(message)` | 409 | Duplicate resource |
331
+ | `VeloxError.internal(message)` | 500 | Server error |
332
+
333
+ ## Complete Example
334
+
335
+ ```typescript
336
+ import { procedure, procedures, paginationInputSchema, z } from '@veloxts/velox';
337
+ import { authenticated, hasRole } from '@veloxts/auth';
338
+ import { VeloxError } from '@veloxts/core';
339
+
340
+ // Schemas
341
+ const PostSchema = z.object({
342
+ id: z.string().uuid(),
343
+ title: z.string(),
344
+ content: z.string(),
345
+ authorId: z.string().uuid(),
346
+ publishedAt: z.coerce.date().nullable(),
347
+ createdAt: z.coerce.date(),
348
+ updatedAt: z.coerce.date(),
349
+ });
350
+
351
+ const CreatePostInput = z.object({
352
+ title: z.string().min(1).max(255),
353
+ content: z.string().min(1),
354
+ });
355
+
356
+ const UpdatePostInput = CreatePostInput.partial();
357
+
358
+ // Procedures
359
+ export const postProcedures = procedures('posts', {
360
+ // Public: list published posts
361
+ listPosts: procedure()
362
+ .input(paginationInputSchema.optional())
363
+ .output(z.object({
364
+ data: z.array(PostSchema),
365
+ meta: z.object({
366
+ page: z.number(),
367
+ limit: z.number(),
368
+ total: z.number(),
369
+ totalPages: z.number(),
370
+ }),
371
+ }))
372
+ .query(async ({ input, ctx }) => {
373
+ const page = input?.page ?? 1;
374
+ const limit = input?.limit ?? 10;
375
+ const skip = (page - 1) * limit;
376
+
377
+ const where = { publishedAt: { not: null } };
378
+
379
+ const [data, total] = await Promise.all([
380
+ ctx.db.post.findMany({ where, skip, take: limit }),
381
+ ctx.db.post.count({ where }),
382
+ ]);
383
+
384
+ return {
385
+ data,
386
+ meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
387
+ };
388
+ }),
389
+
390
+ // Public: get single post
391
+ getPost: procedure()
392
+ .input(z.object({ id: z.string().uuid() }))
393
+ .output(PostSchema.nullable())
394
+ .query(async ({ input, ctx }) => {
395
+ return ctx.db.post.findUnique({ where: { id: input.id } });
396
+ }),
397
+
398
+ // Auth required: create post
399
+ createPost: procedure()
400
+ .guard(authenticated)
401
+ .input(CreatePostInput)
402
+ .output(PostSchema)
403
+ .mutation(async ({ input, ctx }) => {
404
+ return ctx.db.post.create({
405
+ data: {
406
+ ...input,
407
+ authorId: ctx.user!.id,
408
+ },
409
+ });
410
+ }),
411
+
412
+ // Auth required: update own post
413
+ updatePost: procedure()
414
+ .guard(authenticated)
415
+ .input(z.object({ id: z.string().uuid() }).merge(UpdatePostInput))
416
+ .output(PostSchema)
417
+ .mutation(async ({ input, ctx }) => {
418
+ const { id, ...data } = input;
419
+
420
+ const post = await ctx.db.post.findUnique({ where: { id } });
421
+
422
+ if (!post) {
423
+ throw VeloxError.notFound('Post', id);
424
+ }
425
+
426
+ if (post.authorId !== ctx.user!.id) {
427
+ throw VeloxError.forbidden('You can only edit your own posts');
428
+ }
429
+
430
+ return ctx.db.post.update({ where: { id }, data });
431
+ }),
432
+
433
+ // Admin only: delete any post
434
+ deletePost: procedure()
435
+ .guard(hasRole('admin'))
436
+ .input(z.object({ id: z.string().uuid() }))
437
+ .output(z.object({ success: z.boolean() }))
438
+ .mutation(async ({ input, ctx }) => {
439
+ await ctx.db.post.delete({ where: { id: input.id } });
440
+ return { success: true };
441
+ }),
442
+
443
+ // Custom route: publish post
444
+ publishPost: procedure()
445
+ .guard(authenticated)
446
+ .input(z.object({ id: z.string().uuid() }))
447
+ .output(PostSchema)
448
+ .rest({ method: 'POST', path: '/posts/:id/publish' })
449
+ .mutation(async ({ input, ctx }) => {
450
+ const post = await ctx.db.post.findUnique({ where: { id: input.id } });
451
+
452
+ if (!post) {
453
+ throw VeloxError.notFound('Post', input.id);
454
+ }
455
+
456
+ if (post.authorId !== ctx.user!.id) {
457
+ throw VeloxError.forbidden('You can only publish your own posts');
458
+ }
459
+
460
+ return ctx.db.post.update({
461
+ where: { id: input.id },
462
+ data: { publishedAt: new Date() },
463
+ });
464
+ }),
465
+ });
466
+ ```
@@ -0,0 +1,225 @@
1
+ ---
2
+ name: veloxts
3
+ description: VeloxTS framework assistant for building full-stack TypeScript APIs. Helps with procedures, generators (velox make), REST routes, authentication, validation, and common errors. Use when creating endpoints, adding features, debugging issues, or learning VeloxTS patterns.
4
+ ---
5
+
6
+ # VeloxTS Development Assistant
7
+
8
+ I help you build type-safe full-stack applications with VeloxTS. Ask me about:
9
+
10
+ - Creating API endpoints (procedures)
11
+ - Generating code (`velox make resource`, `namespace`, `procedure`)
12
+ - REST route inference from naming conventions
13
+ - Authentication and guards
14
+ - Validation with Zod schemas
15
+ - Troubleshooting common errors
16
+
17
+ ## Quick Decision: Which Generator?
18
+
19
+ **"I want to create a new database entity"**
20
+ ```bash
21
+ velox make resource Post # RECOMMENDED - creates everything
22
+ ```
23
+ Creates: Prisma model + Zod schema + Procedures + Tests + Auto-registers
24
+
25
+ **"I have an existing Prisma model"**
26
+ ```bash
27
+ velox make namespace Order # For existing models
28
+ ```
29
+ Creates: Zod schema + Procedures (no Prisma injection)
30
+
31
+ **"I need a single endpoint"**
32
+ ```bash
33
+ velox make procedure health # Quick single procedure
34
+ ```
35
+ Creates: Procedure file with inline schemas
36
+
37
+ See [GENERATORS.md](GENERATORS.md) for detailed guidance.
38
+
39
+ ## Procedure Naming = REST Routes
40
+
41
+ VeloxTS infers HTTP methods from procedure names:
42
+
43
+ | Name Pattern | HTTP | Route | Hook Type |
44
+ |--------------|------|-------|-----------|
45
+ | `getUser` | GET | `/users/:id` | `useQuery` |
46
+ | `listUsers` | GET | `/users` | `useQuery` |
47
+ | `findUsers` | GET | `/users` (search) | `useQuery` |
48
+ | `createUser` | POST | `/users` | `useMutation` |
49
+ | `updateUser` | PUT | `/users/:id` | `useMutation` |
50
+ | `patchUser` | PATCH | `/users/:id` | `useMutation` |
51
+ | `deleteUser` | DELETE | `/users/:id` | `useMutation` |
52
+
53
+ **Critical**: Non-standard names (like `fetchUsers`) are treated as mutations!
54
+
55
+ See [PROCEDURES.md](PROCEDURES.md) for the complete API reference.
56
+
57
+ ## Common Tasks
58
+
59
+ ### Create a New Resource
60
+
61
+ ```bash
62
+ # Full CRUD with pagination
63
+ velox make resource BlogPost --crud --paginated
64
+
65
+ # With soft delete
66
+ velox make resource Comment --soft-delete
67
+
68
+ # Interactive field definition
69
+ velox make resource Product -i
70
+ ```
71
+
72
+ ### Add Authentication
73
+
74
+ ```typescript
75
+ import { authenticated, hasRole } from '@veloxts/auth';
76
+
77
+ // Require login
78
+ getProfile: procedure()
79
+ .guard(authenticated)
80
+ .query(({ ctx }) => ctx.user),
81
+
82
+ // Require admin role
83
+ deleteUser: procedure()
84
+ .guard(hasRole('admin'))
85
+ .input(z.object({ id: z.string().uuid() }))
86
+ .mutation(async ({ ctx, input }) => {
87
+ await ctx.db.user.delete({ where: { id: input.id } });
88
+ return { success: true };
89
+ }),
90
+ ```
91
+
92
+ ### Add Pagination
93
+
94
+ ```typescript
95
+ import { paginationInputSchema } from '@veloxts/velox';
96
+
97
+ listPosts: procedure()
98
+ .input(paginationInputSchema.optional())
99
+ .output(z.object({
100
+ data: z.array(PostSchema),
101
+ meta: z.object({
102
+ page: z.number(),
103
+ limit: z.number(),
104
+ total: z.number(),
105
+ totalPages: z.number(),
106
+ }),
107
+ }))
108
+ .query(async ({ input, ctx }) => {
109
+ const page = input?.page ?? 1;
110
+ const limit = input?.limit ?? 10;
111
+ const skip = (page - 1) * limit;
112
+
113
+ const [data, total] = await Promise.all([
114
+ ctx.db.post.findMany({ skip, take: limit }),
115
+ ctx.db.post.count(),
116
+ ]);
117
+
118
+ return {
119
+ data,
120
+ meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
121
+ };
122
+ }),
123
+ ```
124
+
125
+ ### Custom REST Route
126
+
127
+ ```typescript
128
+ // Override automatic inference
129
+ publishPost: procedure()
130
+ .input(z.object({ id: z.string().uuid() }))
131
+ .output(PostSchema)
132
+ .rest({ method: 'POST', path: '/posts/:id/publish' }) // No /api prefix!
133
+ .mutation(async ({ ctx, input }) => {
134
+ return ctx.db.post.update({
135
+ where: { id: input.id },
136
+ data: { publishedAt: new Date() },
137
+ });
138
+ }),
139
+ ```
140
+
141
+ ## Troubleshooting
142
+
143
+ ### "useQuery is not a function"
144
+
145
+ Your procedure name doesn't follow query conventions:
146
+
147
+ ```typescript
148
+ // BAD - "fetchUsers" is not a query prefix
149
+ const { data } = api.users.fetchUsers.useQuery({});
150
+
151
+ // GOOD - "listUsers" is a query prefix
152
+ const { data } = api.users.listUsers.useQuery({});
153
+ ```
154
+
155
+ ### "procedure.input is not a function"
156
+
157
+ Missing parentheses after `procedure`:
158
+
159
+ ```typescript
160
+ // BAD
161
+ getUser: procedure.input(...)
162
+
163
+ // GOOD
164
+ getUser: procedure().input(...)
165
+ ```
166
+
167
+ ### Prisma Decimal validation fails
168
+
169
+ Use transforms for decimal fields:
170
+
171
+ ```typescript
172
+ // Input
173
+ price: z.coerce.number().positive()
174
+
175
+ // Output
176
+ price: z.any().transform((val) => Number(val))
177
+ ```
178
+
179
+ See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for more solutions.
180
+
181
+ ## Project Structure
182
+
183
+ ```
184
+ apps/
185
+ ├── api/ # Backend
186
+ │ ├── src/
187
+ │ │ ├── procedures/ # API endpoints (velox make procedure)
188
+ │ │ ├── schemas/ # Zod validation (velox make schema)
189
+ │ │ └── config/ # App configuration
190
+ │ └── prisma/
191
+ │ └── schema.prisma # Database schema
192
+
193
+ └── web/ # Frontend
194
+ └── src/
195
+ ├── routes/ # Pages
196
+ └── components/ # UI components
197
+ ```
198
+
199
+ ## CLI Quick Reference
200
+
201
+ ```bash
202
+ # Development
203
+ pnpm dev # Start API (3030) + Web (8080) with HMR
204
+ pnpm velox dev --verbose # API only with timing metrics
205
+
206
+ # Database
207
+ pnpm db:push # Apply schema changes
208
+ pnpm db:studio # Open Prisma Studio
209
+ pnpm velox migrate status # Check migration status
210
+
211
+ # Code Generation
212
+ pnpm velox make resource Post --crud # Full resource
213
+ pnpm velox make namespace Order # Namespace + schema
214
+ pnpm velox make procedure health # Single procedure
215
+
216
+ # Seeding
217
+ pnpm velox db seed # Run all seeders
218
+ pnpm velox db seed --fresh # Truncate + seed
219
+ ```
220
+
221
+ ## Detailed Guides
222
+
223
+ - [GENERATORS.md](GENERATORS.md) - Complete generator reference with decision tree
224
+ - [PROCEDURES.md](PROCEDURES.md) - Procedure API, guards, context, validation
225
+ - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Error messages and fixes