sumak 0.0.4 → 0.0.6

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 (121) hide show
  1. package/README.md +409 -5
  2. package/dist/ast/expression.d.mts +26 -0
  3. package/dist/ast/expression.mjs +140 -0
  4. package/dist/ast/nodes.d.mts +309 -0
  5. package/dist/ast/nodes.mjs +59 -0
  6. package/dist/ast/transformer.d.mts +10 -0
  7. package/dist/ast/transformer.mjs +140 -0
  8. package/dist/ast/typed-expression.d.mts +37 -0
  9. package/dist/ast/typed-expression.mjs +77 -0
  10. package/dist/ast/visitor.d.mts +13 -0
  11. package/dist/ast/visitor.mjs +11 -0
  12. package/dist/builder/delete.d.mts +20 -0
  13. package/dist/builder/delete.mjs +113 -0
  14. package/dist/builder/eb.d.mts +312 -0
  15. package/dist/builder/eb.mjs +641 -0
  16. package/dist/builder/expression.d.mts +5 -0
  17. package/dist/builder/expression.mjs +10 -0
  18. package/dist/builder/insert.d.mts +40 -0
  19. package/dist/builder/insert.mjs +146 -0
  20. package/dist/builder/merge.d.mts +20 -0
  21. package/dist/builder/merge.mjs +100 -0
  22. package/dist/builder/raw.d.mts +2 -0
  23. package/dist/builder/raw.mjs +4 -0
  24. package/dist/builder/select.d.mts +41 -0
  25. package/dist/builder/select.mjs +280 -0
  26. package/dist/builder/typed-delete.d.mts +49 -0
  27. package/dist/builder/typed-delete.mjs +89 -0
  28. package/dist/builder/typed-insert.d.mts +86 -0
  29. package/dist/builder/typed-insert.mjs +159 -0
  30. package/dist/builder/typed-merge.d.mts +31 -0
  31. package/dist/builder/typed-merge.mjs +93 -0
  32. package/dist/builder/typed-select.d.mts +164 -0
  33. package/dist/builder/typed-select.mjs +309 -0
  34. package/dist/builder/typed-update.d.mts +59 -0
  35. package/dist/builder/typed-update.mjs +110 -0
  36. package/dist/builder/update.d.mts +20 -0
  37. package/dist/builder/update.mjs +121 -0
  38. package/dist/dialect/mssql.d.mts +2 -0
  39. package/dist/dialect/mssql.mjs +9 -0
  40. package/dist/dialect/mysql.d.mts +2 -0
  41. package/dist/dialect/mysql.mjs +9 -0
  42. package/dist/dialect/pg.d.mts +2 -0
  43. package/dist/dialect/pg.mjs +9 -0
  44. package/dist/dialect/sqlite.d.mts +2 -0
  45. package/dist/dialect/sqlite.mjs +9 -0
  46. package/dist/dialect/types.d.mts +6 -0
  47. package/dist/dialect/types.mjs +1 -0
  48. package/dist/errors.d.mts +12 -0
  49. package/dist/errors.mjs +24 -0
  50. package/dist/index.d.mts +50 -806
  51. package/dist/index.mjs +48 -3
  52. package/dist/mssql.d.mts +2 -2
  53. package/dist/mssql.mjs +2 -1
  54. package/dist/mysql.d.mts +2 -2
  55. package/dist/mysql.mjs +2 -1
  56. package/dist/pg.d.mts +2 -2
  57. package/dist/pg.mjs +2 -1
  58. package/dist/plugin/camel-case.d.mts +11 -0
  59. package/dist/plugin/camel-case.mjs +16 -0
  60. package/dist/plugin/hooks.d.mts +72 -0
  61. package/dist/plugin/hooks.mjs +49 -0
  62. package/dist/plugin/plugin-manager.d.mts +17 -0
  63. package/dist/plugin/plugin-manager.mjs +37 -0
  64. package/dist/plugin/soft-delete.d.mts +27 -0
  65. package/dist/plugin/soft-delete.mjs +52 -0
  66. package/dist/plugin/types.d.mts +19 -0
  67. package/dist/plugin/types.mjs +1 -0
  68. package/dist/plugin/with-schema.d.mts +21 -0
  69. package/dist/plugin/with-schema.mjs +53 -0
  70. package/dist/printer/base.d.mts +49 -0
  71. package/dist/printer/base.mjs +472 -0
  72. package/dist/printer/document.d.mts +45 -0
  73. package/dist/printer/document.mjs +153 -0
  74. package/dist/printer/formatter.d.mts +5 -0
  75. package/dist/printer/formatter.mjs +134 -0
  76. package/dist/printer/mssql.d.mts +10 -0
  77. package/dist/printer/mssql.mjs +161 -0
  78. package/dist/printer/mysql.d.mts +8 -0
  79. package/dist/printer/mysql.mjs +41 -0
  80. package/dist/printer/pg.d.mts +6 -0
  81. package/dist/printer/pg.mjs +9 -0
  82. package/dist/printer/sqlite.d.mts +8 -0
  83. package/dist/printer/sqlite.mjs +29 -0
  84. package/dist/printer/types.d.mts +11 -0
  85. package/dist/printer/types.mjs +1 -0
  86. package/dist/schema/column.d.mts +52 -0
  87. package/dist/schema/column.mjs +120 -0
  88. package/dist/schema/index.d.mts +6 -0
  89. package/dist/schema/index.mjs +4 -0
  90. package/dist/schema/table.d.mts +37 -0
  91. package/dist/schema/table.mjs +7 -0
  92. package/dist/schema/type-utils.d.mts +46 -0
  93. package/dist/schema/type-utils.mjs +1 -0
  94. package/dist/schema/types.d.mts +64 -0
  95. package/dist/schema/types.mjs +1 -0
  96. package/dist/schema.d.mts +2 -2
  97. package/dist/schema.mjs +1 -1
  98. package/dist/sqlite.d.mts +2 -2
  99. package/dist/sqlite.mjs +2 -1
  100. package/dist/sumak.d.mts +110 -0
  101. package/dist/sumak.mjs +141 -0
  102. package/dist/types.d.mts +14 -0
  103. package/dist/types.mjs +1 -0
  104. package/dist/utils/identifier.d.mts +3 -0
  105. package/dist/utils/identifier.mjs +14 -0
  106. package/dist/utils/param.d.mts +2 -0
  107. package/dist/utils/param.mjs +8 -0
  108. package/package.json +1 -1
  109. package/dist/_chunks/base.mjs +0 -1
  110. package/dist/_chunks/errors.mjs +0 -1
  111. package/dist/_chunks/index.d.mts +0 -136
  112. package/dist/_chunks/mssql.d.mts +0 -11
  113. package/dist/_chunks/mssql.mjs +0 -1
  114. package/dist/_chunks/mysql.d.mts +0 -9
  115. package/dist/_chunks/mysql.mjs +0 -1
  116. package/dist/_chunks/pg.d.mts +0 -7
  117. package/dist/_chunks/pg.mjs +0 -1
  118. package/dist/_chunks/schema.mjs +0 -1
  119. package/dist/_chunks/sqlite.d.mts +0 -9
  120. package/dist/_chunks/sqlite.mjs +0 -1
  121. package/dist/_chunks/types.d.mts +0 -338
package/README.md CHANGED
@@ -150,6 +150,19 @@ db.selectFrom("users").crossJoin("posts").compile(db.printer())
150
150
  .where(({ name }) =>
151
151
  name.like("%ali%"),
152
152
  )
153
+
154
+ .where(({ name }) =>
155
+ name.notLike("%bob%"),
156
+ )
157
+
158
+ // Case-insensitive (PG)
159
+ .where(({ name }) =>
160
+ name.ilike("%alice%"),
161
+ )
162
+
163
+ .where(({ email }) =>
164
+ email.notIlike("%spam%"),
165
+ )
153
166
  ```
154
167
 
155
168
  ### Range & List
@@ -159,6 +172,15 @@ db.selectFrom("users").crossJoin("posts").compile(db.printer())
159
172
  age.between(18, 65),
160
173
  )
161
174
 
175
+ .where(({ age }) =>
176
+ age.notBetween(18, 65),
177
+ )
178
+
179
+ // Order-independent (PG)
180
+ .where(({ age }) =>
181
+ age.betweenSymmetric(65, 18),
182
+ )
183
+
162
184
  .where(({ id }) =>
163
185
  id.in([1, 2, 3]),
164
186
  )
@@ -205,13 +227,30 @@ db.selectFrom("users").crossJoin("posts").compile(db.printer())
205
227
  )
206
228
  ```
207
229
 
230
+ ### Null-Safe Comparisons
231
+
232
+ ```ts
233
+ // IS DISTINCT FROM — null-safe inequality
234
+ .where(({ age }) =>
235
+ age.isDistinctFrom(null),
236
+ )
237
+
238
+ // IS NOT DISTINCT FROM — null-safe equality
239
+ .where(({ age }) =>
240
+ age.isNotDistinctFrom(25),
241
+ )
242
+ ```
243
+
208
244
  ### Aggregates
209
245
 
210
246
  ```ts
211
- import { count, sum, avg, min, max, coalesce } from "sumak"
247
+ import { count, countDistinct, sumDistinct, avgDistinct, sum, avg, min, max, coalesce } from "sumak"
212
248
 
213
249
  db.selectFrom("users").selectExpr(count(), "total").compile(db.printer())
214
250
 
251
+ db.selectFrom("users").selectExpr(countDistinct(col.dept), "uniqueDepts").compile(db.printer())
252
+ // SELECT COUNT(DISTINCT "dept") AS "uniqueDepts" FROM "users"
253
+
215
254
  db.selectFrom("orders").selectExpr(sum(col.amount), "totalAmount").compile(db.printer())
216
255
 
217
256
  db.selectFrom("orders").selectExpr(avg(col.amount), "avgAmount").compile(db.printer())
@@ -219,6 +258,50 @@ db.selectFrom("orders").selectExpr(avg(col.amount), "avgAmount").compile(db.prin
219
258
  db.selectFrom("orders")
220
259
  .selectExpr(coalesce(col.discount, val(0)), "safeDiscount")
221
260
  .compile(db.printer())
261
+
262
+ // SUM(DISTINCT), AVG(DISTINCT)
263
+ db.selectFrom("orders").selectExpr(sumDistinct(col.amount), "uniqueSum").compile(db.printer())
264
+ db.selectFrom("orders").selectExpr(avgDistinct(col.amount), "uniqueAvg").compile(db.printer())
265
+
266
+ // COALESCE with multiple fallbacks
267
+ db.selectFrom("users")
268
+ .selectExpr(coalesce(col.nick, col.name, val("Anonymous")), "displayName")
269
+ .compile(db.printer())
270
+ ```
271
+
272
+ ### String & JSON Aggregates
273
+
274
+ ```ts
275
+ import { stringAgg, arrayAgg, jsonAgg, jsonBuildObject } from "sumak"
276
+
277
+ // STRING_AGG with ORDER BY
278
+ db.selectFrom("users")
279
+ .selectExpr(stringAgg(col.name, ", ", [{ expr: col.name, direction: "ASC" }]), "names")
280
+ .compile(db.printer())
281
+ // STRING_AGG("name", ', ' ORDER BY "name" ASC)
282
+
283
+ // ARRAY_AGG
284
+ db.selectFrom("users").selectExpr(arrayAgg(col.id), "ids").compile(db.printer())
285
+
286
+ // JSON_AGG / JSON_BUILD_OBJECT
287
+ db.selectFrom("users").selectExpr(jsonAgg(col.name), "namesJson").compile(db.printer())
288
+
289
+ db.selectFrom("users")
290
+ .selectExpr(jsonBuildObject(["name", col.name], ["age", col.age]), "obj")
291
+ .compile(db.printer())
292
+ ```
293
+
294
+ ### Arithmetic
295
+
296
+ ```ts
297
+ import { add, sub, mul, div, mod, neg } from "sumak"
298
+
299
+ db.selectFrom("orders").selectExpr(mul(col.price, col.qty), "total").compile(db.printer())
300
+ // ("price" * "qty") AS "total"
301
+
302
+ db.selectFrom("orders")
303
+ .selectExpr(add(col.price, val(10)), "adjusted")
304
+ .compile(db.printer())
222
305
  ```
223
306
 
224
307
  ### EXISTS / NOT EXISTS
@@ -292,6 +375,122 @@ db.selectFrom("users")
292
375
  .compile(db.printer())
293
376
  ```
294
377
 
378
+ ## Window Functions
379
+
380
+ ```ts
381
+ import { over, rowNumber, rank, denseRank, lag, lead, ntile, count, sum } from "sumak"
382
+
383
+ // ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)
384
+ db.selectFrom("employees")
385
+ .selectExpr(
386
+ over(rowNumber(), (w) => w.partitionBy("dept").orderBy("salary", "DESC")),
387
+ "rn",
388
+ )
389
+ .compile(db.printer())
390
+
391
+ // RANK() OVER (ORDER BY score DESC)
392
+ db.selectFrom("students")
393
+ .selectExpr(
394
+ over(rank(), (w) => w.orderBy("score", "DESC")),
395
+ "rnk",
396
+ )
397
+ .compile(db.printer())
398
+
399
+ // Running total with frame
400
+ db.selectFrom("orders")
401
+ .selectExpr(
402
+ over(sum(col.amount), (w) =>
403
+ w
404
+ .partitionBy("userId")
405
+ .orderBy("createdAt")
406
+ .rows({ type: "unbounded_preceding" }, { type: "current_row" }),
407
+ ),
408
+ "runningTotal",
409
+ )
410
+ .compile(db.printer())
411
+
412
+ // LAG / LEAD
413
+ db.selectFrom("prices")
414
+ .selectExpr(
415
+ over(lag(col.price, 1), (w) => w.orderBy("date")),
416
+ "prevPrice",
417
+ )
418
+ .compile(db.printer())
419
+
420
+ // NTILE(4)
421
+ db.selectFrom("employees")
422
+ .selectExpr(
423
+ over(ntile(4), (w) => w.orderBy("salary", "DESC")),
424
+ "quartile",
425
+ )
426
+ .compile(db.printer())
427
+ ```
428
+
429
+ ## SQL Functions
430
+
431
+ ### String Functions
432
+
433
+ ```ts
434
+ import { upper, lower, concat, substring, trim, length } from "sumak"
435
+
436
+ db.selectFrom("users").selectExpr(upper(col.name), "upperName").compile(db.printer())
437
+ // SELECT UPPER("name") AS "upperName" FROM "users"
438
+
439
+ db.selectFrom("users").selectExpr(lower(col.email), "lowerEmail").compile(db.printer())
440
+
441
+ db.selectFrom("users")
442
+ .selectExpr(concat(col.firstName, val(" "), col.lastName), "fullName")
443
+ .compile(db.printer())
444
+
445
+ db.selectFrom("users")
446
+ .selectExpr(substring(col.name, 1, 3), "prefix")
447
+ .compile(db.printer())
448
+
449
+ db.selectFrom("users").selectExpr(trim(col.name), "trimmed").compile(db.printer())
450
+
451
+ db.selectFrom("users").selectExpr(length(col.name), "nameLen").compile(db.printer())
452
+ ```
453
+
454
+ ### Numeric Functions
455
+
456
+ ```ts
457
+ import { abs, round, ceil, floor } from "sumak"
458
+
459
+ db.selectFrom("orders").selectExpr(abs(col.balance), "absBalance").compile(db.printer())
460
+
461
+ db.selectFrom("orders").selectExpr(round(col.price, 2), "rounded").compile(db.printer())
462
+
463
+ db.selectFrom("orders").selectExpr(ceil(col.amount), "ceiling").compile(db.printer())
464
+
465
+ db.selectFrom("orders").selectExpr(floor(col.amount), "floored").compile(db.printer())
466
+ ```
467
+
468
+ ### Conditional Functions
469
+
470
+ ```ts
471
+ import { nullif, greatest, least } from "sumak"
472
+
473
+ db.selectFrom("users")
474
+ .selectExpr(nullif(col.age, val(0)), "ageOrNull")
475
+ .compile(db.printer())
476
+
477
+ db.selectFrom("products")
478
+ .selectExpr(greatest(col.price, col.minPrice), "effectivePrice")
479
+ .compile(db.printer())
480
+
481
+ db.selectFrom("products")
482
+ .selectExpr(least(col.price, col.maxPrice), "cappedPrice")
483
+ .compile(db.printer())
484
+ ```
485
+
486
+ ### Date/Time Functions
487
+
488
+ ```ts
489
+ import { now, currentTimestamp } from "sumak"
490
+
491
+ db.selectFrom("users").selectExpr(now(), "currentTime").compile(db.printer())
492
+ ```
493
+
295
494
  ## Set Operations
296
495
 
297
496
  ```ts
@@ -311,11 +510,13 @@ active.union(premium).compile(db.printer())
311
510
  // UNION ALL
312
511
  active.unionAll(premium).compile(db.printer())
313
512
 
314
- // INTERSECT
513
+ // INTERSECT / INTERSECT ALL
315
514
  active.intersect(premium).compile(db.printer())
515
+ active.intersectAll(premium).compile(db.printer())
316
516
 
317
- // EXCEPT
517
+ // EXCEPT / EXCEPT ALL
318
518
  active.except(premium).compile(db.printer())
519
+ active.exceptAll(premium).compile(db.printer())
319
520
  ```
320
521
 
321
522
  ## CTEs (WITH)
@@ -362,20 +563,95 @@ db.update("users")
362
563
  // UPDATE "users" SET "name" = $1 FROM "posts" WHERE ("id" = $2)
363
564
  ```
364
565
 
566
+ ## Conditional Query Building
567
+
568
+ ```ts
569
+ const withFilter = true
570
+ const withOrder = false
571
+
572
+ db.selectFrom("users")
573
+ .select("id", "name")
574
+ .$if(withFilter, (qb) => qb.where(({ age }) => age.gt(18)))
575
+ .$if(withOrder, (qb) => qb.orderBy("name"))
576
+ .compile(db.printer())
577
+ // WHERE applied, ORDER BY skipped
578
+
579
+ // Multiple .where() calls are AND'd together
580
+ db.selectFrom("users")
581
+ .select("id")
582
+ .where(({ age }) => age.gt(18))
583
+ .where(({ active }) => active.eq(true))
584
+ .compile(db.printer())
585
+ // WHERE ("age" > $1) AND ("active" = $2)
586
+ ```
587
+
588
+ ## Reusable Query Fragments
589
+
590
+ ```ts
591
+ // $call — pipe builder through a function
592
+ const withPagination = (qb) => qb.limit(10).offset(20)
593
+ const withActiveFilter = (qb) => qb.where(({ active }) => active.eq(true))
594
+
595
+ db.selectFrom("users")
596
+ .select("id", "name")
597
+ .$call(withActiveFilter)
598
+ .$call(withPagination)
599
+ .compile(db.printer())
600
+
601
+ // selectExprs — multiple aliased expressions at once
602
+ db.selectFrom("users")
603
+ .selectExprs({
604
+ total: count(),
605
+ greeting: val("hello"),
606
+ })
607
+ .compile(db.printer())
608
+ ```
609
+
610
+ ## INSERT Advanced
611
+
612
+ ```ts
613
+ // INSERT ... SELECT
614
+ const selectQuery = db.selectFrom("users").select("name", "age").build()
615
+ db.insertInto("archive").fromSelect(selectQuery).compile(db.printer())
616
+
617
+ // INSERT ... DEFAULT VALUES
618
+ db.insertInto("users").defaultValues().compile(db.printer())
619
+
620
+ // SQLite: INSERT OR IGNORE / INSERT OR REPLACE
621
+ db.insertInto("users").values({ name: "Alice" }).orIgnore().compile(db.printer())
622
+ // INSERT OR IGNORE INTO "users" ...
623
+
624
+ db.insertInto("users").values({ name: "Alice" }).orReplace().compile(db.printer())
625
+ // INSERT OR REPLACE INTO "users" ...
626
+ ```
627
+
365
628
  ## ON CONFLICT
366
629
 
367
630
  ```ts
368
- // DO NOTHING
631
+ // DO NOTHING (by columns)
369
632
  db.insertInto("users")
370
633
  .values({ name: "Alice", email: "a@b.com" })
371
634
  .onConflictDoNothing("email")
372
635
  .compile(db.printer())
373
636
 
374
- // DO UPDATE
637
+ // DO UPDATE (by columns)
375
638
  db.insertInto("users")
376
639
  .values({ name: "Alice", email: "a@b.com" })
377
640
  .onConflictDoUpdate(["email"], [{ column: "name", value: val("Alice") }])
378
641
  .compile(db.printer())
642
+
643
+ // DO NOTHING (by constraint name)
644
+ db.insertInto("users")
645
+ .values({ name: "Alice", email: "a@b.com" })
646
+ .onConflictConstraintDoNothing("users_email_key")
647
+ .compile(db.printer())
648
+ // ON CONFLICT ON CONSTRAINT "users_email_key" DO NOTHING
649
+
650
+ // MySQL: ON DUPLICATE KEY UPDATE
651
+ db.insertInto("users")
652
+ .values({ name: "Alice" })
653
+ .onDuplicateKeyUpdate([{ column: "name", value: val("Alice") }])
654
+ .compile(db.printer())
379
655
  ```
380
656
 
381
657
  ## MERGE (SQL:2003)
@@ -395,6 +671,134 @@ db.mergeInto("users", "staging", "s", ({ target, source }) => target.id.eqCol(so
395
671
  .compile(db.printer())
396
672
  ```
397
673
 
674
+ ## Row Locking
675
+
676
+ ```ts
677
+ // FOR UPDATE
678
+ db.selectFrom("users").select("id").forUpdate().compile(db.printer())
679
+
680
+ // FOR SHARE
681
+ db.selectFrom("users").select("id").forShare().compile(db.printer())
682
+
683
+ // FOR NO KEY UPDATE / FOR KEY SHARE (PG)
684
+ db.selectFrom("users").select("id").forNoKeyUpdate().compile(db.printer())
685
+ db.selectFrom("users").select("id").forKeyShare().compile(db.printer())
686
+
687
+ // SKIP LOCKED / NOWAIT
688
+ db.selectFrom("users").select("id").forUpdate().skipLocked().compile(db.printer())
689
+ db.selectFrom("users").select("id").forUpdate().noWait().compile(db.printer())
690
+ ```
691
+
692
+ ## DISTINCT ON (PG)
693
+
694
+ ```ts
695
+ db.selectFrom("users")
696
+ .selectAll()
697
+ .distinctOn("dept")
698
+ .orderBy("dept")
699
+ .orderBy("salary", "DESC")
700
+ .compile(db.printer())
701
+ // SELECT DISTINCT ON ("dept") * FROM "users" ORDER BY "dept" ASC, "salary" DESC
702
+ ```
703
+
704
+ ## DELETE USING / JOIN in UPDATE & DELETE
705
+
706
+ ```ts
707
+ // PG: DELETE ... USING
708
+ db.deleteFrom("orders")
709
+ .using("users")
710
+ .where(eq(col("orders.user_id"), col("users.id")))
711
+ .compile(db.printer())
712
+
713
+ // MySQL: DELETE with JOIN
714
+ db.deleteFrom("orders")
715
+ .innerJoin("users", eq(col("user_id", "orders"), col("id", "users")))
716
+ .where(eq(col("name", "users"), lit("Alice")))
717
+ .compile(db.printer())
718
+
719
+ // MySQL: UPDATE with JOIN
720
+ db.update("orders")
721
+ .set({ total: 0 })
722
+ .innerJoin("users", eq(col("user_id", "orders"), col("id", "users")))
723
+ .compile(db.printer())
724
+ ```
725
+
726
+ ## Lateral JOIN
727
+
728
+ ```ts
729
+ // INNER JOIN LATERAL — correlated subquery join
730
+ const recentPosts = db
731
+ .selectFrom("posts")
732
+ .select("id", "title")
733
+ .where(({ userId }) => userId.eq(1))
734
+ .limit(3)
735
+
736
+ db.selectFrom("users").innerJoinLateral(recentPosts, "rp", onExpr).compile(db.printer())
737
+ // SELECT * FROM "users" INNER JOIN LATERAL (SELECT ...) AS "rp" ON ...
738
+
739
+ // LEFT JOIN LATERAL
740
+ db.selectFrom("users").leftJoinLateral(recentPosts, "rp", onExpr).compile(db.printer())
741
+ ```
742
+
743
+ ## Tuple Comparisons
744
+
745
+ ```ts
746
+ import { tuple, val } from "sumak"
747
+
748
+ // Row-value comparison: (id, age) = (1, 25)
749
+ db.selectFrom("users")
750
+ .selectExpr(tuple(val(1), val(2), val(3)), "triple")
751
+ .compile(db.printer())
752
+ // (1, 2, 3)
753
+ ```
754
+
755
+ ## SQL Template Literal
756
+
757
+ ```ts
758
+ import { sql, val } from "sumak"
759
+
760
+ // Tagged template with auto-parameterization
761
+ sql`SELECT * FROM users WHERE name = ${"Alice"}`
762
+ // params: ["Alice"]
763
+
764
+ // Inline Expression values
765
+ sql`SELECT * FROM users WHERE active = ${val(true)}`
766
+ // → SELECT * FROM users WHERE active = TRUE
767
+
768
+ // Helpers
769
+ sql`SELECT ${sql.ref("id")} FROM ${sql.table("users", "public")}`
770
+ // → SELECT "id" FROM "public"."users"
771
+
772
+ // Use in selectExpr
773
+ db.selectFrom("users")
774
+ .selectExpr(sql`CURRENT_DATE`, "today")
775
+ .compile(db.printer())
776
+ ```
777
+
778
+ ## Aggregate FILTER (WHERE)
779
+
780
+ ```ts
781
+ import { filter, count, sum } from "sumak"
782
+
783
+ // COUNT(*) FILTER (WHERE active = true)
784
+ db.selectFrom("users").selectExpr(filter(count(), activeExpr), "activeCount").compile(db.printer())
785
+ ```
786
+
787
+ ## EXPLAIN
788
+
789
+ ```ts
790
+ // EXPLAIN
791
+ db.selectFrom("users").select("id").explain().compile(db.printer())
792
+ // EXPLAIN SELECT "id" FROM "users"
793
+
794
+ // EXPLAIN ANALYZE
795
+ db.selectFrom("users").select("id").explain({ analyze: true }).compile(db.printer())
796
+
797
+ // EXPLAIN with format
798
+ db.selectFrom("users").select("id").explain({ format: "JSON" }).compile(db.printer())
799
+ // EXPLAIN (FORMAT JSON) SELECT "id" FROM "users"
800
+ ```
801
+
398
802
  ## Full-Text Search
399
803
 
400
804
  Dialect-aware FTS — same API, different SQL per dialect:
@@ -0,0 +1,26 @@
1
+ import type { BetweenNode, BinaryOpNode, CastNode, ColumnRefNode, ExistsNode, ExpressionNode, FunctionCallNode, InNode, IsNullNode, LiteralNode, ParamNode, RawNode, SelectNode, StarNode, SubqueryNode, UnaryOpNode } from "./nodes.mjs";
2
+ export declare function col(column: string, table?: string): ColumnRefNode;
3
+ export declare function colAs(column: string, alias: string, table?: string): ColumnRefNode;
4
+ export declare function lit(value: string | number | boolean | null): LiteralNode;
5
+ export declare function star(table?: string): StarNode;
6
+ export declare function param(index: number, value: unknown): ParamNode;
7
+ export declare function raw(sql: string, params?: unknown[]): RawNode;
8
+ export declare function subquery(query: SelectNode, alias?: string): SubqueryNode;
9
+ export declare function fn(name: string, args: ExpressionNode[], alias?: string): FunctionCallNode;
10
+ export declare function binOp(op: string, left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
11
+ export declare function unaryOp(op: string, operand: ExpressionNode, position?: "prefix" | "postfix"): UnaryOpNode;
12
+ export declare function and(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
13
+ export declare function or(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
14
+ export declare function eq(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
15
+ export declare function neq(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
16
+ export declare function gt(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
17
+ export declare function gte(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
18
+ export declare function lt(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
19
+ export declare function lte(left: ExpressionNode, right: ExpressionNode): BinaryOpNode;
20
+ export declare function like(expr: ExpressionNode, pattern: ExpressionNode): BinaryOpNode;
21
+ export declare function between(expr: ExpressionNode, low: ExpressionNode, high: ExpressionNode, negated?: boolean): BetweenNode;
22
+ export declare function inList(expr: ExpressionNode, values: ExpressionNode[] | SelectNode, negated?: boolean): InNode;
23
+ export declare function isNull(expr: ExpressionNode, negated?: boolean): IsNullNode;
24
+ export declare function cast(expr: ExpressionNode, dataType: string): CastNode;
25
+ export declare function exists(query: SelectNode, negated?: boolean): ExistsNode;
26
+ export declare function not(operand: ExpressionNode): UnaryOpNode;
@@ -0,0 +1,140 @@
1
+ export function col(column, table) {
2
+ return {
3
+ type: "column_ref",
4
+ column,
5
+ table
6
+ };
7
+ }
8
+ export function colAs(column, alias, table) {
9
+ return {
10
+ type: "column_ref",
11
+ column,
12
+ table,
13
+ alias
14
+ };
15
+ }
16
+ export function lit(value) {
17
+ return {
18
+ type: "literal",
19
+ value
20
+ };
21
+ }
22
+ export function star(table) {
23
+ return {
24
+ type: "star",
25
+ table
26
+ };
27
+ }
28
+ export function param(index, value) {
29
+ return {
30
+ type: "param",
31
+ index,
32
+ value
33
+ };
34
+ }
35
+ export function raw(sql, params = []) {
36
+ return {
37
+ type: "raw",
38
+ sql,
39
+ params
40
+ };
41
+ }
42
+ export function subquery(query, alias) {
43
+ return {
44
+ type: "subquery",
45
+ query,
46
+ alias
47
+ };
48
+ }
49
+ export function fn(name, args, alias) {
50
+ return {
51
+ type: "function_call",
52
+ name,
53
+ args,
54
+ alias
55
+ };
56
+ }
57
+ export function binOp(op, left, right) {
58
+ return {
59
+ type: "binary_op",
60
+ op,
61
+ left,
62
+ right
63
+ };
64
+ }
65
+ export function unaryOp(op, operand, position = "prefix") {
66
+ return {
67
+ type: "unary_op",
68
+ op,
69
+ operand,
70
+ position
71
+ };
72
+ }
73
+ export function and(left, right) {
74
+ return binOp("AND", left, right);
75
+ }
76
+ export function or(left, right) {
77
+ return binOp("OR", left, right);
78
+ }
79
+ export function eq(left, right) {
80
+ return binOp("=", left, right);
81
+ }
82
+ export function neq(left, right) {
83
+ return binOp("!=", left, right);
84
+ }
85
+ export function gt(left, right) {
86
+ return binOp(">", left, right);
87
+ }
88
+ export function gte(left, right) {
89
+ return binOp(">=", left, right);
90
+ }
91
+ export function lt(left, right) {
92
+ return binOp("<", left, right);
93
+ }
94
+ export function lte(left, right) {
95
+ return binOp("<=", left, right);
96
+ }
97
+ export function like(expr, pattern) {
98
+ return binOp("LIKE", expr, pattern);
99
+ }
100
+ export function between(expr, low, high, negated = false) {
101
+ return {
102
+ type: "between",
103
+ expr,
104
+ low,
105
+ high,
106
+ negated
107
+ };
108
+ }
109
+ export function inList(expr, values, negated = false) {
110
+ return {
111
+ type: "in",
112
+ expr,
113
+ values,
114
+ negated
115
+ };
116
+ }
117
+ export function isNull(expr, negated = false) {
118
+ return {
119
+ type: "is_null",
120
+ expr,
121
+ negated
122
+ };
123
+ }
124
+ export function cast(expr, dataType) {
125
+ return {
126
+ type: "cast",
127
+ expr,
128
+ dataType
129
+ };
130
+ }
131
+ export function exists(query, negated = false) {
132
+ return {
133
+ type: "exists",
134
+ query,
135
+ negated
136
+ };
137
+ }
138
+ export function not(operand) {
139
+ return unaryOp("NOT", operand);
140
+ }