@martinffx/atelier 2.0.0 → 3.0.1
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 +21 -0
- package/README.md +94 -253
- package/package.json +1 -1
- package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +6 -6
- package/skills/spec-brainstorm/SKILL.md +5 -5
- package/skills/spec-implement/SKILL.md +2 -3
- package/skills/spec-plan/SKILL.md +21 -29
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- package/skills/typescript-testing/references/snapshot-testing.md +0 -774
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: typescript-fastify
|
|
3
|
-
description: Building REST APIs with Fastify in TypeScript. Use when creating routes, handling requests, implementing validation with TypeBox, structuring applications, or working with HTTP handlers and plugins.
|
|
4
|
-
user-invocable: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Fastify
|
|
8
|
-
|
|
9
|
-
Fast, low-overhead web framework for Node.js with TypeBox schema validation.
|
|
10
|
-
|
|
11
|
-
## Additional References
|
|
12
|
-
|
|
13
|
-
- [references/plugins.md](./references/plugins.md) - Plugin architecture and dependency injection
|
|
14
|
-
- [references/typeid.md](./references/typeid.md) - Type-safe prefixed identifiers
|
|
15
|
-
|
|
16
|
-
## Setup
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npm i fastify @fastify/type-provider-typebox @sinclair/typebox
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import Fastify from 'fastify'
|
|
24
|
-
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
|
25
|
-
|
|
26
|
-
const app = Fastify({ logger: true }).withTypeProvider<TypeBoxTypeProvider>()
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Schema Definition
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { Type, Static } from '@sinclair/typebox'
|
|
33
|
-
|
|
34
|
-
// Request/response schemas with $id for OpenAPI
|
|
35
|
-
export const UserSchema = Type.Object({
|
|
36
|
-
id: Type.String({ format: 'uuid' }),
|
|
37
|
-
name: Type.String({ minLength: 1, maxLength: 100 }),
|
|
38
|
-
email: Type.String({ format: 'email' }),
|
|
39
|
-
createdAt: Type.String({ format: 'date-time' }),
|
|
40
|
-
}, { $id: 'UserResponse' })
|
|
41
|
-
|
|
42
|
-
export type User = Static<typeof UserSchema>
|
|
43
|
-
|
|
44
|
-
// Input schemas (omit generated fields)
|
|
45
|
-
export const CreateUserSchema = Type.Object({
|
|
46
|
-
name: Type.String({ minLength: 1, maxLength: 100 }),
|
|
47
|
-
email: Type.String({ format: 'email' }),
|
|
48
|
-
}, { $id: 'CreateUserRequest' })
|
|
49
|
-
|
|
50
|
-
export type CreateUserInput = Static<typeof CreateUserSchema>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Route with Full Schema
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
const TAGS = ['Users']
|
|
57
|
-
|
|
58
|
-
app.post('/users', {
|
|
59
|
-
schema: {
|
|
60
|
-
operationId: 'createUser',
|
|
61
|
-
tags: TAGS,
|
|
62
|
-
summary: 'Create a new user',
|
|
63
|
-
description: 'Create a new user account',
|
|
64
|
-
body: CreateUserSchema,
|
|
65
|
-
response: {
|
|
66
|
-
201: UserSchema,
|
|
67
|
-
400: BadRequestErrorResponse,
|
|
68
|
-
401: UnauthorizedErrorResponse,
|
|
69
|
-
500: InternalServerErrorResponse,
|
|
70
|
-
},
|
|
71
|
-
},
|
|
72
|
-
}, async (request, reply) => {
|
|
73
|
-
const { name, email } = request.body // fully typed
|
|
74
|
-
|
|
75
|
-
const user = await createUser({ name, email })
|
|
76
|
-
return reply.status(201).send(user)
|
|
77
|
-
})
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Common Schema Patterns
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
// Path parameters
|
|
84
|
-
const ParamsSchema = Type.Object({
|
|
85
|
-
id: Type.String({ format: 'uuid' }),
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// Query string with pagination
|
|
89
|
-
const QuerySchema = Type.Object({
|
|
90
|
-
limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 100, default: 20 })),
|
|
91
|
-
cursor: Type.Optional(Type.String()),
|
|
92
|
-
sort: Type.Optional(Type.Union([Type.Literal('asc'), Type.Literal('desc')])),
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// Paginated response wrapper
|
|
96
|
-
const PaginatedResponse = <T extends TSchema>(itemSchema: T) =>
|
|
97
|
-
Type.Object({
|
|
98
|
-
items: Type.Array(itemSchema),
|
|
99
|
-
nextCursor: Type.Optional(Type.String()),
|
|
100
|
-
hasMore: Type.Boolean(),
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
app.get('/users/:id', {
|
|
104
|
-
schema: {
|
|
105
|
-
operationId: 'getUser',
|
|
106
|
-
tags: ['Users'],
|
|
107
|
-
summary: 'Get user by ID',
|
|
108
|
-
params: ParamsSchema,
|
|
109
|
-
querystring: QuerySchema,
|
|
110
|
-
response: {
|
|
111
|
-
200: UserSchema,
|
|
112
|
-
400: BadRequestErrorResponse,
|
|
113
|
-
404: NotFoundErrorResponse,
|
|
114
|
-
500: InternalServerErrorResponse,
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
}, async (request, reply) => {
|
|
118
|
-
const { id } = request.params
|
|
119
|
-
const { limit, cursor } = request.query
|
|
120
|
-
// ...
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
## Modular Route Registration
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
// types.ts - Export typed Fastify instance
|
|
128
|
-
import {
|
|
129
|
-
FastifyInstance,
|
|
130
|
-
FastifyBaseLogger,
|
|
131
|
-
RawReplyDefaultExpression,
|
|
132
|
-
RawRequestDefaultExpression,
|
|
133
|
-
RawServerDefault,
|
|
134
|
-
} from 'fastify'
|
|
135
|
-
import { TypeBoxTypeProvider } from '@fastify/type-provider-typebox'
|
|
136
|
-
|
|
137
|
-
export type FastifyTypebox = FastifyInstance<
|
|
138
|
-
RawServerDefault,
|
|
139
|
-
RawRequestDefaultExpression<RawServerDefault>,
|
|
140
|
-
RawReplyDefaultExpression<RawServerDefault>,
|
|
141
|
-
FastifyBaseLogger,
|
|
142
|
-
TypeBoxTypeProvider
|
|
143
|
-
>
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// routes/users.ts
|
|
148
|
-
import { Type } from '@sinclair/typebox'
|
|
149
|
-
import { FastifyTypebox } from '../types'
|
|
150
|
-
|
|
151
|
-
export async function userRoutes(app: FastifyTypebox) {
|
|
152
|
-
app.get('/users', {
|
|
153
|
-
schema: {
|
|
154
|
-
response: {
|
|
155
|
-
200: Type.Array(UserSchema),
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
}, async () => {
|
|
159
|
-
return await listUsers()
|
|
160
|
-
})
|
|
161
|
-
}
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
// index.ts
|
|
166
|
-
import { userRoutes } from './routes/users'
|
|
167
|
-
|
|
168
|
-
app.register(userRoutes, { prefix: '/api/v1' })
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## Error Schemas (RFC 7807)
|
|
172
|
-
|
|
173
|
-
Use standardized error responses across all routes:
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
import { Type } from '@sinclair/typebox'
|
|
177
|
-
|
|
178
|
-
// Base ProblemDetail schema (RFC 7807)
|
|
179
|
-
const ProblemDetail = Type.Object({
|
|
180
|
-
type: Type.String(),
|
|
181
|
-
status: Type.Number(),
|
|
182
|
-
title: Type.String(),
|
|
183
|
-
detail: Type.String(),
|
|
184
|
-
instance: Type.String(),
|
|
185
|
-
traceId: Type.String(),
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// Specific error responses
|
|
189
|
-
export const BadRequestErrorResponse = Type.Composite([
|
|
190
|
-
ProblemDetail,
|
|
191
|
-
Type.Object({
|
|
192
|
-
type: Type.Literal('BAD_REQUEST'),
|
|
193
|
-
status: Type.Literal(400),
|
|
194
|
-
}),
|
|
195
|
-
], { $id: 'BadRequestErrorResponse' })
|
|
196
|
-
|
|
197
|
-
export const UnauthorizedErrorResponse = Type.Composite([
|
|
198
|
-
ProblemDetail,
|
|
199
|
-
Type.Object({
|
|
200
|
-
type: Type.Literal('UNAUTHORIZED'),
|
|
201
|
-
status: Type.Literal(401),
|
|
202
|
-
}),
|
|
203
|
-
], { $id: 'UnauthorizedErrorResponse' })
|
|
204
|
-
|
|
205
|
-
export const ForbiddenErrorResponse = Type.Composite([
|
|
206
|
-
ProblemDetail,
|
|
207
|
-
Type.Object({
|
|
208
|
-
type: Type.Literal('FORBIDDEN'),
|
|
209
|
-
status: Type.Literal(403),
|
|
210
|
-
}),
|
|
211
|
-
], { $id: 'ForbiddenErrorResponse' })
|
|
212
|
-
|
|
213
|
-
export const NotFoundErrorResponse = Type.Composite([
|
|
214
|
-
ProblemDetail,
|
|
215
|
-
Type.Object({
|
|
216
|
-
type: Type.Literal('NOT_FOUND'),
|
|
217
|
-
status: Type.Literal(404),
|
|
218
|
-
}),
|
|
219
|
-
], { $id: 'NotFoundErrorResponse' })
|
|
220
|
-
|
|
221
|
-
export const InternalServerErrorResponse = Type.Composite([
|
|
222
|
-
ProblemDetail,
|
|
223
|
-
Type.Object({
|
|
224
|
-
type: Type.Literal('INTERNAL_SERVER_ERROR'),
|
|
225
|
-
status: Type.Literal(500),
|
|
226
|
-
}),
|
|
227
|
-
], { $id: 'InternalServerErrorResponse' })
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
## Error Handling
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
import { FastifyError, FastifyRequest, FastifyReply } from 'fastify'
|
|
234
|
-
|
|
235
|
-
// Custom error handler
|
|
236
|
-
const globalErrorHandler = (
|
|
237
|
-
error: FastifyError,
|
|
238
|
-
request: FastifyRequest,
|
|
239
|
-
reply: FastifyReply
|
|
240
|
-
) => {
|
|
241
|
-
// Handle Fastify validation errors
|
|
242
|
-
if (error.code === 'FST_ERR_VALIDATION') {
|
|
243
|
-
return reply.status(400).send({
|
|
244
|
-
type: 'BAD_REQUEST',
|
|
245
|
-
status: 400,
|
|
246
|
-
title: 'Validation Error',
|
|
247
|
-
detail: error.message,
|
|
248
|
-
instance: request.url,
|
|
249
|
-
traceId: request.id,
|
|
250
|
-
})
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Handle domain errors (if using error classes)
|
|
254
|
-
if (error instanceof AppError) {
|
|
255
|
-
return reply.status(error.status).send(error.toResponse())
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Default to internal server error
|
|
259
|
-
request.log.error(error)
|
|
260
|
-
return reply.status(500).send({
|
|
261
|
-
type: 'INTERNAL_SERVER_ERROR',
|
|
262
|
-
status: 500,
|
|
263
|
-
title: 'Internal Server Error',
|
|
264
|
-
detail: 'Something went wrong',
|
|
265
|
-
instance: request.url,
|
|
266
|
-
traceId: request.id,
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
app.setErrorHandler(globalErrorHandler)
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
## Reusable Schemas (Shared References)
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// Add schema to instance for $ref usage
|
|
277
|
-
app.addSchema({
|
|
278
|
-
$id: 'User',
|
|
279
|
-
...UserSchema,
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
app.addSchema({
|
|
283
|
-
$id: 'Error',
|
|
284
|
-
...ErrorSchema,
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
// Reference in routes
|
|
288
|
-
app.get('/me', {
|
|
289
|
-
schema: {
|
|
290
|
-
response: {
|
|
291
|
-
200: Type.Ref('User'),
|
|
292
|
-
401: Type.Ref('Error'),
|
|
293
|
-
},
|
|
294
|
-
},
|
|
295
|
-
}, handler)
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
## Headers and Auth
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
const AuthHeadersSchema = Type.Object({
|
|
302
|
-
authorization: Type.String({ pattern: '^Bearer .+$' }),
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
app.get('/protected', {
|
|
306
|
-
schema: {
|
|
307
|
-
headers: AuthHeadersSchema,
|
|
308
|
-
response: {
|
|
309
|
-
200: UserSchema,
|
|
310
|
-
401: UnauthorizedErrorResponse,
|
|
311
|
-
},
|
|
312
|
-
},
|
|
313
|
-
preValidation: async (request, reply) => {
|
|
314
|
-
const token = request.headers.authorization?.replace('Bearer ', '')
|
|
315
|
-
if (!token || !verifyToken(token)) {
|
|
316
|
-
throw new UnauthorizedError('Invalid or missing token')
|
|
317
|
-
}
|
|
318
|
-
},
|
|
319
|
-
}, handler)
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
## Auth & Permissions
|
|
323
|
-
|
|
324
|
-
Role-based permission checks with decorators:
|
|
325
|
-
|
|
326
|
-
```typescript
|
|
327
|
-
import type { FastifyRequest, FastifyReply } from 'fastify'
|
|
328
|
-
|
|
329
|
-
// Permission constants
|
|
330
|
-
const Permissions = [
|
|
331
|
-
'user:read',
|
|
332
|
-
'user:write',
|
|
333
|
-
'user:delete',
|
|
334
|
-
'admin:access',
|
|
335
|
-
] as const
|
|
336
|
-
|
|
337
|
-
type Permission = typeof Permissions[number]
|
|
338
|
-
|
|
339
|
-
// Role-based permission sets
|
|
340
|
-
const RolePermissions = {
|
|
341
|
-
admin: new Set<Permission>(['user:read', 'user:write', 'user:delete', 'admin:access']),
|
|
342
|
-
user: new Set<Permission>(['user:read', 'user:write']),
|
|
343
|
-
readonly: new Set<Permission>(['user:read']),
|
|
344
|
-
} as const
|
|
345
|
-
|
|
346
|
-
// Extend FastifyRequest with token data
|
|
347
|
-
declare module 'fastify' {
|
|
348
|
-
interface FastifyRequest {
|
|
349
|
-
token: {
|
|
350
|
-
userId: string
|
|
351
|
-
role: keyof typeof RolePermissions
|
|
352
|
-
permissions: Permission[]
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Permission check decorator
|
|
358
|
-
app.decorate('hasPermissions', (requiredPermissions: Permission[]) => {
|
|
359
|
-
return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
|
|
360
|
-
const userPermissions = request.token.permissions
|
|
361
|
-
|
|
362
|
-
for (const permission of requiredPermissions) {
|
|
363
|
-
if (!userPermissions.includes(permission)) {
|
|
364
|
-
throw new ForbiddenError(`Missing permission: ${permission}`)
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
// Usage in routes
|
|
371
|
-
app.delete('/users/:id', {
|
|
372
|
-
schema: {
|
|
373
|
-
operationId: 'deleteUser',
|
|
374
|
-
tags: ['Users'],
|
|
375
|
-
params: Type.Object({ id: Type.String() }),
|
|
376
|
-
response: {
|
|
377
|
-
204: Type.Null(),
|
|
378
|
-
401: UnauthorizedErrorResponse,
|
|
379
|
-
403: ForbiddenErrorResponse,
|
|
380
|
-
404: NotFoundErrorResponse,
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
preHandler: app.hasPermissions(['user:delete']),
|
|
384
|
-
}, async (request, reply) => {
|
|
385
|
-
await deleteUser(request.params.id)
|
|
386
|
-
return reply.status(204).send()
|
|
387
|
-
})
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
## Guidelines
|
|
391
|
-
|
|
392
|
-
1. Always define schemas with `Type.Object({ ... })` - full JSON Schema required in Fastify v5
|
|
393
|
-
2. Add `$id` to all schemas for OpenAPI generation and reusability
|
|
394
|
-
3. Add `operationId`, `tags`, and `summary` to all routes for documentation
|
|
395
|
-
4. Define response schemas for ALL status codes (200, 400, 401, 403, 404, 500)
|
|
396
|
-
5. Use RFC 7807 ProblemDetail format for errors with `Type.Composite`
|
|
397
|
-
6. Use `Static<typeof Schema>` to derive TypeScript types from schemas
|
|
398
|
-
7. Split input schemas (CreateX) from output schemas (X) - omit generated fields
|
|
399
|
-
8. Use `Type.Optional()` for optional fields, not `?` in the type
|
|
400
|
-
9. Export `FastifyTypebox` type for modular route files
|
|
401
|
-
10. Add format validators: `uuid`, `email`, `date-time`, `uri`
|
|
402
|
-
11. Use `Type.Union([Type.Literal(...)])` for string enums
|
|
403
|
-
12. Use Fastify plugins with `fp()` for dependency injection - see [references/plugins.md](./references/plugins.md)
|
|
404
|
-
13. Use `preHandler` with `hasPermissions()` decorator for protected routes
|
|
405
|
-
14. Use TypeID for type-safe prefixed identifiers - see [references/typeid.md](./references/typeid.md)
|