@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,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)
|