@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,167 @@
1
+ /**
2
+ * Aggregation Patterns
3
+ * COUNT, SUM, AVG, GROUP BY, HAVING
4
+ */
5
+ import { db } from "./db";
6
+
7
+ // ============================================
8
+ // BASIC AGGREGATIONS
9
+ // ============================================
10
+
11
+ // COUNT - total rows
12
+ const totalUsers = await db
13
+ .selectFrom("user")
14
+ .select((eb) => eb.fn.count("id").as("totalUsers"))
15
+ .executeTakeFirst();
16
+ // Result: { totalUsers: "5" } - Note: returned as string!
17
+
18
+ // COUNT with GROUP BY
19
+ const usersByRole = await db
20
+ .selectFrom("user")
21
+ .select((eb) => [
22
+ "role",
23
+ eb.fn.count("id").as("count"),
24
+ ])
25
+ .groupBy("role")
26
+ .execute();
27
+ // Result: [{ role: "admin", count: "1" }, { role: "user", count: "3" }]
28
+
29
+ // SUM
30
+ const totalRevenue = await db
31
+ .selectFrom("order")
32
+ .where("status", "=", "completed")
33
+ .select((eb) => eb.fn.sum("total_amount").as("totalRevenue"))
34
+ .executeTakeFirst();
35
+
36
+ // AVG
37
+ const averageRating = await db
38
+ .selectFrom("review")
39
+ .select((eb) => eb.fn.avg("rating").as("averageRating"))
40
+ .executeTakeFirst();
41
+
42
+ // ============================================
43
+ // MULTIPLE AGGREGATIONS
44
+ // ============================================
45
+
46
+ // Multiple aggregations with GROUP BY
47
+ const orderStats = await db
48
+ .selectFrom("order")
49
+ .select((eb) => [
50
+ "status",
51
+ eb.fn.count("id").as("orderCount"),
52
+ eb.fn.sum("total_amount").as("totalAmount"),
53
+ eb.fn.avg("total_amount").as("avgAmount"),
54
+ ])
55
+ .groupBy("status")
56
+ .execute();
57
+
58
+ // ============================================
59
+ // HAVING CLAUSE
60
+ // ============================================
61
+
62
+ // HAVING - filter aggregated results
63
+ // Use HAVING for conditions on aggregated values
64
+ // Use WHERE for conditions on individual rows
65
+ const popularProducts = await db
66
+ .selectFrom("review")
67
+ .innerJoin("product", "product.id", "review.product_id")
68
+ .select((eb) => [
69
+ "product.name",
70
+ eb.fn.count("review.id").as("reviewCount"),
71
+ eb.fn.avg("review.rating").as("avgRating"),
72
+ ])
73
+ .groupBy("product.id")
74
+ .groupBy("product.name") // Group by all non-aggregated columns
75
+ .having((eb) => eb.fn.count("review.id"), ">", 1)
76
+ .execute();
77
+
78
+ // ============================================
79
+ // FILTER (WHERE ...) ON AGGREGATES
80
+ // ============================================
81
+
82
+ // PostgreSQL's FILTER (WHERE ...) clause works on ALL aggregate function builders
83
+ // via .filterWhere(). This is NOT specific to jsonAgg — it works on count, sum, avg, etc.
84
+
85
+ // Basic usage: conditional counting/summing without CASE expressions
86
+ const filteredAggregates = await db
87
+ .selectFrom("user")
88
+ .select((eb) => [
89
+ eb.fn.count("id").filterWhere("status", "=", "active").as("active_count"),
90
+ eb.fn.countAll().filterWhere("role", "!=", "banned").as("non_banned"),
91
+ eb.fn.sum("balance").filterWhere("type", "=", "credit").as("total_credits"),
92
+ eb.fn.avg("rating").filterWhere("verified", "=", true).as("verified_avg_rating"),
93
+ ])
94
+ .executeTakeFirst();
95
+
96
+ // .filterWhere() also works inside .having() as the first argument
97
+ const groupsWithNoUnsigned = await db
98
+ .selectFrom("document")
99
+ .select((eb) => [
100
+ "group_id",
101
+ eb.fn.countAll().as("total"),
102
+ eb.fn.countAll().filterWhere("status", "!=", "signed").as("unsigned_count"),
103
+ ])
104
+ .groupBy("group_id")
105
+ .having(
106
+ (eb) => eb.fn.countAll().filterWhere("status", "!=", "signed"),
107
+ "=",
108
+ 0
109
+ )
110
+ .execute();
111
+
112
+ // ============================================
113
+ // AGGREGATE FUNCTIONS REFERENCE
114
+ // ============================================
115
+
116
+ // Available aggregate functions:
117
+ // eb.fn.count(column) - Count rows
118
+ // eb.fn.countAll() - COUNT(*)
119
+ // eb.fn.sum(column) - Sum values
120
+ // eb.fn.avg(column) - Average value
121
+ // eb.fn.min(column) - Minimum value
122
+ // eb.fn.max(column) - Maximum value
123
+
124
+ // Example with all
125
+ const productStats = await db
126
+ .selectFrom("product")
127
+ .select((eb) => [
128
+ eb.fn.count("id").as("total"),
129
+ eb.fn.countAll().as("totalAll"),
130
+ eb.fn.sum("price").as("totalPrice"),
131
+ eb.fn.avg("price").as("avgPrice"),
132
+ eb.fn.min("price").as("minPrice"),
133
+ eb.fn.max("price").as("maxPrice"),
134
+ ])
135
+ .executeTakeFirst();
136
+
137
+ // ============================================
138
+ // KEY PATTERNS SUMMARY
139
+ // ============================================
140
+
141
+ /*
142
+ 1. Aggregate functions return strings
143
+ - count, sum, avg all return string type by default
144
+ - Cast if you need numbers: eb.fn.count<number>("id")
145
+
146
+ 2. GROUP BY rules:
147
+ - All non-aggregated columns in SELECT must be in GROUP BY
148
+ - Chain multiple .groupBy() calls for multiple columns
149
+
150
+ 3. HAVING vs WHERE:
151
+ - WHERE: filters rows BEFORE aggregation
152
+ - HAVING: filters groups AFTER aggregation
153
+
154
+ 4. Syntax pattern:
155
+ .select((eb) => [
156
+ "groupColumn",
157
+ eb.fn.count("id").as("alias"),
158
+ ])
159
+ .groupBy("groupColumn")
160
+ .having((eb) => eb.fn.count("id"), ">", 5)
161
+
162
+ 5. .filterWhere() - PostgreSQL FILTER (WHERE ...) clause:
163
+ - Available on ALL aggregate function builders (count, countAll, sum, avg, min, max, jsonAgg)
164
+ - Replaces CASE WHEN ... inside aggregates for conditional aggregation
165
+ - Works as the first argument to .having() for filtered aggregate conditions
166
+ - Example: eb.fn.count("id").filterWhere("status", "=", "active").as("active_count")
167
+ */
@@ -0,0 +1,165 @@
1
+ /**
2
+ * CTE (Common Table Expression) Patterns
3
+ * WITH clauses for complex multi-step queries
4
+ */
5
+ import { db } from "./db";
6
+
7
+ // ============================================
8
+ // SIMPLE CTE
9
+ // ============================================
10
+
11
+ // Basic CTE - named subquery
12
+ const activeProducts = await db
13
+ .with("active_products", (db) =>
14
+ db
15
+ .selectFrom("product")
16
+ .select(["id", "name", "price"])
17
+ .where("is_active", "=", true)
18
+ )
19
+ .selectFrom("active_products")
20
+ .selectAll()
21
+ .execute();
22
+
23
+ // ============================================
24
+ // CTE WITH AGGREGATION
25
+ // ============================================
26
+
27
+ // CTE for pre-aggregating data
28
+ const topSpenders = await db
29
+ .with("order_totals", (db) =>
30
+ db
31
+ .selectFrom("order")
32
+ .innerJoin("user", "user.id", "order.user_id")
33
+ .select((eb) => [
34
+ "user.id as userId",
35
+ "user.email",
36
+ eb.fn.sum("order.total_amount").as("totalSpent"),
37
+ eb.fn.count("order.id").as("orderCount"),
38
+ ])
39
+ .groupBy(["user.id", "user.email"])
40
+ )
41
+ .selectFrom("order_totals")
42
+ .selectAll()
43
+ .orderBy("totalSpent", "desc")
44
+ .execute();
45
+
46
+ // ============================================
47
+ // MULTIPLE CTEs
48
+ // ============================================
49
+
50
+ // Chain CTEs for multi-step processing
51
+ const topRatedProducts = await db
52
+ // First CTE: aggregate reviews
53
+ .with("product_reviews", (db) =>
54
+ db
55
+ .selectFrom("review")
56
+ .select((eb) => [
57
+ "product_id",
58
+ eb.fn.avg("rating").as("avgRating"),
59
+ eb.fn.count("id").as("reviewCount"),
60
+ ])
61
+ .groupBy("product_id")
62
+ )
63
+ // Second CTE: join with products
64
+ .with("top_products", (db) =>
65
+ db
66
+ .selectFrom("product")
67
+ .leftJoin("product_reviews", "product_reviews.product_id", "product.id")
68
+ .select([
69
+ "product.name",
70
+ "product.price",
71
+ "product_reviews.avgRating",
72
+ "product_reviews.reviewCount",
73
+ ])
74
+ .where("product.is_active", "=", true)
75
+ )
76
+ // Final query: select from last CTE
77
+ .selectFrom("top_products")
78
+ .selectAll()
79
+ .orderBy("avgRating", (ob) => ob.desc().nullsLast())
80
+ .execute();
81
+
82
+ // ============================================
83
+ // CTE USE CASES
84
+ // ============================================
85
+
86
+ // Use Case 1: Break down complex logic
87
+ const dashboardData = await db
88
+ .with("monthly_orders", (db) =>
89
+ db
90
+ .selectFrom("order")
91
+ .select((eb) => [
92
+ eb.fn.count("id").as("orderCount"),
93
+ eb.fn.sum("total_amount").as("revenue"),
94
+ ])
95
+ .where("status", "=", "completed")
96
+ )
97
+ .with("active_users", (db) =>
98
+ db
99
+ .selectFrom("user")
100
+ .select((eb) => eb.fn.count("id").as("userCount"))
101
+ .where("is_active", "=", true)
102
+ )
103
+ .selectFrom("monthly_orders")
104
+ .innerJoin("active_users", (join) =>
105
+ join.on((eb) => eb.lit(true)) // Cross join
106
+ )
107
+ .select([
108
+ "monthly_orders.orderCount",
109
+ "monthly_orders.revenue",
110
+ "active_users.userCount",
111
+ ])
112
+ .executeTakeFirst();
113
+
114
+ // Use Case 2: Self-referencing/recursive (for hierarchies)
115
+ // Note: Recursive CTEs use withRecursive
116
+ const categoryHierarchy = await db
117
+ .withRecursive("category_tree", (db) =>
118
+ // Base case: root categories
119
+ db
120
+ .selectFrom("category")
121
+ .select(["id", "name", "parent_id"])
122
+ .where("parent_id", "is", null)
123
+ .unionAll(
124
+ // Recursive case: children
125
+ db
126
+ .selectFrom("category as c")
127
+ .innerJoin("category_tree as ct", "ct.id", "c.parent_id")
128
+ .select(["c.id", "c.name", "c.parent_id"])
129
+ )
130
+ )
131
+ .selectFrom("category_tree")
132
+ .selectAll()
133
+ .execute();
134
+
135
+ // ============================================
136
+ // KEY PATTERNS SUMMARY
137
+ // ============================================
138
+
139
+ /*
140
+ 1. Basic CTE syntax:
141
+ db.with("name", (db) => db.selectFrom(...))
142
+ .selectFrom("name")
143
+ .selectAll()
144
+
145
+ 2. Multiple CTEs - chain .with() calls:
146
+ db.with("cte1", ...)
147
+ .with("cte2", ...) // Can reference cte1
148
+ .selectFrom("cte2")
149
+
150
+ 3. CTEs can reference earlier CTEs:
151
+ - cte2 can use cte1 in its FROM
152
+ - Order matters!
153
+
154
+ 4. Recursive CTEs:
155
+ - Use .withRecursive()
156
+ - Base case UNION ALL recursive case
157
+ - Recursive case joins to the CTE itself
158
+
159
+ 5. When to use CTEs:
160
+ - Complex multi-step aggregations
161
+ - Reusing a subquery multiple times
162
+ - Breaking down complex logic
163
+ - Recursive/hierarchical data
164
+ - Improving query readability
165
+ */
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Expression Patterns
3
+ * CASE, $if, subqueries, eb functions, advanced expression building
4
+ */
5
+ import { db } from "./db";
6
+ import { sql, expressionBuilder, Expression, SqlBool } from "kysely";
7
+ import type { DB } from "./db.d";
8
+
9
+ // ============================================
10
+ // CASE EXPRESSIONS
11
+ // ============================================
12
+
13
+ // Simple CASE
14
+ const ordersWithLabels = await db
15
+ .selectFrom("order")
16
+ .select((eb) => [
17
+ "id",
18
+ "status",
19
+ eb
20
+ .case()
21
+ .when("status", "=", "completed")
22
+ .then("Done")
23
+ .when("status", "=", "shipped")
24
+ .then("In Transit")
25
+ .when("status", "=", "pending")
26
+ .then("Processing")
27
+ .else("Unknown")
28
+ .end()
29
+ .as("statusLabel"),
30
+ ])
31
+ .execute();
32
+
33
+ // ============================================
34
+ // $if - CONDITIONAL QUERY BUILDING
35
+ // ============================================
36
+
37
+ // $if for conditional WHERE
38
+ const includeInactive = false;
39
+ const users = await db
40
+ .selectFrom("user")
41
+ .selectAll()
42
+ .$if(!includeInactive, (qb) => qb.where("is_active", "=", true))
43
+ .execute();
44
+
45
+ // $if for conditional SELECT columns
46
+ const includeMetadata = true;
47
+ const usersWithOptionalMeta = await db
48
+ .selectFrom("user")
49
+ .select(["id", "email", "first_name", "last_name"])
50
+ .$if(includeMetadata, (qb) => qb.select("metadata"))
51
+ .execute();
52
+ // Note: metadata becomes OPTIONAL in result type (metadata?: Json)
53
+
54
+ // Multiple $if conditions
55
+ const filterRole: string | null = "admin";
56
+ const onlyActive = true;
57
+ const filteredUsers = await db
58
+ .selectFrom("user")
59
+ .selectAll()
60
+ .$if(!!filterRole, (qb) => qb.where("role", "=", filterRole!))
61
+ .$if(onlyActive, (qb) => qb.where("is_active", "=", true))
62
+ .execute();
63
+
64
+ // ============================================
65
+ // SUBQUERIES
66
+ // ============================================
67
+
68
+ // Subquery in WHERE (IN)
69
+ const usersWithOrders = await db
70
+ .selectFrom("user")
71
+ .selectAll()
72
+ .where(
73
+ "id",
74
+ "in",
75
+ db.selectFrom("order").select("user_id").where("status", "=", "completed")
76
+ )
77
+ .execute();
78
+
79
+ // EXISTS subquery
80
+ const productsWithReviews = await db
81
+ .selectFrom("product")
82
+ .selectAll()
83
+ .where((eb) =>
84
+ eb.exists(
85
+ db
86
+ .selectFrom("review")
87
+ .select(sql`1`.as("one"))
88
+ .whereRef("review.product_id", "=", eb.ref("product.id"))
89
+ )
90
+ )
91
+ .execute();
92
+
93
+ // ============================================
94
+ // eb.fn - FUNCTION CALLS
95
+ // ============================================
96
+
97
+ // String functions with eb.fn<ReturnType>
98
+ const userNames = await db
99
+ .selectFrom("user")
100
+ .select((eb) => [
101
+ eb.fn<string>("concat", [
102
+ eb.ref("first_name"),
103
+ eb.cast(eb.val(" "), "text"), // Cast string literals!
104
+ eb.ref("last_name"),
105
+ ]).as("fullName"),
106
+ eb.fn<string>("upper", [eb.ref("email")]).as("upperEmail"),
107
+ eb.fn<number>("length", [eb.ref("email")]).as("emailLength"),
108
+ ])
109
+ .execute();
110
+
111
+ // COALESCE
112
+ const productsWithDefault = await db
113
+ .selectFrom("product")
114
+ .select((eb) => [
115
+ "name",
116
+ eb.fn.coalesce("cost", sql`0`).as("costOrZero"),
117
+ ])
118
+ .execute();
119
+
120
+ // ============================================
121
+ // BINARY EXPRESSIONS
122
+ // ============================================
123
+
124
+ // Arithmetic with eb()
125
+ const lineItems = await db
126
+ .selectFrom("order_item")
127
+ .select((eb) => [
128
+ "id",
129
+ "quantity",
130
+ "unit_price",
131
+ eb("quantity", "*", eb.ref("unit_price")).as("lineTotal"),
132
+ ])
133
+ .execute();
134
+
135
+ // ============================================
136
+ // eb.val() vs eb.lit()
137
+ // ============================================
138
+
139
+ // eb.val() - Parameterized value ($1, $2) - SAFER for user input
140
+ // Note: May need cast for type inference
141
+ const searchTerm = "alice";
142
+ const searchResults = await db
143
+ .selectFrom("user")
144
+ .select(["id", "email"])
145
+ .where("email", "like", (eb) =>
146
+ eb.fn<string>("concat", [
147
+ eb.cast(eb.val("%"), "text"),
148
+ eb.cast(eb.val(searchTerm), "text"),
149
+ eb.cast(eb.val("%"), "text"),
150
+ ])
151
+ )
152
+ .execute();
153
+
154
+ // eb.lit() - Literal in SQL - ONLY for numbers, booleans, null
155
+ // THROWS "unsafe immediate value" for strings!
156
+ const priceCategories = await db
157
+ .selectFrom("product")
158
+ .select((eb) => [
159
+ "name",
160
+ "price",
161
+ eb
162
+ .case()
163
+ .when("price", ">", eb.lit(100)) // Number literal - OK
164
+ .then(sql<string>`'expensive'`) // String - use sql``
165
+ .when("price", ">", eb.lit(50)) // Number literal - OK
166
+ .then(sql<string>`'moderate'`) // String - use sql``
167
+ .else(sql<string>`'cheap'`) // String - use sql``
168
+ .end()
169
+ .as("priceCategory"),
170
+ ])
171
+ .execute();
172
+
173
+ // ============================================
174
+ // eb.not() - NEGATION
175
+ // ============================================
176
+
177
+ // Negate a condition
178
+ const inactiveProducts = await db
179
+ .selectFrom("product")
180
+ .select(["name", "is_active"])
181
+ .where((eb) => eb.not(eb("is_active", "=", true)))
182
+ .execute();
183
+
184
+ // NOT EXISTS
185
+ const usersWithoutOrders = await db
186
+ .selectFrom("user")
187
+ .select(["id", "email"])
188
+ .where((eb) =>
189
+ eb.not(
190
+ eb.exists(
191
+ db
192
+ .selectFrom("order")
193
+ .select(sql`1`.as("one"))
194
+ .whereRef("order.user_id", "=", "user.id")
195
+ )
196
+ )
197
+ )
198
+ .execute();
199
+
200
+ // ============================================
201
+ // STANDALONE expressionBuilder
202
+ // ============================================
203
+
204
+ // Build expressions outside query context
205
+ const eb = expressionBuilder<DB, "user">();
206
+ const activeCondition = eb("is_active", "=", true);
207
+ const adminCondition = eb("role", "=", "admin");
208
+
209
+ const activeAdmins = await db
210
+ .selectFrom("user")
211
+ .select(["id", "email", "role"])
212
+ .where((qb) => qb.and([activeCondition, adminCondition]))
213
+ .execute();
214
+
215
+ // ============================================
216
+ // CONDITIONAL EXPRESSIONS WITH ARRAYS
217
+ // ============================================
218
+
219
+ // Build conditions dynamically
220
+ const conditions: Expression<SqlBool>[] = [];
221
+ const productEb = expressionBuilder<DB, "product">();
222
+
223
+ const filterActive = true;
224
+ const minPrice = 50;
225
+ const maxPrice: number | null = null;
226
+
227
+ if (filterActive) {
228
+ conditions.push(productEb("is_active", "=", true));
229
+ }
230
+ if (minPrice) {
231
+ conditions.push(productEb("price", ">=", String(minPrice)));
232
+ }
233
+ if (maxPrice) {
234
+ conditions.push(productEb("price", "<=", String(maxPrice)));
235
+ }
236
+
237
+ const filteredProducts = await db
238
+ .selectFrom("product")
239
+ .select(["name", "price", "is_active"])
240
+ .$if(conditions.length > 0, (qb) => qb.where((eb) => eb.and(conditions)))
241
+ .execute();
242
+
243
+ // ============================================
244
+ // KEY PATTERNS SUMMARY
245
+ // ============================================
246
+
247
+ /*
248
+ 1. CASE expressions:
249
+ eb.case().when(...).then(...).else(...).end()
250
+
251
+ 2. $if for conditional query parts:
252
+ .$if(condition, (qb) => qb.where/select/etc)
253
+ - Columns added via $if become OPTIONAL in type
254
+
255
+ 3. eb.val() vs eb.lit():
256
+ - eb.val(): Parameterized ($1) - use for user input
257
+ - eb.lit(): Literal - ONLY numbers, booleans, null
258
+ - For string literals: use sql`'string'`
259
+
260
+ 4. eb.not(): Negate any expression
261
+ eb.not(eb("col", "=", value))
262
+ eb.not(eb.exists(...))
263
+
264
+ 5. Standalone expressionBuilder:
265
+ const eb = expressionBuilder<DB, "table">();
266
+ - Build expressions outside query context
267
+ - Useful for dynamic condition building
268
+
269
+ 6. Expression<SqlBool>[] arrays:
270
+ - Build conditions dynamically
271
+ - Combine with eb.and([...]) or eb.or([...])
272
+ */