@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,321 @@
|
|
|
1
|
+
# Queries Reference (SQL-like API)
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Select](#select)
|
|
6
|
+
- [Insert](#insert)
|
|
7
|
+
- [Update](#update)
|
|
8
|
+
- [Delete](#delete)
|
|
9
|
+
- [Joins](#joins)
|
|
10
|
+
- [Filters (operators)](#filters)
|
|
11
|
+
|
|
12
|
+
## Select
|
|
13
|
+
|
|
14
|
+
### Basic
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// All columns
|
|
18
|
+
await db.select().from(users);
|
|
19
|
+
|
|
20
|
+
// Partial select (flat result)
|
|
21
|
+
await db.select({ id: users.id, name: users.name }).from(users);
|
|
22
|
+
|
|
23
|
+
// With SQL expression
|
|
24
|
+
await db
|
|
25
|
+
.select({
|
|
26
|
+
id: users.id,
|
|
27
|
+
lowerName: sql<string>`lower(${users.name})`.as("lower_name"),
|
|
28
|
+
})
|
|
29
|
+
.from(users);
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Where, orderBy, limit, offset
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
await db.select().from(users)
|
|
36
|
+
.where(eq(users.id, 42))
|
|
37
|
+
.orderBy(asc(users.name))
|
|
38
|
+
.limit(10)
|
|
39
|
+
.offset(20);
|
|
40
|
+
|
|
41
|
+
// Multiple order columns
|
|
42
|
+
.orderBy(desc(users.createdAt), asc(users.name))
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Conditional where (composable)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
async function getProducts({ name, category, maxPrice }: Filters) {
|
|
49
|
+
const filters: SQL[] = [];
|
|
50
|
+
if (name) filters.push(ilike(products.name, `%${name}%`));
|
|
51
|
+
if (category) filters.push(eq(products.category, category));
|
|
52
|
+
if (maxPrice) filters.push(lte(products.price, maxPrice));
|
|
53
|
+
|
|
54
|
+
return db
|
|
55
|
+
.select()
|
|
56
|
+
.from(products)
|
|
57
|
+
.where(and(...filters));
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Subqueries
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
const subquery = db
|
|
65
|
+
.select({ avgPrice: sql<number>`avg(${products.price})`.as("avg_price") })
|
|
66
|
+
.from(products)
|
|
67
|
+
.as("sub");
|
|
68
|
+
|
|
69
|
+
await db.select().from(subquery);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### WITH (CTEs)
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
const topUsers = db.$with("top_users").as(db.select().from(users).where(gt(users.score, 100)));
|
|
76
|
+
|
|
77
|
+
await db.with(topUsers).select().from(topUsers);
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Distinct
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
await db.selectDistinct().from(users);
|
|
84
|
+
await db.selectDistinctOn([users.name]).from(users); // PG-specific
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Aggregations
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
await db
|
|
91
|
+
.select({
|
|
92
|
+
category: products.category,
|
|
93
|
+
count: sql<number>`count(*)`.mapWith(Number),
|
|
94
|
+
avgPrice: sql<number>`avg(${products.price})`.mapWith(Number),
|
|
95
|
+
})
|
|
96
|
+
.from(products)
|
|
97
|
+
.groupBy(products.category)
|
|
98
|
+
.having(sql`count(*) > 5`);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Prepared statements
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
const prepared = db
|
|
105
|
+
.select()
|
|
106
|
+
.from(users)
|
|
107
|
+
.where(eq(users.id, sql.placeholder("id")))
|
|
108
|
+
.prepare("get_user");
|
|
109
|
+
|
|
110
|
+
await prepared.execute({ id: 42 });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Insert
|
|
114
|
+
|
|
115
|
+
### Batch insert
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
await db.insert(users).values([
|
|
119
|
+
{ name: "Dan", email: "dan@example.com" },
|
|
120
|
+
{ name: "Andrew", email: "andrew@example.com" },
|
|
121
|
+
]);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Returning
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const [inserted] = await db.insert(users).values({ name: "Dan" }).returning(); // all columns
|
|
128
|
+
|
|
129
|
+
const [{ id }] = await db.insert(users).values({ name: "Dan" }).returning({ id: users.id }); // partial
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Upsert (on conflict)
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Do nothing
|
|
136
|
+
await db.insert(users).values({ id: 1, name: "Dan" }).onConflictDoNothing();
|
|
137
|
+
await db.insert(users).values({ id: 1, name: "Dan" }).onConflictDoNothing({ target: users.id });
|
|
138
|
+
|
|
139
|
+
// Do update
|
|
140
|
+
await db
|
|
141
|
+
.insert(users)
|
|
142
|
+
.values({ id: 1, name: "Dan" })
|
|
143
|
+
.onConflictDoUpdate({
|
|
144
|
+
target: users.id,
|
|
145
|
+
set: { name: "Dan Updated" },
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Composite target
|
|
149
|
+
await db
|
|
150
|
+
.insert(users)
|
|
151
|
+
.values({ firstName: "Dan", lastName: "Smith" })
|
|
152
|
+
.onConflictDoUpdate({
|
|
153
|
+
target: [users.firstName, users.lastName],
|
|
154
|
+
set: { name: sql`excluded.first_name || ' ' || excluded.last_name` },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// With where on set (conditional upsert)
|
|
158
|
+
await db
|
|
159
|
+
.insert(users)
|
|
160
|
+
.values({ id: 1, name: "Dan" })
|
|
161
|
+
.onConflictDoUpdate({
|
|
162
|
+
target: users.id,
|
|
163
|
+
set: { name: "Dan" },
|
|
164
|
+
setWhere: sql`${users.updatedAt} < now() - interval '1 day'`,
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Insert from select
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
await db.insert(archive).select(db.select().from(users).where(lt(users.createdAt, cutoffDate)));
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Update
|
|
175
|
+
|
|
176
|
+
### With SQL expressions
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
await db.update(users).set({ name: "Dan Updated" }).where(eq(users.id, 1));
|
|
180
|
+
|
|
181
|
+
const [updated] = await db.update(users).set({ name: "Dan" }).where(eq(users.id, 1)).returning();
|
|
182
|
+
|
|
183
|
+
await db
|
|
184
|
+
.update(products)
|
|
185
|
+
.set({ price: sql`${products.price} * 1.1` })
|
|
186
|
+
.where(eq(products.category, "electronics"));
|
|
187
|
+
|
|
188
|
+
// Increment
|
|
189
|
+
await db
|
|
190
|
+
.update(users)
|
|
191
|
+
.set({ viewCount: sql`${users.viewCount} + 1` })
|
|
192
|
+
.where(eq(users.id, 1));
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Delete
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
await db.delete(users).where(eq(users.id, 1));
|
|
199
|
+
|
|
200
|
+
// With returning
|
|
201
|
+
const [deleted] = await db.delete(users).where(eq(users.id, 1)).returning();
|
|
202
|
+
|
|
203
|
+
// With limit + orderBy
|
|
204
|
+
await db.delete(logs).where(lt(logs.createdAt, cutoff)).orderBy(asc(logs.createdAt)).limit(1000);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### With CTE
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
const avgAmount = db
|
|
211
|
+
.$with("avg_amount")
|
|
212
|
+
.as(db.select({ value: sql`avg(${orders.amount})`.as("value") }).from(orders));
|
|
213
|
+
|
|
214
|
+
await db
|
|
215
|
+
.with(avgAmount)
|
|
216
|
+
.delete(orders)
|
|
217
|
+
.where(gt(orders.amount, sql`(select * from ${avgAmount})`));
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Joins
|
|
221
|
+
|
|
222
|
+
### Join types
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// LEFT JOIN -- right table nullable
|
|
226
|
+
db.select().from(users).leftJoin(posts, eq(users.id, posts.authorId));
|
|
227
|
+
// -> { users: User; posts: Post | null }[]
|
|
228
|
+
|
|
229
|
+
// RIGHT JOIN -- left table nullable
|
|
230
|
+
db.select().from(users).rightJoin(posts, eq(users.id, posts.authorId));
|
|
231
|
+
|
|
232
|
+
// INNER JOIN -- neither nullable
|
|
233
|
+
db.select().from(users).innerJoin(posts, eq(users.id, posts.authorId));
|
|
234
|
+
|
|
235
|
+
// FULL JOIN -- both nullable
|
|
236
|
+
db.select().from(users).fullJoin(posts, eq(users.id, posts.authorId));
|
|
237
|
+
|
|
238
|
+
// CROSS JOIN -- no condition
|
|
239
|
+
db.select().from(users).crossJoin(posts);
|
|
240
|
+
|
|
241
|
+
// LATERAL joins (correlated subqueries)
|
|
242
|
+
const sq = db.select().from(posts).where(eq(posts.authorId, users.id)).as("user_posts");
|
|
243
|
+
db.select()
|
|
244
|
+
.from(users)
|
|
245
|
+
.leftJoinLateral(sq, sql`true`);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Partial select (flatten)
|
|
249
|
+
|
|
250
|
+
```typescript
|
|
251
|
+
const result = await db
|
|
252
|
+
.select({
|
|
253
|
+
userId: users.id,
|
|
254
|
+
userName: users.name,
|
|
255
|
+
postTitle: posts.title,
|
|
256
|
+
})
|
|
257
|
+
.from(users)
|
|
258
|
+
.leftJoin(posts, eq(users.id, posts.authorId));
|
|
259
|
+
// -> { userId: number; userName: string; postTitle: string | null }[]
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Table aliases (self-join)
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { alias } from "drizzle-orm/pg-core";
|
|
266
|
+
|
|
267
|
+
const parent = alias(users, "parent");
|
|
268
|
+
await db.select().from(users).leftJoin(parent, eq(parent.id, users.parentId));
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Many-to-many via junction
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
await db
|
|
275
|
+
.select()
|
|
276
|
+
.from(usersToGroups)
|
|
277
|
+
.leftJoin(users, eq(usersToGroups.userId, users.id))
|
|
278
|
+
.leftJoin(groups, eq(usersToGroups.groupId, groups.id))
|
|
279
|
+
.where(eq(groups.id, 1));
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Post-query aggregation (manual)
|
|
283
|
+
|
|
284
|
+
Drizzle returns flat rows -- aggregate one-to-many manually:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const rows = await db
|
|
288
|
+
.select({ user: users, pet: pets })
|
|
289
|
+
.from(users)
|
|
290
|
+
.leftJoin(pets, eq(users.id, pets.ownerId));
|
|
291
|
+
|
|
292
|
+
const result = rows.reduce<Record<number, { user: User; pets: Pet[] }>>((acc, row) => {
|
|
293
|
+
if (!acc[row.user.id]) acc[row.user.id] = { user: row.user, pets: [] };
|
|
294
|
+
if (row.pet) acc[row.user.id].pets.push(row.pet);
|
|
295
|
+
return acc;
|
|
296
|
+
}, {});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Filters
|
|
300
|
+
|
|
301
|
+
All operators imported from `drizzle-orm`:
|
|
302
|
+
|
|
303
|
+
| Operator | SQL equivalent | Example |
|
|
304
|
+
| ----------------------- | ----------------- | ---------------------------------------------------- |
|
|
305
|
+
| `eq(col, val)` | `= val` | `eq(users.id, 1)` |
|
|
306
|
+
| `ne(col, val)` | `!= val` | `ne(users.id, 1)` |
|
|
307
|
+
| `gt(col, val)` | `> val` | `gt(users.age, 18)` |
|
|
308
|
+
| `gte(col, val)` | `>= val` | `gte(users.age, 18)` |
|
|
309
|
+
| `lt(col, val)` | `< val` | `lt(users.age, 65)` |
|
|
310
|
+
| `lte(col, val)` | `<= val` | `lte(users.age, 65)` |
|
|
311
|
+
| `isNull(col)` | `IS NULL` | `isNull(users.deletedAt)` |
|
|
312
|
+
| `isNotNull(col)` | `IS NOT NULL` | `isNotNull(users.email)` |
|
|
313
|
+
| `inArray(col, vals)` | `IN (...)` | `inArray(users.role, ["admin", "mod"])` |
|
|
314
|
+
| `notInArray(col, vals)` | `NOT IN (...)` | `notInArray(users.role, ["banned"])` |
|
|
315
|
+
| `between(col, a, b)` | `BETWEEN a AND b` | `between(users.age, 18, 65)` |
|
|
316
|
+
| `like(col, pat)` | `LIKE pat` | `like(users.name, "%Dan%")` |
|
|
317
|
+
| `ilike(col, pat)` | `ILIKE pat` | `ilike(users.name, "%dan%")` |
|
|
318
|
+
| `exists(subquery)` | `EXISTS (...)` | `exists(db.select().from(posts).where(...))` |
|
|
319
|
+
| `and(...conds)` | `AND` | `and(eq(users.role, "admin"), gt(users.age, 21))` |
|
|
320
|
+
| `or(...conds)` | `OR` | `or(eq(users.role, "admin"), eq(users.role, "mod"))` |
|
|
321
|
+
| `not(cond)` | `NOT` | `not(eq(users.role, "admin"))` |
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# Relations & Relational Query API (v2)
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Defining Relations](#defining-relations)
|
|
6
|
+
- [Relational Query API](#relational-query-api)
|
|
7
|
+
- [Where Filters](#where-filters)
|
|
8
|
+
- [Columns, OrderBy, Limit](#columns-orderby-limit)
|
|
9
|
+
- [Filtering by Relations](#filtering-by-relations)
|
|
10
|
+
|
|
11
|
+
## Defining Relations
|
|
12
|
+
|
|
13
|
+
Relations are defined separately from tables using `defineRelations()`. They do NOT create foreign keys in the database — they are purely for the relational query API.
|
|
14
|
+
|
|
15
|
+
### Setup
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// schema.ts — tables only
|
|
19
|
+
import { pgTable, serial, text, integer } from "drizzle-orm/pg-core";
|
|
20
|
+
|
|
21
|
+
export const users = pgTable("users", {
|
|
22
|
+
id: serial("id").primaryKey(),
|
|
23
|
+
name: text("name").notNull(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const posts = pgTable("posts", {
|
|
27
|
+
id: serial("id").primaryKey(),
|
|
28
|
+
title: text("title").notNull(),
|
|
29
|
+
authorId: integer("author_id")
|
|
30
|
+
.notNull()
|
|
31
|
+
.references(() => users.id),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// relations.ts
|
|
35
|
+
import { defineRelations } from "drizzle-orm";
|
|
36
|
+
import * as schema from "./schema";
|
|
37
|
+
|
|
38
|
+
export const relations = defineRelations(schema, (r) => ({
|
|
39
|
+
users: {
|
|
40
|
+
posts: r.many.posts({
|
|
41
|
+
from: r.users.id,
|
|
42
|
+
to: r.posts.authorId,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
posts: {
|
|
46
|
+
author: r.one.users({
|
|
47
|
+
from: r.posts.authorId,
|
|
48
|
+
to: r.users.id,
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
}));
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### One-to-One
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
export const relations = defineRelations(schema, (r) => ({
|
|
58
|
+
users: {
|
|
59
|
+
profile: r.one.profiles({
|
|
60
|
+
from: r.users.id,
|
|
61
|
+
to: r.profiles.userId,
|
|
62
|
+
}),
|
|
63
|
+
},
|
|
64
|
+
profiles: {
|
|
65
|
+
user: r.one.users({
|
|
66
|
+
from: r.profiles.userId,
|
|
67
|
+
to: r.users.id,
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### One-to-Many
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
export const relations = defineRelations(schema, (r) => ({
|
|
77
|
+
users: {
|
|
78
|
+
posts: r.many.posts({
|
|
79
|
+
from: r.users.id,
|
|
80
|
+
to: r.posts.authorId,
|
|
81
|
+
}),
|
|
82
|
+
},
|
|
83
|
+
posts: {
|
|
84
|
+
author: r.one.users({
|
|
85
|
+
from: r.posts.authorId,
|
|
86
|
+
to: r.users.id,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
}));
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Many-to-Many (through junction table)
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
export const users = pgTable("users", { id: serial("id").primaryKey() });
|
|
96
|
+
export const groups = pgTable("groups", { id: serial("id").primaryKey() });
|
|
97
|
+
export const usersToGroups = pgTable(
|
|
98
|
+
"users_to_groups",
|
|
99
|
+
{
|
|
100
|
+
userId: integer("user_id")
|
|
101
|
+
.notNull()
|
|
102
|
+
.references(() => users.id),
|
|
103
|
+
groupId: integer("group_id")
|
|
104
|
+
.notNull()
|
|
105
|
+
.references(() => groups.id),
|
|
106
|
+
},
|
|
107
|
+
(t) => [primaryKey({ columns: [t.userId, t.groupId] })],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
export const relations = defineRelations(schema, (r) => ({
|
|
111
|
+
users: {
|
|
112
|
+
groups: r.many.groups({
|
|
113
|
+
from: r.users.id.through(r.usersToGroups.userId),
|
|
114
|
+
to: r.groups.id.through(r.usersToGroups.groupId),
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
117
|
+
groups: {
|
|
118
|
+
users: r.many.users({
|
|
119
|
+
from: r.groups.id.through(r.usersToGroups.groupId),
|
|
120
|
+
to: r.users.id.through(r.usersToGroups.userId),
|
|
121
|
+
}),
|
|
122
|
+
},
|
|
123
|
+
}));
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Required relations (optional: false)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
users: {
|
|
130
|
+
posts: r.many.posts({
|
|
131
|
+
from: r.users.id,
|
|
132
|
+
to: r.posts.authorId,
|
|
133
|
+
optional: false, // makes the relation required at type level
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Predefined filters on relations
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
groups: {
|
|
142
|
+
verifiedUsers: r.many.users({
|
|
143
|
+
from: r.groups.id.through(r.usersToGroups.groupId),
|
|
144
|
+
to: r.users.id.through(r.usersToGroups.userId),
|
|
145
|
+
where: { verified: true },
|
|
146
|
+
}),
|
|
147
|
+
},
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Relational Query API
|
|
151
|
+
|
|
152
|
+
Pass schema + relations to `drizzle()`:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { drizzle } from "drizzle-orm/node-postgres";
|
|
156
|
+
import * as schema from "./schema";
|
|
157
|
+
import { relations } from "./relations";
|
|
158
|
+
|
|
159
|
+
const db = drizzle(process.env.DATABASE_URL, { schema, relations });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### findMany / findFirst
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
// All users
|
|
166
|
+
const users = await db.query.users.findMany();
|
|
167
|
+
|
|
168
|
+
// First match
|
|
169
|
+
const user = await db.query.users.findFirst({
|
|
170
|
+
where: { id: 1 },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// With nested relations
|
|
174
|
+
const usersWithPosts = await db.query.users.findMany({
|
|
175
|
+
with: {
|
|
176
|
+
posts: {
|
|
177
|
+
with: {
|
|
178
|
+
comments: true,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Where Filters
|
|
186
|
+
|
|
187
|
+
### Object-based (v2)
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
// Simple equality
|
|
191
|
+
where: { id: 1 }
|
|
192
|
+
|
|
193
|
+
// Multiple = implicit AND
|
|
194
|
+
where: { age: 18, role: "admin" }
|
|
195
|
+
|
|
196
|
+
// Operators
|
|
197
|
+
where: { age: { gt: 18 } }
|
|
198
|
+
where: { name: { like: "Dan%" } }
|
|
199
|
+
where: { name: { ilike: "%dan%" } } // case-insensitive
|
|
200
|
+
where: { id: { gte: 10, lte: 100 } } // BETWEEN via AND
|
|
201
|
+
|
|
202
|
+
// Explicit AND/OR/NOT
|
|
203
|
+
where: { AND: [{ age: { gt: 18 } }, { role: "admin" }] }
|
|
204
|
+
where: { OR: [{ role: "admin" }, { role: "mod" }] }
|
|
205
|
+
where: { NOT: { role: "banned" } }
|
|
206
|
+
|
|
207
|
+
// Raw SQL
|
|
208
|
+
where: { RAW: (table) => sql`${table.name} ~* 'pattern'` }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Available operators
|
|
212
|
+
|
|
213
|
+
`eq` (default), `ne`, `gt`, `gte`, `lt`, `lte`, `like`, `ilike`, `notLike`, `notIlike`, `inArray`, `notInArray`, `isNull`, `isNotNull`, `between`, `notBetween`
|
|
214
|
+
|
|
215
|
+
## Columns, OrderBy, Limit
|
|
216
|
+
|
|
217
|
+
### Select specific columns
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
await db.query.users.findMany({
|
|
221
|
+
columns: {
|
|
222
|
+
id: true,
|
|
223
|
+
name: true,
|
|
224
|
+
// password: false (omit or set false to exclude)
|
|
225
|
+
},
|
|
226
|
+
with: {
|
|
227
|
+
posts: {
|
|
228
|
+
columns: { id: true, title: true },
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### OrderBy
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
await db.query.users.findMany({
|
|
238
|
+
orderBy: { createdAt: "desc" },
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Limit / Offset (including nested)
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
await db.query.posts.findMany({
|
|
246
|
+
limit: 10,
|
|
247
|
+
offset: 20,
|
|
248
|
+
with: {
|
|
249
|
+
comments: {
|
|
250
|
+
limit: 5,
|
|
251
|
+
offset: 0,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Filtering by Relations
|
|
258
|
+
|
|
259
|
+
Query users that have posts matching a condition:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
const usersWithMatchingPosts = await db.query.users.findMany({
|
|
263
|
+
where: {
|
|
264
|
+
id: { gt: 10 },
|
|
265
|
+
posts: {
|
|
266
|
+
content: { like: "Drizzle%" },
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
This generates a subquery/EXISTS condition — only users with at least one matching post are returned.
|