tempest-db-js 0.1.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.
@@ -0,0 +1,1360 @@
1
+ import { createRequire } from 'module';
2
+
3
+ // src/conditions.ts
4
+ var CONDITION = /* @__PURE__ */ Symbol.for("tempest-db-js.condition");
5
+ function isCondition(value) {
6
+ return typeof value === "object" && value !== null && value[CONDITION] === true;
7
+ }
8
+ function toCondNode(input) {
9
+ return isCondition(input) ? input.node : { kind: "fields", fields: input };
10
+ }
11
+ function wrap(node) {
12
+ return { [CONDITION]: true, node };
13
+ }
14
+ function and(...inputs) {
15
+ return wrap({
16
+ kind: "and",
17
+ parts: inputs.map((i) => toCondNode(i))
18
+ });
19
+ }
20
+ function or(...inputs) {
21
+ return wrap({
22
+ kind: "or",
23
+ parts: inputs.map((i) => toCondNode(i))
24
+ });
25
+ }
26
+ function not(input) {
27
+ return wrap({ kind: "not", part: toCondNode(input) });
28
+ }
29
+
30
+ // src/query.ts
31
+ var OPERATORS = [
32
+ "eq",
33
+ "ne",
34
+ "gt",
35
+ "gte",
36
+ "lt",
37
+ "lte",
38
+ "like",
39
+ "ilike",
40
+ "in",
41
+ "notIn",
42
+ "between",
43
+ "isNull"
44
+ ];
45
+ var SelectBuilder = class _SelectBuilder {
46
+ constructor(node, source) {
47
+ this.node = node;
48
+ this.source = source;
49
+ }
50
+ node;
51
+ source;
52
+ with(patch) {
53
+ return new _SelectBuilder({ ...this.node, ...patch }, this.source);
54
+ }
55
+ /** Add a WHERE filter: the object form (keys typed) or an `and`/`or`/`not`. */
56
+ where(input) {
57
+ return this.with({ where: toCondNode(input) });
58
+ }
59
+ /** Order by a column of `Full`. */
60
+ orderBy(column2, direction = "asc") {
61
+ return this.with({
62
+ orderBy: [...this.node.orderBy, { column: column2, direction }]
63
+ });
64
+ }
65
+ /** Limit the number of rows. */
66
+ limit(n) {
67
+ return this.with({ limit: n });
68
+ }
69
+ /** Skip the first `n` rows. */
70
+ offset(n) {
71
+ return this.with({ offset: n });
72
+ }
73
+ };
74
+ function select(model, columns) {
75
+ return new SelectBuilder(
76
+ {
77
+ kind: "select",
78
+ table: model.tablename,
79
+ columns: columns ?? "*",
80
+ where: void 0,
81
+ orderBy: [],
82
+ limit: void 0,
83
+ offset: void 0
84
+ },
85
+ model
86
+ );
87
+ }
88
+
89
+ // src/mutations.ts
90
+ var InsertBuilder = class _InsertBuilder {
91
+ constructor(node, source) {
92
+ this.node = node;
93
+ this.source = source;
94
+ }
95
+ node;
96
+ source;
97
+ with(patch) {
98
+ return new _InsertBuilder({ ...this.node, ...patch }, this.source);
99
+ }
100
+ /** Provide one row or many rows to insert, typed by the insert shape. */
101
+ values(rows) {
102
+ const list = Array.isArray(rows) ? rows : [rows];
103
+ return this.with({ values: list });
104
+ }
105
+ returning(columns) {
106
+ return this.with({ returning: columns ?? "*" });
107
+ }
108
+ };
109
+ function insert(model) {
110
+ return new InsertBuilder(
111
+ {
112
+ kind: "insert",
113
+ table: model.tablename,
114
+ values: [],
115
+ returning: null
116
+ },
117
+ model
118
+ );
119
+ }
120
+ var UpdateBuilder = class _UpdateBuilder {
121
+ constructor(node, source) {
122
+ this.node = node;
123
+ this.source = source;
124
+ }
125
+ node;
126
+ source;
127
+ with(patch) {
128
+ return new _UpdateBuilder({ ...this.node, ...patch }, this.source);
129
+ }
130
+ /** The columns to write. Partial — only the given columns change. */
131
+ set(values) {
132
+ return this.with({ set: values });
133
+ }
134
+ /** Restrict the rows to update. Marks the builder safe to execute. */
135
+ where(input) {
136
+ return this.with({
137
+ where: toCondNode(input),
138
+ guarded: true
139
+ });
140
+ }
141
+ /** Explicit opt-in to update EVERY row. Use deliberately. */
142
+ unguarded() {
143
+ return this.with({ guarded: true });
144
+ }
145
+ returning(columns) {
146
+ return this.with({ returning: columns ?? "*" });
147
+ }
148
+ };
149
+ function update(model) {
150
+ return new UpdateBuilder(
151
+ {
152
+ kind: "update",
153
+ table: model.tablename,
154
+ set: {},
155
+ where: void 0,
156
+ guarded: false,
157
+ returning: null
158
+ },
159
+ model
160
+ );
161
+ }
162
+ var DeleteBuilder = class _DeleteBuilder {
163
+ constructor(node, source) {
164
+ this.node = node;
165
+ this.source = source;
166
+ }
167
+ node;
168
+ source;
169
+ with(patch) {
170
+ return new _DeleteBuilder({ ...this.node, ...patch }, this.source);
171
+ }
172
+ /** Restrict the rows to delete. Marks the builder safe to execute. */
173
+ where(input) {
174
+ return this.with({
175
+ where: toCondNode(input),
176
+ guarded: true
177
+ });
178
+ }
179
+ /** Explicit opt-in to delete EVERY row. Use deliberately. */
180
+ unguarded() {
181
+ return this.with({ guarded: true });
182
+ }
183
+ returning(columns) {
184
+ return this.with({ returning: columns ?? "*" });
185
+ }
186
+ };
187
+ function del(model) {
188
+ return new DeleteBuilder(
189
+ {
190
+ kind: "delete",
191
+ table: model.tablename,
192
+ where: void 0,
193
+ guarded: false,
194
+ returning: null
195
+ },
196
+ model
197
+ );
198
+ }
199
+
200
+ // src/url.ts
201
+ var InvalidDatabaseUrl = class extends Error {
202
+ constructor(url, reason) {
203
+ super(`Invalid database URL ${JSON.stringify(url)}: ${reason}`);
204
+ this.name = "InvalidDatabaseUrl";
205
+ }
206
+ };
207
+ var DIALECT_ALIASES = {
208
+ sqlite: "sqlite",
209
+ sqlite3: "sqlite",
210
+ postgresql: "postgresql",
211
+ postgres: "postgresql",
212
+ pg: "postgresql"
213
+ };
214
+ function splitScheme(scheme) {
215
+ const plus = scheme.indexOf("+");
216
+ if (plus === -1) return { base: scheme.toLowerCase(), driver: null };
217
+ return {
218
+ base: scheme.slice(0, plus).toLowerCase(),
219
+ driver: scheme.slice(plus + 1) || null
220
+ };
221
+ }
222
+ function decode(value) {
223
+ try {
224
+ return decodeURIComponent(value);
225
+ } catch {
226
+ return value;
227
+ }
228
+ }
229
+ function parseSqlite(raw, driver, rest) {
230
+ let database;
231
+ if (rest.endsWith(":memory:")) {
232
+ database = ":memory:";
233
+ } else if (rest.startsWith("///")) {
234
+ database = rest.slice(3) || ":memory:";
235
+ } else if (rest.startsWith("//")) {
236
+ database = rest.slice(2) || ":memory:";
237
+ } else {
238
+ database = rest || ":memory:";
239
+ }
240
+ return {
241
+ dialect: "sqlite",
242
+ driver,
243
+ host: null,
244
+ port: null,
245
+ user: null,
246
+ password: null,
247
+ database: decode(database),
248
+ options: {},
249
+ raw
250
+ };
251
+ }
252
+ function parsePostgres(raw, driver, rest) {
253
+ let parsed;
254
+ try {
255
+ parsed = new URL(`postgresql:${rest}`);
256
+ } catch {
257
+ throw new InvalidDatabaseUrl(raw, "could not parse host/credentials");
258
+ }
259
+ const database = decode(parsed.pathname.replace(/^\//, "")) || null;
260
+ const options = {};
261
+ for (const [key, value] of parsed.searchParams) options[key] = value;
262
+ return {
263
+ dialect: "postgresql",
264
+ driver,
265
+ host: parsed.hostname || null,
266
+ port: parsed.port ? Number(parsed.port) : null,
267
+ user: parsed.username ? decode(parsed.username) : null,
268
+ password: parsed.password ? decode(parsed.password) : null,
269
+ database,
270
+ options,
271
+ raw
272
+ };
273
+ }
274
+ function parseDatabaseUrl(url) {
275
+ const schemeEnd = url.indexOf(":");
276
+ if (schemeEnd === -1) {
277
+ throw new InvalidDatabaseUrl(
278
+ url,
279
+ "missing scheme (expected e.g. sqlite:// or postgresql://)"
280
+ );
281
+ }
282
+ const { base, driver } = splitScheme(url.slice(0, schemeEnd));
283
+ const dialect = DIALECT_ALIASES[base];
284
+ if (!dialect) {
285
+ throw new InvalidDatabaseUrl(url, `unknown dialect ${JSON.stringify(base)}`);
286
+ }
287
+ const rest = url.slice(schemeEnd + 1);
288
+ return dialect === "sqlite" ? parseSqlite(url, driver, rest) : parsePostgres(url, driver, rest);
289
+ }
290
+ function detectDialect(url) {
291
+ return parseDatabaseUrl(url).dialect;
292
+ }
293
+
294
+ // src/serialize.ts
295
+ var ValidationError = class extends Error {
296
+ constructor(table, issues) {
297
+ super(`Validation failed for ${table}:
298
+ - ${issues.join("\n - ")}`);
299
+ this.table = table;
300
+ this.issues = issues;
301
+ this.name = "ValidationError";
302
+ }
303
+ table;
304
+ issues;
305
+ };
306
+ function toBase64(bytes) {
307
+ if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
308
+ let binary = "";
309
+ for (const byte of bytes) binary += String.fromCharCode(byte);
310
+ return btoa(binary);
311
+ }
312
+ function fromBase64(value) {
313
+ if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(value, "base64"));
314
+ const binary = atob(value);
315
+ const bytes = new Uint8Array(binary.length);
316
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
317
+ return bytes;
318
+ }
319
+ function encodeValue(column2, value) {
320
+ if (value === null || value === void 0) return null;
321
+ switch (column2.type.kind) {
322
+ case "bigint":
323
+ return typeof value === "bigint" ? value.toString() : value;
324
+ case "date":
325
+ case "datetime":
326
+ case "timestamp":
327
+ return value instanceof Date ? value.toISOString() : value;
328
+ case "blob":
329
+ return value instanceof Uint8Array ? toBase64(value) : value;
330
+ default:
331
+ return value;
332
+ }
333
+ }
334
+ function decodeValue(column2, value) {
335
+ if (value === null || value === void 0) return null;
336
+ switch (column2.type.kind) {
337
+ case "bigint":
338
+ return typeof value === "bigint" ? value : BigInt(value);
339
+ case "date":
340
+ case "datetime":
341
+ case "timestamp":
342
+ return value instanceof Date ? value : new Date(value);
343
+ case "blob":
344
+ return value instanceof Uint8Array ? value : fromBase64(value);
345
+ case "json":
346
+ return typeof value === "string" ? JSON.parse(value) : value;
347
+ case "numeric":
348
+ return typeof value === "string" ? value : String(value);
349
+ case "boolean":
350
+ return typeof value === "boolean" ? value : value === 1 || value === "true";
351
+ case "smallint":
352
+ case "integer":
353
+ case "real":
354
+ case "double":
355
+ return typeof value === "number" ? value : Number(value);
356
+ default:
357
+ return value;
358
+ }
359
+ }
360
+ function toDict(model, row) {
361
+ const columns = columnsOf(model);
362
+ const out = {};
363
+ for (const name of Object.keys(columns)) {
364
+ out[name] = row[name] ?? null;
365
+ }
366
+ return out;
367
+ }
368
+ function toJSON(model, row) {
369
+ const columns = columnsOf(model);
370
+ const out = {};
371
+ for (const [name, col] of Object.entries(columns)) {
372
+ out[name] = encodeValue(col, row[name] ?? null);
373
+ }
374
+ return out;
375
+ }
376
+ function stringify(model, row) {
377
+ return JSON.stringify(toJSON(model, row));
378
+ }
379
+ function fromDict(model, data) {
380
+ const columns = columnsOf(model);
381
+ const out = {};
382
+ const issues = [];
383
+ for (const [name, col] of Object.entries(columns)) {
384
+ const present = name in data && data[name] !== void 0 && data[name] !== null;
385
+ if (!present) {
386
+ const required = col.flags.notNull && !col.flags.hasDefault;
387
+ if (required) {
388
+ issues.push(`missing required column "${name}"`);
389
+ continue;
390
+ }
391
+ out[name] = null;
392
+ continue;
393
+ }
394
+ try {
395
+ out[name] = decodeValue(col, data[name]);
396
+ } catch (error) {
397
+ issues.push(`column "${name}": ${error.message}`);
398
+ }
399
+ }
400
+ if (issues.length > 0) {
401
+ throw new ValidationError(model.tablename, issues);
402
+ }
403
+ return out;
404
+ }
405
+ function parse(model, json) {
406
+ return fromDict(model, JSON.parse(json));
407
+ }
408
+ function coerceRow(model, raw) {
409
+ const columns = columnsOf(model);
410
+ const out = {};
411
+ for (const [name, value] of Object.entries(raw)) {
412
+ const col = columns[name];
413
+ out[name] = col ? decodeValue(col, value) : value;
414
+ }
415
+ return out;
416
+ }
417
+
418
+ // src/dialect.ts
419
+ var OPERATOR_SET = new Set(OPERATORS);
420
+ function isOperatorObject(value) {
421
+ if (typeof value !== "object" || value === null || Array.isArray(value) || value instanceof Date || value instanceof Uint8Array) {
422
+ return false;
423
+ }
424
+ const keys = Object.keys(value);
425
+ return keys.length > 0 && keys.every((k) => OPERATOR_SET.has(k));
426
+ }
427
+ var Params = class {
428
+ constructor(placeholder) {
429
+ this.placeholder = placeholder;
430
+ }
431
+ placeholder;
432
+ values = [];
433
+ bind(value) {
434
+ this.values.push(value);
435
+ return this.placeholder(this.values.length);
436
+ }
437
+ };
438
+ var BaseDialect = class {
439
+ /** Quote an identifier (column/table) for the active dialect. */
440
+ quoteId(name) {
441
+ return `"${name.replace(/"/g, '""')}"`;
442
+ }
443
+ /** Compile any node to `{ sql, params }`. */
444
+ compile(node) {
445
+ const params = new Params((i) => this.placeholder(i));
446
+ let sql2;
447
+ switch (node.kind) {
448
+ case "select":
449
+ sql2 = this.compileSelect(node, params);
450
+ break;
451
+ case "insert":
452
+ sql2 = this.compileInsert(node, params);
453
+ break;
454
+ case "update":
455
+ sql2 = this.compileUpdate(node, params);
456
+ break;
457
+ case "delete":
458
+ sql2 = this.compileDelete(node, params);
459
+ break;
460
+ case "join_select":
461
+ sql2 = this.compileJoin(node, params);
462
+ break;
463
+ }
464
+ return { sql: sql2, params: params.values };
465
+ }
466
+ /** Render a qualified `alias.column` ref as `"alias"."column"`. */
467
+ qualify(ref) {
468
+ const dot = ref.indexOf(".");
469
+ if (dot === -1) return this.quoteId(ref);
470
+ return `${this.quoteId(ref.slice(0, dot))}.${this.quoteId(ref.slice(dot + 1))}`;
471
+ }
472
+ // ---- statements -------------------------------------------------------
473
+ compileSelect(node, params) {
474
+ const cols = node.columns === "*" ? "*" : node.columns.map((c) => this.quoteId(c)).join(", ");
475
+ let sql2 = `SELECT ${cols} FROM ${this.quoteId(node.table)}`;
476
+ const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
477
+ if (where) sql2 += ` WHERE ${where}`;
478
+ if (node.orderBy.length > 0) {
479
+ const terms = node.orderBy.map(
480
+ (t) => `${this.quoteId(t.column)} ${t.direction === "desc" ? "DESC" : "ASC"}`
481
+ ).join(", ");
482
+ sql2 += ` ORDER BY ${terms}`;
483
+ }
484
+ if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
485
+ if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
486
+ return sql2;
487
+ }
488
+ compileInsert(node, params) {
489
+ const columns = node.values.length > 0 ? Object.keys(node.values[0]) : [];
490
+ const colSql = columns.map((c) => this.quoteId(c)).join(", ");
491
+ const rowsSql = node.values.map(
492
+ (row) => `(${columns.map((c) => params.bind(row[c] ?? null)).join(", ")})`
493
+ ).join(", ");
494
+ let sql2 = `INSERT INTO ${this.quoteId(node.table)} (${colSql}) VALUES ${rowsSql}`;
495
+ sql2 += this.compileReturning(node.returning);
496
+ return sql2;
497
+ }
498
+ compileUpdate(node, params) {
499
+ const sets = Object.entries(node.set).map(([col, value]) => `${this.quoteId(col)} = ${params.bind(value)}`).join(", ");
500
+ let sql2 = `UPDATE ${this.quoteId(node.table)} SET ${sets}`;
501
+ const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
502
+ if (where) sql2 += ` WHERE ${where}`;
503
+ sql2 += this.compileReturning(node.returning);
504
+ return sql2;
505
+ }
506
+ compileDelete(node, params) {
507
+ let sql2 = `DELETE FROM ${this.quoteId(node.table)}`;
508
+ const where = this.compileCondition(node.where, params, (k) => this.quoteId(k));
509
+ if (where) sql2 += ` WHERE ${where}`;
510
+ sql2 += this.compileReturning(node.returning);
511
+ return sql2;
512
+ }
513
+ compileJoin(node, params) {
514
+ const cols = node.selections.map((s) => {
515
+ const ref = `${s.alias}.${s.column}`;
516
+ return `${this.qualify(ref)} AS ${this.quoteId(ref)}`;
517
+ }).join(", ");
518
+ let sql2 = `SELECT ${cols} FROM ${this.quoteId(node.base.table)} AS ${this.quoteId(node.base.alias)}`;
519
+ for (const j of node.joins) {
520
+ const kw = j.kind === "left" ? "LEFT JOIN" : "INNER JOIN";
521
+ const on = j.on.map(([l, r]) => `${this.qualify(l)} = ${this.qualify(r)}`).join(" AND ");
522
+ sql2 += ` ${kw} ${this.quoteId(j.table)} AS ${this.quoteId(j.alias)} ON ${on}`;
523
+ }
524
+ const where = this.compileCondition(node.where, params, (k) => this.qualify(k));
525
+ if (where) sql2 += ` WHERE ${where}`;
526
+ if (node.orderBy.length > 0) {
527
+ const terms = node.orderBy.map((t) => `${this.qualify(t.ref)} ${t.direction === "desc" ? "DESC" : "ASC"}`).join(", ");
528
+ sql2 += ` ORDER BY ${terms}`;
529
+ }
530
+ if (node.limit !== void 0) sql2 += ` LIMIT ${params.bind(node.limit)}`;
531
+ if (node.offset !== void 0) sql2 += ` OFFSET ${params.bind(node.offset)}`;
532
+ return sql2;
533
+ }
534
+ // ---- clauses ----------------------------------------------------------
535
+ compileReturning(returning) {
536
+ if (returning === null) return "";
537
+ if (returning === "*") return " RETURNING *";
538
+ return ` RETURNING ${returning.map((c) => this.quoteId(c)).join(", ")}`;
539
+ }
540
+ /**
541
+ * Compile a condition tree (fields / and / or / not) to SQL. `idFor` renders a
542
+ * key to a quoted identifier — `quoteId` for single-table, `qualify` for joins —
543
+ * so select/update/delete/join all share this one compiler.
544
+ */
545
+ compileCondition(node, params, idFor) {
546
+ if (!node) return "";
547
+ switch (node.kind) {
548
+ case "fields": {
549
+ const conditions = [];
550
+ for (const [key, value] of Object.entries(node.fields)) {
551
+ const id = idFor(key);
552
+ if (isOperatorObject(value)) {
553
+ for (const [op, operand] of Object.entries(value)) {
554
+ conditions.push(this.compileOperator(id, op, operand, params));
555
+ }
556
+ } else {
557
+ conditions.push(
558
+ value === null ? `${id} IS NULL` : `${id} = ${params.bind(value)}`
559
+ );
560
+ }
561
+ }
562
+ return conditions.join(" AND ");
563
+ }
564
+ case "and":
565
+ case "or": {
566
+ const parts = node.parts.map((p) => this.compileCondition(p, params, idFor)).filter((s) => s.length > 0);
567
+ if (parts.length === 0) return "";
568
+ const sep = node.kind === "and" ? " AND " : " OR ";
569
+ return parts.map((p) => `(${p})`).join(sep);
570
+ }
571
+ case "not": {
572
+ const inner = this.compileCondition(node.part, params, idFor);
573
+ return inner ? `NOT (${inner})` : "";
574
+ }
575
+ }
576
+ }
577
+ compileOperator(id, op, operand, params) {
578
+ switch (op) {
579
+ case "eq":
580
+ return operand === null ? `${id} IS NULL` : `${id} = ${params.bind(operand)}`;
581
+ case "ne":
582
+ return operand === null ? `${id} IS NOT NULL` : `${id} <> ${params.bind(operand)}`;
583
+ case "gt":
584
+ return `${id} > ${params.bind(operand)}`;
585
+ case "gte":
586
+ return `${id} >= ${params.bind(operand)}`;
587
+ case "lt":
588
+ return `${id} < ${params.bind(operand)}`;
589
+ case "lte":
590
+ return `${id} <= ${params.bind(operand)}`;
591
+ case "like":
592
+ return `${id} LIKE ${params.bind(operand)}`;
593
+ case "ilike":
594
+ return this.ilike(id, params.bind(operand));
595
+ case "in":
596
+ return this.compileIn(id, operand, params, false);
597
+ case "notIn":
598
+ return this.compileIn(id, operand, params, true);
599
+ case "between": {
600
+ const [lo, hi] = operand;
601
+ return `${id} BETWEEN ${params.bind(lo)} AND ${params.bind(hi)}`;
602
+ }
603
+ case "isNull":
604
+ return operand ? `${id} IS NULL` : `${id} IS NOT NULL`;
605
+ default:
606
+ throw new Error(`Unknown operator ${JSON.stringify(op)}`);
607
+ }
608
+ }
609
+ compileIn(id, values, params, negate) {
610
+ if (values.length === 0) {
611
+ return negate ? "1 = 1" : "1 = 0";
612
+ }
613
+ const list = values.map((v) => params.bind(v)).join(", ");
614
+ return `${id} ${negate ? "NOT IN" : "IN"} (${list})`;
615
+ }
616
+ };
617
+ var SqliteDialect = class extends BaseDialect {
618
+ name = "sqlite";
619
+ placeholder() {
620
+ return "?";
621
+ }
622
+ ilike(column2, param) {
623
+ return `${column2} LIKE ${param}`;
624
+ }
625
+ };
626
+ var PostgresDialect = class extends BaseDialect {
627
+ name = "postgresql";
628
+ placeholder(index) {
629
+ return `$${index}`;
630
+ }
631
+ ilike(column2, param) {
632
+ return `${column2} ILIKE ${param}`;
633
+ }
634
+ };
635
+ function getDialect(name) {
636
+ return name === "sqlite" ? new SqliteDialect() : new PostgresDialect();
637
+ }
638
+
639
+ // src/join.ts
640
+ function selectionsFor(alias, model) {
641
+ return Object.keys(columnsOf(model)).map((column2) => ({ alias, column: column2 }));
642
+ }
643
+ var JoinBuilder = class _JoinBuilder {
644
+ constructor(node, sources) {
645
+ this.node = node;
646
+ this.sources = sources;
647
+ }
648
+ node;
649
+ sources;
650
+ add(clause, model) {
651
+ return new _JoinBuilder(
652
+ {
653
+ ...this.node,
654
+ joins: [...this.node.joins, clause],
655
+ selections: [...this.node.selections, ...selectionsFor(clause.alias, model)]
656
+ },
657
+ { ...this.sources, [clause.alias]: model }
658
+ );
659
+ }
660
+ clause(kind, model, alias, on) {
661
+ return {
662
+ kind,
663
+ table: model.tablename,
664
+ alias,
665
+ on: Object.entries(on)
666
+ };
667
+ }
668
+ /** Inner join another model under `alias`. */
669
+ innerJoin(model, alias, on) {
670
+ return this.add(
671
+ this.clause("inner", model, alias, on),
672
+ model
673
+ );
674
+ }
675
+ /** Left (outer) join another model under `alias` — its side becomes nullable. */
676
+ leftJoin(model, alias, on) {
677
+ return this.add(
678
+ this.clause("left", model, alias, on),
679
+ model
680
+ );
681
+ }
682
+ /** Filter by `alias.column` references (object form) or an `and`/`or`/`not`. */
683
+ where(input) {
684
+ return new _JoinBuilder(
685
+ { ...this.node, where: toCondNode(input) },
686
+ this.sources
687
+ );
688
+ }
689
+ /** Order by an `alias.column` reference. */
690
+ orderBy(ref, direction = "asc") {
691
+ return new _JoinBuilder(
692
+ {
693
+ ...this.node,
694
+ orderBy: [...this.node.orderBy, { ref, direction }]
695
+ },
696
+ this.sources
697
+ );
698
+ }
699
+ limit(n) {
700
+ return new _JoinBuilder({ ...this.node, limit: n }, this.sources);
701
+ }
702
+ offset(n) {
703
+ return new _JoinBuilder({ ...this.node, offset: n }, this.sources);
704
+ }
705
+ };
706
+ function join(model, alias) {
707
+ return new JoinBuilder(
708
+ {
709
+ kind: "join_select",
710
+ base: { table: model.tablename, alias },
711
+ joins: [],
712
+ selections: selectionsFor(alias, model),
713
+ where: void 0,
714
+ orderBy: [],
715
+ limit: void 0,
716
+ offset: void 0
717
+ },
718
+ { [alias]: model }
719
+ );
720
+ }
721
+
722
+ // src/repository.ts
723
+ var RecordNotFound = class extends Error {
724
+ constructor(table, id) {
725
+ super(`${table} not found for id ${JSON.stringify(id)}`);
726
+ this.name = "RecordNotFound";
727
+ }
728
+ };
729
+ function primaryKeyOf(model) {
730
+ for (const [name, col] of Object.entries(columnsOf(model))) {
731
+ if (col.flags.primaryKey) return name;
732
+ }
733
+ throw new Error(`${model.tablename} has no primary key`);
734
+ }
735
+ var BaseRepository = class {
736
+ constructor(model, session) {
737
+ this.model = model;
738
+ this.session = session;
739
+ this.pk = primaryKeyOf(model);
740
+ }
741
+ model;
742
+ session;
743
+ pk;
744
+ /** All rows matching `filters` (or everything). Empty list when none match. */
745
+ async list(filters) {
746
+ const query = filters ? select(this.model).where(filters) : select(this.model);
747
+ return this.session.execute(query).all();
748
+ }
749
+ /** The first row matching `filters`, or `null`. */
750
+ async first(filters) {
751
+ const query = filters ? select(this.model).where(filters) : select(this.model);
752
+ return this.session.execute(query).first();
753
+ }
754
+ /** A single row by primary key, or `null`. */
755
+ async getByIdOrNull(id) {
756
+ return this.session.execute(select(this.model).where({ [this.pk]: id })).first();
757
+ }
758
+ /** A single row by primary key; throws `RecordNotFound` when absent. */
759
+ async getById(id) {
760
+ const row = await this.getByIdOrNull(id);
761
+ if (row === null) throw new RecordNotFound(this.model.tablename, id);
762
+ return row;
763
+ }
764
+ /** Whether any row matches `filters`. */
765
+ async exists(filters) {
766
+ return await this.first(filters) !== null;
767
+ }
768
+ /** How many rows match `filters` (or the whole table). */
769
+ async count(filters) {
770
+ const query = filters ? select(this.model, [this.pk]).where(filters) : select(this.model, [this.pk]);
771
+ return (await this.session.execute(query).all()).length;
772
+ }
773
+ /** Insert one row, returning the created row. */
774
+ async create(data) {
775
+ return this.session.execute(insert(this.model).values(data).returning()).one();
776
+ }
777
+ /** Insert many rows, returning the created rows. */
778
+ async createMany(data) {
779
+ if (data.length === 0) return [];
780
+ return this.session.execute(insert(this.model).values(data).returning()).all();
781
+ }
782
+ /** Update rows matching `filters`; returns the number of rows affected. */
783
+ async update(filters, set) {
784
+ return this.session.execute(update(this.model).set(set).where(filters)).rowsAffected();
785
+ }
786
+ /** Delete rows matching `filters`; returns the number of rows affected. */
787
+ async delete(filters) {
788
+ return this.session.execute(del(this.model).where(filters)).rowsAffected();
789
+ }
790
+ /**
791
+ * A page of rows plus metadata. `total` counts all matching rows.
792
+ *
793
+ * @param filter Page, size, ordering and filters.
794
+ * @returns The page and pagination metadata.
795
+ */
796
+ async paginate(filter = {}) {
797
+ const page = Math.max(1, filter.page ?? 1);
798
+ const pageSize = Math.max(1, filter.pageSize ?? 20);
799
+ const where = filter.filters;
800
+ let query = where ? select(this.model).where(where) : select(this.model);
801
+ if (filter.orderBy) {
802
+ query = query.orderBy(filter.orderBy, filter.ascending === false ? "desc" : "asc");
803
+ }
804
+ query = query.limit(pageSize).offset((page - 1) * pageSize);
805
+ const items = await this.session.execute(query).all();
806
+ const total = await this.count(where);
807
+ return {
808
+ items,
809
+ total,
810
+ page,
811
+ pageSize,
812
+ pages: Math.max(1, Math.ceil(total / pageSize))
813
+ };
814
+ }
815
+ };
816
+
817
+ // src/relations.ts
818
+ function hasMany(target, keys) {
819
+ return {
820
+ kind: "hasMany",
821
+ target,
822
+ localKey: keys.localKey,
823
+ foreignKey: keys.foreignKey
824
+ };
825
+ }
826
+ function belongsTo(target, keys) {
827
+ return {
828
+ kind: "belongsTo",
829
+ target,
830
+ localKey: keys.localKey,
831
+ foreignKey: keys.foreignKey
832
+ };
833
+ }
834
+ async function loadRelations(session, rows, spec) {
835
+ const out = rows.map((r) => ({ ...r }));
836
+ for (const [name, rel] of Object.entries(spec)) {
837
+ const target = rel.target();
838
+ const localValues = [...new Set(rows.map((r) => r[rel.localKey]))];
839
+ const related = localValues.length > 0 ? await session.execute(
840
+ select(target).where({
841
+ [rel.foreignKey]: { in: localValues }
842
+ })
843
+ ).all() : [];
844
+ if (rel.kind === "hasMany") {
845
+ const grouped = /* @__PURE__ */ new Map();
846
+ for (const row of related) {
847
+ const key = row[rel.foreignKey];
848
+ const list = grouped.get(key) ?? [];
849
+ list.push(row);
850
+ grouped.set(key, list);
851
+ }
852
+ out.forEach((r, i) => {
853
+ r[name] = grouped.get(rows[i]?.[rel.localKey]) ?? [];
854
+ });
855
+ } else {
856
+ const byKey = /* @__PURE__ */ new Map();
857
+ for (const row of related) {
858
+ byKey.set(row[rel.foreignKey], row);
859
+ }
860
+ out.forEach((r, i) => {
861
+ r[name] = byKey.get(rows[i]?.[rel.localKey]) ?? null;
862
+ });
863
+ }
864
+ }
865
+ return out;
866
+ }
867
+ var nodeRequire = createRequire(import.meta.url);
868
+ function encodeSqliteParam(value) {
869
+ if (value === void 0 || value === null) return null;
870
+ if (typeof value === "boolean") return value ? 1 : 0;
871
+ if (value instanceof Date) return value.toISOString();
872
+ if (value instanceof Uint8Array) return value;
873
+ if (typeof value === "object") return JSON.stringify(value);
874
+ return value;
875
+ }
876
+ var NodeSqliteDriver = class _NodeSqliteDriver {
877
+ // biome-ignore lint/suspicious/noExplicitAny: node:sqlite DatabaseSync has no shipped types here.
878
+ db;
879
+ // biome-ignore lint/suspicious/noExplicitAny: accept an already-open DatabaseSync handle.
880
+ constructor(database) {
881
+ this.db = database;
882
+ }
883
+ /** Open a `node:sqlite` database at the given path (or `:memory:`). */
884
+ static open(path) {
885
+ const { DatabaseSync } = nodeRequire("node:sqlite");
886
+ return new _NodeSqliteDriver(new DatabaseSync(path));
887
+ }
888
+ execute(sql2, params) {
889
+ const stmt = this.db.prepare(sql2);
890
+ const bound = params.map(encodeSqliteParam);
891
+ if (returnsRows(sql2)) {
892
+ return { rows: stmt.all(...bound), changes: 0 };
893
+ }
894
+ const info = stmt.run(...bound);
895
+ return { rows: [], changes: Number(info.changes ?? 0) };
896
+ }
897
+ *iterate(sql2, params) {
898
+ const stmt = this.db.prepare(sql2);
899
+ const bound = params.map(encodeSqliteParam);
900
+ yield* stmt.iterate(...bound);
901
+ }
902
+ close() {
903
+ this.db.close();
904
+ }
905
+ };
906
+ function returnsRows(sql2) {
907
+ return /^\s*(select|pragma)/i.test(sql2) || /\breturning\b/i.test(sql2);
908
+ }
909
+ function splitJoinRow(node, sources, raw) {
910
+ const leftAliases = new Set(
911
+ node.joins.filter((j) => j.kind === "left").map((j) => j.alias)
912
+ );
913
+ const out = {};
914
+ for (const [alias, model] of Object.entries(sources)) {
915
+ const sub = {};
916
+ let allNull = true;
917
+ for (const colName of Object.keys(columnsOf(model))) {
918
+ const value = raw[`${alias}.${colName}`];
919
+ if (value !== null && value !== void 0) allNull = false;
920
+ sub[colName] = value;
921
+ }
922
+ out[alias] = leftAliases.has(alias) && allNull ? null : coerceRow(model, sub);
923
+ }
924
+ return out;
925
+ }
926
+ function coerceOne(builder, raw) {
927
+ const node = builder.node;
928
+ if (node.kind === "join_select") {
929
+ const b2 = builder;
930
+ return splitJoinRow(b2.node, b2.sources, raw);
931
+ }
932
+ const b = builder;
933
+ return coerceRow(b.source, raw);
934
+ }
935
+ function mapRows(builder, raw) {
936
+ return raw.map((r) => coerceOne(builder, r));
937
+ }
938
+ var NoResultError = class extends Error {
939
+ constructor(message) {
940
+ super(message);
941
+ this.name = "NoResultError";
942
+ }
943
+ };
944
+ function firstScalar(row) {
945
+ if (!row) return null;
946
+ const keys = Object.keys(row);
947
+ return keys.length > 0 ? row[keys[0]] : null;
948
+ }
949
+ var SyncResult = class {
950
+ constructor(rows, changes) {
951
+ this.rows = rows;
952
+ this.changes = changes;
953
+ }
954
+ rows;
955
+ changes;
956
+ all() {
957
+ return this.rows;
958
+ }
959
+ first() {
960
+ return this.rows[0] ?? null;
961
+ }
962
+ one() {
963
+ if (this.rows.length !== 1) {
964
+ throw new NoResultError(`expected exactly one row, got ${this.rows.length}`);
965
+ }
966
+ return this.rows[0];
967
+ }
968
+ oneOrNull() {
969
+ if (this.rows.length > 1) {
970
+ throw new NoResultError(`expected at most one row, got ${this.rows.length}`);
971
+ }
972
+ return this.rows[0] ?? null;
973
+ }
974
+ scalar() {
975
+ return firstScalar(this.rows[0]);
976
+ }
977
+ scalars() {
978
+ return this.rows.map((r) => firstScalar(r));
979
+ }
980
+ rowsAffected() {
981
+ return this.changes;
982
+ }
983
+ };
984
+ var AsyncResult = class {
985
+ constructor(inner) {
986
+ this.inner = inner;
987
+ }
988
+ inner;
989
+ async all() {
990
+ return (await this.inner).all();
991
+ }
992
+ async first() {
993
+ return (await this.inner).first();
994
+ }
995
+ async one() {
996
+ return (await this.inner).one();
997
+ }
998
+ async oneOrNull() {
999
+ return (await this.inner).oneOrNull();
1000
+ }
1001
+ async scalar() {
1002
+ return (await this.inner).scalar();
1003
+ }
1004
+ async scalars() {
1005
+ return (await this.inner).scalars();
1006
+ }
1007
+ async rowsAffected() {
1008
+ return (await this.inner).rowsAffected();
1009
+ }
1010
+ };
1011
+ var savepointCounter = 0;
1012
+ var SyncSession = class {
1013
+ constructor(driver, dialect) {
1014
+ this.driver = driver;
1015
+ this.dialect = dialect;
1016
+ }
1017
+ driver;
1018
+ dialect;
1019
+ /** Compile, run, and coerce a builder into a result. */
1020
+ execute(builder) {
1021
+ const node = builder.node;
1022
+ const { sql: sql2, params } = this.dialect.compile(node);
1023
+ const result = this.driver.execute(sql2, params);
1024
+ const rows = mapRows(builder, result.rows);
1025
+ return new SyncResult(rows, result.changes);
1026
+ }
1027
+ /** Run `fn` inside a transaction: commit on success, rollback on throw. */
1028
+ transaction(fn) {
1029
+ this.driver.execute("BEGIN", []);
1030
+ try {
1031
+ const out = fn(this);
1032
+ this.driver.execute("COMMIT", []);
1033
+ return out;
1034
+ } catch (error) {
1035
+ this.driver.execute("ROLLBACK", []);
1036
+ throw error;
1037
+ }
1038
+ }
1039
+ /** Run `fn` inside a SAVEPOINT (nested transaction). */
1040
+ beginNested(fn) {
1041
+ savepointCounter += 1;
1042
+ const name = `qsp_${savepointCounter}`;
1043
+ this.driver.execute(`SAVEPOINT ${name}`, []);
1044
+ try {
1045
+ const out = fn(this);
1046
+ this.driver.execute(`RELEASE ${name}`, []);
1047
+ return out;
1048
+ } catch (error) {
1049
+ this.driver.execute(`ROLLBACK TO ${name}`, []);
1050
+ throw error;
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Lazily iterate result rows without materializing them all. Falls back to a
1055
+ * buffered fetch when the driver has no native iteration.
1056
+ */
1057
+ *stream(builder) {
1058
+ const node = builder.node;
1059
+ const { sql: sql2, params } = this.dialect.compile(node);
1060
+ if (this.driver.iterate) {
1061
+ for (const raw of this.driver.iterate(sql2, params)) {
1062
+ yield coerceOne(builder, raw);
1063
+ }
1064
+ return;
1065
+ }
1066
+ for (const raw of this.driver.execute(sql2, params).rows) {
1067
+ yield coerceOne(builder, raw);
1068
+ }
1069
+ }
1070
+ close() {
1071
+ this.driver.close();
1072
+ }
1073
+ };
1074
+ var AsyncSession = class {
1075
+ constructor(driver, dialect) {
1076
+ this.driver = driver;
1077
+ this.dialect = dialect;
1078
+ }
1079
+ driver;
1080
+ dialect;
1081
+ execute(builder) {
1082
+ const node = builder.node;
1083
+ const { sql: sql2, params } = this.dialect.compile(node);
1084
+ const inner = this.driver.execute(sql2, params).then((result) => {
1085
+ const rows = mapRows(builder, result.rows);
1086
+ return new SyncResult(rows, result.changes);
1087
+ });
1088
+ return new AsyncResult(inner);
1089
+ }
1090
+ /** Lazily iterate result rows. Uses driver streaming when available. */
1091
+ async *stream(builder) {
1092
+ const node = builder.node;
1093
+ const { sql: sql2, params } = this.dialect.compile(node);
1094
+ if (this.driver.iterate) {
1095
+ for await (const raw of this.driver.iterate(sql2, params)) {
1096
+ yield coerceOne(builder, raw);
1097
+ }
1098
+ return;
1099
+ }
1100
+ const result = await this.driver.execute(sql2, params);
1101
+ for (const raw of result.rows) {
1102
+ yield coerceOne(builder, raw);
1103
+ }
1104
+ }
1105
+ async transaction(fn) {
1106
+ await this.driver.execute("BEGIN", []);
1107
+ try {
1108
+ const out = await fn(this);
1109
+ await this.driver.execute("COMMIT", []);
1110
+ return out;
1111
+ } catch (error) {
1112
+ await this.driver.execute("ROLLBACK", []);
1113
+ throw error;
1114
+ }
1115
+ }
1116
+ async close() {
1117
+ await this.driver.close();
1118
+ }
1119
+ };
1120
+ var SyncEngine = class {
1121
+ constructor(driver) {
1122
+ this.driver = driver;
1123
+ }
1124
+ driver;
1125
+ dialect = "sqlite";
1126
+ session() {
1127
+ return new SyncSession(this.driver, getDialect("sqlite"));
1128
+ }
1129
+ transaction(fn) {
1130
+ return this.session().transaction(fn);
1131
+ }
1132
+ close() {
1133
+ this.driver.close();
1134
+ }
1135
+ };
1136
+ var AsyncEngine = class {
1137
+ constructor(driver, dialect) {
1138
+ this.driver = driver;
1139
+ this.dialect = dialect;
1140
+ }
1141
+ driver;
1142
+ dialect;
1143
+ session() {
1144
+ return new AsyncSession(this.driver, getDialect(this.dialect));
1145
+ }
1146
+ transaction(fn) {
1147
+ return this.session().transaction(fn);
1148
+ }
1149
+ async close() {
1150
+ await this.driver.close();
1151
+ }
1152
+ };
1153
+ function asAsync(driver) {
1154
+ const syncIterate = driver.iterate?.bind(driver);
1155
+ return {
1156
+ execute: (sql2, params) => Promise.resolve(driver.execute(sql2, params)),
1157
+ close: () => Promise.resolve(driver.close()),
1158
+ ...syncIterate ? {
1159
+ iterate: async function* (sql2, params) {
1160
+ yield* syncIterate(sql2, params);
1161
+ }
1162
+ } : {}
1163
+ };
1164
+ }
1165
+ function openSqliteDriver(path, _options) {
1166
+ return NodeSqliteDriver.open(path);
1167
+ }
1168
+ function createSyncEngine(url, options) {
1169
+ const parsed = parseDatabaseUrl(url);
1170
+ if (parsed.dialect !== "sqlite") {
1171
+ throw new Error(
1172
+ `createSyncEngine supports only SQLite; ${parsed.dialect} is async-only \u2014 use createEngine.`
1173
+ );
1174
+ }
1175
+ return new SyncEngine(openSqliteDriver(parsed.database ?? ":memory:"));
1176
+ }
1177
+ function createEngine(url, options) {
1178
+ const parsed = parseDatabaseUrl(url);
1179
+ if (parsed.dialect === "sqlite") {
1180
+ return new AsyncEngine(
1181
+ asAsync(openSqliteDriver(parsed.database ?? ":memory:")),
1182
+ "sqlite"
1183
+ );
1184
+ }
1185
+ return new AsyncEngine(createPostgresDriver(parsed.raw, options?.pool), "postgresql");
1186
+ }
1187
+ function createPostgresDriver(url, pool) {
1188
+ let client;
1189
+ const ensure = async () => {
1190
+ if (client) return;
1191
+ const moduleName = "postgres";
1192
+ const mod = await import(
1193
+ /* @vite-ignore */
1194
+ moduleName
1195
+ );
1196
+ const opts = {};
1197
+ if (pool?.size !== void 0) opts.max = pool.size;
1198
+ if (pool?.idleTimeoutMs !== void 0)
1199
+ opts.idle_timeout = Math.ceil(pool.idleTimeoutMs / 1e3);
1200
+ if (pool?.connectTimeoutMs !== void 0) {
1201
+ opts.connect_timeout = Math.ceil(pool.connectTimeoutMs / 1e3);
1202
+ }
1203
+ client = (mod.default ?? mod)(url, opts);
1204
+ };
1205
+ return {
1206
+ async execute(sql2, params) {
1207
+ await ensure();
1208
+ const rows = await client.unsafe(sql2, params);
1209
+ return {
1210
+ rows: Array.from(rows),
1211
+ changes: rows.count ?? rows.length
1212
+ };
1213
+ },
1214
+ async close() {
1215
+ if (client) await client.end();
1216
+ }
1217
+ };
1218
+ }
1219
+
1220
+ // src/index.ts
1221
+ var DEFAULT_FLAGS = {
1222
+ primaryKey: false,
1223
+ notNull: false,
1224
+ hasDefault: false
1225
+ };
1226
+ var sql = {
1227
+ /** Current timestamp at insert (`CURRENT_TIMESTAMP` / `now()`). */
1228
+ now: () => ({ kind: "expression", expression: "now" }),
1229
+ /** Current date. */
1230
+ currentDate: () => ({ kind: "expression", expression: "current_date" }),
1231
+ /** Current time. */
1232
+ currentTime: () => ({ kind: "expression", expression: "current_time" }),
1233
+ /** A freshly generated UUID v4 (`gen_random_uuid()` / portable fallback). */
1234
+ uuidv4: () => ({ kind: "expression", expression: "uuidv4" }),
1235
+ /** Escape hatch: a verbatim SQL expression rendered as-is. */
1236
+ raw: (expression) => ({
1237
+ kind: "expression",
1238
+ expression: { raw: expression }
1239
+ })
1240
+ };
1241
+ function isDefaultValue(value) {
1242
+ return typeof value === "object" && value !== null && "kind" in value && (value.kind === "literal" || value.kind === "expression");
1243
+ }
1244
+ var Column = class _Column {
1245
+ constructor(type, flags, defaultValue = null, onUpdateValue = null) {
1246
+ this.type = type;
1247
+ this.flags = flags;
1248
+ this.defaultValue = defaultValue;
1249
+ this.onUpdateValue = onUpdateValue;
1250
+ }
1251
+ type;
1252
+ flags;
1253
+ defaultValue;
1254
+ onUpdateValue;
1255
+ primaryKey() {
1256
+ return new _Column(
1257
+ this.type,
1258
+ { ...this.flags, primaryKey: true, hasDefault: true },
1259
+ this.defaultValue,
1260
+ this.onUpdateValue
1261
+ );
1262
+ }
1263
+ notNull() {
1264
+ return new _Column(
1265
+ this.type,
1266
+ { ...this.flags, notNull: true },
1267
+ this.defaultValue,
1268
+ this.onUpdateValue
1269
+ );
1270
+ }
1271
+ /**
1272
+ * Set the insert-time default: a constant value of type `T`, or a portable
1273
+ * server-side expression from {@link sql} (e.g. `sql.now()`, `sql.uuidv4()`).
1274
+ */
1275
+ default(value) {
1276
+ const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1277
+ return new _Column(
1278
+ this.type,
1279
+ { ...this.flags, hasDefault: true },
1280
+ resolved,
1281
+ this.onUpdateValue
1282
+ );
1283
+ }
1284
+ /**
1285
+ * Re-apply a value whenever the row is updated (e.g. an `updated_at` column
1286
+ * with `sql.now()`). Mirrors SQLAlchemy's `onupdate`.
1287
+ */
1288
+ onUpdate(value) {
1289
+ const resolved = isDefaultValue(value) ? value : { kind: "literal", value };
1290
+ return new _Column(this.type, this.flags, this.defaultValue, resolved);
1291
+ }
1292
+ };
1293
+ function makeColumn(kind, meta = {}) {
1294
+ return new Column({ kind, meta }, DEFAULT_FLAGS);
1295
+ }
1296
+ var column = {
1297
+ /** `SMALLINT` → `number`. */
1298
+ smallInteger: () => makeColumn("smallint"),
1299
+ /** `INTEGER` → `number`. */
1300
+ integer: () => makeColumn("integer"),
1301
+ /** `BIGINT` → `bigint` (64-bit precision preserved). */
1302
+ bigInteger: () => makeColumn("bigint"),
1303
+ /** `NUMERIC(precision, scale)` → `string` (exact decimal, no float loss). */
1304
+ numeric: (precision, scale) => makeColumn("numeric", { precision, scale }),
1305
+ /** Alias of {@link column.numeric}. */
1306
+ decimal: (precision, scale) => makeColumn("numeric", { precision, scale }),
1307
+ /** `REAL` → `number`. */
1308
+ real: () => makeColumn("real"),
1309
+ /** `DOUBLE PRECISION` → `number`. */
1310
+ double: () => makeColumn("double"),
1311
+ /** `VARCHAR(length)` → `string`. Distinct from {@link column.text}. */
1312
+ varchar: (length) => makeColumn("varchar", { length }),
1313
+ /** Alias of {@link column.varchar} (SQLAlchemy's `String`). */
1314
+ string: (length) => makeColumn("varchar", { length }),
1315
+ /** `CHAR(length)` → `string` (fixed-width). */
1316
+ char: (length) => makeColumn("char", { length }),
1317
+ /** `TEXT` → `string` (unbounded). Distinct from {@link column.varchar}. */
1318
+ text: () => makeColumn("text"),
1319
+ /** `BOOLEAN` → `boolean`. */
1320
+ boolean: () => makeColumn("boolean"),
1321
+ /** `DATE` → `Date`. */
1322
+ date: () => makeColumn("date"),
1323
+ /** `TIME` → `string`. Pass `{ timezone: true }` for `WITH TIME ZONE`. */
1324
+ time: (options) => makeColumn("time", { withTimezone: options?.timezone }),
1325
+ /**
1326
+ * `DATETIME`/`TIMESTAMP` → `Date` (SQLAlchemy's generic `DateTime`). Pass
1327
+ * `{ timezone: true }` for `WITH TIME ZONE`. Pair with `.default(sql.now())`
1328
+ * and `.onUpdate(sql.now())` for managed `created_at`/`updated_at` columns.
1329
+ */
1330
+ datetime: (options) => makeColumn("datetime", { withTimezone: options?.timezone }),
1331
+ /** `TIMESTAMP` → `Date` (SQL-specific). Pass `{ timezone: true }`. */
1332
+ timestamp: (options) => makeColumn("timestamp", { withTimezone: options?.timezone }),
1333
+ /** `BLOB`/`BYTEA` → `Uint8Array`. */
1334
+ blob: () => makeColumn("blob"),
1335
+ /** `JSON` → the given parsed value type `T` (defaults to `unknown`). */
1336
+ json: () => makeColumn("json"),
1337
+ /** `JSONB` (PostgreSQL) → the given parsed value type `T`. */
1338
+ jsonb: () => makeColumn("json", { jsonb: true }),
1339
+ /** `UUID` → `string`. */
1340
+ uuid: () => makeColumn("uuid"),
1341
+ /** `ENUM(...values)` → a string-literal union of the given values. */
1342
+ enum: (...values) => makeColumn("enum", { values })
1343
+ };
1344
+ var Model = class {
1345
+ static tablename;
1346
+ };
1347
+ function columnsOf(model) {
1348
+ const instance = new model();
1349
+ const out = {};
1350
+ for (const [key, value] of Object.entries(instance)) {
1351
+ if (value instanceof Column) {
1352
+ out[key] = value;
1353
+ }
1354
+ }
1355
+ return out;
1356
+ }
1357
+
1358
+ export { AsyncEngine, AsyncResult, AsyncSession, BaseDialect, BaseRepository, Column, DeleteBuilder, InsertBuilder, InvalidDatabaseUrl, JoinBuilder, Model, NoResultError, NodeSqliteDriver, OPERATORS, PostgresDialect, RecordNotFound, SelectBuilder, SqliteDialect, SyncEngine, SyncResult, SyncSession, UpdateBuilder, ValidationError, and, belongsTo, column, columnsOf, createEngine, createSyncEngine, del, detectDialect, fromDict, getDialect, hasMany, insert, isCondition, join, loadRelations, not, or, parse, parseDatabaseUrl, select, sql, stringify, toCondNode, toDict, toJSON, update };
1359
+ //# sourceMappingURL=chunk-F36ZSQAN.js.map
1360
+ //# sourceMappingURL=chunk-F36ZSQAN.js.map