@b9g/zen 0.1.0 → 0.1.2
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/CHANGELOG.md +40 -0
- package/README.md +178 -64
- package/{chunk-QXGEP5PB.js → chunk-ARUUB3H4.js} +27 -2
- package/{chunk-56M5Z3A6.js → chunk-BEX6VPES.js} +192 -4
- package/chunk-NBXBBEMA.js +63 -0
- package/ddl-OT6HPLQY.js +11 -0
- package/package.json +10 -1
- package/src/bun.d.ts +12 -1
- package/src/bun.js +137 -5
- package/src/mysql.d.ts +12 -0
- package/src/mysql.js +121 -4
- package/src/postgres.d.ts +12 -0
- package/src/postgres.js +101 -4
- package/src/sqlite.d.ts +12 -1
- package/src/sqlite.js +113 -4
- package/src/zen.d.ts +6 -7
- package/src/zen.js +193 -49
- package/chunk-2IEEEMRN.js +0 -38
- package/ddl-NAJM37GQ.js +0 -9
|
@@ -665,9 +665,126 @@ function extendZod(zodModule) {
|
|
|
665
665
|
extendZod(z);
|
|
666
666
|
var TABLE_MARKER = Symbol.for("@b9g/zen:table");
|
|
667
667
|
var TABLE_META = Symbol.for("@b9g/zen:table-meta");
|
|
668
|
+
function isTable(value) {
|
|
669
|
+
return value !== null && typeof value === "object" && TABLE_MARKER in value && value[TABLE_MARKER] === true;
|
|
670
|
+
}
|
|
668
671
|
function getTableMeta(table2) {
|
|
669
672
|
return table2[TABLE_META];
|
|
670
673
|
}
|
|
674
|
+
var VIEW_MARKER = Symbol("view");
|
|
675
|
+
var VIEW_META = Symbol("viewMeta");
|
|
676
|
+
function isView(value) {
|
|
677
|
+
return typeof value === "object" && value !== null && value[VIEW_MARKER] === true;
|
|
678
|
+
}
|
|
679
|
+
function getViewMeta(view2) {
|
|
680
|
+
return view2[VIEW_META];
|
|
681
|
+
}
|
|
682
|
+
function view(name, baseTable) {
|
|
683
|
+
validateIdentifier(name, "table");
|
|
684
|
+
if (name.includes(".")) {
|
|
685
|
+
throw new TableDefinitionError(
|
|
686
|
+
`Invalid view name "${name}": view names cannot contain "." as it conflicts with normalization prefixes`,
|
|
687
|
+
name
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
return (strings, ...templateValues) => {
|
|
691
|
+
const resultStrings = [];
|
|
692
|
+
const resultValues = [];
|
|
693
|
+
for (let i = 0; i < strings.length; i++) {
|
|
694
|
+
if (i === 0) {
|
|
695
|
+
resultStrings.push(strings[i]);
|
|
696
|
+
}
|
|
697
|
+
if (i < templateValues.length) {
|
|
698
|
+
const value = templateValues[i];
|
|
699
|
+
if (isSQLTemplate(value)) {
|
|
700
|
+
mergeFragment(resultStrings, resultValues, value, strings[i + 1]);
|
|
701
|
+
} else {
|
|
702
|
+
resultValues.push(value);
|
|
703
|
+
resultStrings.push(strings[i + 1]);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (resultStrings.length > 0) {
|
|
708
|
+
resultStrings[0] = resultStrings[0].trimStart();
|
|
709
|
+
resultStrings[resultStrings.length - 1] = resultStrings[resultStrings.length - 1].trimEnd();
|
|
710
|
+
}
|
|
711
|
+
const whereTemplate = createTemplate(
|
|
712
|
+
makeTemplate(resultStrings),
|
|
713
|
+
resultValues
|
|
714
|
+
);
|
|
715
|
+
return createViewObject(name, baseTable, whereTemplate);
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function createViewObject(name, baseTable, whereTemplate) {
|
|
719
|
+
const baseMeta = getTableMeta(baseTable);
|
|
720
|
+
const cols = new Proxy({}, {
|
|
721
|
+
get(_target, prop) {
|
|
722
|
+
if (prop in baseTable.schema.shape) {
|
|
723
|
+
return createTemplate(makeTemplate(["", ".", ""]), [
|
|
724
|
+
ident(name),
|
|
725
|
+
ident(prop)
|
|
726
|
+
]);
|
|
727
|
+
}
|
|
728
|
+
return void 0;
|
|
729
|
+
},
|
|
730
|
+
has(_target, prop) {
|
|
731
|
+
return prop in baseTable.schema.shape;
|
|
732
|
+
},
|
|
733
|
+
ownKeys() {
|
|
734
|
+
return Object.keys(baseTable.schema.shape);
|
|
735
|
+
},
|
|
736
|
+
getOwnPropertyDescriptor(_target, prop) {
|
|
737
|
+
if (prop in baseTable.schema.shape) {
|
|
738
|
+
return { enumerable: true, configurable: true };
|
|
739
|
+
}
|
|
740
|
+
return void 0;
|
|
741
|
+
}
|
|
742
|
+
});
|
|
743
|
+
const primary = baseMeta.primary ? createTemplate(makeTemplate(["", ".", ""]), [
|
|
744
|
+
ident(name),
|
|
745
|
+
ident(baseMeta.primary)
|
|
746
|
+
]) : null;
|
|
747
|
+
const viewMeta = {
|
|
748
|
+
baseTable,
|
|
749
|
+
whereTemplate
|
|
750
|
+
};
|
|
751
|
+
const tableMeta = {
|
|
752
|
+
...baseMeta,
|
|
753
|
+
isView: true,
|
|
754
|
+
viewOf: baseTable.name,
|
|
755
|
+
derivedExprs: void 0,
|
|
756
|
+
derivedFields: void 0,
|
|
757
|
+
isDerived: void 0
|
|
758
|
+
};
|
|
759
|
+
const viewObj = {
|
|
760
|
+
name,
|
|
761
|
+
schema: baseTable.schema,
|
|
762
|
+
meta: tableMeta,
|
|
763
|
+
cols,
|
|
764
|
+
primary,
|
|
765
|
+
baseTable,
|
|
766
|
+
fields() {
|
|
767
|
+
return baseTable.fields();
|
|
768
|
+
},
|
|
769
|
+
references() {
|
|
770
|
+
return baseTable.references();
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
Object.defineProperty(viewObj, VIEW_MARKER, { value: true, enumerable: false });
|
|
774
|
+
Object.defineProperty(viewObj, VIEW_META, {
|
|
775
|
+
value: viewMeta,
|
|
776
|
+
enumerable: false
|
|
777
|
+
});
|
|
778
|
+
Object.defineProperty(viewObj, TABLE_MARKER, {
|
|
779
|
+
value: true,
|
|
780
|
+
enumerable: false
|
|
781
|
+
});
|
|
782
|
+
Object.defineProperty(viewObj, TABLE_META, {
|
|
783
|
+
value: tableMeta,
|
|
784
|
+
enumerable: false
|
|
785
|
+
});
|
|
786
|
+
return viewObj;
|
|
787
|
+
}
|
|
671
788
|
function table(name, shape, options = {}) {
|
|
672
789
|
validateIdentifier(name, "table");
|
|
673
790
|
if (name.includes(".")) {
|
|
@@ -886,7 +1003,7 @@ function createTableObject(name, schema, zodShape, meta, options) {
|
|
|
886
1003
|
`Table "${name}" does not have a soft delete field. Use softDelete() wrapper to mark a field.`
|
|
887
1004
|
);
|
|
888
1005
|
}
|
|
889
|
-
return createTemplate(makeTemplate(["", ".", " IS NOT NULL"]), [
|
|
1006
|
+
return createTemplate(makeTemplate(["(", ".", " IS NOT NULL)"]), [
|
|
890
1007
|
ident(name),
|
|
891
1008
|
ident(softDeleteField)
|
|
892
1009
|
]);
|
|
@@ -1113,6 +1230,28 @@ function createTableObject(name, schema, zodShape, meta, options) {
|
|
|
1113
1230
|
}
|
|
1114
1231
|
}
|
|
1115
1232
|
return createTemplate(makeTemplate(strings), templateValues);
|
|
1233
|
+
},
|
|
1234
|
+
get active() {
|
|
1235
|
+
const softDeleteField = meta.softDeleteField;
|
|
1236
|
+
if (!softDeleteField) {
|
|
1237
|
+
throw new Error(
|
|
1238
|
+
`Table "${name}" does not have a soft delete field. Use .db.softDelete() to mark a field for soft delete.`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1241
|
+
const whereTemplate = createTemplate(
|
|
1242
|
+
makeTemplate(["WHERE ", ".", " IS NULL"]),
|
|
1243
|
+
[ident(name), ident(softDeleteField)]
|
|
1244
|
+
);
|
|
1245
|
+
const activeViewName = `${name}_active`;
|
|
1246
|
+
const activeView = createViewObject(
|
|
1247
|
+
activeViewName,
|
|
1248
|
+
table2,
|
|
1249
|
+
whereTemplate
|
|
1250
|
+
);
|
|
1251
|
+
if (!internalMeta.activeView) {
|
|
1252
|
+
internalMeta.activeView = activeView;
|
|
1253
|
+
}
|
|
1254
|
+
return activeView;
|
|
1116
1255
|
}
|
|
1117
1256
|
};
|
|
1118
1257
|
return table2;
|
|
@@ -1250,7 +1389,25 @@ function extractFieldMeta(name, zodType, dbMeta) {
|
|
|
1250
1389
|
}
|
|
1251
1390
|
return meta;
|
|
1252
1391
|
}
|
|
1253
|
-
function
|
|
1392
|
+
function inferFieldType(schema) {
|
|
1393
|
+
let core = schema;
|
|
1394
|
+
while (typeof core.unwrap === "function") {
|
|
1395
|
+
if (core instanceof z.ZodArray || core instanceof z.ZodObject || core instanceof z.ZodDate || core instanceof z.ZodBoolean || core instanceof z.ZodNumber) {
|
|
1396
|
+
break;
|
|
1397
|
+
}
|
|
1398
|
+
core = core.unwrap();
|
|
1399
|
+
}
|
|
1400
|
+
if (core instanceof z.ZodDate)
|
|
1401
|
+
return "datetime";
|
|
1402
|
+
if (core instanceof z.ZodBoolean)
|
|
1403
|
+
return "boolean";
|
|
1404
|
+
if (core instanceof z.ZodObject || core instanceof z.ZodArray)
|
|
1405
|
+
return "json";
|
|
1406
|
+
if (core instanceof z.ZodNumber)
|
|
1407
|
+
return "real";
|
|
1408
|
+
return "text";
|
|
1409
|
+
}
|
|
1410
|
+
function decodeData(table2, data, driver) {
|
|
1254
1411
|
if (!data)
|
|
1255
1412
|
return data;
|
|
1256
1413
|
const decoded = {};
|
|
@@ -1260,10 +1417,13 @@ function decodeData(table2, data) {
|
|
|
1260
1417
|
const fieldSchema = shape?.[key];
|
|
1261
1418
|
if (fieldMeta?.decode && typeof fieldMeta.decode === "function") {
|
|
1262
1419
|
decoded[key] = fieldMeta.decode(value);
|
|
1420
|
+
} else if (driver?.decodeValue && fieldSchema) {
|
|
1421
|
+
const fieldType = inferFieldType(fieldSchema);
|
|
1422
|
+
decoded[key] = driver.decodeValue(value, fieldType);
|
|
1263
1423
|
} else if (fieldSchema) {
|
|
1264
1424
|
let core = fieldSchema;
|
|
1265
1425
|
while (typeof core.unwrap === "function") {
|
|
1266
|
-
if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
|
|
1426
|
+
if (core instanceof z.ZodArray || core instanceof z.ZodObject || core instanceof z.ZodBoolean || core instanceof z.ZodDate) {
|
|
1267
1427
|
break;
|
|
1268
1428
|
}
|
|
1269
1429
|
core = core.unwrap();
|
|
@@ -1280,9 +1440,20 @@ function decodeData(table2, data) {
|
|
|
1280
1440
|
} else {
|
|
1281
1441
|
decoded[key] = value;
|
|
1282
1442
|
}
|
|
1443
|
+
} else if (core instanceof z.ZodBoolean) {
|
|
1444
|
+
if (typeof value === "number") {
|
|
1445
|
+
decoded[key] = value !== 0;
|
|
1446
|
+
} else if (typeof value === "string") {
|
|
1447
|
+
decoded[key] = value !== "0" && value !== "";
|
|
1448
|
+
} else if (typeof value === "boolean") {
|
|
1449
|
+
decoded[key] = value;
|
|
1450
|
+
} else {
|
|
1451
|
+
decoded[key] = value;
|
|
1452
|
+
}
|
|
1283
1453
|
} else if (core instanceof z.ZodDate) {
|
|
1284
1454
|
if (typeof value === "string") {
|
|
1285
|
-
const
|
|
1455
|
+
const normalized = value.includes("T") ? value : value.replace(" ", "T") + "Z";
|
|
1456
|
+
const date = new Date(normalized);
|
|
1286
1457
|
if (isNaN(date.getTime())) {
|
|
1287
1458
|
throw new Error(
|
|
1288
1459
|
`Invalid date value for field "${key}": "${value}" cannot be parsed as a valid date`
|
|
@@ -1306,6 +1477,18 @@ function decodeData(table2, data) {
|
|
|
1306
1477
|
decoded[key] = value;
|
|
1307
1478
|
}
|
|
1308
1479
|
}
|
|
1480
|
+
const derive = getTableMeta(table2).derive;
|
|
1481
|
+
if (derive) {
|
|
1482
|
+
for (const [propName, deriveFn] of Object.entries(derive)) {
|
|
1483
|
+
Object.defineProperty(decoded, propName, {
|
|
1484
|
+
get() {
|
|
1485
|
+
return deriveFn(this);
|
|
1486
|
+
},
|
|
1487
|
+
enumerable: false,
|
|
1488
|
+
configurable: true
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1309
1492
|
return decoded;
|
|
1310
1493
|
}
|
|
1311
1494
|
|
|
@@ -1340,7 +1523,12 @@ export {
|
|
|
1340
1523
|
makeTemplate,
|
|
1341
1524
|
validateWithStandardSchema,
|
|
1342
1525
|
extendZod,
|
|
1526
|
+
isTable,
|
|
1343
1527
|
getTableMeta,
|
|
1528
|
+
isView,
|
|
1529
|
+
getViewMeta,
|
|
1530
|
+
view,
|
|
1344
1531
|
table,
|
|
1532
|
+
inferFieldType,
|
|
1345
1533
|
decodeData
|
|
1346
1534
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSQLBuiltin,
|
|
3
|
+
isSQLIdentifier,
|
|
4
|
+
resolveSQLBuiltin
|
|
5
|
+
} from "./chunk-BEX6VPES.js";
|
|
6
|
+
|
|
7
|
+
// src/impl/sql.ts
|
|
8
|
+
function quoteIdent(name, dialect) {
|
|
9
|
+
if (dialect === "mysql") {
|
|
10
|
+
return `\`${name.replace(/`/g, "``")}\``;
|
|
11
|
+
}
|
|
12
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
13
|
+
}
|
|
14
|
+
function placeholder(index, dialect) {
|
|
15
|
+
if (dialect === "postgresql") {
|
|
16
|
+
return `$${index}`;
|
|
17
|
+
}
|
|
18
|
+
return "?";
|
|
19
|
+
}
|
|
20
|
+
function renderDDL(strings, values, dialect) {
|
|
21
|
+
let sql = "";
|
|
22
|
+
for (let i = 0; i < strings.length; i++) {
|
|
23
|
+
sql += strings[i];
|
|
24
|
+
if (i < values.length) {
|
|
25
|
+
const value = values[i];
|
|
26
|
+
if (isSQLBuiltin(value)) {
|
|
27
|
+
sql += resolveSQLBuiltin(value);
|
|
28
|
+
} else if (isSQLIdentifier(value)) {
|
|
29
|
+
sql += quoteIdent(value.name, dialect);
|
|
30
|
+
} else {
|
|
31
|
+
sql += inlineLiteral(value, dialect);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return sql;
|
|
36
|
+
}
|
|
37
|
+
function inlineLiteral(value, dialect) {
|
|
38
|
+
if (value === null || value === void 0) {
|
|
39
|
+
return "NULL";
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "boolean") {
|
|
42
|
+
if (dialect === "sqlite") {
|
|
43
|
+
return value ? "1" : "0";
|
|
44
|
+
}
|
|
45
|
+
return value ? "TRUE" : "FALSE";
|
|
46
|
+
}
|
|
47
|
+
if (typeof value === "number") {
|
|
48
|
+
return String(value);
|
|
49
|
+
}
|
|
50
|
+
if (typeof value === "string") {
|
|
51
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
52
|
+
}
|
|
53
|
+
if (value instanceof Date) {
|
|
54
|
+
return `'${value.toISOString()}'`;
|
|
55
|
+
}
|
|
56
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
quoteIdent,
|
|
61
|
+
placeholder,
|
|
62
|
+
renderDDL
|
|
63
|
+
};
|
package/ddl-OT6HPLQY.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b9g/zen",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "The simple database client. Define tables. Write SQL. Get objects.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"database",
|
|
@@ -12,7 +12,16 @@
|
|
|
12
12
|
"postgres",
|
|
13
13
|
"mysql"
|
|
14
14
|
],
|
|
15
|
+
"author": "Brian Kim <briankimdev@gmail.com>",
|
|
15
16
|
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/bikeshaving/zen.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/bikeshaving/zen/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://zendb.org",
|
|
16
25
|
"dependencies": {
|
|
17
26
|
"zod": "^4.0.0"
|
|
18
27
|
},
|
package/src/bun.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Unified driver supporting PostgreSQL, MySQL, and SQLite via Bun's built-in SQL.
|
|
5
5
|
* Zero dependencies - uses native Bun implementation.
|
|
6
6
|
*/
|
|
7
|
-
import type { Driver, Table, EnsureResult } from "./zen.js";
|
|
7
|
+
import type { Driver, Table, View, EnsureResult } from "./zen.js";
|
|
8
8
|
/**
|
|
9
9
|
* Bun driver using Bun's built-in SQL.
|
|
10
10
|
* Supports PostgreSQL, MySQL, and SQLite with automatic dialect detection.
|
|
@@ -34,9 +34,20 @@ export default class BunDriver implements Driver {
|
|
|
34
34
|
run(strings: TemplateStringsArray, values: unknown[]): Promise<number>;
|
|
35
35
|
val<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
|
|
36
36
|
close(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Encode a JS value for database insertion.
|
|
39
|
+
* Dialect-aware: PostgreSQL uses native types, SQLite/MySQL need conversion.
|
|
40
|
+
*/
|
|
41
|
+
encodeValue(value: unknown, fieldType: string): unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Decode a database value to JS.
|
|
44
|
+
* Dialect-aware: handles differences in how values are returned.
|
|
45
|
+
*/
|
|
46
|
+
decodeValue(value: unknown, fieldType: string): unknown;
|
|
37
47
|
transaction<T>(fn: (txDriver: Driver) => Promise<T>): Promise<T>;
|
|
38
48
|
withMigrationLock<T>(fn: () => Promise<T>): Promise<T>;
|
|
39
49
|
ensureTable<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
50
|
+
ensureView<T extends View<any>>(viewObj: T): Promise<EnsureResult>;
|
|
40
51
|
ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
41
52
|
/**
|
|
42
53
|
* Optional introspection: list columns for a table.
|
package/src/bun.js
CHANGED
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
placeholder,
|
|
4
4
|
quoteIdent,
|
|
5
5
|
renderDDL
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-NBXBBEMA.js";
|
|
7
7
|
import {
|
|
8
|
-
generateDDL
|
|
9
|
-
|
|
8
|
+
generateDDL,
|
|
9
|
+
generateViewDDL
|
|
10
|
+
} from "../chunk-ARUUB3H4.js";
|
|
10
11
|
import {
|
|
12
|
+
getTableMeta,
|
|
11
13
|
resolveSQLBuiltin
|
|
12
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-BEX6VPES.js";
|
|
13
15
|
|
|
14
16
|
// src/bun.ts
|
|
15
17
|
import { SQL } from "bun";
|
|
@@ -227,13 +229,103 @@ var BunDriver = class {
|
|
|
227
229
|
async close() {
|
|
228
230
|
await this.#sql.close();
|
|
229
231
|
}
|
|
232
|
+
// ==========================================================================
|
|
233
|
+
// Type Encoding/Decoding
|
|
234
|
+
// ==========================================================================
|
|
235
|
+
/**
|
|
236
|
+
* Encode a JS value for database insertion.
|
|
237
|
+
* Dialect-aware: PostgreSQL uses native types, SQLite/MySQL need conversion.
|
|
238
|
+
*/
|
|
239
|
+
encodeValue(value, fieldType) {
|
|
240
|
+
if (value === null || value === void 0) {
|
|
241
|
+
return value;
|
|
242
|
+
}
|
|
243
|
+
switch (fieldType) {
|
|
244
|
+
case "datetime":
|
|
245
|
+
if (value instanceof Date && !isNaN(value.getTime())) {
|
|
246
|
+
if (this.#dialect === "postgresql") {
|
|
247
|
+
return value;
|
|
248
|
+
} else if (this.#dialect === "mysql") {
|
|
249
|
+
return value.toISOString().replace("T", " ").slice(0, 23);
|
|
250
|
+
} else {
|
|
251
|
+
return value.toISOString();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
case "boolean":
|
|
256
|
+
if (this.#dialect === "postgresql") {
|
|
257
|
+
return value;
|
|
258
|
+
}
|
|
259
|
+
return value ? 1 : 0;
|
|
260
|
+
case "json":
|
|
261
|
+
return JSON.stringify(value);
|
|
262
|
+
default:
|
|
263
|
+
return value;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Decode a database value to JS.
|
|
268
|
+
* Dialect-aware: handles differences in how values are returned.
|
|
269
|
+
*/
|
|
270
|
+
decodeValue(value, fieldType) {
|
|
271
|
+
if (value === null || value === void 0) {
|
|
272
|
+
return value;
|
|
273
|
+
}
|
|
274
|
+
switch (fieldType) {
|
|
275
|
+
case "datetime":
|
|
276
|
+
if (value instanceof Date) {
|
|
277
|
+
if (isNaN(value.getTime())) {
|
|
278
|
+
throw new Error(`Invalid Date object received from database`);
|
|
279
|
+
}
|
|
280
|
+
return value;
|
|
281
|
+
}
|
|
282
|
+
if (typeof value === "string") {
|
|
283
|
+
let date;
|
|
284
|
+
if (this.#dialect === "mysql") {
|
|
285
|
+
const normalized = value.includes("T") ? value : value.replace(" ", "T") + "Z";
|
|
286
|
+
date = new Date(normalized);
|
|
287
|
+
} else {
|
|
288
|
+
date = new Date(value);
|
|
289
|
+
}
|
|
290
|
+
if (isNaN(date.getTime())) {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Invalid date value: "${value}" cannot be parsed as a valid date`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
return date;
|
|
296
|
+
}
|
|
297
|
+
return value;
|
|
298
|
+
case "boolean":
|
|
299
|
+
if (this.#dialect === "postgresql") {
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
if (typeof value === "number") {
|
|
303
|
+
return value !== 0;
|
|
304
|
+
}
|
|
305
|
+
if (typeof value === "string") {
|
|
306
|
+
return value !== "0" && value !== "";
|
|
307
|
+
}
|
|
308
|
+
return value;
|
|
309
|
+
case "json":
|
|
310
|
+
if (typeof value === "string") {
|
|
311
|
+
return JSON.parse(value);
|
|
312
|
+
}
|
|
313
|
+
return value;
|
|
314
|
+
default:
|
|
315
|
+
return value;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
230
318
|
async transaction(fn) {
|
|
231
319
|
const dialect = this.#dialect;
|
|
232
320
|
const handleError = this.#handleError.bind(this);
|
|
233
321
|
const supportsReturning = this.supportsReturning;
|
|
322
|
+
const encodeValue = this.encodeValue.bind(this);
|
|
323
|
+
const decodeValue = this.decodeValue.bind(this);
|
|
234
324
|
return await this.#sql.transaction(async (txSql) => {
|
|
235
325
|
const txDriver = {
|
|
236
326
|
supportsReturning,
|
|
327
|
+
encodeValue,
|
|
328
|
+
decodeValue,
|
|
237
329
|
all: async (strings, values) => {
|
|
238
330
|
try {
|
|
239
331
|
const { sql, params } = buildSQL(strings, values, dialect);
|
|
@@ -359,6 +451,9 @@ var BunDriver = class {
|
|
|
359
451
|
step = 4;
|
|
360
452
|
await this.#checkMissingConstraints(table);
|
|
361
453
|
}
|
|
454
|
+
step = 5;
|
|
455
|
+
const viewApplied = await this.#ensureViews(table);
|
|
456
|
+
applied = applied || viewApplied;
|
|
362
457
|
return { applied };
|
|
363
458
|
} catch (error) {
|
|
364
459
|
if (error instanceof SchemaDriftError || error instanceof EnsureError) {
|
|
@@ -371,6 +466,19 @@ var BunDriver = class {
|
|
|
371
466
|
);
|
|
372
467
|
}
|
|
373
468
|
}
|
|
469
|
+
async ensureView(viewObj) {
|
|
470
|
+
await this.#ensureSqliteInit();
|
|
471
|
+
const ddlTemplate = generateViewDDL(viewObj, { dialect: this.#dialect });
|
|
472
|
+
const ddlSQL = renderDDL(
|
|
473
|
+
ddlTemplate[0],
|
|
474
|
+
ddlTemplate.slice(1),
|
|
475
|
+
this.#dialect
|
|
476
|
+
);
|
|
477
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
478
|
+
await this.#sql.unsafe(stmt.trim(), []);
|
|
479
|
+
}
|
|
480
|
+
return { applied: true };
|
|
481
|
+
}
|
|
374
482
|
async ensureConstraints(table) {
|
|
375
483
|
await this.#ensureSqliteInit();
|
|
376
484
|
const tableName = table.name;
|
|
@@ -634,7 +742,7 @@ var BunDriver = class {
|
|
|
634
742
|
return applied;
|
|
635
743
|
}
|
|
636
744
|
async #addColumn(table, fieldName) {
|
|
637
|
-
const { generateColumnDDL } = await import("../ddl-
|
|
745
|
+
const { generateColumnDDL } = await import("../ddl-OT6HPLQY.js");
|
|
638
746
|
const zodType = table.schema.shape[fieldName];
|
|
639
747
|
const fieldMeta = table.meta.fields[fieldName] || {};
|
|
640
748
|
const colTemplate = generateColumnDDL(
|
|
@@ -673,6 +781,30 @@ var BunDriver = class {
|
|
|
673
781
|
}
|
|
674
782
|
return applied;
|
|
675
783
|
}
|
|
784
|
+
/**
|
|
785
|
+
* Ensure the active view exists for this table (if it has soft delete).
|
|
786
|
+
* Creates the view using generateViewDDL.
|
|
787
|
+
*/
|
|
788
|
+
async #ensureViews(table) {
|
|
789
|
+
const meta = getTableMeta(table);
|
|
790
|
+
if (meta.softDeleteField && !meta.activeView) {
|
|
791
|
+
void table.active;
|
|
792
|
+
}
|
|
793
|
+
const activeView = meta.activeView;
|
|
794
|
+
if (!activeView) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
const ddlTemplate = generateViewDDL(activeView, { dialect: this.#dialect });
|
|
798
|
+
const ddlSQL = renderDDL(
|
|
799
|
+
ddlTemplate[0],
|
|
800
|
+
ddlTemplate.slice(1),
|
|
801
|
+
this.#dialect
|
|
802
|
+
);
|
|
803
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
804
|
+
await this.#sql.unsafe(stmt.trim(), []);
|
|
805
|
+
}
|
|
806
|
+
return true;
|
|
807
|
+
}
|
|
676
808
|
async #createIndex(tableName, indexName, columns, unique) {
|
|
677
809
|
const uniqueKw = unique ? "UNIQUE " : "";
|
|
678
810
|
const colList = columns.map((c) => quoteIdent(c, this.#dialect)).join(", ");
|
package/src/mysql.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Requires: mysql2
|
|
8
8
|
*/
|
|
9
9
|
import type { Driver, Table, EnsureResult } from "./zen.js";
|
|
10
|
+
import type { View } from "./impl/table.js";
|
|
10
11
|
/**
|
|
11
12
|
* Options for the mysql adapter.
|
|
12
13
|
*/
|
|
@@ -46,6 +47,16 @@ export default class MySQLDriver implements Driver {
|
|
|
46
47
|
run(strings: TemplateStringsArray, values: unknown[]): Promise<number>;
|
|
47
48
|
val<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
|
|
48
49
|
close(): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Encode a JS value for database insertion.
|
|
52
|
+
* MySQL: needs conversion for Date and boolean.
|
|
53
|
+
*/
|
|
54
|
+
encodeValue(value: unknown, fieldType: string): unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Decode a database value to JS.
|
|
57
|
+
* MySQL: handles 0/1 booleans and datetime strings.
|
|
58
|
+
*/
|
|
59
|
+
decodeValue(value: unknown, fieldType: string): unknown;
|
|
49
60
|
transaction<T>(fn: (txDriver: Driver) => Promise<T>): Promise<T>;
|
|
50
61
|
withMigrationLock<T>(fn: () => Promise<T>): Promise<T>;
|
|
51
62
|
/**
|
|
@@ -54,6 +65,7 @@ export default class MySQLDriver implements Driver {
|
|
|
54
65
|
* Throws SchemaDriftError if constraints are missing.
|
|
55
66
|
*/
|
|
56
67
|
ensureTable<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
68
|
+
ensureView<T extends View<any>>(viewObj: T): Promise<EnsureResult>;
|
|
57
69
|
/**
|
|
58
70
|
* Ensure constraints exist on the table.
|
|
59
71
|
* Applies unique and foreign key constraints with preflight checks.
|