@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.
Files changed (117) hide show
  1. package/.claude-plugin/marketplace.json +24 -0
  2. package/.claude-plugin/plugin.json +24 -0
  3. package/CHANGELOG.md +24 -0
  4. package/LICENSE +21 -0
  5. package/README.md +128 -0
  6. package/agents/accessibility-pro.md +139 -0
  7. package/agents/api-builder.md +110 -0
  8. package/agents/code-reviewer.md +190 -0
  9. package/agents/database-expert.md +138 -0
  10. package/agents/debugger.md +241 -0
  11. package/agents/docker-expert.md +51 -0
  12. package/agents/multi-agent-coordinator.md +378 -0
  13. package/agents/nextjs-expert.md +136 -0
  14. package/agents/performance-engineer.md +138 -0
  15. package/agents/playwright-expert.md +126 -0
  16. package/agents/react-specialist.md +97 -0
  17. package/agents/security-scanner.md +105 -0
  18. package/agents/test-generator.md +221 -0
  19. package/agents/typescript-pro.md +253 -0
  20. package/agents/ux-optimizer.md +93 -0
  21. package/docs/rules/orchestration.md.template +126 -0
  22. package/package.json +28 -0
  23. package/skills/bullmq/SKILL.md +225 -0
  24. package/skills/bullmq/references/flows-and-schedulers.md +186 -0
  25. package/skills/bullmq/references/job-types-and-options.md +163 -0
  26. package/skills/bullmq/references/patterns.md +273 -0
  27. package/skills/bullmq/references/production.md +308 -0
  28. package/skills/composition-patterns/SKILL.md +58 -0
  29. package/skills/composition-patterns/references/architecture-avoid-boolean-props.md +87 -0
  30. package/skills/composition-patterns/references/architecture-compound-components.md +107 -0
  31. package/skills/composition-patterns/references/patterns-children-over-render-props.md +77 -0
  32. package/skills/composition-patterns/references/patterns-explicit-variants.md +87 -0
  33. package/skills/composition-patterns/references/react19-no-forwardref.md +37 -0
  34. package/skills/composition-patterns/references/state-context-interface.md +194 -0
  35. package/skills/composition-patterns/references/state-decouple-implementation.md +96 -0
  36. package/skills/composition-patterns/references/state-lift-state.md +126 -0
  37. package/skills/conventional-commits/SKILL.md +148 -0
  38. package/skills/docker/SKILL.md +55 -0
  39. package/skills/docker/references/compose-configs.md +95 -0
  40. package/skills/docker/references/monorepo-dockerfile.md +111 -0
  41. package/skills/drizzle-pg/SKILL.md +202 -0
  42. package/skills/drizzle-pg/references/advanced.md +299 -0
  43. package/skills/drizzle-pg/references/migrations.md +214 -0
  44. package/skills/drizzle-pg/references/queries.md +321 -0
  45. package/skills/drizzle-pg/references/relations.md +272 -0
  46. package/skills/drizzle-pg/references/schema-pg.md +256 -0
  47. package/skills/drizzle-pg/references/sql-operator.md +215 -0
  48. package/skills/fastify-best-practices/SKILL.md +143 -0
  49. package/skills/fastify-best-practices/references/hooks-and-lifecycle.md +122 -0
  50. package/skills/fastify-best-practices/references/plugins-and-encapsulation.md +137 -0
  51. package/skills/fastify-best-practices/references/request-reply-errors.md +189 -0
  52. package/skills/fastify-best-practices/references/routes-and-handlers.md +134 -0
  53. package/skills/fastify-best-practices/references/server-and-options.md +127 -0
  54. package/skills/fastify-best-practices/references/typescript-and-logging.md +223 -0
  55. package/skills/fastify-best-practices/references/validation-and-serialization.md +190 -0
  56. package/skills/ioredis/SKILL.md +51 -0
  57. package/skills/ioredis/references/advanced-patterns.md +312 -0
  58. package/skills/ioredis/references/cluster-sentinel.md +280 -0
  59. package/skills/ioredis/references/connection-options.md +187 -0
  60. package/skills/ioredis/references/core-api.md +179 -0
  61. package/skills/nextjs-best-practices/SKILL.md +194 -0
  62. package/skills/nextjs-best-practices/references/async-patterns.md +84 -0
  63. package/skills/nextjs-best-practices/references/bundling.md +192 -0
  64. package/skills/nextjs-best-practices/references/data-patterns.md +310 -0
  65. package/skills/nextjs-best-practices/references/debug-tricks.md +127 -0
  66. package/skills/nextjs-best-practices/references/directives.md +74 -0
  67. package/skills/nextjs-best-practices/references/error-handling.md +237 -0
  68. package/skills/nextjs-best-practices/references/file-conventions.md +152 -0
  69. package/skills/nextjs-best-practices/references/font.md +175 -0
  70. package/skills/nextjs-best-practices/references/functions.md +116 -0
  71. package/skills/nextjs-best-practices/references/hydration-error.md +86 -0
  72. package/skills/nextjs-best-practices/references/image.md +184 -0
  73. package/skills/nextjs-best-practices/references/metadata.md +305 -0
  74. package/skills/nextjs-best-practices/references/parallel-routes.md +299 -0
  75. package/skills/nextjs-best-practices/references/route-handlers.md +154 -0
  76. package/skills/nextjs-best-practices/references/rsc-boundaries.md +168 -0
  77. package/skills/nextjs-best-practices/references/runtime-selection.md +40 -0
  78. package/skills/nextjs-best-practices/references/scripts.md +148 -0
  79. package/skills/nextjs-best-practices/references/self-hosting.md +210 -0
  80. package/skills/nextjs-best-practices/references/suspense-boundaries.md +67 -0
  81. package/skills/owasp-security-review/SKILL.md +98 -0
  82. package/skills/owasp-security-review/references/a01-broken-access-control.md +78 -0
  83. package/skills/owasp-security-review/references/a02-security-misconfiguration.md +81 -0
  84. package/skills/owasp-security-review/references/a03-supply-chain-failures.md +65 -0
  85. package/skills/owasp-security-review/references/a04-cryptographic-failures.md +82 -0
  86. package/skills/owasp-security-review/references/a05-injection.md +106 -0
  87. package/skills/owasp-security-review/references/a06-insecure-design.md +76 -0
  88. package/skills/owasp-security-review/references/a07-authentication-failures.md +83 -0
  89. package/skills/owasp-security-review/references/a08-integrity-failures.md +72 -0
  90. package/skills/owasp-security-review/references/a09-logging-alerting-failures.md +76 -0
  91. package/skills/owasp-security-review/references/a10-exceptional-conditions.md +131 -0
  92. package/skills/postgresql/SKILL.md +50 -0
  93. package/skills/postgresql/references/ddl-schema.md +300 -0
  94. package/skills/postgresql/references/indexes.md +257 -0
  95. package/skills/postgresql/references/jsonb.md +261 -0
  96. package/skills/postgresql/references/performance.md +291 -0
  97. package/skills/postgresql/references/psql-cli.md +153 -0
  98. package/skills/postgresql/references/queries.md +287 -0
  99. package/skills/postgresql/references/transactions.md +280 -0
  100. package/skills/react-best-practices/SKILL.md +110 -0
  101. package/skills/react-best-practices/references/advanced-patterns.md +91 -0
  102. package/skills/react-best-practices/references/async-patterns.md +233 -0
  103. package/skills/react-best-practices/references/bundle-optimization.md +201 -0
  104. package/skills/react-best-practices/references/client-patterns.md +178 -0
  105. package/skills/react-best-practices/references/js-performance.md +210 -0
  106. package/skills/react-best-practices/references/rendering-performance.md +209 -0
  107. package/skills/react-best-practices/references/rerender-optimization.md +316 -0
  108. package/skills/react-best-practices/references/server-performance.md +274 -0
  109. package/skills/service-worker/SKILL.md +195 -0
  110. package/skills/service-worker/references/api-reference.md +114 -0
  111. package/skills/service-worker/references/caching-strategies.md +202 -0
  112. package/skills/service-worker/references/push-and-sync.md +261 -0
  113. package/skills/typescript-conventions/SKILL.md +51 -0
  114. package/skills/ui-ux-guidelines/SKILL.md +105 -0
  115. package/skills/ui-ux-guidelines/references/accessibility-and-interaction.md +74 -0
  116. package/skills/ui-ux-guidelines/references/forms-content-checklist.md +126 -0
  117. package/skills/ui-ux-guidelines/references/layout-typography-animation.md +95 -0
@@ -0,0 +1,256 @@
1
+ # PostgreSQL Schema Reference
2
+
3
+ ## Table of Contents
4
+
5
+ - [Column Types](#column-types)
6
+ - [Column Modifiers](#column-modifiers)
7
+ - [Enums](#enums)
8
+ - [Indexes](#indexes)
9
+ - [Constraints](#constraints)
10
+ - [Foreign Keys](#foreign-keys)
11
+ - [PostGIS](#postgis)
12
+ - [pg_vector](#pg_vector)
13
+
14
+ ## Column Types
15
+
16
+ All imports from `drizzle-orm/pg-core`.
17
+
18
+ ### Integer types
19
+
20
+ ```typescript
21
+ integer("age"); // 4-byte signed integer
22
+ smallint("small"); // 2-byte integer
23
+ bigint("big", { mode: "number" }); // 8-byte; mode: "number" | "bigint"
24
+ serial("id"); // auto-increment 4-byte (NOT NULL by default)
25
+ smallserial("small_id"); // auto-increment 2-byte
26
+ bigserial("big_id", { mode: "number" }); // auto-increment 8-byte
27
+ ```
28
+
29
+ ### Text types
30
+
31
+ ```typescript
32
+ text("bio"); // unlimited length
33
+ text("role", { enum: ["admin", "user"] }); // text with enum check
34
+ varchar("name", { length: 256 }); // variable length
35
+ char("code", { length: 3 }); // fixed length
36
+ ```
37
+
38
+ ### Numeric types
39
+
40
+ ```typescript
41
+ numeric("price", { precision: 10, scale: 2 }); // exact; mode: "string" (default) | "number" | "bigint"
42
+ real("score"); // float4
43
+ doublePrecision("precise"); // float8
44
+ ```
45
+
46
+ ### JSON types
47
+
48
+ ```typescript
49
+ json("data"); // textual JSON
50
+ jsonb("metadata"); // binary JSON (preferred for queries)
51
+ // Type-safe:
52
+ jsonb("config").$type<{ theme: string; lang: string }>();
53
+ ```
54
+
55
+ ### Date/time types
56
+
57
+ ```typescript
58
+ timestamp("created_at", { withTimezone: true, mode: "date" }); // mode: "date" | "string"
59
+ timestamp("updated_at", { precision: 3, withTimezone: true }).defaultNow();
60
+ date("birth_date", { mode: "date" }); // mode: "date" | "string"
61
+ time("start_time", { precision: 0 });
62
+ interval("duration", { fields: "day" });
63
+ ```
64
+
65
+ ### Special types
66
+
67
+ ```typescript
68
+ uuid("id").defaultRandom(); // UUID v4 default
69
+ boolean("active");
70
+ bytea("file_data"); // binary
71
+ point("location", { mode: "xy" }); // { x: number, y: number } or mode: "tuple" for [number, number]
72
+ line("path", { mode: "abc" }); // { a, b, c } or mode: "tuple"
73
+ ```
74
+
75
+ ### Identity columns (PG 10+)
76
+
77
+ ```typescript
78
+ integer("id").generatedAlwaysAsIdentity({ startWith: 1000 });
79
+ integer("id").generatedByDefaultAsIdentity(); // allows manual override
80
+ ```
81
+
82
+ ## Column Modifiers
83
+
84
+ ```typescript
85
+ column
86
+ .notNull() // NOT NULL
87
+ .primaryKey() // PRIMARY KEY
88
+ .default(42) // static default
89
+ .default(sql`gen_random_uuid()`) // SQL-level default
90
+ .defaultNow() // timestamp shortcut: default now()
91
+ .defaultRandom() // UUID shortcut: default gen_random_uuid()
92
+ .$defaultFn(() => cuid2()) // runtime JS default (insert-time)
93
+ .$onUpdateFn(() => new Date()) // runtime JS default (update-time)
94
+ .$type<MyType>() // override TS type (compile-time only)
95
+ .references(() => otherTable.id) // inline foreign key
96
+ .unique(); // UNIQUE constraint
97
+ ```
98
+
99
+ ## Enums
100
+
101
+ ```typescript
102
+ import { pgEnum } from "drizzle-orm/pg-core";
103
+
104
+ export const roleEnum = pgEnum("role", ["admin", "user", "moderator"]);
105
+
106
+ // Use in table:
107
+ export const users = pgTable("users", {
108
+ role: roleEnum().default("user").notNull(),
109
+ });
110
+ ```
111
+
112
+ ## Indexes
113
+
114
+ Defined in pgTable's third argument (callback returning array):
115
+
116
+ ```typescript
117
+ import { pgTable, index, uniqueIndex } from "drizzle-orm/pg-core";
118
+
119
+ export const users = pgTable(
120
+ "users",
121
+ {
122
+ id: serial("id").primaryKey(),
123
+ email: text("email").notNull(),
124
+ name: text("name"),
125
+ age: integer("age"),
126
+ },
127
+ (t) => [
128
+ // Single column
129
+ index("name_idx").on(t.name),
130
+
131
+ // Composite
132
+ index("name_age_idx").on(t.name, t.age),
133
+
134
+ // Unique index
135
+ uniqueIndex("email_idx").on(t.email),
136
+
137
+ // Partial index
138
+ index("active_idx")
139
+ .on(t.id)
140
+ .where(sql`${t.age} >= 18`),
141
+
142
+ // Expression index with btree
143
+ index("lower_name_idx").using("btree", sql`lower(${t.name})`),
144
+
145
+ // Advanced sorting
146
+ index("sorted_idx")
147
+ .on(t.name.asc(), t.age.desc().nullsFirst())
148
+ .concurrently()
149
+ .with({ fillfactor: "70" }),
150
+
151
+ // GiST index (for PostGIS/pg_vector)
152
+ index("geo_idx").using("gist", t.location),
153
+
154
+ // HNSW index (for pg_vector)
155
+ index("embedding_idx").using("hnsw", t.embedding.op("vector_cosine_ops")),
156
+ ],
157
+ );
158
+ ```
159
+
160
+ ## Constraints
161
+
162
+ ```typescript
163
+ import { pgTable, unique, check, primaryKey, foreignKey } from "drizzle-orm/pg-core";
164
+
165
+ export const table = pgTable(
166
+ "table",
167
+ {
168
+ id: serial("id").primaryKey(),
169
+ email: text("email").notNull().unique(),
170
+ age: integer("age"),
171
+ firstName: text("first_name"),
172
+ lastName: text("last_name"),
173
+ },
174
+ (t) => [
175
+ // Composite unique
176
+ unique("name_unique").on(t.firstName, t.lastName),
177
+
178
+ // Unique with NULLS NOT DISTINCT (PG 15+)
179
+ unique("email_unique").on(t.email).nullsNotDistinct(),
180
+
181
+ // Check constraint
182
+ check("age_positive", sql`${t.age} >= 0`),
183
+
184
+ // Composite primary key
185
+ primaryKey({ columns: [t.firstName, t.lastName] }),
186
+ ],
187
+ );
188
+ ```
189
+
190
+ ## Foreign Keys
191
+
192
+ ### Inline (single column)
193
+
194
+ ```typescript
195
+ integer("author_id").references(() => users.id);
196
+ integer("author_id").references(() => users.id, { onDelete: "cascade", onUpdate: "cascade" });
197
+ ```
198
+
199
+ ### Self-reference (requires AnyPgColumn)
200
+
201
+ ```typescript
202
+ import type { AnyPgColumn } from "drizzle-orm/pg-core";
203
+
204
+ integer("parent_id").references((): AnyPgColumn => categories.id);
205
+ ```
206
+
207
+ ### Multi-column (in third argument)
208
+
209
+ ```typescript
210
+ pgTable("profile", { firstName, lastName }, (t) => [
211
+ foreignKey({
212
+ columns: [t.firstName, t.lastName],
213
+ foreignColumns: [users.firstName, users.lastName],
214
+ name: "profile_user_fk",
215
+ })
216
+ .onDelete("cascade")
217
+ .onUpdate("cascade"),
218
+ ]);
219
+ ```
220
+
221
+ ### onDelete/onUpdate values
222
+
223
+ `"cascade"` | `"restrict"` | `"no action"` | `"set null"` | `"set default"`
224
+
225
+ ## PostGIS
226
+
227
+ ```typescript
228
+ import { geometry } from "drizzle-orm/pg-core";
229
+
230
+ const places = pgTable("places", {
231
+ location: geometry("location", { type: "point", mode: "xy", srid: 4326 }),
232
+ // mode: "xy" → { x: number, y: number }
233
+ // mode: "tuple" → [number, number]
234
+ });
235
+
236
+ // GiST index for spatial queries
237
+ pgTable("places", { location }, (t) => [index("geo_idx").using("gist", t.location)]);
238
+ ```
239
+
240
+ ## pg_vector
241
+
242
+ ```typescript
243
+ import { vector } from "drizzle-orm/pg-core";
244
+ import { l2Distance, cosineDistance, innerProduct } from "drizzle-orm";
245
+
246
+ const items = pgTable(
247
+ "items",
248
+ {
249
+ embedding: vector("embedding", { dimensions: 1536 }),
250
+ },
251
+ (t) => [index("embedding_idx").using("hnsw", t.embedding.op("vector_cosine_ops"))],
252
+ );
253
+
254
+ // Similarity search
255
+ await db.select().from(items).orderBy(cosineDistance(items.embedding, queryVector)).limit(10);
256
+ ```
@@ -0,0 +1,215 @@
1
+ # Magic sql`` Operator Reference
2
+
3
+ ## Table of Contents
4
+
5
+ - [Basic Usage](#basic-usage)
6
+ - [Type Annotations](#type-annotations)
7
+ - [sql.raw()](#sqlraw)
8
+ - [sql.empty() + append()](#sqlempty--append)
9
+ - [sql.join()](#sqljoin)
10
+ - [sql.identifier()](#sqlidentifier)
11
+ - [Placeholders](#placeholders)
12
+ - [Helpers: .as(), .mapWith()](#helpers)
13
+ - [Converting to String](#converting-to-string)
14
+ - [Usage in Queries](#usage-in-queries)
15
+
16
+ ## Basic Usage
17
+
18
+ The `sql` template literal auto-parameterizes values and escapes table/column references:
19
+
20
+ ```typescript
21
+ import { sql } from "drizzle-orm";
22
+
23
+ const id = 69;
24
+ await db.execute(sql`select * from ${usersTable} where ${usersTable.id} = ${id}`);
25
+ // → select * from "users" where "users"."id" = $1; params: [69]
26
+ ```
27
+
28
+ Table/column references (`${usersTable}`, `${usersTable.id}`) are escaped as identifiers.
29
+ Scalar values (`${id}`) become parameterized placeholders.
30
+
31
+ ## Type Annotations
32
+
33
+ Use `sql<T>` to annotate the return type:
34
+
35
+ ```typescript
36
+ const result = await db
37
+ .select({
38
+ lowerName: sql<string>`lower(${users.name})`,
39
+ count: sql<number>`count(*)`,
40
+ })
41
+ .from(users);
42
+ // result[0].lowerName → string
43
+ // result[0].count → number
44
+ ```
45
+
46
+ For nullable contexts (e.g., LEFT JOIN), explicitly type as nullable:
47
+
48
+ ```typescript
49
+ sql<string | null>`upper(${pets.name})`;
50
+ ```
51
+
52
+ ## sql.raw()
53
+
54
+ Insert raw, unescaped SQL (no parameterization). Use for dynamic SQL fragments, not user input:
55
+
56
+ ```typescript
57
+ const column = "name";
58
+ sql`select ${sql.raw(column)} from users`;
59
+ // → select name from users (no quoting, no $1)
60
+
61
+ // vs parameterized
62
+ sql`select * from users where id = ${12}`;
63
+ // → select * from users where id = $1; params: [12]
64
+ ```
65
+
66
+ ## sql.empty() + append()
67
+
68
+ Build SQL incrementally:
69
+
70
+ ```typescript
71
+ const query = sql.empty();
72
+ query.append(sql`select * from users`);
73
+ query.append(sql` where `);
74
+
75
+ const conditions = [sql`id = ${1}`, sql`name = ${"Dan"}`];
76
+ for (let i = 0; i < conditions.length; i++) {
77
+ query.append(conditions[i]);
78
+ if (i < conditions.length - 1) query.append(sql` or `);
79
+ }
80
+ // → select * from users where id = $1 or name = $2; params: [1, "Dan"]
81
+ ```
82
+
83
+ ## sql.join()
84
+
85
+ Join an array of SQL chunks with a separator:
86
+
87
+ ```typescript
88
+ const conditions: SQL[] = [sql`id = ${1}`, sql`name = ${"Dan"}`, sql`age > ${18}`];
89
+
90
+ const where = sql.join(conditions, sql` AND `);
91
+ const query = sql`select * from users where ${where}`;
92
+ // → select * from users where id = $1 AND name = $2 AND age > $3
93
+ ```
94
+
95
+ ## sql.identifier()
96
+
97
+ Escape a dynamic identifier (table/column name):
98
+
99
+ ```typescript
100
+ const tableName = "users";
101
+ sql`SELECT * FROM ${sql.identifier(tableName)}`;
102
+ // → SELECT * FROM "users"
103
+ ```
104
+
105
+ ## Placeholders
106
+
107
+ Use `sql.placeholder()` for prepared statements:
108
+
109
+ ```typescript
110
+ const stmt = db
111
+ .select()
112
+ .from(users)
113
+ .where(eq(users.id, sql.placeholder("userId")))
114
+ .prepare("get_user_by_id");
115
+
116
+ // Execute with named params
117
+ await stmt.execute({ userId: 42 });
118
+ ```
119
+
120
+ ## Helpers
121
+
122
+ ### .as() — alias a column
123
+
124
+ ```typescript
125
+ sql<number>`count(*)`.as("total_count");
126
+ // → count(*) AS "total_count"
127
+ ```
128
+
129
+ ### .mapWith() — transform driver output
130
+
131
+ ```typescript
132
+ sql<number>`count(*)`.mapWith(Number);
133
+ // Converts string "42" from driver to number 42
134
+ ```
135
+
136
+ ### .inlineParams() — inline parameters instead of $N
137
+
138
+ ```typescript
139
+ sql`select * from users where id = ${42}`.inlineParams();
140
+ // → select * from users where id = 42 (no parameterization)
141
+ ```
142
+
143
+ ## Converting to String
144
+
145
+ ```typescript
146
+ import { PgDialect } from "drizzle-orm/pg-core";
147
+
148
+ const pgDialect = new PgDialect();
149
+ const { sql: sqlStr, params } = pgDialect.sqlToQuery(
150
+ sql`select * from ${usersTable} where ${usersTable.id} = ${12}`,
151
+ );
152
+ // sqlStr: 'select * from "users" where "users"."id" = $1'
153
+ // params: [12]
154
+ ```
155
+
156
+ ## Usage in Queries
157
+
158
+ ### In SELECT
159
+
160
+ ```typescript
161
+ await db
162
+ .select({
163
+ id: users.id,
164
+ fullName: sql<string>`${users.firstName} || ' ' || ${users.lastName}`.as("full_name"),
165
+ })
166
+ .from(users);
167
+ ```
168
+
169
+ ### In WHERE
170
+
171
+ ```typescript
172
+ await db
173
+ .select()
174
+ .from(users)
175
+ .where(sql`${users.age} > 18 AND ${users.role} = 'admin'`);
176
+
177
+ // Full-text search
178
+ await db
179
+ .select()
180
+ .from(users)
181
+ .where(sql`to_tsvector('english', ${users.bio}) @@ to_tsquery('english', ${searchTerm})`);
182
+ ```
183
+
184
+ ### In ORDER BY
185
+
186
+ ```typescript
187
+ await db
188
+ .select()
189
+ .from(users)
190
+ .orderBy(sql`${users.id} desc nulls first`);
191
+ ```
192
+
193
+ ### In GROUP BY / HAVING
194
+
195
+ ```typescript
196
+ await db
197
+ .select({
198
+ category: products.category,
199
+ total: sql<number>`sum(${products.price})`.mapWith(Number),
200
+ })
201
+ .from(products)
202
+ .groupBy(sql`${products.category}`)
203
+ .having(sql`sum(${products.price}) > 1000`);
204
+ ```
205
+
206
+ ### In schema defaults
207
+
208
+ ```typescript
209
+ const users = pgTable("users", {
210
+ id: uuid("id")
211
+ .default(sql`gen_random_uuid()`)
212
+ .primaryKey(),
213
+ createdAt: timestamp("created_at").default(sql`now()`),
214
+ });
215
+ ```
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: fastify-best-practices
3
+ description: "Fastify 5 best practices, API reference, and patterns for routes, plugins, hooks, validation, error handling, and TypeScript. Use when: (1) writing new Fastify routes, plugins, or hooks, (2) looking up Fastify API signatures or options, (3) debugging Fastify issues (lifecycle, encapsulation, validation, plugin timeout), (4) reviewing Fastify code for anti-patterns. Triggers: 'add a route', 'create plugin', 'Fastify hook', 'validation schema', 'Fastify error', 'setErrorHandler', 'fastify-plugin'."
4
+ ---
5
+
6
+ # Fastify 5 Best Practices
7
+
8
+ ## Table of Contents
9
+
10
+ - [Request lifecycle](#request-lifecycle-exact-order)
11
+ - [Top anti-patterns](#top-anti-patterns)
12
+ - [Quick patterns](#quick-patterns)
13
+ - [Plugin with fastify-plugin (FastifyPluginCallback)](#plugin-with-fastify-plugin-fastifyplugincallback)
14
+ - [Route with validation](#route-with-validation)
15
+ - [Hook (application-level)](#hook-application-level)
16
+ - [Error handler](#error-handler)
17
+ - [Reference files](#reference-files)
18
+
19
+ ## Request lifecycle (exact order)
20
+
21
+ ```
22
+ Incoming Request
23
+ └─ Routing
24
+ └─ onRequest hooks
25
+ └─ preParsing hooks
26
+ └─ Content-Type Parsing
27
+ └─ preValidation hooks
28
+ └─ Schema Validation (→ 400 on failure)
29
+ └─ preHandler hooks
30
+ └─ Route Handler
31
+ └─ preSerialization hooks
32
+ └─ onSend hooks
33
+ └─ Response Sent
34
+ └─ onResponse hooks
35
+ ```
36
+
37
+ Error at any stage → `onError` hooks → error handler → `onSend` → response → `onResponse`.
38
+
39
+ ## Top anti-patterns
40
+
41
+ 1. **Mixing async/callback in handlers** — Use `async` OR callbacks, never both. With async, `return` the value; don't call `reply.send()` AND return.
42
+
43
+ 2. **Returning `undefined` from async handler** — Fastify treats this as "no response yet". Return the data or call `reply.send()`.
44
+
45
+ 3. **Using arrow functions when you need `this`** — Arrow functions don't bind `this` to the Fastify instance. Use `function` declarations for handlers that need `this`.
46
+
47
+ 4. **Forgetting `fastify-plugin` wrapper** — Without it, decorators/hooks stay scoped to the child context. Parent and sibling plugins won't see them.
48
+
49
+ 5. **Decorating with reference types directly** — `decorateRequest('data', {})` shares the SAME object across all requests. Use `null` initial + `onRequest` hook to assign per-request.
50
+
51
+ 6. **Sending response in `onError` hook** — `onError` is read-only for logging. Use `setErrorHandler()` to modify error responses.
52
+
53
+ 7. **Not handling `reply.send()` in async hooks** — Call `return reply` after `reply.send()` in async hooks to prevent "Reply already sent" errors.
54
+
55
+ 8. **Ignoring encapsulation** — Decorators/hooks registered in child plugins are invisible to parents. Design your plugin tree carefully.
56
+
57
+ 9. **String concatenation in SQL from route params** — Always use parameterized queries. Fastify validates input shape, not content safety.
58
+
59
+ 10. **Missing response schema** — Without `response` schema, Fastify serializes with `JSON.stringify()` (slow) and may leak sensitive fields. Use `fast-json-stringify` via response schemas.
60
+
61
+ ## Quick patterns
62
+
63
+ ### Plugin with fastify-plugin (FastifyPluginCallback)
64
+
65
+ Project convention: use `FastifyPluginCallback` + `done()` (avoids `require-await` lint errors).
66
+
67
+ ```ts
68
+ import fp from "fastify-plugin";
69
+ import type { FastifyPluginCallback } from "fastify";
70
+
71
+ const myPlugin: FastifyPluginCallback = (fastify, opts, done) => {
72
+ fastify.decorate("myService", new MyService());
73
+ done();
74
+ };
75
+
76
+ export default fp(myPlugin, { name: "my-plugin" });
77
+ ```
78
+
79
+ ### Route with validation
80
+
81
+ ```ts
82
+ fastify.post<{ Body: CreateUserBody }>("/users", {
83
+ schema: {
84
+ body: {
85
+ type: "object",
86
+ required: ["email", "name"],
87
+ properties: {
88
+ email: { type: "string", format: "email" },
89
+ name: { type: "string", minLength: 1 },
90
+ },
91
+ },
92
+ response: {
93
+ 201: {
94
+ type: "object",
95
+ properties: {
96
+ id: { type: "string" },
97
+ email: { type: "string" },
98
+ },
99
+ },
100
+ },
101
+ },
102
+ handler: async (request, reply) => {
103
+ const user = await createUser(request.body);
104
+ return reply.code(201).send(user);
105
+ },
106
+ });
107
+ ```
108
+
109
+ ### Hook (application-level)
110
+
111
+ ```ts
112
+ fastify.addHook("onRequest", async (request, reply) => {
113
+ request.startTime = Date.now();
114
+ });
115
+
116
+ fastify.addHook("onResponse", async (request, reply) => {
117
+ request.log.info({ elapsed: Date.now() - request.startTime }, "request completed");
118
+ });
119
+ ```
120
+
121
+ ### Error handler
122
+
123
+ ```ts
124
+ fastify.setErrorHandler((error, request, reply) => {
125
+ request.log.error(error);
126
+ const statusCode = error.statusCode ?? 500;
127
+ reply.code(statusCode).send({
128
+ error: statusCode >= 500 ? "Internal Server Error" : error.message,
129
+ });
130
+ });
131
+ ```
132
+
133
+ ## Reference files
134
+
135
+ Load the relevant file when you need detailed API information:
136
+
137
+ - **Server factory & options** — constructor options, server methods, properties: [references/server-and-options.md](references/server-and-options.md)
138
+ - **Routes & handlers** — declaration, URL params, async patterns, constraints: [references/routes-and-handlers.md](references/routes-and-handlers.md)
139
+ - **Hooks & lifecycle** — all 16 hook types, signatures, scope, early response: [references/hooks-and-lifecycle.md](references/hooks-and-lifecycle.md)
140
+ - **Plugins & encapsulation** — creating plugins, fastify-plugin, context inheritance: [references/plugins-and-encapsulation.md](references/plugins-and-encapsulation.md)
141
+ - **Validation & serialization** — JSON Schema, Ajv, response schemas, custom validators: [references/validation-and-serialization.md](references/validation-and-serialization.md)
142
+ - **Request, Reply & errors** — request/reply API, error handling, FST_ERR codes: [references/request-reply-errors.md](references/request-reply-errors.md)
143
+ - **TypeScript & logging** — route generics, type providers, Pino config, decorators: [references/typescript-and-logging.md](references/typescript-and-logging.md)