@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
package/src/mysql.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
quoteIdent,
|
|
4
4
|
renderDDL
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-NBXBBEMA.js";
|
|
6
6
|
import {
|
|
7
7
|
generateColumnDDL,
|
|
8
|
-
generateDDL
|
|
9
|
-
|
|
8
|
+
generateDDL,
|
|
9
|
+
generateViewDDL
|
|
10
|
+
} from "../chunk-ARUUB3H4.js";
|
|
10
11
|
import {
|
|
11
12
|
ConstraintPreflightError,
|
|
12
13
|
EnsureError,
|
|
13
14
|
SchemaDriftError,
|
|
14
15
|
getTableMeta,
|
|
15
16
|
resolveSQLBuiltin
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-BEX6VPES.js";
|
|
17
18
|
|
|
18
19
|
// src/mysql.ts
|
|
19
20
|
import {
|
|
@@ -143,13 +144,86 @@ var MySQLDriver = class {
|
|
|
143
144
|
async close() {
|
|
144
145
|
await this.#pool.end();
|
|
145
146
|
}
|
|
147
|
+
// ==========================================================================
|
|
148
|
+
// Type Encoding/Decoding
|
|
149
|
+
// ==========================================================================
|
|
150
|
+
/**
|
|
151
|
+
* Encode a JS value for database insertion.
|
|
152
|
+
* MySQL: needs conversion for Date and boolean.
|
|
153
|
+
*/
|
|
154
|
+
encodeValue(value, fieldType) {
|
|
155
|
+
if (value === null || value === void 0) {
|
|
156
|
+
return value;
|
|
157
|
+
}
|
|
158
|
+
switch (fieldType) {
|
|
159
|
+
case "datetime":
|
|
160
|
+
if (value instanceof Date && !isNaN(value.getTime())) {
|
|
161
|
+
return value.toISOString().replace("T", " ").slice(0, 23);
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
case "boolean":
|
|
165
|
+
return value ? 1 : 0;
|
|
166
|
+
case "json":
|
|
167
|
+
return JSON.stringify(value);
|
|
168
|
+
default:
|
|
169
|
+
return value;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Decode a database value to JS.
|
|
174
|
+
* MySQL: handles 0/1 booleans and datetime strings.
|
|
175
|
+
*/
|
|
176
|
+
decodeValue(value, fieldType) {
|
|
177
|
+
if (value === null || value === void 0) {
|
|
178
|
+
return value;
|
|
179
|
+
}
|
|
180
|
+
switch (fieldType) {
|
|
181
|
+
case "datetime":
|
|
182
|
+
if (value instanceof Date) {
|
|
183
|
+
if (isNaN(value.getTime())) {
|
|
184
|
+
throw new Error(`Invalid Date object received from database`);
|
|
185
|
+
}
|
|
186
|
+
return value;
|
|
187
|
+
}
|
|
188
|
+
if (typeof value === "string") {
|
|
189
|
+
const normalized = value.includes("T") ? value : value.replace(" ", "T") + "Z";
|
|
190
|
+
const date = new Date(normalized);
|
|
191
|
+
if (isNaN(date.getTime())) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`Invalid date value: "${value}" cannot be parsed as a valid date`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return date;
|
|
197
|
+
}
|
|
198
|
+
return value;
|
|
199
|
+
case "boolean":
|
|
200
|
+
if (typeof value === "number") {
|
|
201
|
+
return value !== 0;
|
|
202
|
+
}
|
|
203
|
+
if (typeof value === "string") {
|
|
204
|
+
return value !== "0" && value !== "";
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
case "json":
|
|
208
|
+
if (typeof value === "string") {
|
|
209
|
+
return JSON.parse(value);
|
|
210
|
+
}
|
|
211
|
+
return value;
|
|
212
|
+
default:
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
146
216
|
async transaction(fn) {
|
|
147
217
|
const connection = await this.#pool.getConnection();
|
|
148
218
|
const handleError = this.#handleError.bind(this);
|
|
219
|
+
const encodeValue = this.encodeValue.bind(this);
|
|
220
|
+
const decodeValue = this.decodeValue.bind(this);
|
|
149
221
|
try {
|
|
150
222
|
await connection.query("START TRANSACTION");
|
|
151
223
|
const txDriver = {
|
|
152
224
|
supportsReturning: false,
|
|
225
|
+
encodeValue,
|
|
226
|
+
decodeValue,
|
|
153
227
|
all: async (strings, values) => {
|
|
154
228
|
try {
|
|
155
229
|
const { sql, params } = buildSQL(strings, values);
|
|
@@ -234,6 +308,12 @@ var MySQLDriver = class {
|
|
|
234
308
|
* Throws SchemaDriftError if constraints are missing.
|
|
235
309
|
*/
|
|
236
310
|
async ensureTable(table) {
|
|
311
|
+
const meta = getTableMeta(table);
|
|
312
|
+
if (meta.isView) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
237
317
|
const tableName = table.name;
|
|
238
318
|
let step = 0;
|
|
239
319
|
let applied = false;
|
|
@@ -257,6 +337,9 @@ var MySQLDriver = class {
|
|
|
257
337
|
step = 4;
|
|
258
338
|
await this.#checkMissingConstraints(table);
|
|
259
339
|
}
|
|
340
|
+
step = 5;
|
|
341
|
+
const viewsApplied = await this.#ensureViews(table);
|
|
342
|
+
applied = applied || viewsApplied;
|
|
260
343
|
return { applied };
|
|
261
344
|
} catch (error) {
|
|
262
345
|
if (error instanceof SchemaDriftError || error instanceof ConstraintPreflightError) {
|
|
@@ -275,11 +358,45 @@ var MySQLDriver = class {
|
|
|
275
358
|
);
|
|
276
359
|
}
|
|
277
360
|
}
|
|
361
|
+
async ensureView(viewObj) {
|
|
362
|
+
const ddlTemplate = generateViewDDL(viewObj, { dialect: DIALECT });
|
|
363
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
364
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
365
|
+
await this.#pool.execute(stmt.trim(), []);
|
|
366
|
+
}
|
|
367
|
+
return { applied: true };
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Ensure the active view exists for this table (if it has soft delete).
|
|
371
|
+
* Creates the view using generateViewDDL.
|
|
372
|
+
*/
|
|
373
|
+
async #ensureViews(table) {
|
|
374
|
+
const meta = getTableMeta(table);
|
|
375
|
+
if (meta.softDeleteField && !meta.activeView) {
|
|
376
|
+
void table.active;
|
|
377
|
+
}
|
|
378
|
+
const activeView = meta.activeView;
|
|
379
|
+
if (!activeView) {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
const ddlTemplate = generateViewDDL(activeView, { dialect: DIALECT });
|
|
383
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
384
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
385
|
+
await this.#pool.execute(stmt.trim(), []);
|
|
386
|
+
}
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
278
389
|
/**
|
|
279
390
|
* Ensure constraints exist on the table.
|
|
280
391
|
* Applies unique and foreign key constraints with preflight checks.
|
|
281
392
|
*/
|
|
282
393
|
async ensureConstraints(table) {
|
|
394
|
+
const meta = getTableMeta(table);
|
|
395
|
+
if (meta.isView) {
|
|
396
|
+
throw new Error(
|
|
397
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
283
400
|
const tableName = table.name;
|
|
284
401
|
let step = 0;
|
|
285
402
|
let applied = false;
|
package/src/postgres.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Requires: postgres
|
|
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 postgres adapter.
|
|
12
13
|
*/
|
|
@@ -46,6 +47,16 @@ export default class PostgresDriver 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
|
+
* PostgreSQL: postgres.js library handles Date and boolean natively.
|
|
53
|
+
*/
|
|
54
|
+
encodeValue(value: unknown, fieldType: string): unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Decode a database value to JS.
|
|
57
|
+
* PostgreSQL: postgres.js returns proper types for most things.
|
|
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 PostgresDriver 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.
|
package/src/postgres.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
quoteIdent,
|
|
4
4
|
renderDDL
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-NBXBBEMA.js";
|
|
6
6
|
import {
|
|
7
7
|
generateColumnDDL,
|
|
8
|
-
generateDDL
|
|
9
|
-
|
|
8
|
+
generateDDL,
|
|
9
|
+
generateViewDDL
|
|
10
|
+
} from "../chunk-ARUUB3H4.js";
|
|
10
11
|
import {
|
|
11
12
|
ConstraintPreflightError,
|
|
12
13
|
EnsureError,
|
|
13
14
|
SchemaDriftError,
|
|
14
15
|
getTableMeta,
|
|
15
16
|
resolveSQLBuiltin
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-BEX6VPES.js";
|
|
17
18
|
|
|
18
19
|
// src/postgres.ts
|
|
19
20
|
import {
|
|
@@ -132,11 +133,64 @@ var PostgresDriver = class {
|
|
|
132
133
|
async close() {
|
|
133
134
|
await this.#sql.end();
|
|
134
135
|
}
|
|
136
|
+
// ==========================================================================
|
|
137
|
+
// Type Encoding/Decoding
|
|
138
|
+
// ==========================================================================
|
|
139
|
+
/**
|
|
140
|
+
* Encode a JS value for database insertion.
|
|
141
|
+
* PostgreSQL: postgres.js library handles Date and boolean natively.
|
|
142
|
+
*/
|
|
143
|
+
encodeValue(value, fieldType) {
|
|
144
|
+
if (value === null || value === void 0) {
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
switch (fieldType) {
|
|
148
|
+
case "datetime":
|
|
149
|
+
return value;
|
|
150
|
+
case "boolean":
|
|
151
|
+
return value;
|
|
152
|
+
case "json":
|
|
153
|
+
return JSON.stringify(value);
|
|
154
|
+
default:
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Decode a database value to JS.
|
|
160
|
+
* PostgreSQL: postgres.js returns proper types for most things.
|
|
161
|
+
*/
|
|
162
|
+
decodeValue(value, fieldType) {
|
|
163
|
+
if (value === null || value === void 0) {
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
switch (fieldType) {
|
|
167
|
+
case "datetime":
|
|
168
|
+
if (value instanceof Date) {
|
|
169
|
+
if (isNaN(value.getTime())) {
|
|
170
|
+
throw new Error(`Invalid Date object received from database`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return value;
|
|
174
|
+
case "boolean":
|
|
175
|
+
return value;
|
|
176
|
+
case "json":
|
|
177
|
+
if (typeof value === "string") {
|
|
178
|
+
return JSON.parse(value);
|
|
179
|
+
}
|
|
180
|
+
return value;
|
|
181
|
+
default:
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
135
185
|
async transaction(fn) {
|
|
136
186
|
const handleError = this.#handleError.bind(this);
|
|
187
|
+
const encodeValue = this.encodeValue.bind(this);
|
|
188
|
+
const decodeValue = this.decodeValue.bind(this);
|
|
137
189
|
const result = await this.#sql.begin(async (txSql) => {
|
|
138
190
|
const txDriver = {
|
|
139
191
|
supportsReturning: true,
|
|
192
|
+
encodeValue,
|
|
193
|
+
decodeValue,
|
|
140
194
|
all: async (strings, values) => {
|
|
141
195
|
try {
|
|
142
196
|
const { sql, params } = buildSQL(strings, values);
|
|
@@ -205,6 +259,12 @@ var PostgresDriver = class {
|
|
|
205
259
|
* Throws SchemaDriftError if constraints are missing.
|
|
206
260
|
*/
|
|
207
261
|
async ensureTable(table) {
|
|
262
|
+
const meta = getTableMeta(table);
|
|
263
|
+
if (meta.isView) {
|
|
264
|
+
throw new Error(
|
|
265
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
208
268
|
const tableName = table.name;
|
|
209
269
|
let step = 0;
|
|
210
270
|
let applied = false;
|
|
@@ -228,6 +288,9 @@ var PostgresDriver = class {
|
|
|
228
288
|
step = 4;
|
|
229
289
|
await this.#checkMissingConstraints(table);
|
|
230
290
|
}
|
|
291
|
+
step = 5;
|
|
292
|
+
const viewsApplied = await this.#ensureViews(table);
|
|
293
|
+
applied = applied || viewsApplied;
|
|
231
294
|
return { applied };
|
|
232
295
|
} catch (error) {
|
|
233
296
|
if (error instanceof SchemaDriftError || error instanceof ConstraintPreflightError) {
|
|
@@ -246,11 +309,45 @@ var PostgresDriver = class {
|
|
|
246
309
|
);
|
|
247
310
|
}
|
|
248
311
|
}
|
|
312
|
+
async ensureView(viewObj) {
|
|
313
|
+
const ddlTemplate = generateViewDDL(viewObj, { dialect: DIALECT });
|
|
314
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
315
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
316
|
+
await this.#sql.unsafe(stmt.trim());
|
|
317
|
+
}
|
|
318
|
+
return { applied: true };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Ensure the active view exists for this table (if it has soft delete).
|
|
322
|
+
* Creates the view using generateViewDDL.
|
|
323
|
+
*/
|
|
324
|
+
async #ensureViews(table) {
|
|
325
|
+
const meta = getTableMeta(table);
|
|
326
|
+
if (meta.softDeleteField && !meta.activeView) {
|
|
327
|
+
void table.active;
|
|
328
|
+
}
|
|
329
|
+
const activeView = meta.activeView;
|
|
330
|
+
if (!activeView) {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
const ddlTemplate = generateViewDDL(activeView, { dialect: DIALECT });
|
|
334
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
335
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
336
|
+
await this.#sql.unsafe(stmt.trim());
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
249
340
|
/**
|
|
250
341
|
* Ensure constraints exist on the table.
|
|
251
342
|
* Applies unique and foreign key constraints with preflight checks.
|
|
252
343
|
*/
|
|
253
344
|
async ensureConstraints(table) {
|
|
345
|
+
const meta = getTableMeta(table);
|
|
346
|
+
if (meta.isView) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
254
351
|
const tableName = table.name;
|
|
255
352
|
let step = 0;
|
|
256
353
|
let applied = false;
|
package/src/sqlite.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Requires: better-sqlite3
|
|
8
8
|
*/
|
|
9
9
|
import type { Driver, EnsureResult } from "./zen.js";
|
|
10
|
-
import type { Table } from "./impl/table.js";
|
|
10
|
+
import type { Table, View } from "./impl/table.js";
|
|
11
11
|
/**
|
|
12
12
|
* SQLite driver using better-sqlite3.
|
|
13
13
|
*
|
|
@@ -36,8 +36,19 @@ export default class SQLiteDriver implements Driver {
|
|
|
36
36
|
run(strings: TemplateStringsArray, values: unknown[]): Promise<number>;
|
|
37
37
|
val<T>(strings: TemplateStringsArray, values: unknown[]): Promise<T | null>;
|
|
38
38
|
close(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Encode a JS value for database insertion.
|
|
41
|
+
* SQLite: needs conversion for Date and boolean.
|
|
42
|
+
*/
|
|
43
|
+
encodeValue(value: unknown, fieldType: string): unknown;
|
|
44
|
+
/**
|
|
45
|
+
* Decode a database value to JS.
|
|
46
|
+
* SQLite: handles 0/1 booleans and datetime strings.
|
|
47
|
+
*/
|
|
48
|
+
decodeValue(value: unknown, fieldType: string): unknown;
|
|
39
49
|
transaction<T>(fn: (txDriver: Driver) => Promise<T>): Promise<T>;
|
|
40
50
|
withMigrationLock<T>(fn: () => Promise<T>): Promise<T>;
|
|
41
51
|
ensureTable<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
52
|
+
ensureView<T extends View<any>>(viewObj: T): Promise<EnsureResult>;
|
|
42
53
|
ensureConstraints<T extends Table<any>>(table: T): Promise<EnsureResult>;
|
|
43
54
|
}
|
package/src/sqlite.js
CHANGED
|
@@ -2,18 +2,19 @@
|
|
|
2
2
|
import {
|
|
3
3
|
quoteIdent,
|
|
4
4
|
renderDDL
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-NBXBBEMA.js";
|
|
6
6
|
import {
|
|
7
7
|
generateColumnDDL,
|
|
8
|
-
generateDDL
|
|
9
|
-
|
|
8
|
+
generateDDL,
|
|
9
|
+
generateViewDDL
|
|
10
|
+
} from "../chunk-ARUUB3H4.js";
|
|
10
11
|
import {
|
|
11
12
|
ConstraintPreflightError,
|
|
12
13
|
EnsureError,
|
|
13
14
|
SchemaDriftError,
|
|
14
15
|
getTableMeta,
|
|
15
16
|
resolveSQLBuiltin
|
|
16
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-BEX6VPES.js";
|
|
17
18
|
|
|
18
19
|
// src/sqlite.ts
|
|
19
20
|
import {
|
|
@@ -129,6 +130,71 @@ var SQLiteDriver = class {
|
|
|
129
130
|
async close() {
|
|
130
131
|
this.#db.close();
|
|
131
132
|
}
|
|
133
|
+
// ==========================================================================
|
|
134
|
+
// Type Encoding/Decoding
|
|
135
|
+
// ==========================================================================
|
|
136
|
+
/**
|
|
137
|
+
* Encode a JS value for database insertion.
|
|
138
|
+
* SQLite: needs conversion for Date and boolean.
|
|
139
|
+
*/
|
|
140
|
+
encodeValue(value, fieldType) {
|
|
141
|
+
if (value === null || value === void 0) {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
switch (fieldType) {
|
|
145
|
+
case "datetime":
|
|
146
|
+
if (value instanceof Date && !isNaN(value.getTime())) {
|
|
147
|
+
return value.toISOString();
|
|
148
|
+
}
|
|
149
|
+
return value;
|
|
150
|
+
case "boolean":
|
|
151
|
+
return value ? 1 : 0;
|
|
152
|
+
case "json":
|
|
153
|
+
return JSON.stringify(value);
|
|
154
|
+
default:
|
|
155
|
+
return value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Decode a database value to JS.
|
|
160
|
+
* SQLite: handles 0/1 booleans and datetime strings.
|
|
161
|
+
*/
|
|
162
|
+
decodeValue(value, fieldType) {
|
|
163
|
+
if (value === null || value === void 0) {
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
switch (fieldType) {
|
|
167
|
+
case "datetime":
|
|
168
|
+
if (value instanceof Date) {
|
|
169
|
+
if (isNaN(value.getTime())) {
|
|
170
|
+
throw new Error(`Invalid Date object received from database`);
|
|
171
|
+
}
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
if (typeof value === "string") {
|
|
175
|
+
const date = new Date(value);
|
|
176
|
+
if (isNaN(date.getTime())) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Invalid date value: "${value}" cannot be parsed as a valid date`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
return date;
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
case "boolean":
|
|
185
|
+
if (typeof value === "number") {
|
|
186
|
+
return value !== 0;
|
|
187
|
+
}
|
|
188
|
+
return value;
|
|
189
|
+
case "json":
|
|
190
|
+
if (typeof value === "string") {
|
|
191
|
+
return JSON.parse(value);
|
|
192
|
+
}
|
|
193
|
+
return value;
|
|
194
|
+
default:
|
|
195
|
+
return value;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
132
198
|
async transaction(fn) {
|
|
133
199
|
this.#db.exec("BEGIN");
|
|
134
200
|
try {
|
|
@@ -155,6 +221,12 @@ var SQLiteDriver = class {
|
|
|
155
221
|
// Schema Management
|
|
156
222
|
// ==========================================================================
|
|
157
223
|
async ensureTable(table) {
|
|
224
|
+
const meta = getTableMeta(table);
|
|
225
|
+
if (meta.isView) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
228
|
+
);
|
|
229
|
+
}
|
|
158
230
|
const tableName = table.name;
|
|
159
231
|
let step = 0;
|
|
160
232
|
let applied = false;
|
|
@@ -178,6 +250,9 @@ var SQLiteDriver = class {
|
|
|
178
250
|
step = 4;
|
|
179
251
|
await this.#checkMissingConstraints(table);
|
|
180
252
|
}
|
|
253
|
+
step = 5;
|
|
254
|
+
const viewsApplied = await this.#ensureViews(table);
|
|
255
|
+
applied = applied || viewsApplied;
|
|
181
256
|
return { applied };
|
|
182
257
|
} catch (error) {
|
|
183
258
|
if (error instanceof SchemaDriftError || error instanceof EnsureError) {
|
|
@@ -190,7 +265,41 @@ var SQLiteDriver = class {
|
|
|
190
265
|
);
|
|
191
266
|
}
|
|
192
267
|
}
|
|
268
|
+
async ensureView(viewObj) {
|
|
269
|
+
const ddlTemplate = generateViewDDL(viewObj, { dialect: DIALECT });
|
|
270
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
271
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
272
|
+
this.#db.exec(stmt.trim());
|
|
273
|
+
}
|
|
274
|
+
return { applied: true };
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Ensure the active view exists for this table (if it has soft delete).
|
|
278
|
+
* Creates the view using generateViewDDL.
|
|
279
|
+
*/
|
|
280
|
+
async #ensureViews(table) {
|
|
281
|
+
const meta = getTableMeta(table);
|
|
282
|
+
if (meta.softDeleteField && !meta.activeView) {
|
|
283
|
+
void table.active;
|
|
284
|
+
}
|
|
285
|
+
const activeView = meta.activeView;
|
|
286
|
+
if (!activeView) {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
const ddlTemplate = generateViewDDL(activeView, { dialect: DIALECT });
|
|
290
|
+
const ddlSQL = renderDDL(ddlTemplate[0], ddlTemplate.slice(1), DIALECT);
|
|
291
|
+
for (const stmt of ddlSQL.split(";").filter((s) => s.trim())) {
|
|
292
|
+
this.#db.exec(stmt.trim());
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
193
296
|
async ensureConstraints(table) {
|
|
297
|
+
const meta = getTableMeta(table);
|
|
298
|
+
if (meta.isView) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`Cannot ensure view "${table.name}". Use the base table "${meta.viewOf}" instead.`
|
|
301
|
+
);
|
|
302
|
+
}
|
|
194
303
|
const tableName = table.name;
|
|
195
304
|
let step = 0;
|
|
196
305
|
let applied = false;
|
package/src/zen.d.ts
CHANGED
|
@@ -5,10 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { z as zod } from "zod";
|
|
7
7
|
export { zod as z };
|
|
8
|
-
export { table, type Table, type TableOptions, type Row, type Insert, type Update, type FieldMeta, type FieldType, type Relation, type
|
|
9
|
-
export { Database, Transaction, DatabaseUpgradeEvent, type Driver, } from "./impl/database.js";
|
|
10
|
-
export {
|
|
11
|
-
export { ident, isSQLIdentifier, } from "./impl/template.js";
|
|
12
|
-
export { type
|
|
13
|
-
export { type
|
|
14
|
-
export { DatabaseError, ValidationError, TableDefinitionError, MigrationError, MigrationLockError, QueryError, NotFoundError, AlreadyExistsError, ConstraintViolationError, ConnectionError, TransactionError, EnsureError, SchemaDriftError, ConstraintPreflightError, isDatabaseError, hasErrorCode, type DatabaseErrorCode, type EnsureOperation, } from "./impl/errors.js";
|
|
8
|
+
export { table, view, extendZod, isTable, isView, getViewMeta, type Table, type PartialTable, type DerivedTable, type View, type Queryable, type TableOptions, type Row, type Insert, type Update, type SetValues, type FieldMeta, type FieldType, type FieldDBMeta, type Relation, type ReferenceInfo, type CompoundReference, type ViewMeta, } from "./impl/table.js";
|
|
9
|
+
export { Database, Transaction, DatabaseUpgradeEvent, type Driver, type TaggedQuery, type EnsureResult, } from "./impl/database.js";
|
|
10
|
+
export { NOW, TODAY, CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, isSQLBuiltin, } from "./impl/database.js";
|
|
11
|
+
export { ident, isSQLIdentifier, type SQLTemplate, isSQLTemplate, } from "./impl/template.js";
|
|
12
|
+
export { type SQLDialect } from "./impl/sql.js";
|
|
13
|
+
export { DatabaseError, isDatabaseError, hasErrorCode, ValidationError, TableDefinitionError, QueryError, NotFoundError, AlreadyExistsError, ConstraintViolationError, MigrationError, MigrationLockError, EnsureError, SchemaDriftError, ConstraintPreflightError, ConnectionError, TransactionError, type DatabaseErrorCode, type EnsureOperation, } from "./impl/errors.js";
|