@jgamaraalv/ts-dev-kit 1.0.0
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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +24 -0
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +128 -0
- package/agents/accessibility-pro.md +139 -0
- package/agents/api-builder.md +110 -0
- package/agents/code-reviewer.md +190 -0
- package/agents/database-expert.md +138 -0
- package/agents/debugger.md +241 -0
- package/agents/docker-expert.md +51 -0
- package/agents/multi-agent-coordinator.md +378 -0
- package/agents/nextjs-expert.md +136 -0
- package/agents/performance-engineer.md +138 -0
- package/agents/playwright-expert.md +126 -0
- package/agents/react-specialist.md +97 -0
- package/agents/security-scanner.md +105 -0
- package/agents/test-generator.md +221 -0
- package/agents/typescript-pro.md +253 -0
- package/agents/ux-optimizer.md +93 -0
- package/docs/rules/orchestration.md.template +126 -0
- package/package.json +28 -0
- package/skills/bullmq/SKILL.md +225 -0
- package/skills/bullmq/references/flows-and-schedulers.md +186 -0
- package/skills/bullmq/references/job-types-and-options.md +163 -0
- package/skills/bullmq/references/patterns.md +273 -0
- package/skills/bullmq/references/production.md +308 -0
- package/skills/composition-patterns/SKILL.md +58 -0
- package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
- package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
- package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
- package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
- package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
- package/skills/composition-patterns/references/state-context-interface.md +194 -0
- package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
- package/skills/composition-patterns/references/state-lift-state.md +126 -0
- package/skills/conventional-commits/SKILL.md +148 -0
- package/skills/docker/SKILL.md +55 -0
- package/skills/docker/references/compose-configs.md +95 -0
- package/skills/docker/references/monorepo-dockerfile.md +111 -0
- package/skills/drizzle-pg/SKILL.md +202 -0
- package/skills/drizzle-pg/references/advanced.md +299 -0
- package/skills/drizzle-pg/references/migrations.md +214 -0
- package/skills/drizzle-pg/references/queries.md +321 -0
- package/skills/drizzle-pg/references/relations.md +272 -0
- package/skills/drizzle-pg/references/schema-pg.md +256 -0
- package/skills/drizzle-pg/references/sql-operator.md +215 -0
- package/skills/fastify-best-practices/SKILL.md +143 -0
- package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
- package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
- package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
- package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
- package/skills/fastify-best-practices/references/server-and-options.md +127 -0
- package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
- package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
- package/skills/ioredis/SKILL.md +51 -0
- package/skills/ioredis/references/advanced-patterns.md +312 -0
- package/skills/ioredis/references/cluster-sentinel.md +280 -0
- package/skills/ioredis/references/connection-options.md +187 -0
- package/skills/ioredis/references/core-api.md +179 -0
- package/skills/nextjs-best-practices/SKILL.md +194 -0
- package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
- package/skills/nextjs-best-practices/references/bundling.md +192 -0
- package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
- package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
- package/skills/nextjs-best-practices/references/directives.md +74 -0
- package/skills/nextjs-best-practices/references/error-handling.md +237 -0
- package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
- package/skills/nextjs-best-practices/references/font.md +175 -0
- package/skills/nextjs-best-practices/references/functions.md +116 -0
- package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
- package/skills/nextjs-best-practices/references/image.md +184 -0
- package/skills/nextjs-best-practices/references/metadata.md +305 -0
- package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
- package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
- package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
- package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
- package/skills/nextjs-best-practices/references/scripts.md +148 -0
- package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
- package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
- package/skills/owasp-security-review/SKILL.md +98 -0
- package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
- package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
- package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
- package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
- package/skills/owasp-security-review/references/a05-injection.md +106 -0
- package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
- package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
- package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
- package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
- package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
- package/skills/postgresql/SKILL.md +50 -0
- package/skills/postgresql/references/ddl-schema.md +300 -0
- package/skills/postgresql/references/indexes.md +257 -0
- package/skills/postgresql/references/jsonb.md +261 -0
- package/skills/postgresql/references/performance.md +291 -0
- package/skills/postgresql/references/psql-cli.md +153 -0
- package/skills/postgresql/references/queries.md +287 -0
- package/skills/postgresql/references/transactions.md +280 -0
- package/skills/react-best-practices/SKILL.md +110 -0
- package/skills/react-best-practices/references/advanced-patterns.md +91 -0
- package/skills/react-best-practices/references/async-patterns.md +233 -0
- package/skills/react-best-practices/references/bundle-optimization.md +201 -0
- package/skills/react-best-practices/references/client-patterns.md +178 -0
- package/skills/react-best-practices/references/js-performance.md +210 -0
- package/skills/react-best-practices/references/rendering-performance.md +209 -0
- package/skills/react-best-practices/references/rerender-optimization.md +316 -0
- package/skills/react-best-practices/references/server-performance.md +274 -0
- package/skills/service-worker/SKILL.md +195 -0
- package/skills/service-worker/references/api-reference.md +114 -0
- package/skills/service-worker/references/caching-strategies.md +202 -0
- package/skills/service-worker/references/push-and-sync.md +261 -0
- package/skills/typescript-conventions/SKILL.md +51 -0
- package/skills/ui-ux-guidelines/SKILL.md +105 -0
- package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
- package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
- package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Server Factory & Options
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Factory: fastify(options)](#factory-fastifyoptions)
|
|
6
|
+
- [Core options](#core-options)
|
|
7
|
+
- [Router options](#router-options)
|
|
8
|
+
- [Security options](#security-options)
|
|
9
|
+
- [Validation/serialization options](#validationserialization-options)
|
|
10
|
+
- [Advanced options](#advanced-options)
|
|
11
|
+
- [Key server methods](#key-server-methods)
|
|
12
|
+
- [Key properties](#key-properties)
|
|
13
|
+
- [Graceful shutdown pattern](#graceful-shutdown-pattern)
|
|
14
|
+
|
|
15
|
+
## Factory: `fastify(options)`
|
|
16
|
+
|
|
17
|
+
### Core options
|
|
18
|
+
|
|
19
|
+
| Option | Type | Default | Description |
|
|
20
|
+
| ----------------------- | --------------------- | --------------- | ------------------------------------------------ |
|
|
21
|
+
| `logger` | bool/object | `false` | Pino logger config. `true` enables with defaults |
|
|
22
|
+
| `loggerInstance` | object | — | Custom Pino-compatible logger instance |
|
|
23
|
+
| `disableRequestLogging` | bool/fn | `false` | Disable auto request/response logging |
|
|
24
|
+
| `trustProxy` | bool/string/number/fn | `false` | Trust X-Forwarded-\* headers |
|
|
25
|
+
| `bodyLimit` | number | `1048576` (1MB) | Max request body bytes |
|
|
26
|
+
| `maxParamLength` | number | 100 | Max URL parameter length |
|
|
27
|
+
| `pluginTimeout` | number | `10000` | Max ms for plugin load |
|
|
28
|
+
| `requestTimeout` | number | `0` | Max ms to receive full request |
|
|
29
|
+
| `connectionTimeout` | number | `0` | Socket timeout ms |
|
|
30
|
+
| `keepAliveTimeout` | number | `72000` | HTTP/1 keep-alive timeout ms |
|
|
31
|
+
| `requestIdHeader` | string | `'request-id'` | Header for request ID |
|
|
32
|
+
| `genReqId` | fn(rawReq) | auto-increment | Custom request ID generator |
|
|
33
|
+
|
|
34
|
+
### Router options
|
|
35
|
+
|
|
36
|
+
| Option | Type | Default | Description |
|
|
37
|
+
| ------------------------ | ---- | ------- | ------------------------------------ |
|
|
38
|
+
| `caseSensitive` | bool | `true` | Case-sensitive route matching |
|
|
39
|
+
| `ignoreTrailingSlash` | bool | `false` | Match `/foo` and `/foo/` |
|
|
40
|
+
| `ignoreDuplicateSlashes` | bool | `false` | Treat `//` as `/` |
|
|
41
|
+
| `exposeHeadRoutes` | bool | `true` | Auto-create HEAD for GET routes |
|
|
42
|
+
| `return503OnClosing` | bool | `true` | 503 for new requests during shutdown |
|
|
43
|
+
|
|
44
|
+
### Security options
|
|
45
|
+
|
|
46
|
+
| Option | Type | Default | Description |
|
|
47
|
+
| ------------------------ | ------ | --------- | ----------------------------------------------------------- |
|
|
48
|
+
| `onProtoPoisoning` | string | `'error'` | Handle `__proto__` in JSON: `'error'`/`'remove'`/`'ignore'` |
|
|
49
|
+
| `onConstructorPoisoning` | string | `'error'` | Handle `constructor` in JSON |
|
|
50
|
+
|
|
51
|
+
### Validation/serialization options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Default | Description |
|
|
54
|
+
| ---------------- | ------ | ------- | --------------------------------------- |
|
|
55
|
+
| `ajv` | object | — | `{ customOptions, plugins }` for Ajv v8 |
|
|
56
|
+
| `serializerOpts` | object | — | fast-json-stringify options |
|
|
57
|
+
|
|
58
|
+
### Advanced options
|
|
59
|
+
|
|
60
|
+
| Option | Type | Default | Description |
|
|
61
|
+
| ------------------- | ------- | ------- | ----------------------------- |
|
|
62
|
+
| `http2` | bool | `false` | Enable HTTP/2 |
|
|
63
|
+
| `https` | object | — | TLS options |
|
|
64
|
+
| `serverFactory` | fn | — | Custom HTTP server factory |
|
|
65
|
+
| `rewriteUrl` | fn(req) | — | Rewrite request URL |
|
|
66
|
+
| `querystringParser` | fn | — | Custom query string parser |
|
|
67
|
+
| `frameworkErrors` | fn | — | Handle framework-level errors |
|
|
68
|
+
|
|
69
|
+
## Key server methods
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
.listen({ port, host }) → Start server (returns Promise)
|
|
73
|
+
.close() → Graceful shutdown (returns Promise)
|
|
74
|
+
.ready() → Wait for plugins to load (returns Promise)
|
|
75
|
+
.inject(request) → Fake HTTP request for testing (returns Promise)
|
|
76
|
+
|
|
77
|
+
.register(plugin, opts?) → Register plugin (creates new scope)
|
|
78
|
+
.addHook(name, handler) → Add lifecycle hook
|
|
79
|
+
.route(options) → Add route (full form)
|
|
80
|
+
.get/post/put/delete/patch(...) → Add route (shorthand)
|
|
81
|
+
|
|
82
|
+
.decorate(name, value) → Decorate server instance
|
|
83
|
+
.decorateRequest(name, value) → Decorate Request prototype
|
|
84
|
+
.decorateReply(name, value) → Decorate Reply prototype
|
|
85
|
+
.hasDecorator(name) → Check if decorator exists
|
|
86
|
+
|
|
87
|
+
.addSchema({ $id, ... }) → Add reusable JSON schema
|
|
88
|
+
.getSchema(id) → Retrieve schema by $id
|
|
89
|
+
.getSchemas() → Get all schemas (encapsulation-aware)
|
|
90
|
+
|
|
91
|
+
.setErrorHandler(fn) → Set custom error handler
|
|
92
|
+
.setNotFoundHandler(fn) → Set custom 404 handler
|
|
93
|
+
.setValidatorCompiler(fn) → Set schema validator compiler
|
|
94
|
+
.setSerializerCompiler(fn) → Set response serializer compiler
|
|
95
|
+
|
|
96
|
+
.addContentTypeParser(type, fn) → Register custom content-type parser
|
|
97
|
+
.hasContentTypeParser(type) → Check if parser exists
|
|
98
|
+
|
|
99
|
+
.printRoutes() → Print route tree (debugging)
|
|
100
|
+
.printPlugins() → Print plugin tree (debugging)
|
|
101
|
+
.addresses() → Get listening addresses [{port, family, address}]
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Key properties
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
.server → Node.js HTTP/HTTPS server instance
|
|
108
|
+
.log → Pino logger instance
|
|
109
|
+
.prefix → Current route prefix (scoped to plugin)
|
|
110
|
+
.pluginName → Current plugin name (root = 'fastify')
|
|
111
|
+
.version → Fastify version string
|
|
112
|
+
.initialConfig → Frozen read-only initial options
|
|
113
|
+
.listeningOrigin → Current listening address string
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Graceful shutdown pattern
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const signals = ["SIGTERM", "SIGINT"];
|
|
120
|
+
for (const signal of signals) {
|
|
121
|
+
process.on(signal, async () => {
|
|
122
|
+
fastify.log.info(`Received ${signal}, shutting down...`);
|
|
123
|
+
await fastify.close();
|
|
124
|
+
process.exit(0);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
```
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# TypeScript & Logging
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [TypeScript Patterns](#typescript-patterns)
|
|
6
|
+
- [Route generics](#route-generics)
|
|
7
|
+
- [Type providers (Zod integration)](#type-providers-zod-integration)
|
|
8
|
+
- [Augmenting FastifyInstance (custom decorators)](#augmenting-fastifyinstance-custom-decorators)
|
|
9
|
+
- [Plugin typing](#plugin-typing)
|
|
10
|
+
- [Decorator typing gotchas](#decorator-typing-gotchas)
|
|
11
|
+
- [Import style (ESM)](#import-style-esm)
|
|
12
|
+
- [Logging with Pino](#logging-with-pino)
|
|
13
|
+
- [Basic setup](#basic-setup)
|
|
14
|
+
- [Development (pretty printing)](#development-pretty-printing)
|
|
15
|
+
- [Production recommendations](#production-recommendations)
|
|
16
|
+
- [Disable request logging](#disable-request-logging)
|
|
17
|
+
- [Request-scoped logging](#request-scoped-logging)
|
|
18
|
+
- [Custom request ID](#custom-request-id)
|
|
19
|
+
|
|
20
|
+
## TypeScript Patterns
|
|
21
|
+
|
|
22
|
+
## Route generics
|
|
23
|
+
|
|
24
|
+
Type request parts using generics:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
interface CreateUserBody {
|
|
28
|
+
email: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface UserParams {
|
|
33
|
+
id: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface UserQuery {
|
|
37
|
+
include?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fastify.post<{
|
|
41
|
+
Body: CreateUserBody;
|
|
42
|
+
Params: UserParams;
|
|
43
|
+
Querystring: UserQuery;
|
|
44
|
+
Headers: { "x-api-key": string };
|
|
45
|
+
}>("/users/:id", async (request) => {
|
|
46
|
+
request.body.email; // typed as string
|
|
47
|
+
request.params.id; // typed as string
|
|
48
|
+
request.query.include; // typed as string | undefined
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Type providers (Zod integration)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import Fastify from "fastify";
|
|
56
|
+
import { serializerCompiler, validatorCompiler, ZodTypeProvider } from "fastify-type-provider-zod";
|
|
57
|
+
import { z } from "zod";
|
|
58
|
+
|
|
59
|
+
const fastify = Fastify().withTypeProvider<ZodTypeProvider>();
|
|
60
|
+
fastify.setValidatorCompiler(validatorCompiler);
|
|
61
|
+
fastify.setSerializerCompiler(serializerCompiler);
|
|
62
|
+
|
|
63
|
+
fastify.post(
|
|
64
|
+
"/users",
|
|
65
|
+
{
|
|
66
|
+
schema: {
|
|
67
|
+
body: z.object({ email: z.string().email() }),
|
|
68
|
+
response: { 201: z.object({ id: z.string() }) },
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
async (request) => {
|
|
72
|
+
request.body.email; // inferred from Zod schema
|
|
73
|
+
return { id: "123" };
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Augmenting FastifyInstance (custom decorators)
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
declare module "fastify" {
|
|
82
|
+
interface FastifyInstance {
|
|
83
|
+
db: Pool;
|
|
84
|
+
redis: Redis;
|
|
85
|
+
}
|
|
86
|
+
interface FastifyRequest {
|
|
87
|
+
user?: { id: string; email: string };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Plugin typing
|
|
93
|
+
|
|
94
|
+
See [plugins-and-encapsulation.md](plugins-and-encapsulation.md) for the full `FastifyPluginCallback` and `FastifyPluginAsync` patterns.
|
|
95
|
+
|
|
96
|
+
**Quick reference for type annotations:**
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import type { FastifyPluginCallback, FastifyPluginAsync } from "fastify";
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Logging with Pino
|
|
103
|
+
|
|
104
|
+
## Pino logging configuration
|
|
105
|
+
|
|
106
|
+
### Basic setup
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const fastify = Fastify({
|
|
110
|
+
logger: true, // default Pino with info level
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Or with options:
|
|
114
|
+
const fastify = Fastify({
|
|
115
|
+
logger: {
|
|
116
|
+
level: "info",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Development (pretty printing)
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const fastify = Fastify({
|
|
125
|
+
logger: {
|
|
126
|
+
level: "debug",
|
|
127
|
+
transport: {
|
|
128
|
+
target: "pino-pretty",
|
|
129
|
+
options: {
|
|
130
|
+
translateTime: "HH:MM:ss Z",
|
|
131
|
+
ignore: "pid,hostname",
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Production recommendations
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const fastify = Fastify({
|
|
142
|
+
logger: {
|
|
143
|
+
level: process.env.LOG_LEVEL ?? "info",
|
|
144
|
+
// Redact sensitive data
|
|
145
|
+
redact: ["req.headers.authorization", "req.headers.cookie"],
|
|
146
|
+
serializers: {
|
|
147
|
+
req(request) {
|
|
148
|
+
return {
|
|
149
|
+
method: request.method,
|
|
150
|
+
url: request.url,
|
|
151
|
+
hostname: request.hostname,
|
|
152
|
+
remoteAddress: request.ip,
|
|
153
|
+
};
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Disable request logging
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const fastify = Fastify({
|
|
164
|
+
disableRequestLogging: true, // no auto req/res logs
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Or conditionally:
|
|
168
|
+
const fastify = Fastify({
|
|
169
|
+
disableRequestLogging: (req) => req.url === "/health",
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Request-scoped logging
|
|
174
|
+
|
|
175
|
+
Every request has `request.log` — a child logger with `reqId` bound:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
fastify.get("/", async (request) => {
|
|
179
|
+
request.log.info("processing request"); // includes reqId
|
|
180
|
+
request.log.info({ userId: 123 }, "user found"); // structured data
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Custom request ID
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const fastify = Fastify({
|
|
188
|
+
requestIdHeader: "x-request-id", // read from header
|
|
189
|
+
genReqId: (req) => crypto.randomUUID(), // or generate
|
|
190
|
+
requestIdLogLabel: "reqId", // log field name
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Decorator typing gotchas
|
|
195
|
+
|
|
196
|
+
**Wrong — shared reference:**
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
fastify.decorateRequest("data", {}); // ALL requests share same object!
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Right — null initial + onRequest assignment:**
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
fastify.decorateRequest("user", null);
|
|
206
|
+
|
|
207
|
+
fastify.addHook("onRequest", async (request) => {
|
|
208
|
+
request.user = await getUser(request); // unique per request
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Import style (ESM)
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
// Fastify:
|
|
216
|
+
import Fastify from "fastify";
|
|
217
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from "fastify";
|
|
218
|
+
|
|
219
|
+
// fastify-plugin:
|
|
220
|
+
import fp from "fastify-plugin";
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
> **ioredis**: See CLAUDE.md for the canonical ioredis import convention (`import { Redis } from "ioredis"`).
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Validation & Serialization
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Request validation (JSON Schema + Ajv v8)](#request-validation-json-schema--ajv-v8)
|
|
6
|
+
- [Ajv defaults](#ajv-defaults)
|
|
7
|
+
- [Customizing Ajv](#customizing-ajv)
|
|
8
|
+
- [Response serialization (fast-json-stringify)](#response-serialization-fast-json-stringify)
|
|
9
|
+
- [Schema reuse with $ref](#schema-reuse-with-ref)
|
|
10
|
+
- [Custom validator compiler (e.g., Zod)](#custom-validator-compiler-eg-zod)
|
|
11
|
+
- [Error formatting](#error-formatting)
|
|
12
|
+
- [Gotchas](#gotchas)
|
|
13
|
+
|
|
14
|
+
## Request validation (JSON Schema + Ajv v8)
|
|
15
|
+
|
|
16
|
+
Fastify validates request data against JSON Schema. Invalid requests get **400** automatically.
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
fastify.post("/users", {
|
|
20
|
+
schema: {
|
|
21
|
+
body: {
|
|
22
|
+
type: "object",
|
|
23
|
+
required: ["email"],
|
|
24
|
+
properties: {
|
|
25
|
+
email: { type: "string", format: "email" },
|
|
26
|
+
name: { type: "string", minLength: 1, maxLength: 100 },
|
|
27
|
+
age: { type: "integer", minimum: 0 },
|
|
28
|
+
},
|
|
29
|
+
additionalProperties: false,
|
|
30
|
+
},
|
|
31
|
+
querystring: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
page: { type: "integer", default: 1 },
|
|
35
|
+
limit: { type: "integer", default: 20, maximum: 100 },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
params: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
id: { type: "string", format: "uuid" },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
headers: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
"x-api-key": { type: "string" },
|
|
48
|
+
},
|
|
49
|
+
required: ["x-api-key"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
handler: async (request, reply) => { ... },
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Ajv defaults
|
|
57
|
+
|
|
58
|
+
Fastify configures Ajv with:
|
|
59
|
+
|
|
60
|
+
- `coerceTypes: true` — strings coerced to numbers/booleans for query/params
|
|
61
|
+
- `removeAdditional: true` — extra properties stripped
|
|
62
|
+
- `useDefaults: true` — missing properties get defaults
|
|
63
|
+
- `allErrors: false` — stops at first error (faster)
|
|
64
|
+
|
|
65
|
+
### Customizing Ajv
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const fastify = Fastify({
|
|
69
|
+
ajv: {
|
|
70
|
+
customOptions: {
|
|
71
|
+
allErrors: true, // return all errors
|
|
72
|
+
removeAdditional: "all", // strip all extra properties
|
|
73
|
+
},
|
|
74
|
+
plugins: [ajvFormats], // add format validators
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Response serialization (fast-json-stringify)
|
|
80
|
+
|
|
81
|
+
Define response schemas per status code. Fastify uses `fast-json-stringify` for 2-5x faster serialization than `JSON.stringify`, and it **prevents leaking fields** not in the schema.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
schema: {
|
|
85
|
+
response: {
|
|
86
|
+
200: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
id: { type: "string" },
|
|
90
|
+
email: { type: "string" },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
"4xx": {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
error: { type: "string" },
|
|
97
|
+
message: { type: "string" },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
default: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
error: { type: "string" },
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Schema reuse with $ref
|
|
111
|
+
|
|
112
|
+
**Register shared schemas:**
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
fastify.addSchema({
|
|
116
|
+
$id: "user",
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
id: { type: "string" },
|
|
120
|
+
email: { type: "string" },
|
|
121
|
+
name: { type: "string" },
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
fastify.addSchema({
|
|
126
|
+
$id: "pagination",
|
|
127
|
+
type: "object",
|
|
128
|
+
properties: {
|
|
129
|
+
page: { type: "integer", default: 1 },
|
|
130
|
+
limit: { type: "integer", default: 20 },
|
|
131
|
+
total: { type: "integer" },
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Reference in routes:**
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
schema: {
|
|
140
|
+
response: {
|
|
141
|
+
200: { $ref: "user#" }, // whole schema
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Or reference properties:
|
|
146
|
+
schema: {
|
|
147
|
+
body: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
user: { $ref: "user#" },
|
|
151
|
+
meta: { $ref: "pagination#" },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Custom validator compiler (e.g., Zod)
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import { serializerCompiler, validatorCompiler } from "fastify-type-provider-zod";
|
|
161
|
+
|
|
162
|
+
fastify.setValidatorCompiler(validatorCompiler);
|
|
163
|
+
fastify.setSerializerCompiler(serializerCompiler);
|
|
164
|
+
|
|
165
|
+
// Then in routes:
|
|
166
|
+
fastify.post("/users", {
|
|
167
|
+
schema: {
|
|
168
|
+
body: z.object({ email: z.string().email(), name: z.string() }),
|
|
169
|
+
response: { 201: z.object({ id: z.string(), email: z.string() }) },
|
|
170
|
+
},
|
|
171
|
+
handler: async (request) => { ... },
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Error formatting
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
fastify.setSchemaErrorFormatter((errors, dataVar) => {
|
|
179
|
+
const message = errors.map((e) => `${dataVar}${e.instancePath} ${e.message}`).join("; ");
|
|
180
|
+
return new Error(message);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Gotchas
|
|
185
|
+
|
|
186
|
+
- **No response schema = slow + leaky** — `JSON.stringify()` is used, and all object properties are included
|
|
187
|
+
- **`additionalProperties: false`** is important — without it, extra fields pass validation
|
|
188
|
+
- **Ajv formats** (`email`, `uuid`, `date-time`, etc.) require `ajv-formats` plugin
|
|
189
|
+
- **Schema compilation** happens at startup — errors are thrown during `.listen()`, not at runtime
|
|
190
|
+
- **Shared schemas** are encapsulation-aware — schemas in child plugins are invisible to parents unless using `fastify-plugin`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ioredis
|
|
3
|
+
description: "ioredis v5 reference for Node.js Redis client — connection setup, RedisOptions, pipelines, transactions, Pub/Sub, Lua scripting, Cluster, and Sentinel. Use when: (1) creating or configuring Redis connections (standalone, cluster, sentinel), (2) writing Redis commands with ioredis (get/set, pipelines, multi/exec), (3) setting up Pub/Sub or Streams, (4) configuring retryStrategy, TLS, or auto-pipelining, (5) working with Redis Cluster options (scaleReads, NAT mapping), or (6) debugging ioredis connection issues. Important: use named import `import { Redis } from 'ioredis'` for correct TypeScript types with NodeNext."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ioredis v5 — Node.js Redis Client
|
|
7
|
+
|
|
8
|
+
ioredis v5.x. Requires Node.js >= 12, Redis >= 2.6.12. 100% TypeScript.
|
|
9
|
+
|
|
10
|
+
## Critical: Import Style
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
// CORRECT — named import (required for NodeNext / moduleResolution: "nodenext")
|
|
14
|
+
import { Redis } from "ioredis";
|
|
15
|
+
|
|
16
|
+
// For Cluster:
|
|
17
|
+
import { Redis, Cluster } from "ioredis";
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## When to Load References
|
|
21
|
+
|
|
22
|
+
| Need | Reference file |
|
|
23
|
+
| ------------------------------------------------------------------------------ | -------------------------------------------------------------------- |
|
|
24
|
+
| Connection setup, RedisOptions, TLS, retryStrategy, lifecycle | [references/connection-options.md](references/connection-options.md) |
|
|
25
|
+
| Core API: pipelines, transactions, Pub/Sub, Lua scripting, scanning, events | [references/core-api.md](references/core-api.md) |
|
|
26
|
+
| Streams, auto-pipelining, transformers, binary data, error handling, debugging | [references/advanced-patterns.md](references/advanced-patterns.md) |
|
|
27
|
+
| Redis Cluster setup, ClusterOptions, Sentinel config, failover | [references/cluster-sentinel.md](references/cluster-sentinel.md) |
|
|
28
|
+
|
|
29
|
+
## Quick Reference
|
|
30
|
+
|
|
31
|
+
| Operation | Code |
|
|
32
|
+
| -------------- | -------------------------------------------------------------------------- |
|
|
33
|
+
| Connect | `new Redis()` or `new Redis(6379, "host")` or `new Redis("redis://...")` |
|
|
34
|
+
| Get/Set | `await redis.set("key", "val")` / `await redis.get("key")` |
|
|
35
|
+
| Pipeline | `await redis.pipeline().set("a","1").get("a").exec()` |
|
|
36
|
+
| Transaction | `await redis.multi().set("a","1").get("a").exec()` |
|
|
37
|
+
| Pub/Sub | `sub.subscribe("ch")` / `sub.on("message", cb)` / `pub.publish("ch", msg)` |
|
|
38
|
+
| Lua script | `redis.defineCommand("name", { numberOfKeys: 1, lua: "..." })` |
|
|
39
|
+
| Scan | `redis.scanStream({ match: "prefix:*", count: 100 })` |
|
|
40
|
+
| Graceful close | `await redis.quit()` |
|
|
41
|
+
| Force close | `redis.disconnect()` |
|
|
42
|
+
|
|
43
|
+
## Common Gotchas
|
|
44
|
+
|
|
45
|
+
1. **Named import**: Always `import { Redis } from "ioredis"` with NodeNext resolution
|
|
46
|
+
2. **Pub/Sub isolation**: A subscribed client cannot run other commands — use separate instances
|
|
47
|
+
3. **`maxRetriesPerRequest`**: Default is 20. Set to `null` for infinite retries (required by BullMQ)
|
|
48
|
+
4. **Pipeline errors**: `pipeline.exec()` never rejects — errors are in each result's `[0]` position
|
|
49
|
+
5. **`showFriendlyErrorStack`**: Performance cost — never enable in production
|
|
50
|
+
6. **Cluster pipelines**: All keys in a pipeline must hash to slots served by the same node
|
|
51
|
+
7. **`enableAutoPipelining`**: 35-50% throughput improvement, safe to enable globally
|