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