@b9g/zen 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,1346 @@
1
+ // src/impl/errors.ts
2
+ var DatabaseError = class extends Error {
3
+ code;
4
+ constructor(code, message, options) {
5
+ super(message, options);
6
+ this.name = "DatabaseError";
7
+ this.code = code;
8
+ if (Error.captureStackTrace) {
9
+ Error.captureStackTrace(this, this.constructor);
10
+ }
11
+ }
12
+ };
13
+ var ValidationError = class extends DatabaseError {
14
+ fieldErrors;
15
+ constructor(message, fieldErrors = {}, options) {
16
+ super("VALIDATION_ERROR", message, options);
17
+ this.name = "ValidationError";
18
+ this.fieldErrors = fieldErrors;
19
+ }
20
+ };
21
+ var TableDefinitionError = class extends DatabaseError {
22
+ tableName;
23
+ fieldName;
24
+ constructor(message, tableName, fieldName, options) {
25
+ super("TABLE_DEFINITION_ERROR", message, options);
26
+ this.name = "TableDefinitionError";
27
+ this.tableName = tableName;
28
+ this.fieldName = fieldName;
29
+ }
30
+ };
31
+ var MigrationError = class extends DatabaseError {
32
+ fromVersion;
33
+ toVersion;
34
+ constructor(message, fromVersion, toVersion, options) {
35
+ super("MIGRATION_ERROR", message, options);
36
+ this.name = "MigrationError";
37
+ this.fromVersion = fromVersion;
38
+ this.toVersion = toVersion;
39
+ }
40
+ };
41
+ var MigrationLockError = class extends DatabaseError {
42
+ constructor(message, options) {
43
+ super("MIGRATION_LOCK_ERROR", message, options);
44
+ this.name = "MigrationLockError";
45
+ }
46
+ };
47
+ var QueryError = class extends DatabaseError {
48
+ sql;
49
+ constructor(message, sql, options) {
50
+ super("QUERY_ERROR", message, options);
51
+ this.name = "QueryError";
52
+ this.sql = sql;
53
+ }
54
+ };
55
+ var NotFoundError = class extends DatabaseError {
56
+ tableName;
57
+ id;
58
+ constructor(tableName, id, options) {
59
+ const message = id ? `${tableName} with id "${id}" not found` : `${tableName} not found`;
60
+ super("NOT_FOUND", message, options);
61
+ this.name = "NotFoundError";
62
+ this.tableName = tableName;
63
+ this.id = id;
64
+ }
65
+ };
66
+ var AlreadyExistsError = class extends DatabaseError {
67
+ tableName;
68
+ field;
69
+ value;
70
+ constructor(tableName, field, value, options) {
71
+ const message = field ? `${tableName} with ${field}="${value}" already exists` : `${tableName} already exists`;
72
+ super("ALREADY_EXISTS", message, options);
73
+ this.name = "AlreadyExistsError";
74
+ this.tableName = tableName;
75
+ this.field = field;
76
+ this.value = value;
77
+ }
78
+ };
79
+ var ConstraintViolationError = class extends DatabaseError {
80
+ /**
81
+ * Type of constraint that was violated.
82
+ * "unknown" if the specific type couldn't be determined from the error.
83
+ */
84
+ kind;
85
+ /**
86
+ * Name of the constraint (e.g., "users_email_unique", "users.email").
87
+ * May be undefined if the database error didn't include it.
88
+ */
89
+ constraint;
90
+ /**
91
+ * Table name where the violation occurred.
92
+ * May be undefined if not extractable from the error.
93
+ */
94
+ table;
95
+ /**
96
+ * Column name involved in the violation.
97
+ * May be undefined if not extractable from the error.
98
+ */
99
+ column;
100
+ constructor(message, details, options) {
101
+ super("CONSTRAINT_VIOLATION", message, options);
102
+ this.name = "ConstraintViolationError";
103
+ this.kind = details.kind;
104
+ this.constraint = details.constraint;
105
+ this.table = details.table;
106
+ this.column = details.column;
107
+ }
108
+ };
109
+ var ConnectionError = class extends DatabaseError {
110
+ constructor(message, options) {
111
+ super("CONNECTION_ERROR", message, options);
112
+ this.name = "ConnectionError";
113
+ }
114
+ };
115
+ var TransactionError = class extends DatabaseError {
116
+ constructor(message, options) {
117
+ super("TRANSACTION_ERROR", message, options);
118
+ this.name = "TransactionError";
119
+ }
120
+ };
121
+ var EnsureError = class extends DatabaseError {
122
+ /** The operation that failed */
123
+ operation;
124
+ /** The table being operated on */
125
+ table;
126
+ /** The step index where failure occurred (0-based) */
127
+ step;
128
+ constructor(message, details, options) {
129
+ super("ENSURE_ERROR", message, options);
130
+ this.name = "EnsureError";
131
+ this.operation = details.operation;
132
+ this.table = details.table;
133
+ this.step = details.step;
134
+ }
135
+ };
136
+ var SchemaDriftError = class extends DatabaseError {
137
+ /** The table where drift was detected */
138
+ table;
139
+ /** Description of what drifted */
140
+ drift;
141
+ /** Suggested action to resolve */
142
+ suggestion;
143
+ constructor(message, details, options) {
144
+ super("SCHEMA_DRIFT_ERROR", message, options);
145
+ this.name = "SchemaDriftError";
146
+ this.table = details.table;
147
+ this.drift = details.drift;
148
+ this.suggestion = details.suggestion;
149
+ }
150
+ };
151
+ var ConstraintPreflightError = class extends DatabaseError {
152
+ /** The table being constrained */
153
+ table;
154
+ /** The constraint being added (e.g., "unique:email" or "fk:authorId") */
155
+ constraint;
156
+ /** Number of violating rows */
157
+ violationCount;
158
+ /** The SQL query that found the violations - run it to see details */
159
+ query;
160
+ constructor(message, details, options) {
161
+ super("CONSTRAINT_PREFLIGHT_ERROR", message, options);
162
+ this.name = "ConstraintPreflightError";
163
+ this.table = details.table;
164
+ this.constraint = details.constraint;
165
+ this.violationCount = details.violationCount;
166
+ this.query = details.query;
167
+ }
168
+ };
169
+ function isDatabaseError(error) {
170
+ return error instanceof DatabaseError;
171
+ }
172
+ function hasErrorCode(error, code) {
173
+ return isDatabaseError(error) && error.code === code;
174
+ }
175
+
176
+ // src/impl/builtins.ts
177
+ var CURRENT_TIMESTAMP = Symbol.for("@b9g/zen:CURRENT_TIMESTAMP");
178
+ var CURRENT_DATE = Symbol.for("@b9g/zen:CURRENT_DATE");
179
+ var CURRENT_TIME = Symbol.for("@b9g/zen:CURRENT_TIME");
180
+ var NOW = CURRENT_TIMESTAMP;
181
+ var TODAY = CURRENT_DATE;
182
+ function isSQLBuiltin(value) {
183
+ if (typeof value !== "symbol")
184
+ return false;
185
+ const key = Symbol.keyFor(value);
186
+ return key === "@b9g/zen:CURRENT_TIMESTAMP" || key === "@b9g/zen:CURRENT_DATE" || key === "@b9g/zen:CURRENT_TIME";
187
+ }
188
+ function resolveSQLBuiltin(sym) {
189
+ const key = Symbol.keyFor(sym);
190
+ if (!key?.startsWith("@b9g/zen:")) {
191
+ throw new Error(`Unknown SQL builtin: ${String(sym)}`);
192
+ }
193
+ return key.slice("@b9g/zen:".length);
194
+ }
195
+
196
+ // src/impl/template.ts
197
+ var SQL_TEMPLATE = Symbol.for("@b9g/zen:template");
198
+ function createTemplate(strings, values = []) {
199
+ const tuple = [strings, ...values];
200
+ return Object.assign(tuple, { [SQL_TEMPLATE]: true });
201
+ }
202
+ function isSQLTemplate(value) {
203
+ return Array.isArray(value) && Object.prototype.hasOwnProperty.call(value, SQL_TEMPLATE) && value[SQL_TEMPLATE] === true;
204
+ }
205
+ var SQL_IDENT = Symbol.for("@b9g/zen:ident");
206
+ function ident(name) {
207
+ return { [SQL_IDENT]: true, name };
208
+ }
209
+ function isSQLIdentifier(value) {
210
+ return value !== null && typeof value === "object" && SQL_IDENT in value && value[SQL_IDENT] === true;
211
+ }
212
+ function makeTemplate(parts) {
213
+ return Object.assign([...parts], { raw: parts });
214
+ }
215
+
216
+ // src/impl/table.ts
217
+ import { z } from "zod";
218
+ function validateIdentifier(name, type) {
219
+ const controlCharRegex = /[\x00-\x1f\x7f]/;
220
+ if (controlCharRegex.test(name)) {
221
+ throw new TableDefinitionError(
222
+ // eslint-disable-next-line no-control-regex
223
+ `Invalid ${type} identifier "${name.replace(/[\x00-\x1f\x7f]/g, "\\x")}": ${type} names cannot contain control characters`
224
+ );
225
+ }
226
+ if (name.includes(";")) {
227
+ throw new TableDefinitionError(
228
+ `Invalid ${type} identifier "${name}": ${type} names cannot contain semicolons`
229
+ );
230
+ }
231
+ if (name.includes("`")) {
232
+ throw new TableDefinitionError(
233
+ `Invalid ${type} identifier "${name}": ${type} names cannot contain backticks`
234
+ );
235
+ }
236
+ }
237
+ function validateWithStandardSchema(schema, data) {
238
+ const standard = schema["~standard"];
239
+ if (!standard?.validate) {
240
+ throw new Error(
241
+ "Schema does not implement Standard Schema (~standard.validate). Ensure you're using Zod v3.23+ or another Standard Schema-compliant library."
242
+ );
243
+ }
244
+ const result = standard.validate(data);
245
+ if (result && typeof result.then === "function") {
246
+ throw new Error(
247
+ "Async validation is not supported. Standard Schema validate() must be synchronous."
248
+ );
249
+ }
250
+ if (result.issues) {
251
+ throw new ValidationError(
252
+ "Validation failed",
253
+ result.issues.reduce(
254
+ (acc, issue) => {
255
+ const path = issue.path && issue.path.length > 0 ? issue.path.map(String).join(".") : "_root";
256
+ if (!acc[path])
257
+ acc[path] = [];
258
+ acc[path].push(issue.message);
259
+ return acc;
260
+ },
261
+ {}
262
+ )
263
+ );
264
+ }
265
+ return result.value;
266
+ }
267
+ var DB_META_NAMESPACE = "db";
268
+ function getDBMeta(schema) {
269
+ try {
270
+ const meta = typeof schema.meta === "function" ? schema.meta() : {};
271
+ const dbMeta = meta?.[DB_META_NAMESPACE];
272
+ if (dbMeta && Object.keys(dbMeta).length > 0) {
273
+ return dbMeta;
274
+ }
275
+ if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
276
+ return getDBMeta(schema.unwrap());
277
+ }
278
+ if (schema instanceof z.ZodDefault) {
279
+ return getDBMeta(schema.removeDefault());
280
+ }
281
+ if (schema instanceof z.ZodCatch) {
282
+ return getDBMeta(schema.removeCatch());
283
+ }
284
+ return {};
285
+ } catch {
286
+ return {};
287
+ }
288
+ }
289
+ function setDBMeta(schema, dbMeta) {
290
+ const existing = (typeof schema.meta === "function" ? schema.meta() : void 0) ?? {};
291
+ return schema.meta({
292
+ ...existing,
293
+ [DB_META_NAMESPACE]: {
294
+ ...existing[DB_META_NAMESPACE] ?? {},
295
+ ...dbMeta
296
+ }
297
+ });
298
+ }
299
+ function isTemplateStringsArray(value) {
300
+ return Array.isArray(value) && "raw" in value;
301
+ }
302
+ function mergeFragment(strings, values, template, suffix) {
303
+ const templateStrings = template[0];
304
+ const templateValues = template.slice(1);
305
+ strings[strings.length - 1] += templateStrings[0];
306
+ for (let j = 1; j < templateStrings.length; j++) {
307
+ strings.push(templateStrings[j]);
308
+ }
309
+ values.push(...templateValues);
310
+ strings[strings.length - 1] += suffix;
311
+ }
312
+ function hasZodDefault(schema) {
313
+ return typeof schema.removeDefault === "function";
314
+ }
315
+ function isUuidSchema(schema) {
316
+ return schema instanceof z.ZodString && schema.format === "uuid";
317
+ }
318
+ function isIntSchema(schema) {
319
+ if (!(schema instanceof z.ZodNumber))
320
+ return false;
321
+ const checks = schema._def?.checks;
322
+ if (!Array.isArray(checks))
323
+ return false;
324
+ return checks.some((c) => c.isInt === true);
325
+ }
326
+ function createDBMethods(schema) {
327
+ return {
328
+ /**
329
+ * Mark field as primary key.
330
+ * @example z.string().uuid().db.primary()
331
+ */
332
+ primary() {
333
+ return setDBMeta(schema, { primary: true });
334
+ },
335
+ /**
336
+ * Mark field as unique.
337
+ * @example z.string().email().db.unique()
338
+ */
339
+ unique() {
340
+ return setDBMeta(schema, { unique: true });
341
+ },
342
+ /**
343
+ * Create an index on this field.
344
+ * @example z.date().db.index()
345
+ */
346
+ index() {
347
+ return setDBMeta(schema, { indexed: true });
348
+ },
349
+ /**
350
+ * Mark field as soft delete timestamp.
351
+ * @example z.date().nullable().default(null).db.softDelete()
352
+ */
353
+ softDelete() {
354
+ return setDBMeta(schema, { softDelete: true });
355
+ },
356
+ /**
357
+ * Define a foreign key reference with optional reverse relationship.
358
+ *
359
+ * @example
360
+ * // Forward reference only
361
+ * authorId: z.string().uuid().db.references(Users, "author")
362
+ *
363
+ * @example
364
+ * // With options
365
+ * authorId: z.string().uuid().db.references(Users, "author", {
366
+ * reverseAs: "posts", // user.posts = Post[]
367
+ * ondelete: "cascade",
368
+ * })
369
+ */
370
+ references(table2, as, options) {
371
+ return setDBMeta(schema, {
372
+ reference: {
373
+ table: table2,
374
+ as,
375
+ ...options
376
+ }
377
+ });
378
+ },
379
+ /**
380
+ * Encode app values to DB values (for INSERT/UPDATE).
381
+ * One-way transformation is fine (e.g., password hashing).
382
+ *
383
+ * @example
384
+ * password: z.string().db.encode(hashPassword)
385
+ *
386
+ * @example
387
+ * // Bidirectional: pair with .db.decode()
388
+ * status: z.enum(["pending", "active"])
389
+ * .db.encode(s => statusMap.indexOf(s))
390
+ * .db.decode(i => statusMap[i])
391
+ */
392
+ encode(encodeFn) {
393
+ const existing = getDBMeta(schema);
394
+ if (existing.inserted || existing.updated) {
395
+ throw new TableDefinitionError(
396
+ `encode() cannot be combined with inserted() or updated(). DB expressions bypass encoding and are sent directly to the database.`
397
+ );
398
+ }
399
+ return setDBMeta(schema, { encode: encodeFn });
400
+ },
401
+ /**
402
+ * Decode DB values to app values (for SELECT).
403
+ * One-way transformation is fine.
404
+ *
405
+ * @example
406
+ * legacy: z.string().db.decode(deserializeLegacyFormat)
407
+ */
408
+ decode(decodeFn) {
409
+ const existing = getDBMeta(schema);
410
+ if (existing.inserted || existing.updated) {
411
+ throw new TableDefinitionError(
412
+ `decode() cannot be combined with inserted() or updated(). DB expressions bypass decoding and are sent directly to the database.`
413
+ );
414
+ }
415
+ return setDBMeta(schema, { decode: decodeFn });
416
+ },
417
+ /**
418
+ * Specify explicit column type for DDL generation.
419
+ * Required when using custom encode/decode on objects/arrays
420
+ * that transform to a different storage type.
421
+ *
422
+ * @example
423
+ * // Store array as CSV instead of JSON
424
+ * tags: z.array(z.string())
425
+ * .db.encode((arr) => arr.join(","))
426
+ * .db.decode((str) => str.split(","))
427
+ * .db.type("TEXT")
428
+ */
429
+ type(columnType) {
430
+ return setDBMeta(schema, { columnType });
431
+ },
432
+ /**
433
+ * Set a value to apply on INSERT.
434
+ *
435
+ * Three forms:
436
+ * - Tagged template: .db.inserted`CURRENT_TIMESTAMP` → raw SQL
437
+ * - Symbol: .db.inserted(NOW) → dialect-aware SQL
438
+ * - Function: .db.inserted(() => "draft") → client-side per-insert
439
+ *
440
+ * Field becomes optional for insert.
441
+ *
442
+ * **Note:** SQL expressions (tagged templates and symbols) bypass encode/decode
443
+ * since they're executed by the database, not the application. Use function
444
+ * form if you need encoding applied.
445
+ *
446
+ * **Note:** Interpolated values in tagged templates are parameterized but not
447
+ * schema-validated. Ensure values are appropriate for the column type.
448
+ *
449
+ * @example
450
+ * createdAt: z.date().db.inserted(NOW)
451
+ * token: z.string().db.inserted(() => crypto.randomUUID())
452
+ * slug: z.string().db.inserted`LOWER(name)`
453
+ */
454
+ inserted(stringsOrValue, ...templateValues) {
455
+ let insertedMeta;
456
+ if (isTemplateStringsArray(stringsOrValue)) {
457
+ const strings = [];
458
+ const values = [];
459
+ for (let i = 0; i < stringsOrValue.length; i++) {
460
+ if (i === 0) {
461
+ strings.push(stringsOrValue[i]);
462
+ }
463
+ if (i < templateValues.length) {
464
+ const value = templateValues[i];
465
+ if (isSQLTemplate(value)) {
466
+ mergeFragment(strings, values, value, stringsOrValue[i + 1]);
467
+ } else {
468
+ values.push(value);
469
+ strings.push(stringsOrValue[i + 1]);
470
+ }
471
+ }
472
+ }
473
+ insertedMeta = {
474
+ type: "sql",
475
+ template: createTemplate(makeTemplate(strings), values)
476
+ };
477
+ } else if (isSQLBuiltin(stringsOrValue)) {
478
+ insertedMeta = { type: "symbol", symbol: stringsOrValue };
479
+ } else if (typeof stringsOrValue === "function") {
480
+ insertedMeta = { type: "function", fn: stringsOrValue };
481
+ } else {
482
+ throw new TableDefinitionError(
483
+ `inserted() requires a tagged template, symbol (NOW), or function. Got: ${typeof stringsOrValue}`
484
+ );
485
+ }
486
+ const existing = getDBMeta(schema);
487
+ if (existing.encode || existing.decode) {
488
+ throw new TableDefinitionError(
489
+ `inserted() cannot be combined with encode() or decode(). DB expressions and functions bypass encoding/decoding.`
490
+ );
491
+ }
492
+ const optionalSchema = schema.optional();
493
+ return setDBMeta(optionalSchema, { ...existing, inserted: insertedMeta });
494
+ },
495
+ /**
496
+ * Set a value to apply on UPDATE only.
497
+ *
498
+ * Same forms as inserted(). See inserted() for notes on codec bypass
499
+ * and template parameter validation.
500
+ *
501
+ * Field becomes optional for update operations.
502
+ *
503
+ * @example
504
+ * modifiedAt: z.date().db.updated(NOW)
505
+ * lastModified: z.date().db.updated(() => new Date())
506
+ */
507
+ updated(stringsOrValue, ...templateValues) {
508
+ let updatedMeta;
509
+ if (isTemplateStringsArray(stringsOrValue)) {
510
+ const strings = [];
511
+ const values = [];
512
+ for (let i = 0; i < stringsOrValue.length; i++) {
513
+ if (i === 0) {
514
+ strings.push(stringsOrValue[i]);
515
+ }
516
+ if (i < templateValues.length) {
517
+ const value = templateValues[i];
518
+ if (isSQLTemplate(value)) {
519
+ mergeFragment(strings, values, value, stringsOrValue[i + 1]);
520
+ } else {
521
+ values.push(value);
522
+ strings.push(stringsOrValue[i + 1]);
523
+ }
524
+ }
525
+ }
526
+ updatedMeta = {
527
+ type: "sql",
528
+ template: createTemplate(makeTemplate(strings), values)
529
+ };
530
+ } else if (isSQLBuiltin(stringsOrValue)) {
531
+ updatedMeta = { type: "symbol", symbol: stringsOrValue };
532
+ } else if (typeof stringsOrValue === "function") {
533
+ updatedMeta = { type: "function", fn: stringsOrValue };
534
+ } else {
535
+ throw new TableDefinitionError(
536
+ `updated() requires a tagged template, symbol (NOW), or function. Got: ${typeof stringsOrValue}`
537
+ );
538
+ }
539
+ const existing = getDBMeta(schema);
540
+ if (existing.encode || existing.decode) {
541
+ throw new TableDefinitionError(
542
+ `updated() cannot be combined with encode() or decode(). DB expressions and functions bypass encoding/decoding.`
543
+ );
544
+ }
545
+ const optionalSchema = schema.optional();
546
+ return setDBMeta(optionalSchema, { ...existing, updated: updatedMeta });
547
+ },
548
+ /**
549
+ * Set a value to apply on both INSERT and UPDATE.
550
+ *
551
+ * Same forms as inserted(). See inserted() for notes on codec bypass
552
+ * and template parameter validation.
553
+ *
554
+ * Field becomes optional for insert/update.
555
+ *
556
+ * @example
557
+ * updatedAt: z.date().db.upserted(NOW)
558
+ * lastModified: z.date().db.upserted(() => new Date())
559
+ */
560
+ upserted(stringsOrValue, ...templateValues) {
561
+ let upsertedMeta;
562
+ if (isTemplateStringsArray(stringsOrValue)) {
563
+ const strings = [];
564
+ const values = [];
565
+ for (let i = 0; i < stringsOrValue.length; i++) {
566
+ if (i === 0) {
567
+ strings.push(stringsOrValue[i]);
568
+ }
569
+ if (i < templateValues.length) {
570
+ const value = templateValues[i];
571
+ if (isSQLTemplate(value)) {
572
+ mergeFragment(strings, values, value, stringsOrValue[i + 1]);
573
+ } else {
574
+ values.push(value);
575
+ strings.push(stringsOrValue[i + 1]);
576
+ }
577
+ }
578
+ }
579
+ upsertedMeta = {
580
+ type: "sql",
581
+ template: createTemplate(makeTemplate(strings), values)
582
+ };
583
+ } else if (isSQLBuiltin(stringsOrValue)) {
584
+ upsertedMeta = { type: "symbol", symbol: stringsOrValue };
585
+ } else if (typeof stringsOrValue === "function") {
586
+ upsertedMeta = { type: "function", fn: stringsOrValue };
587
+ } else {
588
+ throw new TableDefinitionError(
589
+ `upserted() requires a tagged template, symbol (NOW), or function. Got: ${typeof stringsOrValue}`
590
+ );
591
+ }
592
+ const existing = getDBMeta(schema);
593
+ if (existing.encode || existing.decode) {
594
+ throw new TableDefinitionError(
595
+ `upserted() cannot be combined with encode() or decode(). DB expressions and functions bypass encoding/decoding.`
596
+ );
597
+ }
598
+ const optionalSchema = schema.optional();
599
+ return setDBMeta(optionalSchema, { ...existing, upserted: upsertedMeta });
600
+ },
601
+ /**
602
+ * Auto-generate value on insert based on field type.
603
+ *
604
+ * Type-aware behavior:
605
+ * - `z.string().uuid()` → generates UUID via `crypto.randomUUID()`
606
+ * - `z.number().int()` on primary key → auto-increment (database-side)
607
+ * - `z.date()` → current timestamp via NOW
608
+ *
609
+ * Field becomes optional for insert.
610
+ *
611
+ * @example
612
+ * id: z.string().uuid().db.primary().db.auto()
613
+ * // → crypto.randomUUID() on insert
614
+ *
615
+ * @example
616
+ * id: z.number().int().db.primary().db.auto()
617
+ * // → auto-increment
618
+ *
619
+ * @example
620
+ * createdAt: z.date().db.auto()
621
+ * // → NOW on insert
622
+ */
623
+ auto() {
624
+ const existing = getDBMeta(schema);
625
+ const optionalSchema = schema.optional();
626
+ if (isUuidSchema(schema)) {
627
+ const insertedMeta = {
628
+ type: "function",
629
+ fn: () => crypto.randomUUID()
630
+ };
631
+ return setDBMeta(optionalSchema, { ...existing, inserted: insertedMeta });
632
+ }
633
+ if (isIntSchema(schema)) {
634
+ return setDBMeta(optionalSchema, { ...existing, autoIncrement: true });
635
+ }
636
+ if (schema instanceof z.ZodDate) {
637
+ const insertedMeta = {
638
+ type: "symbol",
639
+ symbol: NOW
640
+ };
641
+ return setDBMeta(optionalSchema, { ...existing, inserted: insertedMeta });
642
+ }
643
+ throw new Error(
644
+ `.db.auto() is not supported for this type. Supported: z.string().uuid(), z.number().int(), z.date()`
645
+ );
646
+ }
647
+ };
648
+ }
649
+ function extendZod(zodModule) {
650
+ for (const key of Object.keys(zodModule)) {
651
+ const value = zodModule[key];
652
+ if (typeof value === "function" && value.prototype && key.startsWith("Zod")) {
653
+ if (!("db" in value.prototype)) {
654
+ Object.defineProperty(value.prototype, "db", {
655
+ get() {
656
+ return createDBMethods(this);
657
+ },
658
+ enumerable: false,
659
+ configurable: true
660
+ });
661
+ }
662
+ }
663
+ }
664
+ }
665
+ extendZod(z);
666
+ var TABLE_MARKER = Symbol.for("@b9g/zen:table");
667
+ var TABLE_META = Symbol.for("@b9g/zen:table-meta");
668
+ function getTableMeta(table2) {
669
+ return table2[TABLE_META];
670
+ }
671
+ function table(name, shape, options = {}) {
672
+ validateIdentifier(name, "table");
673
+ if (name.includes(".")) {
674
+ throw new TableDefinitionError(
675
+ `Invalid table name "${name}": table names cannot contain "." as it conflicts with normalization prefixes`,
676
+ name
677
+ );
678
+ }
679
+ const zodShape = {};
680
+ const meta = {
681
+ primary: null,
682
+ unique: [],
683
+ indexed: [],
684
+ softDeleteField: null,
685
+ references: [],
686
+ fields: {}
687
+ };
688
+ for (const [key, value] of Object.entries(shape)) {
689
+ validateIdentifier(key, "column");
690
+ if (key.includes(".")) {
691
+ throw new TableDefinitionError(
692
+ `Invalid field name "${key}" in table "${name}": field names cannot contain "." as it conflicts with normalization prefixes`,
693
+ name,
694
+ key
695
+ );
696
+ }
697
+ const fieldSchema = value;
698
+ zodShape[key] = fieldSchema;
699
+ if (hasZodDefault(fieldSchema)) {
700
+ throw new TableDefinitionError(
701
+ `Field "${key}" uses Zod .default() which applies at parse time, not write time. Use .db.inserted() or .db.updated() instead.`,
702
+ name,
703
+ key
704
+ );
705
+ }
706
+ const fieldDBMeta = getDBMeta(fieldSchema);
707
+ const dbMeta = {};
708
+ if (fieldDBMeta.primary) {
709
+ if (meta.primary !== null) {
710
+ throw new TableDefinitionError(
711
+ `Table "${name}" has multiple primary keys: "${meta.primary}" and "${key}". Only one primary key is allowed.`,
712
+ name
713
+ );
714
+ }
715
+ meta.primary = key;
716
+ dbMeta.primaryKey = true;
717
+ }
718
+ if (fieldDBMeta.unique) {
719
+ meta.unique.push(key);
720
+ dbMeta.unique = true;
721
+ }
722
+ if (fieldDBMeta.indexed) {
723
+ meta.indexed.push(key);
724
+ dbMeta.indexed = true;
725
+ }
726
+ if (fieldDBMeta.softDelete) {
727
+ if (meta.softDeleteField !== null) {
728
+ throw new TableDefinitionError(
729
+ `Table "${name}" has multiple soft delete fields: "${meta.softDeleteField}" and "${key}". Only one soft delete field is allowed.`,
730
+ name
731
+ );
732
+ }
733
+ meta.softDeleteField = key;
734
+ dbMeta.softDelete = true;
735
+ }
736
+ if (fieldDBMeta.reference) {
737
+ const ref = fieldDBMeta.reference;
738
+ if (ref.as in shape) {
739
+ throw new TableDefinitionError(
740
+ `Table "${name}": reference property "${ref.as}" (from field "${key}") collides with existing schema field. Choose a different 'as' name.`,
741
+ name,
742
+ key
743
+ );
744
+ }
745
+ if (options.derive && ref.as in options.derive) {
746
+ throw new TableDefinitionError(
747
+ `Table "${name}": reference property "${ref.as}" (from field "${key}") collides with derived property. Choose a different 'as' name.`,
748
+ name,
749
+ key
750
+ );
751
+ }
752
+ const existingRef = meta.references.find((r) => r.as === ref.as);
753
+ if (existingRef) {
754
+ throw new TableDefinitionError(
755
+ `Table "${name}": duplicate reference alias "${ref.as}" used by fields "${existingRef.fieldName}" and "${key}". Each reference must have a unique 'as' name.`,
756
+ name,
757
+ key
758
+ );
759
+ }
760
+ if (ref.reverseAs) {
761
+ const targetShape = ref.table.schema.shape;
762
+ if (ref.reverseAs in targetShape) {
763
+ throw new TableDefinitionError(
764
+ `Table "${name}": reverse reference property "${ref.reverseAs}" (from field "${key}") collides with existing field in target table "${ref.table.name}". Choose a different 'reverseAs' name.`,
765
+ name,
766
+ key
767
+ );
768
+ }
769
+ }
770
+ meta.references.push({
771
+ fieldName: key,
772
+ table: ref.table,
773
+ referencedField: ref.field ?? getTableMeta(ref.table).primary ?? "id",
774
+ as: ref.as,
775
+ reverseAs: ref.reverseAs,
776
+ onDelete: ref.onDelete
777
+ });
778
+ dbMeta.reference = ref;
779
+ }
780
+ if (fieldDBMeta.encode) {
781
+ dbMeta.encode = fieldDBMeta.encode;
782
+ }
783
+ if (fieldDBMeta.decode) {
784
+ dbMeta.decode = fieldDBMeta.decode;
785
+ }
786
+ if (fieldDBMeta.columnType) {
787
+ dbMeta.columnType = fieldDBMeta.columnType;
788
+ }
789
+ if (fieldDBMeta.inserted) {
790
+ dbMeta.inserted = fieldDBMeta.inserted;
791
+ }
792
+ if (fieldDBMeta.updated) {
793
+ dbMeta.updated = fieldDBMeta.updated;
794
+ }
795
+ if (fieldDBMeta.upserted) {
796
+ dbMeta.upserted = fieldDBMeta.upserted;
797
+ }
798
+ if (fieldDBMeta.autoIncrement) {
799
+ dbMeta.autoIncrement = fieldDBMeta.autoIncrement;
800
+ }
801
+ meta.fields[key] = dbMeta;
802
+ }
803
+ const schema = z.object(zodShape);
804
+ return createTableObject(name, schema, zodShape, meta, options);
805
+ }
806
+ function createColsProxy(tableName, zodShape) {
807
+ return new Proxy({}, {
808
+ get(_target, prop) {
809
+ if (prop in zodShape) {
810
+ return createTemplate(makeTemplate(["", ".", ""]), [
811
+ ident(tableName),
812
+ ident(prop)
813
+ ]);
814
+ }
815
+ return void 0;
816
+ },
817
+ has(_target, prop) {
818
+ return prop in zodShape;
819
+ },
820
+ ownKeys() {
821
+ return Object.keys(zodShape);
822
+ },
823
+ getOwnPropertyDescriptor(_target, prop) {
824
+ if (prop in zodShape) {
825
+ return { enumerable: true, configurable: true };
826
+ }
827
+ return void 0;
828
+ }
829
+ });
830
+ }
831
+ function createTableObject(name, schema, zodShape, meta, options) {
832
+ const cols = createColsProxy(name, zodShape);
833
+ const primary = meta.primary ? createTemplate(makeTemplate(["", ".", ""]), [
834
+ ident(name),
835
+ ident(meta.primary)
836
+ ]) : null;
837
+ if (options.derive) {
838
+ for (const key of Object.keys(options.derive)) {
839
+ if (key in zodShape) {
840
+ throw new TableDefinitionError(
841
+ `Table "${name}": derived property "${key}" collides with existing schema field. Choose a different name.`,
842
+ name
843
+ );
844
+ }
845
+ }
846
+ }
847
+ const internalMeta = { ...meta, derive: options.derive };
848
+ const table2 = {
849
+ [TABLE_MARKER]: true,
850
+ [TABLE_META]: internalMeta,
851
+ name,
852
+ schema,
853
+ indexes: options.indexes ?? [],
854
+ unique: options.unique ?? [],
855
+ compoundReferences: options.references ?? [],
856
+ cols,
857
+ primary,
858
+ // Internal getter for backward compatibility - use getTableMeta() instead
859
+ get meta() {
860
+ return internalMeta;
861
+ },
862
+ fields() {
863
+ const result = {};
864
+ for (const [key, zodType] of Object.entries(zodShape)) {
865
+ const dbMeta = meta.fields[key] || {};
866
+ result[key] = extractFieldMeta(key, zodType, dbMeta);
867
+ }
868
+ for (const ref of meta.references) {
869
+ result[ref.as] = {
870
+ fields: () => ref.table.fields(),
871
+ table: ref.table
872
+ };
873
+ }
874
+ return result;
875
+ },
876
+ primaryKey() {
877
+ return meta.primary;
878
+ },
879
+ references() {
880
+ return meta.references;
881
+ },
882
+ deleted() {
883
+ const softDeleteField = meta.softDeleteField;
884
+ if (!softDeleteField) {
885
+ throw new Error(
886
+ `Table "${name}" does not have a soft delete field. Use softDelete() wrapper to mark a field.`
887
+ );
888
+ }
889
+ return createTemplate(makeTemplate(["", ".", " IS NOT NULL"]), [
890
+ ident(name),
891
+ ident(softDeleteField)
892
+ ]);
893
+ },
894
+ in(field, values) {
895
+ if (!(field in zodShape)) {
896
+ throw new Error(
897
+ `Field "${field}" does not exist in table "${name}". Available fields: ${Object.keys(zodShape).join(", ")}`
898
+ );
899
+ }
900
+ if (values.length === 0) {
901
+ return createTemplate(makeTemplate(["1 = 0"]), []);
902
+ }
903
+ const POSTGRESQL_PARAM_LIMIT = 32767;
904
+ if (values.length > POSTGRESQL_PARAM_LIMIT) {
905
+ throw new Error(
906
+ `Too many values in IN clause: ${values.length} exceeds PostgreSQL's parameter limit of ${POSTGRESQL_PARAM_LIMIT}. Consider using a temporary table or splitting the query.`
907
+ );
908
+ }
909
+ const strings = ["", ".", " IN ("];
910
+ const templateValues = [ident(name), ident(field)];
911
+ for (let i = 0; i < values.length; i++) {
912
+ templateValues.push(values[i]);
913
+ strings.push(i < values.length - 1 ? ", " : ")");
914
+ }
915
+ return createTemplate(makeTemplate(strings), templateValues);
916
+ },
917
+ pick(...fields) {
918
+ const fieldSet = new Set(fields);
919
+ const pickObj = {};
920
+ for (const f of fields) {
921
+ pickObj[f] = true;
922
+ }
923
+ const pickedSchema = schema.pick(pickObj);
924
+ const pickedZodShape = {};
925
+ for (const f of fields) {
926
+ if (f in zodShape) {
927
+ pickedZodShape[f] = zodShape[f];
928
+ }
929
+ }
930
+ const existingDerivedExprs = meta.derivedExprs ?? [];
931
+ const existingDerivedFields = meta.derivedFields ?? [];
932
+ const pickedDerivedExprs = existingDerivedExprs.filter(
933
+ (expr) => fieldSet.has(expr.fieldName)
934
+ );
935
+ const pickedDerivedFields = existingDerivedFields.filter(
936
+ (f) => fieldSet.has(f)
937
+ );
938
+ const pickedMeta = {
939
+ primary: meta.primary && fieldSet.has(meta.primary) ? meta.primary : null,
940
+ unique: meta.unique.filter((f) => fieldSet.has(f)),
941
+ indexed: meta.indexed.filter((f) => fieldSet.has(f)),
942
+ softDeleteField: meta.softDeleteField && fieldSet.has(meta.softDeleteField) ? meta.softDeleteField : null,
943
+ references: meta.references.filter((r) => fieldSet.has(r.fieldName)),
944
+ fields: Object.fromEntries(
945
+ Object.entries(meta.fields).filter(([k]) => fieldSet.has(k))
946
+ ),
947
+ isPartial: true,
948
+ isDerived: void 0,
949
+ derivedExprs: void 0,
950
+ derivedFields: void 0
951
+ };
952
+ if (pickedDerivedExprs.length > 0) {
953
+ pickedMeta.isDerived = true;
954
+ pickedMeta.derivedExprs = pickedDerivedExprs;
955
+ pickedMeta.derivedFields = pickedDerivedFields;
956
+ }
957
+ const pickedIndexes = (options.indexes ?? []).filter(
958
+ (idx) => idx.every((f) => fieldSet.has(f))
959
+ );
960
+ const pickedUnique = (options.unique ?? []).filter(
961
+ (u) => u.every((f) => fieldSet.has(f))
962
+ );
963
+ const pickedCompoundRefs = (options.references ?? []).filter(
964
+ (ref) => ref.fields.every((f) => fieldSet.has(f))
965
+ );
966
+ return createTableObject(name, pickedSchema, pickedZodShape, pickedMeta, {
967
+ indexes: pickedIndexes,
968
+ unique: pickedUnique,
969
+ references: pickedCompoundRefs
970
+ });
971
+ },
972
+ derive(fieldName, fieldType) {
973
+ return (stringsOrValue, ...templateValues) => {
974
+ const strings = [];
975
+ const values = [];
976
+ for (let i = 0; i < stringsOrValue.length; i++) {
977
+ if (i === 0) {
978
+ strings.push(stringsOrValue[i]);
979
+ }
980
+ if (i < templateValues.length) {
981
+ const value = templateValues[i];
982
+ if (isSQLTemplate(value)) {
983
+ mergeFragment(strings, values, value, stringsOrValue[i + 1]);
984
+ } else {
985
+ values.push(value);
986
+ strings.push(stringsOrValue[i + 1]);
987
+ }
988
+ }
989
+ }
990
+ if (strings.length > 0) {
991
+ strings[0] = strings[0].trimStart();
992
+ strings[strings.length - 1] = strings[strings.length - 1].trimEnd();
993
+ }
994
+ const derivedExpr = {
995
+ fieldName,
996
+ template: createTemplate(makeTemplate(strings), values)
997
+ };
998
+ const mergedSchema = schema.extend({ [fieldName]: fieldType });
999
+ const mergedZodShape = { ...zodShape, [fieldName]: fieldType };
1000
+ const existingExprs = meta.derivedExprs ?? [];
1001
+ const existingDerivedFields = meta.derivedFields ?? [];
1002
+ const derivedMeta = {
1003
+ ...meta,
1004
+ isDerived: true,
1005
+ derivedExprs: [...existingExprs, derivedExpr],
1006
+ derivedFields: [...existingDerivedFields, fieldName],
1007
+ fields: {
1008
+ ...meta.fields,
1009
+ [fieldName]: {}
1010
+ }
1011
+ };
1012
+ return createTableObject(
1013
+ name,
1014
+ mergedSchema,
1015
+ mergedZodShape,
1016
+ derivedMeta,
1017
+ options
1018
+ );
1019
+ };
1020
+ },
1021
+ set(values) {
1022
+ const entries = Object.entries(values).filter(([, v]) => v !== void 0);
1023
+ if (entries.length === 0) {
1024
+ throw new Error("set() requires at least one non-undefined field");
1025
+ }
1026
+ const strings = [""];
1027
+ const templateValues = [];
1028
+ for (let i = 0; i < entries.length; i++) {
1029
+ const [field, value] = entries[i];
1030
+ templateValues.push(ident(field));
1031
+ strings.push(" = ");
1032
+ templateValues.push(value);
1033
+ strings.push(i < entries.length - 1 ? ", " : "");
1034
+ }
1035
+ return createTemplate(makeTemplate(strings), templateValues);
1036
+ },
1037
+ on(referencingTable, alias) {
1038
+ const refs = getTableMeta(referencingTable).references.filter(
1039
+ (r) => r.table.name === name
1040
+ );
1041
+ if (refs.length === 0) {
1042
+ throw new Error(
1043
+ `Table "${referencingTable.name}" has no foreign key references to "${name}"`
1044
+ );
1045
+ }
1046
+ let ref;
1047
+ if (refs.length === 1) {
1048
+ ref = refs[0];
1049
+ } else if (alias) {
1050
+ const found = refs.find((r) => r.as === alias);
1051
+ if (!found) {
1052
+ const availableAliases = refs.map((r) => `"${r.as}"`).join(", ");
1053
+ throw new Error(
1054
+ `No foreign key with alias "${alias}" found. Available aliases: ${availableAliases}`
1055
+ );
1056
+ }
1057
+ ref = found;
1058
+ } else {
1059
+ const availableAliases = refs.map((r) => `"${r.as}"`).join(", ");
1060
+ throw new Error(
1061
+ `Multiple foreign keys from "${referencingTable.name}" to "${name}". Specify an alias: ${availableAliases}`
1062
+ );
1063
+ }
1064
+ const tableRef = alias ?? name;
1065
+ return createTemplate(makeTemplate(["", ".", " = ", ".", ""]), [
1066
+ ident(tableRef),
1067
+ ident(ref.referencedField),
1068
+ ident(referencingTable.name),
1069
+ ident(ref.fieldName)
1070
+ ]);
1071
+ },
1072
+ values(rows) {
1073
+ if (rows.length === 0) {
1074
+ throw new Error("values() requires at least one row");
1075
+ }
1076
+ const columns = Object.keys(rows[0]);
1077
+ if (columns.length === 0) {
1078
+ throw new Error("values() requires at least one column");
1079
+ }
1080
+ const schemaKeys = Object.keys(schema.shape);
1081
+ for (const col of columns) {
1082
+ if (!schemaKeys.includes(col)) {
1083
+ throw new TableDefinitionError(
1084
+ `Column "${col}" does not exist in table schema`,
1085
+ name,
1086
+ col
1087
+ );
1088
+ }
1089
+ }
1090
+ const partialSchema = schema.partial();
1091
+ const strings = ["("];
1092
+ const templateValues = [];
1093
+ for (let i = 0; i < columns.length; i++) {
1094
+ templateValues.push(ident(columns[i]));
1095
+ strings.push(i < columns.length - 1 ? ", " : ") VALUES ");
1096
+ }
1097
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
1098
+ const row = rows[rowIdx];
1099
+ const validated = validateWithStandardSchema(partialSchema, row);
1100
+ strings[strings.length - 1] += "(";
1101
+ for (let colIdx = 0; colIdx < columns.length; colIdx++) {
1102
+ const col = columns[colIdx];
1103
+ if (!(col in row)) {
1104
+ throw new Error(
1105
+ `All rows must have the same columns. Row is missing column "${col}"`
1106
+ );
1107
+ }
1108
+ templateValues.push(validated[col]);
1109
+ strings.push(colIdx < columns.length - 1 ? ", " : ")");
1110
+ }
1111
+ if (rowIdx < rows.length - 1) {
1112
+ strings[strings.length - 1] += ", ";
1113
+ }
1114
+ }
1115
+ return createTemplate(makeTemplate(strings), templateValues);
1116
+ }
1117
+ };
1118
+ return table2;
1119
+ }
1120
+ function getLayerMeta(schema) {
1121
+ if (typeof schema.meta === "function") {
1122
+ return schema.meta() ?? {};
1123
+ }
1124
+ return {};
1125
+ }
1126
+ function unwrapType(schema) {
1127
+ let core = schema;
1128
+ let hasDefault = false;
1129
+ let defaultValue = void 0;
1130
+ const metaLayers = [];
1131
+ const isOptional = schema.isOptional();
1132
+ const isNullable = schema.isNullable();
1133
+ while (true) {
1134
+ metaLayers.push(getLayerMeta(core));
1135
+ if (typeof core.removeDefault === "function") {
1136
+ hasDefault = true;
1137
+ try {
1138
+ defaultValue = core.parse(void 0);
1139
+ } catch {
1140
+ }
1141
+ core = core.removeDefault();
1142
+ continue;
1143
+ }
1144
+ if (typeof core.unwrap === "function") {
1145
+ core = core.unwrap();
1146
+ continue;
1147
+ }
1148
+ if (typeof core.innerType === "function") {
1149
+ core = core.innerType();
1150
+ continue;
1151
+ }
1152
+ break;
1153
+ }
1154
+ const collectedMeta = {};
1155
+ for (let i = metaLayers.length - 1; i >= 0; i--) {
1156
+ Object.assign(collectedMeta, metaLayers[i]);
1157
+ }
1158
+ return {
1159
+ core,
1160
+ isOptional,
1161
+ isNullable,
1162
+ hasDefault,
1163
+ defaultValue,
1164
+ collectedMeta
1165
+ };
1166
+ }
1167
+ function extractFieldMeta(name, zodType, dbMeta) {
1168
+ const {
1169
+ core,
1170
+ isOptional,
1171
+ isNullable,
1172
+ hasDefault,
1173
+ defaultValue,
1174
+ collectedMeta
1175
+ } = unwrapType(zodType);
1176
+ const { db: _db, ...userMeta } = collectedMeta;
1177
+ const meta = {
1178
+ name,
1179
+ type: "text",
1180
+ required: !isOptional && !isNullable && !hasDefault,
1181
+ ...userMeta
1182
+ // Spread user-defined metadata (label, helpText, widget, etc.)
1183
+ };
1184
+ if (dbMeta.primaryKey)
1185
+ meta.primaryKey = true;
1186
+ if (dbMeta.unique)
1187
+ meta.unique = true;
1188
+ if (dbMeta.indexed)
1189
+ meta.indexed = true;
1190
+ if (dbMeta.softDelete)
1191
+ meta.softDelete = true;
1192
+ if (dbMeta.reference) {
1193
+ meta.reference = {
1194
+ table: dbMeta.reference.table,
1195
+ field: dbMeta.reference.field ?? getTableMeta(dbMeta.reference.table).primary ?? "id",
1196
+ as: dbMeta.reference.as,
1197
+ onDelete: dbMeta.reference.onDelete
1198
+ };
1199
+ }
1200
+ if (dbMeta.encode)
1201
+ meta.encode = dbMeta.encode;
1202
+ if (dbMeta.decode)
1203
+ meta.decode = dbMeta.decode;
1204
+ if (dbMeta.columnType)
1205
+ meta.columnType = dbMeta.columnType;
1206
+ if (dbMeta.autoIncrement)
1207
+ meta.autoIncrement = true;
1208
+ if (dbMeta.inserted)
1209
+ meta.inserted = dbMeta.inserted;
1210
+ if (dbMeta.updated)
1211
+ meta.updated = dbMeta.updated;
1212
+ if (dbMeta.upserted)
1213
+ meta.upserted = dbMeta.upserted;
1214
+ if (defaultValue !== void 0) {
1215
+ meta.default = defaultValue;
1216
+ }
1217
+ if (core instanceof z.ZodString) {
1218
+ meta.type = "text";
1219
+ const str = core;
1220
+ if (str.format === "email" || str.isEmail)
1221
+ meta.type = "email";
1222
+ if (str.format === "url" || str.isURL)
1223
+ meta.type = "url";
1224
+ if (str.maxLength !== void 0) {
1225
+ meta.maxLength = str.maxLength;
1226
+ if (str.maxLength > 500)
1227
+ meta.type = "textarea";
1228
+ }
1229
+ if (str.minLength !== void 0) {
1230
+ meta.minLength = str.minLength;
1231
+ }
1232
+ } else if (core instanceof z.ZodNumber) {
1233
+ meta.type = "number";
1234
+ const num = core;
1235
+ if (num.format === "int" || num.isInt)
1236
+ meta.type = "integer";
1237
+ if (num.minValue !== void 0)
1238
+ meta.min = num.minValue;
1239
+ if (num.maxValue !== void 0)
1240
+ meta.max = num.maxValue;
1241
+ } else if (core instanceof z.ZodBoolean) {
1242
+ meta.type = "checkbox";
1243
+ } else if (core instanceof z.ZodDate) {
1244
+ meta.type = "datetime";
1245
+ } else if (core instanceof z.ZodEnum) {
1246
+ meta.type = "select";
1247
+ meta.options = core.options;
1248
+ } else if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
1249
+ meta.type = "json";
1250
+ }
1251
+ return meta;
1252
+ }
1253
+ function decodeData(table2, data) {
1254
+ if (!data)
1255
+ return data;
1256
+ const decoded = {};
1257
+ const shape = table2.schema.shape;
1258
+ for (const [key, value] of Object.entries(data)) {
1259
+ const fieldMeta = getTableMeta(table2).fields[key];
1260
+ const fieldSchema = shape?.[key];
1261
+ if (fieldMeta?.decode && typeof fieldMeta.decode === "function") {
1262
+ decoded[key] = fieldMeta.decode(value);
1263
+ } else if (fieldSchema) {
1264
+ let core = fieldSchema;
1265
+ while (typeof core.unwrap === "function") {
1266
+ if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
1267
+ break;
1268
+ }
1269
+ core = core.unwrap();
1270
+ }
1271
+ if (core instanceof z.ZodObject || core instanceof z.ZodArray) {
1272
+ if (typeof value === "string") {
1273
+ try {
1274
+ decoded[key] = JSON.parse(value);
1275
+ } catch (e) {
1276
+ throw new Error(
1277
+ `JSON parse error for field "${key}": ${e instanceof Error ? e.message : String(e)}. Value was: ${value.slice(0, 100)}${value.length > 100 ? "..." : ""}`
1278
+ );
1279
+ }
1280
+ } else {
1281
+ decoded[key] = value;
1282
+ }
1283
+ } else if (core instanceof z.ZodDate) {
1284
+ if (typeof value === "string") {
1285
+ const date = new Date(value);
1286
+ if (isNaN(date.getTime())) {
1287
+ throw new Error(
1288
+ `Invalid date value for field "${key}": "${value}" cannot be parsed as a valid date`
1289
+ );
1290
+ }
1291
+ decoded[key] = date;
1292
+ } else if (value instanceof Date) {
1293
+ if (isNaN(value.getTime())) {
1294
+ throw new Error(
1295
+ `Invalid Date object for field "${key}": received an Invalid Date`
1296
+ );
1297
+ }
1298
+ decoded[key] = value;
1299
+ } else {
1300
+ decoded[key] = value;
1301
+ }
1302
+ } else {
1303
+ decoded[key] = value;
1304
+ }
1305
+ } else {
1306
+ decoded[key] = value;
1307
+ }
1308
+ }
1309
+ return decoded;
1310
+ }
1311
+
1312
+ export {
1313
+ DatabaseError,
1314
+ ValidationError,
1315
+ TableDefinitionError,
1316
+ MigrationError,
1317
+ MigrationLockError,
1318
+ QueryError,
1319
+ NotFoundError,
1320
+ AlreadyExistsError,
1321
+ ConstraintViolationError,
1322
+ ConnectionError,
1323
+ TransactionError,
1324
+ EnsureError,
1325
+ SchemaDriftError,
1326
+ ConstraintPreflightError,
1327
+ isDatabaseError,
1328
+ hasErrorCode,
1329
+ CURRENT_TIMESTAMP,
1330
+ CURRENT_DATE,
1331
+ CURRENT_TIME,
1332
+ NOW,
1333
+ TODAY,
1334
+ isSQLBuiltin,
1335
+ resolveSQLBuiltin,
1336
+ createTemplate,
1337
+ isSQLTemplate,
1338
+ ident,
1339
+ isSQLIdentifier,
1340
+ makeTemplate,
1341
+ validateWithStandardSchema,
1342
+ extendZod,
1343
+ getTableMeta,
1344
+ table,
1345
+ decodeData
1346
+ };