@gallopsystems/agent-skills 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 (52) hide show
  1. package/README.md +137 -0
  2. package/package.json +26 -0
  3. package/plugins/doctl/.claude-plugin/plugin.json +8 -0
  4. package/plugins/doctl/skills/doctl/SKILL.md +93 -0
  5. package/plugins/kysely-postgres/.claude-plugin/plugin.json +8 -0
  6. package/plugins/kysely-postgres/skills/kysely-postgres/SKILL.md +1101 -0
  7. package/plugins/kysely-postgres/skills/kysely-postgres/references/aggregations.ts +167 -0
  8. package/plugins/kysely-postgres/skills/kysely-postgres/references/ctes.ts +165 -0
  9. package/plugins/kysely-postgres/skills/kysely-postgres/references/expressions.ts +272 -0
  10. package/plugins/kysely-postgres/skills/kysely-postgres/references/joins.ts +206 -0
  11. package/plugins/kysely-postgres/skills/kysely-postgres/references/json-arrays.ts +398 -0
  12. package/plugins/kysely-postgres/skills/kysely-postgres/references/mutations.ts +199 -0
  13. package/plugins/kysely-postgres/skills/kysely-postgres/references/orderby-pagination.ts +117 -0
  14. package/plugins/kysely-postgres/skills/kysely-postgres/references/relations.ts +176 -0
  15. package/plugins/kysely-postgres/skills/kysely-postgres/references/select-where.ts +146 -0
  16. package/plugins/linear/.claude-plugin/plugin.json +8 -0
  17. package/plugins/linear/skills/linear/SKILL.md +1040 -0
  18. package/plugins/linear/skills/linear/bin/linear.mjs +1228 -0
  19. package/plugins/linear/skills/linear/tech-stack.md +273 -0
  20. package/plugins/nitro-testing/.claude-plugin/plugin.json +8 -0
  21. package/plugins/nitro-testing/skills/nitro-testing/SKILL.md +497 -0
  22. package/plugins/nitro-testing/skills/nitro-testing/async-testing.md +270 -0
  23. package/plugins/nitro-testing/skills/nitro-testing/ci-setup.md +226 -0
  24. package/plugins/nitro-testing/skills/nitro-testing/examples/global-setup.ts +90 -0
  25. package/plugins/nitro-testing/skills/nitro-testing/examples/handler.test.ts +167 -0
  26. package/plugins/nitro-testing/skills/nitro-testing/examples/setup.ts +29 -0
  27. package/plugins/nitro-testing/skills/nitro-testing/examples/test-utils-index.ts +297 -0
  28. package/plugins/nitro-testing/skills/nitro-testing/examples/vitest.config.ts +42 -0
  29. package/plugins/nitro-testing/skills/nitro-testing/factories.md +278 -0
  30. package/plugins/nitro-testing/skills/nitro-testing/frontend-testing.md +512 -0
  31. package/plugins/nitro-testing/skills/nitro-testing/test-utils.md +262 -0
  32. package/plugins/nitro-testing/skills/nitro-testing/transaction-rollback.md +183 -0
  33. package/plugins/nitro-testing/skills/nitro-testing/vitest-config.md +236 -0
  34. package/plugins/nuxt-nitro-api/.claude-plugin/plugin.json +8 -0
  35. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/SKILL.md +260 -0
  36. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/auth-patterns.md +228 -0
  37. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/composables-utils.md +174 -0
  38. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/deep-linking.md +190 -0
  39. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-middleware.ts +32 -0
  40. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/auth-utils.ts +51 -0
  41. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/deep-link-page.vue +61 -0
  42. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/service-util.ts +63 -0
  43. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/sse-endpoint.ts +59 -0
  44. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/examples/validation-endpoint.ts +38 -0
  45. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/fetch-patterns.md +178 -0
  46. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/nitro-tasks.md +243 -0
  47. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/page-structure.md +162 -0
  48. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/server-services.md +238 -0
  49. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/sse.md +221 -0
  50. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/ssr-client.md +166 -0
  51. package/plugins/nuxt-nitro-api/skills/nuxt-nitro-api/validation.md +131 -0
  52. package/scripts/link-skills.mjs +252 -0
@@ -0,0 +1,206 @@
1
+ /**
2
+ * JOIN Patterns
3
+ * Simple joins, multi-table joins, and complex callback joins
4
+ */
5
+ import { db } from "./db";
6
+ import { sql } from "kysely";
7
+
8
+ // ============================================
9
+ // BASIC JOINS
10
+ // ============================================
11
+
12
+ // Inner join - simple format
13
+ const ordersWithUsers = await db
14
+ .selectFrom("order")
15
+ .innerJoin("user", "user.id", "order.user_id")
16
+ .select([
17
+ "order.id as orderId",
18
+ "order.status",
19
+ "user.email",
20
+ "user.first_name",
21
+ ])
22
+ .execute();
23
+
24
+ // Left join - includes rows without matches
25
+ const productsWithCategories = await db
26
+ .selectFrom("product")
27
+ .leftJoin("category", "category.id", "product.category_id")
28
+ .select([
29
+ "product.name as productName",
30
+ "category.name as categoryName", // null if no category
31
+ ])
32
+ .execute();
33
+
34
+ // Multiple joins - chain them
35
+ const orderDetails = await db
36
+ .selectFrom("order_item")
37
+ .innerJoin("order", "order.id", "order_item.order_id")
38
+ .innerJoin("product", "product.id", "order_item.product_id")
39
+ .innerJoin("user", "user.id", "order.user_id")
40
+ .select([
41
+ "user.email",
42
+ "product.name as productName",
43
+ "order_item.quantity",
44
+ "order_item.unit_price",
45
+ ])
46
+ .execute();
47
+
48
+ // Self-join with aliases
49
+ const categoriesWithParent = await db
50
+ .selectFrom("category as c")
51
+ .leftJoin("category as parent", "parent.id", "c.parent_id")
52
+ .select([
53
+ "c.name as categoryName",
54
+ "parent.name as parentCategoryName",
55
+ ])
56
+ .execute();
57
+
58
+ // ============================================
59
+ // COMPLEX JOINS (Callback Format)
60
+ // ============================================
61
+
62
+ // When to use callback format:
63
+ // 1. Multiple join conditions (composite keys)
64
+ // 2. Mixed column-to-column AND column-to-literal
65
+ // 3. OR conditions in joins
66
+ // 4. Subquery joins (derived tables)
67
+
68
+ // Multi-condition join: onRef + on
69
+ // onRef = column-to-column, on = column-to-literal
70
+ const activeProductItems = await db
71
+ .selectFrom("order_item as oi")
72
+ .innerJoin("product as p", (join) =>
73
+ join
74
+ .onRef("p.id", "=", "oi.product_id")
75
+ .on("p.is_active", "=", true) // Filter in join, not WHERE
76
+ )
77
+ .select([
78
+ "oi.id as orderItemId",
79
+ "p.name as productName",
80
+ "oi.quantity",
81
+ ])
82
+ .execute();
83
+
84
+ // Join with OR conditions
85
+ const usersWithCompletedOrShipped = await db
86
+ .selectFrom("user as u")
87
+ .leftJoin("order as o", (join) =>
88
+ join
89
+ .onRef("o.user_id", "=", "u.id")
90
+ .on((eb) =>
91
+ eb.or([
92
+ eb("o.status", "=", "completed"),
93
+ eb("o.status", "=", "shipped"),
94
+ ])
95
+ )
96
+ )
97
+ .select([
98
+ "u.email",
99
+ "o.id as orderId",
100
+ "o.status",
101
+ ])
102
+ .execute();
103
+
104
+ // ============================================
105
+ // SUBQUERY JOINS (Derived Tables)
106
+ // ============================================
107
+
108
+ // Two-callback format: first builds subquery, second defines join
109
+ const usersWithOrderStats = await db
110
+ .selectFrom("user as u")
111
+ .leftJoin(
112
+ // First callback: build the subquery
113
+ (eb) =>
114
+ eb
115
+ .selectFrom("order")
116
+ .select((eb) => [
117
+ "user_id",
118
+ eb.fn.count("id").as("order_count"),
119
+ eb.fn.sum("total_amount").as("total_spent"),
120
+ ])
121
+ .groupBy("user_id")
122
+ .as("order_stats"), // MUST have alias!
123
+ // Second callback: define join condition
124
+ (join) => join.onRef("order_stats.user_id", "=", "u.id")
125
+ )
126
+ .select([
127
+ "u.email",
128
+ "u.first_name",
129
+ "order_stats.order_count",
130
+ "order_stats.total_spent",
131
+ ])
132
+ .execute();
133
+
134
+ // Subquery join with MAX aggregation
135
+ const productsWithLatestReview = await db
136
+ .selectFrom("product as p")
137
+ .leftJoin(
138
+ (eb) =>
139
+ eb
140
+ .selectFrom("review")
141
+ .select((eb) => [
142
+ "product_id",
143
+ eb.fn.max("created_at").as("latest_review_at"),
144
+ eb.fn.count("id").as("review_count"),
145
+ ])
146
+ .groupBy("product_id")
147
+ .as("review_stats"),
148
+ (join) => join.onRef("review_stats.product_id", "=", "p.id")
149
+ )
150
+ .select([
151
+ "p.name",
152
+ "p.price",
153
+ "review_stats.latest_review_at",
154
+ "review_stats.review_count",
155
+ ])
156
+ .orderBy("review_stats.review_count", (ob) => ob.desc().nullsLast())
157
+ .execute();
158
+
159
+ // ============================================
160
+ // CROSS JOIN
161
+ // ============================================
162
+
163
+ // Cross join using always-true condition
164
+ // Use case: Join CTE aggregation to main query
165
+ const usersWithSummary = await db
166
+ .with("summary", (db) =>
167
+ db
168
+ .selectFrom("order")
169
+ .select((eb) => [
170
+ eb.fn.count("id").as("total_orders"),
171
+ eb.fn.sum("total_amount").as("total_revenue"),
172
+ ])
173
+ )
174
+ .selectFrom("user as u")
175
+ .leftJoin("summary", (join) =>
176
+ join.on(sql`true`, "=", sql`true`)
177
+ )
178
+ .select([
179
+ "u.email",
180
+ "summary.total_orders",
181
+ "summary.total_revenue",
182
+ ])
183
+ .where("u.role", "=", "admin")
184
+ .execute();
185
+
186
+ // ============================================
187
+ // KEY PATTERNS SUMMARY
188
+ // ============================================
189
+
190
+ /*
191
+ 1. Simple joins: .innerJoin("table", "a.col", "b.col")
192
+ - Use when joining on single column equality
193
+
194
+ 2. Callback joins: .innerJoin("table", (join) => join.onRef(...).on(...))
195
+ - onRef(col1, op, col2): column-to-column
196
+ - on(col, op, value): column-to-literal
197
+ - on((eb) => eb.or([...])): OR conditions
198
+
199
+ 3. Subquery joins: Two callbacks
200
+ - First: (eb) => eb.selectFrom(...).as("alias")
201
+ - Second: (join) => join.onRef(...)
202
+ - ALWAYS include .as("alias") on subquery!
203
+
204
+ 4. Cross joins: join.on(sql`true`, "=", sql`true`)
205
+ - For joining unrelated data (like CTE summaries)
206
+ */
@@ -0,0 +1,398 @@
1
+ /**
2
+ * JSON/JSONB and Array Patterns
3
+ * PostgreSQL-specific JSON functions and array handling
4
+ */
5
+ import { db } from "./db";
6
+ import { sql } from "kysely";
7
+ import { jsonBuildObject } from "kysely/helpers/postgres";
8
+
9
+ // ============================================
10
+ // JSONB INSERT/UPDATE - NO JSON.stringify!
11
+ // ============================================
12
+
13
+ // The pg driver handles JSONB serialization automatically
14
+ // Just pass JavaScript objects directly!
15
+
16
+ // Insert with JSONB
17
+ const newUser = await db
18
+ .insertInto("user")
19
+ .values({
20
+ email: "test@example.com",
21
+ first_name: "Test",
22
+ last_name: "User",
23
+ metadata: { preferences: { theme: "dark" }, notifications: true } as any,
24
+ })
25
+ .returning(["id", "email", "metadata"])
26
+ .executeTakeFirst();
27
+
28
+ // Update JSONB
29
+ const updatedUser = await db
30
+ .updateTable("user")
31
+ .set({
32
+ metadata: { preferences: { theme: "light" }, notifications: false } as any,
33
+ })
34
+ .where("email", "=", "test@example.com")
35
+ .returning(["id", "metadata"])
36
+ .executeTakeFirst();
37
+
38
+ // ============================================
39
+ // JSONB READ - NO JSON.parse!
40
+ // ============================================
41
+
42
+ // Returns parsed objects automatically
43
+ const user = await db
44
+ .selectFrom("user")
45
+ .select(["id", "email", "metadata"])
46
+ .where("email", "=", "test@example.com")
47
+ .executeTakeFirst();
48
+
49
+ // Access directly - already an object!
50
+ console.log(user?.metadata?.preferences?.theme); // "light"
51
+ console.log(typeof user?.metadata); // "object"
52
+
53
+ // ============================================
54
+ // ARRAY COLUMNS - NO JSON.stringify!
55
+ // ============================================
56
+
57
+ // Insert with text[] array - pass array directly
58
+ const product = await db
59
+ .insertInto("product")
60
+ .values({
61
+ name: "Test Product",
62
+ sku: "TEST-001",
63
+ price: "19.99",
64
+ tags: ["electronics", "sale", "featured"], // Direct array!
65
+ })
66
+ .returning(["id", "name", "tags"])
67
+ .executeTakeFirst();
68
+
69
+ // Read array - returns native JavaScript array
70
+ const productWithTags = await db
71
+ .selectFrom("product")
72
+ .select(["name", "tags"])
73
+ .where("sku", "=", "TEST-001")
74
+ .executeTakeFirst();
75
+
76
+ console.log(productWithTags?.tags); // ["electronics", "sale", "featured"]
77
+ console.log(Array.isArray(productWithTags?.tags)); // true
78
+
79
+ // Update array
80
+ await db
81
+ .updateTable("product")
82
+ .set({ tags: ["updated", "tags"] })
83
+ .where("sku", "=", "TEST-001")
84
+ .execute();
85
+
86
+ // ============================================
87
+ // ARRAY QUERIES
88
+ // ============================================
89
+
90
+ // Array contains all (@>) - operator works natively!
91
+ const hasAllTags = await db
92
+ .selectFrom("product")
93
+ .select(["name", "tags"])
94
+ .where("tags", "@>", sql`ARRAY['electronics', 'premium']::text[]`)
95
+ .execute();
96
+
97
+ // Array overlap (&&) - operator works natively!
98
+ const premiumOrBasic = await db
99
+ .selectFrom("product")
100
+ .select(["name", "tags"])
101
+ .where("tags", "&&", sql`ARRAY['premium', 'basic']::text[]`)
102
+ .execute();
103
+
104
+ // Array contains value (ANY) - type-safe with eb.fn
105
+ // eb.ref("tags") validates column exists at compile time
106
+ const searchTag = "phone";
107
+ const phonesProducts = await db
108
+ .selectFrom("product")
109
+ .select(["name", "tags"])
110
+ .where((eb) => eb(sql`${searchTag}`, "=", eb.fn("any", [eb.ref("tags")])))
111
+ .execute();
112
+ // Using eb.ref("invalid_column") would be a TypeScript error!
113
+
114
+ // ============================================
115
+ // JSONB QUERIES - Operators that work natively
116
+ // ============================================
117
+
118
+ // Key exists (?) - works as native operator!
119
+ const hasThemeKey = await db
120
+ .selectFrom("user")
121
+ .selectAll()
122
+ .where("metadata", "?", "theme")
123
+ .execute();
124
+
125
+ // Any key exists (?|) - works as native operator!
126
+ const hasAnyKey = await db
127
+ .selectFrom("user")
128
+ .selectAll()
129
+ .where("metadata", "?|", sql`array['theme', 'language']`)
130
+ .execute();
131
+
132
+ // All keys exist (?&) - works as native operator!
133
+ const hasAllKeys = await db
134
+ .selectFrom("user")
135
+ .selectAll()
136
+ .where("metadata", "?&", sql`array['theme', 'notifications']`)
137
+ .execute();
138
+
139
+ // JSONB contains (@>) - works as native operator!
140
+ const usersWithPrefs = await db
141
+ .selectFrom("user")
142
+ .selectAll()
143
+ .where("metadata", "@>", sql`'{"notifications": true}'::jsonb`)
144
+ .execute();
145
+
146
+ // JSONB contained by (<@) - works as native operator!
147
+ const simpleMetadata = await db
148
+ .selectFrom("user")
149
+ .selectAll()
150
+ .where("metadata", "<@", sql`'{"theme": "dark", "notifications": true}'::jsonb`)
151
+ .execute();
152
+
153
+ // ============================================
154
+ // JSONB QUERIES - Extraction
155
+ // ============================================
156
+
157
+ // Single-level extraction: -> and ->> work with eb() - type-safe!
158
+ const extracted = await db
159
+ .selectFrom("user")
160
+ .select((eb) => [
161
+ "id",
162
+ eb("metadata", "->", "preferences").as("preferences"), // Returns JSONB
163
+ eb("metadata", "->>", "theme").as("theme"), // Returns text
164
+ ])
165
+ .execute();
166
+
167
+ // Nested path extraction: #> and #>> need sql``
168
+ const nestedJson = await db
169
+ .selectFrom("user")
170
+ .select([
171
+ "id",
172
+ sql`metadata#>'{preferences,theme}'`.as("theme"), // Returns JSONB
173
+ ])
174
+ .execute();
175
+
176
+ const nestedText = await db
177
+ .selectFrom("user")
178
+ .select([
179
+ "id",
180
+ sql<string>`metadata#>>'{preferences,theme}'`.as("theme"), // Returns text
181
+ ])
182
+ .execute();
183
+
184
+ // Filter by JSON field value - type-safe!
185
+ const darkThemeUsers = await db
186
+ .selectFrom("user")
187
+ .selectAll()
188
+ .where((eb) => eb(eb("metadata", "->>", "theme"), "=", "dark"))
189
+ .execute();
190
+ // eb("metadata", ...) validates column - eb("invalid", ...) would be TS error
191
+
192
+ // Filter by nested JSON value (path syntax still needs sql``)
193
+ const specificUsers = await db
194
+ .selectFrom("user")
195
+ .selectAll()
196
+ .where(sql`metadata#>>'{preferences,theme}'`, "=", "dark")
197
+ .execute();
198
+
199
+ // ============================================
200
+ // JSONPath (PostgreSQL 12+)
201
+ // ============================================
202
+
203
+ // JSONPath match (@@) - works as native operator!
204
+ const matchingUsers = await db
205
+ .selectFrom("user")
206
+ .selectAll()
207
+ .where("metadata", "@@", sql`'$.preferences.theme == "dark"'`)
208
+ .execute();
209
+
210
+ // JSONPath exists (@?) - NOT in Kysely's allowlist, use function instead
211
+ // Use jsonb_path_exists() for type-safe column validation
212
+ const usersWithTheme = await db
213
+ .selectFrom("user")
214
+ .selectAll()
215
+ .where((eb) =>
216
+ eb.fn("jsonb_path_exists", [eb.ref("metadata"), sql`'$.preferences.theme'`])
217
+ )
218
+ .execute();
219
+ // eb.ref("metadata") validates column exists - eb.ref("invalid") would be TS error
220
+
221
+ // Extract value with JSONPath - type-safe with eb.fn
222
+ const themes = await db
223
+ .selectFrom("user")
224
+ .select((eb) => [
225
+ "id",
226
+ eb
227
+ .fn("jsonb_path_query_first", [
228
+ eb.ref("metadata"),
229
+ sql`'$.preferences.theme'`,
230
+ ])
231
+ .as("theme"),
232
+ ])
233
+ .execute();
234
+
235
+ // JSONPath with variables
236
+ const searchValue = "dark";
237
+ const filtered = await db
238
+ .selectFrom("user")
239
+ .selectAll()
240
+ .where((eb) =>
241
+ eb.fn("jsonb_path_exists", [
242
+ eb.ref("metadata"),
243
+ sql`'$.preferences.theme ? (@ == $val)'`,
244
+ sql`jsonb_build_object('val', ${searchValue}::text)`,
245
+ ])
246
+ )
247
+ .execute();
248
+
249
+ // ============================================
250
+ // jsonBuildObject
251
+ // ============================================
252
+
253
+ // Build JSON objects in SELECT
254
+ const usersWithInfo = await db
255
+ .selectFrom("user")
256
+ .select((eb) => [
257
+ "id",
258
+ jsonBuildObject({
259
+ fullName: eb.fn<string>("concat", [
260
+ eb.ref("first_name"),
261
+ eb.cast(eb.val(" "), "text"),
262
+ eb.ref("last_name"),
263
+ ]),
264
+ email: eb.ref("email"),
265
+ role: eb.ref("role"),
266
+ }).as("userInfo"),
267
+ ])
268
+ .execute();
269
+
270
+ // ============================================
271
+ // jsonAgg with CTEs
272
+ // ============================================
273
+
274
+ // Aggregate rows into JSON array
275
+ const usersWithOrders = await db
276
+ .with("user_orders", (db) =>
277
+ db
278
+ .selectFrom("order")
279
+ .innerJoin("user", "user.id", "order.user_id")
280
+ .select((eb) => [
281
+ "user.id as userId",
282
+ "user.email",
283
+ eb.fn
284
+ .jsonAgg(
285
+ jsonBuildObject({
286
+ orderId: eb.ref("order.id"),
287
+ status: eb.ref("order.status"),
288
+ total: eb.ref("order.total_amount"),
289
+ })
290
+ )
291
+ .as("orders"),
292
+ ])
293
+ .groupBy(["user.id", "user.email"])
294
+ )
295
+ .selectFrom("user_orders")
296
+ .selectAll()
297
+ .execute();
298
+
299
+ // ============================================
300
+ // NESTED jsonAgg
301
+ // ============================================
302
+
303
+ // Multiple levels of nesting
304
+ const productsWithReviews = await db
305
+ .with("product_with_reviews", (db) =>
306
+ db
307
+ .selectFrom("product")
308
+ .leftJoin("review", "review.product_id", "product.id")
309
+ .leftJoin("user", "user.id", "review.user_id")
310
+ .select((eb) => [
311
+ "product.id as productId",
312
+ "product.name as productName",
313
+ eb.fn
314
+ .jsonAgg(
315
+ jsonBuildObject({
316
+ reviewId: eb.ref("review.id"),
317
+ rating: eb.ref("review.rating"),
318
+ title: eb.ref("review.title"),
319
+ // Nested object!
320
+ reviewer: jsonBuildObject({
321
+ name: eb.fn<string>("concat", [
322
+ eb.ref("user.first_name"),
323
+ eb.cast(eb.val(" "), "text"),
324
+ eb.ref("user.last_name"),
325
+ ]),
326
+ email: eb.ref("user.email"),
327
+ }),
328
+ })
329
+ )
330
+ // .filterWhere() is available on ALL aggregate functions (count, sum, avg, etc.),
331
+ // not just jsonAgg. See aggregations.ts for general usage.
332
+ .filterWhere("review.id", "is not", null) // Filter nulls!
333
+ .as("reviews"),
334
+ ])
335
+ .groupBy(["product.id", "product.name"])
336
+ )
337
+ .selectFrom("product_with_reviews")
338
+ .selectAll()
339
+ .where("reviews", "is not", null)
340
+ .execute();
341
+
342
+ // ============================================
343
+ // KEY PATTERNS SUMMARY
344
+ // ============================================
345
+
346
+ /*
347
+ 1. JSONB columns:
348
+ - INSERT/UPDATE: Pass objects directly (no JSON.stringify)
349
+ - SELECT: Returns parsed objects (no JSON.parse)
350
+ - The pg driver handles serialization automatically
351
+
352
+ 2. Array columns (text[], int[], etc.):
353
+ - INSERT/UPDATE: Pass arrays directly (no JSON.stringify)
354
+ - SELECT: Returns native JavaScript arrays
355
+ - The pg driver handles this automatically
356
+
357
+ 3. Array queries - operators work natively:
358
+ - @> : .where("tags", "@>", sql`ARRAY[...]::text[]`)
359
+ - && : .where("tags", "&&", sql`ARRAY[...]::text[]`)
360
+ - ANY : .where((eb) => eb(sql`${val}`, "=", eb.fn("any", [eb.ref("tags")])))
361
+ eb.ref() provides type-safety - invalid columns are TS errors
362
+
363
+ 4. JSONB queries - operators that work natively:
364
+ - ? : .where("col", "?", "key")
365
+ - ?| : .where("col", "?|", sql`array[...]`)
366
+ - ?& : .where("col", "?&", sql`array[...]`)
367
+ - @> : .where("col", "@>", sql`'{...}'::jsonb`)
368
+ - <@ : .where("col", "<@", sql`'{...}'::jsonb`)
369
+
370
+ Extraction operators:
371
+ - ->> : eb(eb("col", "->>", "key"), "=", val) (type-safe!)
372
+ - -> : eb("col", "->", "key") (returns JSONB)
373
+ - #> : sql`col#>'{a,b}'` (nested path, needs sql``)
374
+ - #>> : sql`col#>>'{a,b}'` (nested path, needs sql``)
375
+
376
+ 5. JSONPath (PostgreSQL 12+):
377
+ - @@ : .where("col", "@@", sql`'$.path == "val"'`) (native operator!)
378
+ - @? : NOT in allowlist - use jsonb_path_exists() instead
379
+ - Functions (type-safe with eb.fn):
380
+ - jsonb_path_exists(col, path)
381
+ - jsonb_path_query_first(col, path)
382
+ - eb.ref() provides type-safety for column references
383
+
384
+ 6. jsonBuildObject:
385
+ - Import from "kysely/helpers/postgres"
386
+ - Use eb.ref() for column references
387
+ - Can be nested for deep structures
388
+
389
+ 7. jsonAgg:
390
+ - Use eb.fn.jsonAgg() (NOT imported from helpers!)
391
+ - Use .filterWhere() to exclude nulls
392
+ - Combine with jsonBuildObject for structured arrays
393
+
394
+ 8. .filterWhere() (PostgreSQL FILTER (WHERE ...)):
395
+ - Available on ALL aggregate builders, not just jsonAgg
396
+ - Works on: count, countAll, sum, avg, min, max, jsonAgg
397
+ - See aggregations.ts for general examples
398
+ */