sumak 0.0.2 → 0.0.4
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 +424 -51
- package/dist/_chunks/base.mjs +1 -1
- package/dist/_chunks/index.d.mts +31 -14
- package/dist/_chunks/mssql.d.mts +11 -0
- package/dist/_chunks/mssql.mjs +1 -0
- package/dist/_chunks/mysql.d.mts +2 -1
- package/dist/_chunks/mysql.mjs +1 -1
- package/dist/_chunks/pg.d.mts +1 -1
- package/dist/_chunks/sqlite.d.mts +2 -1
- package/dist/_chunks/sqlite.mjs +1 -1
- package/dist/_chunks/types.d.mts +68 -4
- package/dist/index.d.mts +265 -118
- package/dist/index.mjs +3 -3
- package/dist/mssql.d.mts +2 -0
- package/dist/mssql.mjs +1 -0
- package/dist/schema.d.mts +1 -1
- package/package.json +18 -13
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ npm install sumak
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
-
import { sumak, pgDialect, serial, text, boolean, integer } from "sumak"
|
|
24
|
+
import { sumak, pgDialect, serial, text, boolean, integer, jsonb } from "sumak"
|
|
25
25
|
|
|
26
26
|
const db = sumak({
|
|
27
27
|
dialect: pgDialect(),
|
|
@@ -30,7 +30,9 @@ const db = sumak({
|
|
|
30
30
|
id: serial().primaryKey(),
|
|
31
31
|
name: text().notNull(),
|
|
32
32
|
email: text().notNull(),
|
|
33
|
+
age: integer(),
|
|
33
34
|
active: boolean().defaultTo(true),
|
|
35
|
+
meta: jsonb(),
|
|
34
36
|
},
|
|
35
37
|
posts: {
|
|
36
38
|
id: serial().primaryKey(),
|
|
@@ -38,62 +40,422 @@ const db = sumak({
|
|
|
38
40
|
userId: integer().references("users", "id"),
|
|
39
41
|
},
|
|
40
42
|
},
|
|
41
|
-
})
|
|
43
|
+
})
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
## Query Building
|
|
45
47
|
|
|
48
|
+
### SELECT
|
|
49
|
+
|
|
46
50
|
```ts
|
|
47
|
-
// SELECT
|
|
48
51
|
db.selectFrom("users")
|
|
49
52
|
.select("id", "name")
|
|
50
53
|
.where(({ age, active }) => and(age.gte(18), active.eq(true)))
|
|
51
54
|
.orderBy("name")
|
|
52
55
|
.limit(10)
|
|
53
|
-
.compile(db.printer())
|
|
54
|
-
|
|
56
|
+
.compile(db.printer())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### INSERT
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
```ts
|
|
57
62
|
db.insertInto("users")
|
|
58
63
|
.values({
|
|
59
64
|
name: "Alice",
|
|
60
65
|
email: "alice@example.com",
|
|
61
66
|
})
|
|
62
67
|
.returningAll()
|
|
63
|
-
.compile(db.printer())
|
|
68
|
+
.compile(db.printer())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### UPDATE
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
```ts
|
|
66
74
|
db.update("users")
|
|
67
75
|
.set({ active: false })
|
|
68
76
|
.where(({ id }) => id.eq(1))
|
|
69
|
-
.compile(db.printer())
|
|
77
|
+
.compile(db.printer())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### DELETE
|
|
70
81
|
|
|
71
|
-
|
|
82
|
+
```ts
|
|
72
83
|
db.deleteFrom("users")
|
|
73
84
|
.where(({ id }) => id.eq(1))
|
|
74
85
|
.returning("id")
|
|
75
|
-
.compile(db.printer())
|
|
86
|
+
.compile(db.printer())
|
|
76
87
|
```
|
|
77
88
|
|
|
78
89
|
## Joins
|
|
79
90
|
|
|
80
91
|
```ts
|
|
92
|
+
// INNER JOIN
|
|
81
93
|
db.selectFrom("users")
|
|
82
94
|
.innerJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
83
|
-
.
|
|
84
|
-
|
|
95
|
+
.select("id", "title")
|
|
96
|
+
.compile(db.printer())
|
|
97
|
+
|
|
98
|
+
// LEFT JOIN — joined columns become nullable
|
|
99
|
+
db.selectFrom("users")
|
|
100
|
+
.leftJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
101
|
+
.compile(db.printer())
|
|
102
|
+
|
|
103
|
+
// RIGHT JOIN
|
|
104
|
+
db.selectFrom("users")
|
|
105
|
+
.rightJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
106
|
+
.compile(db.printer())
|
|
107
|
+
|
|
108
|
+
// FULL JOIN — both sides become nullable
|
|
109
|
+
db.selectFrom("users")
|
|
110
|
+
.fullJoin("posts", ({ users, posts }) => users.id.eqCol(posts.userId))
|
|
111
|
+
.compile(db.printer())
|
|
112
|
+
|
|
113
|
+
// CROSS JOIN — cartesian product
|
|
114
|
+
db.selectFrom("users").crossJoin("posts").compile(db.printer())
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Expression API
|
|
118
|
+
|
|
119
|
+
### Comparisons
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
.where(({ id }) =>
|
|
123
|
+
id.eq(42),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
.where(({ age }) =>
|
|
127
|
+
age.gt(18),
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
.where(({ age }) =>
|
|
131
|
+
age.gte(18),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
.where(({ age }) =>
|
|
135
|
+
age.lt(65),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
.where(({ age }) =>
|
|
139
|
+
age.lte(65),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
.where(({ active }) =>
|
|
143
|
+
active.neq(false),
|
|
144
|
+
)
|
|
85
145
|
```
|
|
86
146
|
|
|
147
|
+
### String Matching
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
.where(({ name }) =>
|
|
151
|
+
name.like("%ali%"),
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Range & List
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
.where(({ age }) =>
|
|
159
|
+
age.between(18, 65),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
.where(({ id }) =>
|
|
163
|
+
id.in([1, 2, 3]),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
.where(({ id }) =>
|
|
167
|
+
id.notIn([99, 100]),
|
|
168
|
+
)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Null Checks
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
.where(({ bio }) =>
|
|
175
|
+
bio.isNull(),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
.where(({ email }) =>
|
|
179
|
+
email.isNotNull(),
|
|
180
|
+
)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Logical Combinators
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
// AND
|
|
187
|
+
.where(({ age, active }) =>
|
|
188
|
+
and(
|
|
189
|
+
age.gt(0),
|
|
190
|
+
active.eq(true),
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
// OR
|
|
195
|
+
.where(({ name, email }) =>
|
|
196
|
+
or(
|
|
197
|
+
name.like("%alice%"),
|
|
198
|
+
email.like("%alice%"),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
// NOT
|
|
203
|
+
.where(({ active }) =>
|
|
204
|
+
not(active.eq(true)),
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Aggregates
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { count, sum, avg, min, max, coalesce } from "sumak"
|
|
212
|
+
|
|
213
|
+
db.selectFrom("users").selectExpr(count(), "total").compile(db.printer())
|
|
214
|
+
|
|
215
|
+
db.selectFrom("orders").selectExpr(sum(col.amount), "totalAmount").compile(db.printer())
|
|
216
|
+
|
|
217
|
+
db.selectFrom("orders").selectExpr(avg(col.amount), "avgAmount").compile(db.printer())
|
|
218
|
+
|
|
219
|
+
db.selectFrom("orders")
|
|
220
|
+
.selectExpr(coalesce(col.discount, val(0)), "safeDiscount")
|
|
221
|
+
.compile(db.printer())
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### EXISTS / NOT EXISTS
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
import { exists, notExists } from "sumak"
|
|
228
|
+
|
|
229
|
+
db.selectFrom("users")
|
|
230
|
+
.where(() =>
|
|
231
|
+
exists(
|
|
232
|
+
db
|
|
233
|
+
.selectFrom("posts")
|
|
234
|
+
.where(({ userId }) => userId.eq(1))
|
|
235
|
+
.build(),
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
.compile(db.printer())
|
|
239
|
+
|
|
240
|
+
db.selectFrom("users")
|
|
241
|
+
.where(() =>
|
|
242
|
+
notExists(
|
|
243
|
+
db
|
|
244
|
+
.selectFrom("posts")
|
|
245
|
+
.where(({ userId }) => userId.eq(1))
|
|
246
|
+
.build(),
|
|
247
|
+
),
|
|
248
|
+
)
|
|
249
|
+
.compile(db.printer())
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### CASE Expression
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { case_, val } from "sumak"
|
|
256
|
+
|
|
257
|
+
db.selectFrom("users")
|
|
258
|
+
.selectExpr(
|
|
259
|
+
case_()
|
|
260
|
+
.when(col.active.eq(true), val("active"))
|
|
261
|
+
.when(col.active.eq(false), val("inactive"))
|
|
262
|
+
.else_(val("unknown"))
|
|
263
|
+
.end(),
|
|
264
|
+
"status",
|
|
265
|
+
)
|
|
266
|
+
.compile(db.printer())
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### CAST
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { cast, val } from "sumak"
|
|
273
|
+
|
|
274
|
+
db.selectFrom("users")
|
|
275
|
+
.selectExpr(cast(val(42), "text"), "idAsText")
|
|
276
|
+
.compile(db.printer())
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### JSON Operations
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import { jsonRef } from "sumak"
|
|
283
|
+
|
|
284
|
+
// -> (JSON object)
|
|
285
|
+
db.selectFrom("users")
|
|
286
|
+
.selectExpr(jsonRef(col.meta, "address", "->"), "address")
|
|
287
|
+
.compile(db.printer())
|
|
288
|
+
|
|
289
|
+
// ->> (text value)
|
|
290
|
+
db.selectFrom("users")
|
|
291
|
+
.selectExpr(jsonRef(col.meta, "name", "->>"), "metaName")
|
|
292
|
+
.compile(db.printer())
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Set Operations
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
const active = db
|
|
299
|
+
.selectFrom("users")
|
|
300
|
+
.select("id")
|
|
301
|
+
.where(({ active }) => active.eq(true))
|
|
302
|
+
|
|
303
|
+
const premium = db
|
|
304
|
+
.selectFrom("users")
|
|
305
|
+
.select("id")
|
|
306
|
+
.where(({ active }) => active.eq(true))
|
|
307
|
+
|
|
308
|
+
// UNION
|
|
309
|
+
active.union(premium).compile(db.printer())
|
|
310
|
+
|
|
311
|
+
// UNION ALL
|
|
312
|
+
active.unionAll(premium).compile(db.printer())
|
|
313
|
+
|
|
314
|
+
// INTERSECT
|
|
315
|
+
active.intersect(premium).compile(db.printer())
|
|
316
|
+
|
|
317
|
+
// EXCEPT
|
|
318
|
+
active.except(premium).compile(db.printer())
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## CTEs (WITH)
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
// SELECT with CTE
|
|
325
|
+
db.selectFrom("users")
|
|
326
|
+
.with(
|
|
327
|
+
"active_users",
|
|
328
|
+
db
|
|
329
|
+
.selectFrom("users")
|
|
330
|
+
.where(({ active }) => active.eq(true))
|
|
331
|
+
.build(),
|
|
332
|
+
)
|
|
333
|
+
.compile(db.printer())
|
|
334
|
+
|
|
335
|
+
// INSERT with CTE
|
|
336
|
+
db.insertInto("users")
|
|
337
|
+
.with("source", sourceCte)
|
|
338
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
339
|
+
.compile(db.printer())
|
|
340
|
+
|
|
341
|
+
// UPDATE with CTE
|
|
342
|
+
db.update("users").with("target", targetCte).set({ active: false }).compile(db.printer())
|
|
343
|
+
|
|
344
|
+
// DELETE with CTE
|
|
345
|
+
db.deleteFrom("users")
|
|
346
|
+
.with("to_delete", deleteCte)
|
|
347
|
+
.where(({ id }) => id.eq(1))
|
|
348
|
+
.compile(db.printer())
|
|
349
|
+
|
|
350
|
+
// Recursive CTE
|
|
351
|
+
db.selectFrom("users").with("tree", recursiveQuery, true).compile(db.printer())
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## UPDATE FROM
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
db.update("users")
|
|
358
|
+
.set({ name: "Bob" })
|
|
359
|
+
.from("posts")
|
|
360
|
+
.where(({ id }) => id.eq(1))
|
|
361
|
+
.compile(db.printer())
|
|
362
|
+
// UPDATE "users" SET "name" = $1 FROM "posts" WHERE ("id" = $2)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## ON CONFLICT
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
// DO NOTHING
|
|
369
|
+
db.insertInto("users")
|
|
370
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
371
|
+
.onConflictDoNothing("email")
|
|
372
|
+
.compile(db.printer())
|
|
373
|
+
|
|
374
|
+
// DO UPDATE
|
|
375
|
+
db.insertInto("users")
|
|
376
|
+
.values({ name: "Alice", email: "a@b.com" })
|
|
377
|
+
.onConflictDoUpdate(["email"], [{ column: "name", value: val("Alice") }])
|
|
378
|
+
.compile(db.printer())
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## MERGE (SQL:2003)
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
db.mergeInto("users", "staging", "s", ({ target, source }) => target.id.eqCol(source.id))
|
|
385
|
+
.whenMatchedThenUpdate({ name: "updated" })
|
|
386
|
+
.whenNotMatchedThenInsert({
|
|
387
|
+
name: "Alice",
|
|
388
|
+
email: "alice@example.com",
|
|
389
|
+
})
|
|
390
|
+
.compile(db.printer())
|
|
391
|
+
|
|
392
|
+
// MERGE with conditional delete
|
|
393
|
+
db.mergeInto("users", "staging", "s", ({ target, source }) => target.id.eqCol(source.id))
|
|
394
|
+
.whenMatchedThenDelete()
|
|
395
|
+
.compile(db.printer())
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Full-Text Search
|
|
399
|
+
|
|
400
|
+
Dialect-aware FTS — same API, different SQL per dialect:
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { textSearch } from "sumak"
|
|
404
|
+
|
|
405
|
+
// PostgreSQL: to_tsvector("name") @@ to_tsquery('alice')
|
|
406
|
+
db.selectFrom("users")
|
|
407
|
+
.where(({ name }) => textSearch([name.toExpr()], val("alice")))
|
|
408
|
+
.compile(db.printer())
|
|
409
|
+
|
|
410
|
+
// With language config
|
|
411
|
+
db.selectFrom("users")
|
|
412
|
+
.where(({ name }) => textSearch([name.toExpr()], val("alice"), { language: "english" }))
|
|
413
|
+
.compile(db.printer())
|
|
414
|
+
|
|
415
|
+
// MySQL: MATCH(`name`) AGAINST(? IN BOOLEAN MODE)
|
|
416
|
+
// SQLite: ("name" MATCH ?)
|
|
417
|
+
// MSSQL: CONTAINS(([name]), @p0)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Temporal Tables (SQL:2011)
|
|
421
|
+
|
|
422
|
+
Query historical data with `FOR SYSTEM_TIME`:
|
|
423
|
+
|
|
424
|
+
```ts
|
|
425
|
+
// AS OF — point-in-time query
|
|
426
|
+
db.selectFrom("users")
|
|
427
|
+
.forSystemTime({
|
|
428
|
+
kind: "as_of",
|
|
429
|
+
timestamp: lit("2024-01-01"),
|
|
430
|
+
})
|
|
431
|
+
.compile(db.printer())
|
|
432
|
+
|
|
433
|
+
// BETWEEN — time range
|
|
434
|
+
db.selectFrom("users")
|
|
435
|
+
.forSystemTime({
|
|
436
|
+
kind: "between",
|
|
437
|
+
start: lit("2024-01-01"),
|
|
438
|
+
end: lit("2024-12-31"),
|
|
439
|
+
})
|
|
440
|
+
.compile(db.printer())
|
|
441
|
+
|
|
442
|
+
// ALL — full history
|
|
443
|
+
db.selectFrom("users").forSystemTime({ kind: "all" }).compile(db.printer())
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
Supported modes: `as_of`, `from_to`, `between`, `contained_in`, `all`.
|
|
447
|
+
|
|
87
448
|
## Tree Shaking
|
|
88
449
|
|
|
89
450
|
Import only the dialect you need:
|
|
90
451
|
|
|
91
452
|
```ts
|
|
92
|
-
import { sumak } from "sumak"
|
|
93
|
-
import { pgDialect } from "sumak/pg"
|
|
94
|
-
import {
|
|
95
|
-
import {
|
|
96
|
-
import {
|
|
453
|
+
import { sumak } from "sumak"
|
|
454
|
+
import { pgDialect } from "sumak/pg"
|
|
455
|
+
import { mssqlDialect } from "sumak/mssql"
|
|
456
|
+
import { mysqlDialect } from "sumak/mysql"
|
|
457
|
+
import { sqliteDialect } from "sumak/sqlite"
|
|
458
|
+
import { serial, text } from "sumak/schema"
|
|
97
459
|
```
|
|
98
460
|
|
|
99
461
|
## Dialects
|
|
@@ -104,12 +466,40 @@ Same query, different SQL:
|
|
|
104
466
|
// PostgreSQL → SELECT "id" FROM "users" WHERE ("id" = $1)
|
|
105
467
|
// MySQL → SELECT `id` FROM `users` WHERE (`id` = ?)
|
|
106
468
|
// SQLite → SELECT "id" FROM "users" WHERE ("id" = ?)
|
|
469
|
+
// MSSQL → SELECT [id] FROM [users] WHERE ([id] = @p0)
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### MSSQL Specifics
|
|
473
|
+
|
|
474
|
+
```ts
|
|
475
|
+
import { mssqlDialect } from "sumak/mssql"
|
|
476
|
+
|
|
477
|
+
const db = sumak({
|
|
478
|
+
dialect: mssqlDialect(),
|
|
479
|
+
tables: { ... },
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// LIMIT → TOP N
|
|
483
|
+
// SELECT TOP 10 * FROM [users]
|
|
484
|
+
|
|
485
|
+
// LIMIT + OFFSET → OFFSET/FETCH
|
|
486
|
+
// SELECT * FROM [users] ORDER BY [id] ASC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
|
|
487
|
+
|
|
488
|
+
// RETURNING → OUTPUT INSERTED.*
|
|
489
|
+
// INSERT INTO [users] ([name]) OUTPUT INSERTED.* VALUES (@p0)
|
|
490
|
+
|
|
491
|
+
// DELETE RETURNING → OUTPUT DELETED.*
|
|
492
|
+
// DELETE FROM [users] OUTPUT DELETED.* WHERE ([id] = @p0)
|
|
107
493
|
```
|
|
108
494
|
|
|
109
495
|
## Plugins
|
|
110
496
|
|
|
111
497
|
```ts
|
|
112
|
-
import {
|
|
498
|
+
import {
|
|
499
|
+
WithSchemaPlugin,
|
|
500
|
+
SoftDeletePlugin,
|
|
501
|
+
CamelCasePlugin,
|
|
502
|
+
} from "sumak"
|
|
113
503
|
|
|
114
504
|
const db = sumak({
|
|
115
505
|
dialect: pgDialect(),
|
|
@@ -118,7 +508,7 @@ const db = sumak({
|
|
|
118
508
|
new SoftDeletePlugin({ tables: ["users"] }),
|
|
119
509
|
],
|
|
120
510
|
tables: { ... },
|
|
121
|
-
})
|
|
511
|
+
})
|
|
122
512
|
|
|
123
513
|
// SELECT * FROM "public"."users" WHERE ("deleted_at" IS NULL)
|
|
124
514
|
```
|
|
@@ -128,52 +518,35 @@ const db = sumak({
|
|
|
128
518
|
```ts
|
|
129
519
|
// Query logging
|
|
130
520
|
db.hook("query:after", (ctx) => {
|
|
131
|
-
console.log(`[SQL] ${ctx.query.sql}`)
|
|
132
|
-
})
|
|
521
|
+
console.log(`[SQL] ${ctx.query.sql}`)
|
|
522
|
+
})
|
|
133
523
|
|
|
134
524
|
// Add request tracing
|
|
135
525
|
db.hook("query:after", (ctx) => {
|
|
136
526
|
return {
|
|
137
527
|
...ctx.query,
|
|
138
528
|
sql: `${ctx.query.sql} /* request_id=${requestId} */`,
|
|
139
|
-
}
|
|
140
|
-
})
|
|
529
|
+
}
|
|
530
|
+
})
|
|
141
531
|
|
|
142
532
|
// Modify AST before compilation
|
|
143
533
|
db.hook("select:before", (ctx) => {
|
|
144
534
|
// Add tenant isolation, audit filters, etc.
|
|
145
|
-
})
|
|
535
|
+
})
|
|
146
536
|
|
|
147
537
|
// Transform results
|
|
148
538
|
db.hook("result:transform", (rows) => {
|
|
149
|
-
return rows.map(toCamelCase)
|
|
150
|
-
})
|
|
539
|
+
return rows.map(toCamelCase)
|
|
540
|
+
})
|
|
151
541
|
|
|
152
542
|
// Unregister
|
|
153
|
-
const off = db.hook("query:before", handler)
|
|
154
|
-
off()
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Expression API
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
.where(({ id }) => id.eq(42)) // "id" = $1
|
|
161
|
-
.where(({ name }) => name.like("%ali%")) // "name" LIKE '%ali%'
|
|
162
|
-
.where(({ age }) => age.between(18, 65)) // "age" BETWEEN $1 AND $2
|
|
163
|
-
.where(({ id }) => id.in([1, 2, 3])) // "id" IN ($1, $2, $3)
|
|
164
|
-
.where(({ bio }) => bio.isNull()) // "bio" IS NULL
|
|
165
|
-
.where(({ email }) => email.isNotNull()) // "email" IS NOT NULL
|
|
166
|
-
.where(({ a, b }) => // ("a" > $1 AND "b" != $2)
|
|
167
|
-
and(a.gt(0), b.neq("x")),
|
|
168
|
-
)
|
|
169
|
-
.where(({ a, b }) => // ("a" = $1 OR "b" = $2)
|
|
170
|
-
or(a.eq(1), b.eq(2)),
|
|
171
|
-
)
|
|
543
|
+
const off = db.hook("query:before", handler)
|
|
544
|
+
off()
|
|
172
545
|
```
|
|
173
546
|
|
|
174
|
-
## Why
|
|
547
|
+
## Why sumak?
|
|
175
548
|
|
|
176
|
-
| |
|
|
549
|
+
| | sumak | Drizzle | Kysely |
|
|
177
550
|
| ------------------ | ----------------- | --------------- | -------------- |
|
|
178
551
|
| **Architecture** | AST-first | Template | AST (98 nodes) |
|
|
179
552
|
| **Type inference** | Auto (no codegen) | Auto | Manual DB type |
|
|
@@ -190,10 +563,10 @@ Schema → Builder → AST → Plugin/Hook → Printer → SQL
|
|
|
190
563
|
```
|
|
191
564
|
|
|
192
565
|
- **Schema Layer** — `defineTable()`, `ColumnType<S,I,U>`, auto type inference
|
|
193
|
-
- **Builder Layer** — `
|
|
566
|
+
- **Builder Layer** — `Sumak<DB>`, `TypedSelectBuilder<DB,TB,O>`, proxy-based expressions
|
|
194
567
|
- **AST Layer** — ~35 frozen node types, discriminated unions, visitor pattern
|
|
195
|
-
- **Plugin Layer** — `
|
|
196
|
-
- **Printer Layer** — `BasePrinter` with dialect subclasses, Wadler document algebra
|
|
568
|
+
- **Plugin Layer** — `SumakPlugin` interface, `Hookable` lifecycle hooks
|
|
569
|
+
- **Printer Layer** — `BasePrinter` with 4 dialect subclasses (PG, MySQL, SQLite, MSSQL), Wadler document algebra
|
|
197
570
|
|
|
198
571
|
## License
|
|
199
572
|
|
package/dist/_chunks/base.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function e(e,t){switch(t){case`pg`:case`sqlite`:return`"${e.replaceAll(`"`,`""`)}"`;case`mysql`:return`\`${e.replaceAll("`","``")}
|
|
1
|
+
function e(e,t){switch(t){case`pg`:case`sqlite`:return`"${e.replaceAll(`"`,`""`)}"`;case`mysql`:return`\`${e.replaceAll("`","``")}\``;case`mssql`:return`[${e.replaceAll(`]`,`]]`)}]`}}function t(t,n,r){return r?`${e(r,n)}.${e(t,n)}`:e(t,n)}function n(e,t){switch(t){case`pg`:return`$${e+1}`;case`mysql`:case`sqlite`:return`?`;case`mssql`:return`@p${e}`}}var r=class{params=[];dialect;constructor(e){this.dialect=e}print(e){return this.params=[],{sql:this.printNode(e),params:[...this.params]}}printNode(e){switch(e.type){case`select`:return this.printSelect(e);case`insert`:return this.printInsert(e);case`update`:return this.printUpdate(e);case`delete`:return this.printDelete(e);case`merge`:return this.printMerge(e);default:return this.printExpression(e)}}printSelect(e){let t=[];e.ctes.length>0&&t.push(this.printCTEs(e.ctes)),t.push(`SELECT`),e.distinct&&t.push(`DISTINCT`),e.columns.length===0?t.push(`*`):t.push(e.columns.map(e=>this.printExpression(e)).join(`, `)),e.from&&(t.push(`FROM`),e.from.type===`subquery`?t.push(this.printSubquery(e.from)):t.push(this.printTableRef(e.from)));for(let n of e.joins)t.push(this.printJoin(n));return e.where&&t.push(`WHERE`,this.printExpression(e.where)),e.groupBy.length>0&&t.push(`GROUP BY`,e.groupBy.map(e=>this.printExpression(e)).join(`, `)),e.having&&t.push(`HAVING`,this.printExpression(e.having)),e.orderBy.length>0&&t.push(`ORDER BY`,e.orderBy.map(e=>this.printOrderBy(e)).join(`, `)),e.limit&&t.push(`LIMIT`,this.printExpression(e.limit)),e.offset&&t.push(`OFFSET`,this.printExpression(e.offset)),e.setOp&&t.push(e.setOp.op,this.printSelect(e.setOp.query)),e.forUpdate&&t.push(`FOR UPDATE`),t.join(` `)}printInsert(t){let n=[];t.ctes.length>0&&n.push(this.printCTEs(t.ctes)),n.push(`INSERT INTO`,this.printTableRef(t.table)),t.columns.length>0&&n.push(`(${t.columns.map(t=>e(t,this.dialect)).join(`, `)})`),n.push(`VALUES`);let r=t.values.map(e=>`(${e.map(e=>this.printExpression(e)).join(`, `)})`);return n.push(r.join(`, `)),t.onConflict&&n.push(this.printOnConflict(t.onConflict)),t.returning.length>0&&n.push(`RETURNING`,t.returning.map(e=>this.printExpression(e)).join(`, `)),n.join(` `)}printOnConflict(t){let n=[`ON CONFLICT`];if(t.columns.length>0&&n.push(`(${t.columns.map(t=>e(t,this.dialect)).join(`, `)})`),t.action===`nothing`)n.push(`DO NOTHING`);else{n.push(`DO UPDATE SET`);let r=t.action.set.map(t=>`${e(t.column,this.dialect)} = ${this.printExpression(t.value)}`);n.push(r.join(`, `))}return t.where&&n.push(`WHERE`,this.printExpression(t.where)),n.join(` `)}printUpdate(t){let n=[];t.ctes.length>0&&n.push(this.printCTEs(t.ctes)),n.push(`UPDATE`,this.printTableRef(t.table),`SET`);let r=t.set.map(t=>`${e(t.column,this.dialect)} = ${this.printExpression(t.value)}`);return n.push(r.join(`, `)),t.from&&n.push(`FROM`,this.printTableRef(t.from)),t.where&&n.push(`WHERE`,this.printExpression(t.where)),t.returning.length>0&&n.push(`RETURNING`,t.returning.map(e=>this.printExpression(e)).join(`, `)),n.join(` `)}printDelete(e){let t=[];return e.ctes.length>0&&t.push(this.printCTEs(e.ctes)),t.push(`DELETE FROM`,this.printTableRef(e.table)),e.where&&t.push(`WHERE`,this.printExpression(e.where)),e.returning.length>0&&t.push(`RETURNING`,e.returning.map(e=>this.printExpression(e)).join(`, `)),t.join(` `)}printExpression(e){switch(e.type){case`column_ref`:return this.printColumnRef(e);case`literal`:return this.printLiteral(e);case`binary_op`:return this.printBinaryOp(e);case`unary_op`:return this.printUnaryOp(e);case`function_call`:return this.printFunctionCall(e);case`param`:return this.printParam(e);case`raw`:return this.printRaw(e);case`subquery`:return this.printSubquery(e);case`between`:return this.printBetween(e);case`in`:return this.printIn(e);case`is_null`:return this.printIsNull(e);case`cast`:return this.printCast(e);case`exists`:return this.printExists(e);case`star`:return this.printStar(e);case`case`:return this.printCase(e);case`json_access`:return this.printJsonAccess(e);case`array_expr`:return this.printArrayExpr(e);case`window_function`:return this.printWindowFunction(e);case`aliased_expr`:return this.printAliasedExpr(e);case`full_text_search`:return this.printFullTextSearch(e)}}printColumnRef(t){let n=t.table?`${e(t.table,this.dialect)}.${e(t.column,this.dialect)}`:e(t.column,this.dialect);return t.alias&&(n+=` AS ${e(t.alias,this.dialect)}`),n}printLiteral(e){return e.value===null?`NULL`:typeof e.value==`boolean`?e.value?`TRUE`:`FALSE`:typeof e.value==`number`?String(e.value):`'${String(e.value).replaceAll(`'`,`''`)}'`}printBinaryOp(e){return`(${this.printExpression(e.left)} ${e.op} ${this.printExpression(e.right)})`}printUnaryOp(e){return e.position===`postfix`?`(${this.printExpression(e.operand)} ${e.op})`:`(${e.op} ${this.printExpression(e.operand)})`}printFunctionCall(t){let n=`${t.name}(${t.args.map(e=>this.printExpression(e)).join(`, `)})`;return t.alias&&(n+=` AS ${e(t.alias,this.dialect)}`),n}printParam(e){return this.params.push(e.value),n(this.params.length-1,this.dialect)}printRaw(e){return this.params.push(...e.params),e.sql}printSubquery(t){let n=`(${this.printSelect(t.query)})`;return t.alias&&(n+=` AS ${e(t.alias,this.dialect)}`),n}printBetween(e){let t=e.negated?`NOT `:``;return`(${this.printExpression(e.expr)} ${t}BETWEEN ${this.printExpression(e.low)} AND ${this.printExpression(e.high)})`}printIn(e){let t=e.negated?`NOT `:``;return Array.isArray(e.values)?`(${this.printExpression(e.expr)} ${t}IN (${e.values.map(e=>this.printExpression(e)).join(`, `)}))`:`(${this.printExpression(e.expr)} ${t}IN (${this.printSelect(e.values)}))`}printIsNull(e){let t=e.negated?` NOT`:``;return`(${this.printExpression(e.expr)} IS${t} NULL)`}printCase(e){let t=[`CASE`];e.operand&&t.push(this.printExpression(e.operand));for(let n of e.whens)t.push(`WHEN`,this.printExpression(n.condition),`THEN`,this.printExpression(n.result));return e.else_&&t.push(`ELSE`,this.printExpression(e.else_)),t.push(`END`),t.join(` `)}printCast(e){return`CAST(${this.printExpression(e.expr)} AS ${e.dataType})`}printExists(e){return`(${e.negated?`NOT `:``}EXISTS (${this.printSelect(e.query)}))`}printStar(t){return t.table?`${e(t.table,this.dialect)}.*`:`*`}printTableRef(n){let r=t(n.name,this.dialect,n.schema);return n.temporal&&(r+=` ${this.printTemporalClause(n.temporal)}`),n.alias&&(r+=` AS ${e(n.alias,this.dialect)}`),r}printTemporalClause(e){switch(e.kind){case`as_of`:return`FOR SYSTEM_TIME AS OF ${this.printExpression(e.timestamp)}`;case`from_to`:return`FOR SYSTEM_TIME FROM ${this.printExpression(e.start)} TO ${this.printExpression(e.end)}`;case`between`:return`FOR SYSTEM_TIME BETWEEN ${this.printExpression(e.start)} AND ${this.printExpression(e.end)}`;case`contained_in`:return`FOR SYSTEM_TIME CONTAINED IN (${this.printExpression(e.start)}, ${this.printExpression(e.end)})`;case`all`:return`FOR SYSTEM_TIME ALL`}}printJoin(e){let t=[];return t.push(`${e.joinType} JOIN`),e.table.type===`subquery`?t.push(this.printSubquery(e.table)):t.push(this.printTableRef(e.table)),e.on&&t.push(`ON`,this.printExpression(e.on)),t.join(` `)}printOrderBy(e){let t=`${this.printExpression(e.expr)} ${e.direction}`;return e.nulls&&(t+=` NULLS ${e.nulls}`),t}printCTEs(t){return`${t.some(e=>e.recursive)?`WITH RECURSIVE`:`WITH`} ${t.map(t=>`${e(t.name,this.dialect)} AS (${this.printSelect(t.query)})`).join(`, `)}`}printJsonAccess(t){let n=`${this.printExpression(t.expr)}${t.operator}${this.printLiteral({type:`literal`,value:t.path})}`;return t.alias&&(n+=` AS ${e(t.alias,this.dialect)}`),n}printArrayExpr(e){return`ARRAY[${e.elements.map(e=>this.printExpression(e)).join(`, `)}]`}printWindowFunction(t){let n=[];n.push(this.printFunctionCall(t.fn)),n.push(`OVER`);let r=[];return t.partitionBy.length>0&&r.push(`PARTITION BY ${t.partitionBy.map(e=>this.printExpression(e)).join(`, `)}`),t.orderBy.length>0&&r.push(`ORDER BY ${t.orderBy.map(e=>this.printOrderBy(e)).join(`, `)}`),t.frame&&r.push(this.printFrameSpec(t.frame)),n.push(`(${r.join(` `)})`),t.alias&&n.push(`AS`,e(t.alias,this.dialect)),n.join(` `)}printFrameSpec(e){let t=this.printFrameBound(e.start);return e.end?`${e.kind} BETWEEN ${t} AND ${this.printFrameBound(e.end)}`:`${e.kind} ${t}`}printFrameBound(e){switch(e.type){case`unbounded_preceding`:return`UNBOUNDED PRECEDING`;case`preceding`:return`${e.value} PRECEDING`;case`current_row`:return`CURRENT ROW`;case`following`:return`${e.value} FOLLOWING`;case`unbounded_following`:return`UNBOUNDED FOLLOWING`}}printMerge(t){let n=[];t.ctes.length>0&&n.push(this.printCTEs(t.ctes)),n.push(`MERGE INTO`,this.printTableRef(t.target)),n.push(`USING`),t.source.type===`subquery`?n.push(this.printSubquery(t.source)):n.push(this.printTableRef(t.source)),n.push(`AS`,e(t.sourceAlias,this.dialect)),n.push(`ON`,this.printExpression(t.on));for(let e of t.whens)e.type===`matched`?n.push(this.printMergeWhenMatched(e)):n.push(this.printMergeWhenNotMatched(e));return n.join(` `)}printMergeWhenMatched(t){let n=[`WHEN MATCHED`];if(t.condition&&n.push(`AND`,this.printExpression(t.condition)),t.action===`delete`)n.push(`THEN DELETE`);else{n.push(`THEN UPDATE SET`);let r=(t.set??[]).map(t=>`${e(t.column,this.dialect)} = ${this.printExpression(t.value)}`);n.push(r.join(`, `))}return n.join(` `)}printMergeWhenNotMatched(t){let n=[`WHEN NOT MATCHED`];return t.condition&&n.push(`AND`,this.printExpression(t.condition)),n.push(`THEN INSERT`),n.push(`(${t.columns.map(t=>e(t,this.dialect)).join(`, `)})`),n.push(`VALUES (${t.values.map(e=>this.printExpression(e)).join(`, `)})`),n.join(` `)}printAliasedExpr(t){return`${this.printExpression(t.expr)} AS ${e(t.alias,this.dialect)}`}printFullTextSearch(t){let n=t.columns.map(e=>this.printExpression(e)).join(` || ' ' || `),r=t.language?`'${t.language}', `:``,i=`(to_tsvector(${r}${n}) @@ to_tsquery(${r}${this.printExpression(t.query)}))`;return t.alias&&(i+=` AS ${e(t.alias,this.dialect)}`),i}};export{t as i,n,e as r,r as t};
|