sumak 0.0.5 → 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.
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,10 +227,24 @@ 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, countDistinct, 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
 
@@ -222,6 +258,50 @@ db.selectFrom("orders").selectExpr(avg(col.amount), "avgAmount").compile(db.prin
222
258
  db.selectFrom("orders")
223
259
  .selectExpr(coalesce(col.discount, val(0)), "safeDiscount")
224
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())
225
305
  ```
226
306
 
227
307
  ### EXISTS / NOT EXISTS
@@ -505,6 +585,28 @@ db.selectFrom("users")
505
585
  // WHERE ("age" > $1) AND ("active" = $2)
506
586
  ```
507
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
+
508
610
  ## INSERT Advanced
509
611
 
510
612
  ```ts
@@ -621,6 +723,58 @@ db.update("orders")
621
723
  .compile(db.printer())
622
724
  ```
623
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
+
624
778
  ## Aggregate FILTER (WHERE)
625
779
 
626
780
  ```ts
@@ -6,7 +6,7 @@ export interface ExplainNode {
6
6
  analyze?: boolean;
7
7
  format?: "TEXT" | "JSON" | "YAML" | "XML";
8
8
  }
9
- export type ExpressionNode = ColumnRefNode | LiteralNode | BinaryOpNode | UnaryOpNode | FunctionCallNode | ParamNode | RawNode | SubqueryNode | BetweenNode | InNode | IsNullNode | CaseNode | CastNode | ExistsNode | StarNode | JsonAccessNode | ArrayExprNode | WindowFunctionNode | AliasedExprNode | FullTextSearchNode;
9
+ export type ExpressionNode = ColumnRefNode | LiteralNode | BinaryOpNode | UnaryOpNode | FunctionCallNode | ParamNode | RawNode | SubqueryNode | BetweenNode | InNode | IsNullNode | CaseNode | CastNode | ExistsNode | StarNode | JsonAccessNode | ArrayExprNode | WindowFunctionNode | AliasedExprNode | FullTextSearchNode | TupleNode;
10
10
  export interface ColumnRefNode {
11
11
  type: "column_ref";
12
12
  table?: string;
@@ -35,6 +35,7 @@ export interface FunctionCallNode {
35
35
  args: ExpressionNode[];
36
36
  distinct?: boolean;
37
37
  filter?: ExpressionNode;
38
+ orderBy?: OrderByNode[];
38
39
  alias?: string;
39
40
  }
40
41
  export interface ParamNode {
@@ -58,6 +59,7 @@ export interface BetweenNode {
58
59
  low: ExpressionNode;
59
60
  high: ExpressionNode;
60
61
  negated: boolean;
62
+ symmetric?: boolean;
61
63
  }
62
64
  export interface InNode {
63
65
  type: "in";
@@ -102,6 +104,10 @@ export interface FullTextSearchNode {
102
104
  language?: string;
103
105
  alias?: string;
104
106
  }
107
+ export interface TupleNode {
108
+ type: "tuple";
109
+ elements: ExpressionNode[];
110
+ }
105
111
  export interface AliasedExprNode {
106
112
  type: "aliased_expr";
107
113
  expr: ExpressionNode;
@@ -137,6 +143,7 @@ export interface JoinNode {
137
143
  joinType: JoinType;
138
144
  table: TableRefNode | SubqueryNode;
139
145
  on?: ExpressionNode;
146
+ lateral?: boolean;
140
147
  }
141
148
  export interface JsonAccessNode {
142
149
  type: "json_access";
@@ -256,6 +263,8 @@ export interface UpdateNode {
256
263
  from?: TableRefNode;
257
264
  joins: JoinNode[];
258
265
  ctes: CTENode[];
266
+ orderBy?: OrderByNode[];
267
+ limit?: ExpressionNode;
259
268
  }
260
269
  export interface DeleteNode {
261
270
  type: "delete";
@@ -265,6 +274,8 @@ export interface DeleteNode {
265
274
  ctes: CTENode[];
266
275
  using?: TableRefNode;
267
276
  joins: JoinNode[];
277
+ orderBy?: OrderByNode[];
278
+ limit?: ExpressionNode;
268
279
  }
269
280
  export interface MergeWhenMatched {
270
281
  type: "matched";
@@ -11,6 +11,8 @@ export declare class DeleteBuilder {
11
11
  join(type: JoinType, table: string | TableRefNode, on?: ExpressionNode): DeleteBuilder;
12
12
  innerJoin(table: string | TableRefNode, on: ExpressionNode): DeleteBuilder;
13
13
  leftJoin(table: string | TableRefNode, on: ExpressionNode): DeleteBuilder;
14
+ orderBy(expr: string | ExpressionNode, direction?: "ASC" | "DESC"): DeleteBuilder;
15
+ limit(n: ExpressionNode): DeleteBuilder;
14
16
  returning(...exprs: ExpressionNode[]): DeleteBuilder;
15
17
  with(name: string, query: SelectNode, recursive?: boolean): DeleteBuilder;
16
18
  build(): DeleteNode;
@@ -68,6 +68,25 @@ export class DeleteBuilder {
68
68
  leftJoin(table, on) {
69
69
  return this.join("LEFT", table, on);
70
70
  }
71
+ orderBy(expr, direction = "ASC") {
72
+ const node = {
73
+ expr: typeof expr === "string" ? {
74
+ type: "column_ref",
75
+ column: expr
76
+ } : expr,
77
+ direction
78
+ };
79
+ return new DeleteBuilder({
80
+ ...this.node,
81
+ orderBy: [...this.node.orderBy ?? [], node]
82
+ });
83
+ }
84
+ limit(n) {
85
+ return new DeleteBuilder({
86
+ ...this.node,
87
+ limit: n
88
+ });
89
+ }
71
90
  returning(...exprs) {
72
91
  return new DeleteBuilder({
73
92
  ...this.node,
@@ -26,6 +26,12 @@ export declare class Col<T> {
26
26
  lte(value: T): Expression<boolean>;
27
27
  /** LIKE (string columns only) */
28
28
  like(this: Col<string>, pattern: string): Expression<boolean>;
29
+ /** NOT LIKE */
30
+ notLike(this: Col<string>, pattern: string): Expression<boolean>;
31
+ /** ILIKE — case-insensitive LIKE (PG) */
32
+ ilike(this: Col<string>, pattern: string): Expression<boolean>;
33
+ /** NOT ILIKE (PG) */
34
+ notIlike(this: Col<string>, pattern: string): Expression<boolean>;
29
35
  /** IN (value1, value2, ...) */
30
36
  in(values: T[]): Expression<boolean>;
31
37
  /** NOT IN */
@@ -36,8 +42,42 @@ export declare class Col<T> {
36
42
  isNotNull(): Expression<boolean>;
37
43
  /** BETWEEN low AND high */
38
44
  between(low: T, high: T): Expression<boolean>;
45
+ /** NOT BETWEEN low AND high */
46
+ notBetween(low: T, high: T): Expression<boolean>;
47
+ /** BETWEEN SYMMETRIC low AND high (PG) — order-independent range check */
48
+ betweenSymmetric(low: T, high: T): Expression<boolean>;
49
+ /** IN (SELECT ...) — subquery */
50
+ inSubquery(query: SelectNode): Expression<boolean>;
51
+ /** NOT IN (SELECT ...) — subquery */
52
+ notInSubquery(query: SelectNode): Expression<boolean>;
53
+ /** = with Expression value */
54
+ eqExpr(value: Expression<T>): Expression<boolean>;
55
+ /** != with Expression value */
56
+ neqExpr(value: Expression<T>): Expression<boolean>;
57
+ /** > with Expression value */
58
+ gtExpr(value: Expression<T>): Expression<boolean>;
59
+ /** >= with Expression value */
60
+ gteExpr(value: Expression<T>): Expression<boolean>;
61
+ /** < with Expression value */
62
+ ltExpr(value: Expression<T>): Expression<boolean>;
63
+ /** <= with Expression value */
64
+ lteExpr(value: Expression<T>): Expression<boolean>;
65
+ /** IS DISTINCT FROM — null-safe inequality */
66
+ isDistinctFrom(value: T): Expression<boolean>;
67
+ /** IS NOT DISTINCT FROM — null-safe equality */
68
+ isNotDistinctFrom(value: T): Expression<boolean>;
39
69
  /** Compare with another column: col1.eqCol(col2) */
40
70
  eqCol(other: Col<T>): Expression<boolean>;
71
+ /** != another column */
72
+ neqCol(other: Col<T>): Expression<boolean>;
73
+ /** > another column */
74
+ gtCol(other: Col<T>): Expression<boolean>;
75
+ /** < another column */
76
+ ltCol(other: Col<T>): Expression<boolean>;
77
+ /** >= another column */
78
+ gteCol(other: Col<T>): Expression<boolean>;
79
+ /** <= another column */
80
+ lteCol(other: Col<T>): Expression<boolean>;
41
81
  /** As raw Expression<T> for advanced use */
42
82
  toExpr(): Expression<T>;
43
83
  }
@@ -69,12 +109,21 @@ export type WhereCallback<
69
109
  DB,
70
110
  TB extends keyof DB
71
111
  > = (cols: ColumnProxies<DB, TB>) => Expression<boolean>;
72
- /** AND two expressions */
73
- export declare function and(left: Expression<boolean>, right: Expression<boolean>): Expression<boolean>;
74
- /** OR two expressions */
75
- export declare function or(left: Expression<boolean>, right: Expression<boolean>): Expression<boolean>;
112
+ /** AND expressions — variadic: and(a, b) or and(a, b, c, ...) */
113
+ export declare function and(...exprs: Expression<boolean>[]): Expression<boolean>;
114
+ /** OR expressions — variadic: or(a, b) or or(a, b, c, ...) */
115
+ export declare function or(...exprs: Expression<boolean>[]): Expression<boolean>;
76
116
  /** Raw literal value as expression */
77
117
  export declare function val<T extends string | number | boolean | null>(value: T): Expression<T>;
118
+ /**
119
+ * Raw SQL expression — escape hatch for arbitrary SQL in expressions.
120
+ *
121
+ * ```ts
122
+ * .where(() => rawExpr("age > 18"))
123
+ * .selectExpr(rawExpr<number>("EXTRACT(YEAR FROM created_at)"), "year")
124
+ * ```
125
+ */
126
+ export declare function rawExpr<T = unknown>(sql: string, params?: unknown[]): Expression<T>;
78
127
  /** SQL function call */
79
128
  export declare function sqlFn(name: string, ...args: Expression<any>[]): Expression<any>;
80
129
  /** COUNT(*) */
@@ -83,16 +132,32 @@ export declare function count(): Expression<number>;
83
132
  export declare function countDistinct(expr: Expression<any>): Expression<number>;
84
133
  /** SUM(expr) */
85
134
  export declare function sum(expr: Expression<number>): Expression<number>;
135
+ /** SUM(DISTINCT expr) */
136
+ export declare function sumDistinct(expr: Expression<number>): Expression<number>;
86
137
  /** AVG(expr) */
87
138
  export declare function avg(expr: Expression<number>): Expression<number>;
139
+ /** AVG(DISTINCT expr) */
140
+ export declare function avgDistinct(expr: Expression<number>): Expression<number>;
88
141
  /** MIN(expr) */
89
142
  export declare function min<T>(expr: Expression<T>): Expression<T>;
90
143
  /** MAX(expr) */
91
144
  export declare function max<T>(expr: Expression<T>): Expression<T>;
92
- /** COALESCE(expr, fallback) */
93
- export declare function coalesce<T>(expr: Expression<T | null>, fallback: Expression<T>): Expression<T>;
145
+ /** COALESCE(a, b, c, ...) — returns first non-null value */
146
+ export declare function coalesce<T>(...args: Expression<T | null>[]): Expression<T>;
94
147
  /** NOT expr */
95
148
  export declare function not(expr: Expression<boolean>): Expression<boolean>;
149
+ /** Add: a + b */
150
+ export declare function add(a: Expression<number>, b: Expression<number>): Expression<number>;
151
+ /** Subtract: a - b */
152
+ export declare function sub(a: Expression<number>, b: Expression<number>): Expression<number>;
153
+ /** Multiply: a * b */
154
+ export declare function mul(a: Expression<number>, b: Expression<number>): Expression<number>;
155
+ /** Divide: a / b */
156
+ export declare function div(a: Expression<number>, b: Expression<number>): Expression<number>;
157
+ /** Modulo: a % b */
158
+ export declare function mod(a: Expression<number>, b: Expression<number>): Expression<number>;
159
+ /** Unary minus: -expr */
160
+ export declare function neg(expr: Expression<number>): Expression<number>;
96
161
  /** EXISTS (subquery) */
97
162
  export declare function exists(query: SelectNode): Expression<boolean>;
98
163
  /** NOT EXISTS (subquery) */
@@ -156,6 +221,7 @@ export declare class WindowBuilder {
156
221
  orderBy(column: string, direction?: "ASC" | "DESC"): WindowBuilder;
157
222
  rows(start: FrameBound, end?: FrameBound): WindowBuilder;
158
223
  range(start: FrameBound, end?: FrameBound): WindowBuilder;
224
+ groups(start: FrameBound, end?: FrameBound): WindowBuilder;
159
225
  }
160
226
  /** ROW_NUMBER() — must be used with over() */
161
227
  export declare function rowNumber(): Expression<number>;
@@ -197,8 +263,44 @@ export declare function abs(expr: Expression<number>): Expression<number>;
197
263
  export declare function round(expr: Expression<number>, precision?: number): Expression<number>;
198
264
  /** CEIL(expr) */
199
265
  export declare function ceil(expr: Expression<number>): Expression<number>;
266
+ /** JSON_AGG(expr) — aggregate rows into JSON array (PG) */
267
+ export declare function jsonAgg<T>(expr: Expression<T>): Expression<T[]>;
268
+ /** TO_JSON(expr) — convert value to JSON (PG) */
269
+ export declare function toJson<T>(expr: Expression<T>): Expression<unknown>;
270
+ /** JSON_BUILD_OBJECT(key, value, ...) — build JSON object (PG) */
271
+ export declare function jsonBuildObject(...pairs: [string, Expression<any>][]): Expression<Record<string, unknown>>;
272
+ /** @> (array contains) */
273
+ export declare function arrayContains(arr: Expression<any>, values: Expression<any>): Expression<boolean>;
274
+ /** <@ (array contained by) */
275
+ export declare function arrayContainedBy(arr: Expression<any>, values: Expression<any>): Expression<boolean>;
276
+ /** && (array overlaps) */
277
+ export declare function arrayOverlaps(arr: Expression<any>, values: Expression<any>): Expression<boolean>;
200
278
  /** FLOOR(expr) */
201
279
  export declare function floor(expr: Expression<number>): Expression<number>;
280
+ /** STRING_AGG(expr, delimiter) — aggregate strings with separator */
281
+ export declare function stringAgg(expr: Expression<string>, delimiter: string, orderBy?: {
282
+ expr: Expression<any>;
283
+ direction?: "ASC" | "DESC";
284
+ }[]): Expression<string>;
285
+ /** ARRAY_AGG(expr) — aggregate values into array */
286
+ export declare function arrayAgg<T>(expr: Expression<T>, orderBy?: {
287
+ expr: Expression<any>;
288
+ direction?: "ASC" | "DESC";
289
+ }[]): Expression<T[]>;
290
+ /** Attach ORDER BY to an existing aggregate expression. */
291
+ export declare function aggOrderBy<T>(agg: Expression<T>, orderBy: {
292
+ expr: Expression<any>;
293
+ direction?: "ASC" | "DESC";
294
+ }[]): Expression<T>;
295
+ /**
296
+ * Row-value tuple for comparisons.
297
+ *
298
+ * ```ts
299
+ * // (a, b) = (1, 2)
300
+ * tuple(cols.a.toExpr(), cols.b.toExpr())
301
+ * ```
302
+ */
303
+ export declare function tuple(...exprs: Expression<any>[]): Expression<any>;
202
304
  /**
203
305
  * Attach FILTER (WHERE ...) to an aggregate expression.
204
306
  *