@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/src/mysql.js CHANGED
@@ -2,18 +2,19 @@
2
2
  import {
3
3
  quoteIdent,
4
4
  renderDDL
5
- } from "../chunk-2IEEEMRN.js";
5
+ } from "../chunk-NBXBBEMA.js";
6
6
  import {
7
7
  generateColumnDDL,
8
- generateDDL
9
- } from "../chunk-QXGEP5PB.js";
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-56M5Z3A6.js";
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-2IEEEMRN.js";
5
+ } from "../chunk-NBXBBEMA.js";
6
6
  import {
7
7
  generateColumnDDL,
8
- generateDDL
9
- } from "../chunk-QXGEP5PB.js";
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-56M5Z3A6.js";
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-2IEEEMRN.js";
5
+ } from "../chunk-NBXBBEMA.js";
6
6
  import {
7
7
  generateColumnDDL,
8
- generateDDL
9
- } from "../chunk-QXGEP5PB.js";
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-56M5Z3A6.js";
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 InferRefs, } from "./impl/table.js";
9
- export { Database, Transaction, DatabaseUpgradeEvent, type Driver, } from "./impl/database.js";
10
- export { CURRENT_TIMESTAMP, CURRENT_DATE, CURRENT_TIME, NOW, TODAY, isSQLBuiltin, } from "./impl/database.js";
11
- export { ident, isSQLIdentifier, } from "./impl/template.js";
12
- export { type SQLTemplate, isSQLTemplate, } from "./impl/template.js";
13
- export { type EnsureResult, } from "./impl/database.js";
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";