@b9g/zen 0.1.0 → 0.1.1

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 CHANGED
@@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.1] - 2025-12-21
9
+
10
+ ### Added
11
+
12
+ - Driver-level type encoding/decoding for dialect-specific handling
13
+ - `encodeValue(value, fieldType)` and `decodeValue(value, fieldType)` methods on Driver interface
14
+ - SQLite: Date→ISO string, boolean→1/0, JSON stringify/parse
15
+ - MySQL: Date→"YYYY-MM-DD HH:MM:SS", boolean→1/0, JSON stringify/parse
16
+ - PostgreSQL: Mostly passthrough (pg handles natively), JSON stringify
17
+ - `inferFieldType()` helper to infer field type from Zod schema
18
+ - Node.js tests for encode/decode functionality
19
+
20
+ ### Changed
21
+
22
+ - **Breaking:** Removed deprecated `Infer<T>` type alias (use `Row<T>` instead)
23
+ - Renamed internal types for clarity:
24
+ - `InferRefs` → `RowRefs`
25
+ - `WithRefs` → `JoinedRow`
26
+
27
+ ### Fixed
28
+
29
+ - Invalid datetime values now throw errors instead of returning Invalid Date
30
+
8
31
  ## [0.1.0] - 2025-12-20
9
32
 
10
33
  Initial release of @b9g/zen - the simple database client.
package/README.md CHANGED
@@ -253,7 +253,14 @@ await db.delete(Users, userId);
253
253
  const activeUsers = await db.all(Users)`
254
254
  WHERE NOT ${Users.deleted()}
255
255
  `;
256
- // → WHERE NOT "users"."deletedAt" IS NOT NULL
256
+
257
+ // Or use the .active view (auto-generated, read-only)
258
+ const activeUsers = await db.all(Users.active)``;
259
+
260
+ // JOINs with .active automatically filter deleted rows
261
+ const posts = await db.all([Posts, Users.active])`
262
+ JOIN "users_active" ON ${Users.active.cols.id} = ${Posts.cols.authorId}
263
+ `;
257
264
  ```
258
265
 
259
266
  **Compound indexes** via table options:
@@ -815,17 +822,27 @@ console.log(Posts.ddl().toString());
815
822
 
816
823
  | Feature | SQLite | PostgreSQL | MySQL |
817
824
  |---------|--------|------------|-------|
818
- | **DDL Generation** | ✅ | ✅ | |
819
- | **RETURNING** | ✅ | ✅ | (uses SELECT after) |
820
- | **IF NOT EXISTS** (CREATE TABLE) | ✅ | ✅ | |
821
- | **IF NOT EXISTS** (ADD COLUMN) | | | ❌ (may error if exists) |
822
- | **Migration Locks** | BEGIN EXCLUSIVE | pg_advisory_lock | GET_LOCK |
823
- | **EXPLAIN** | EXPLAIN QUERY PLAN | EXPLAIN | EXPLAIN |
824
- | **JSON Type** | TEXT | JSONB | TEXT |
825
- | **Boolean Type** | INTEGER (0/1) | BOOLEAN | BOOLEAN |
826
- | **Date Type** | TEXT (ISO) | TIMESTAMPTZ | DATETIME |
827
- | **Transactions** | ✅ | ✅ | ✅ |
828
- | **Advisory Locks** | | | (named) |
825
+ | RETURNING | ✅ | ✅ | ⚠️ fallback |
826
+ | IF NOT EXISTS (CREATE TABLE) | ✅ | ✅ | |
827
+ | IF NOT EXISTS (ADD COLUMN) | ✅ | ✅ | ⚠️ may error |
828
+ | Migration Locks | BEGIN EXCLUSIVE | pg_advisory_lock | GET_LOCK |
829
+ | Advisory Locks | | | |
830
+
831
+ ### Zod to SQL Type Mapping
832
+
833
+ | Zod Type | SQLite | PostgreSQL | MySQL |
834
+ |----------|--------|------------|-------|
835
+ | `z.string()` | TEXT | TEXT | TEXT |
836
+ | `z.string().max(n)` (n ≤ 255) | TEXT | VARCHAR(n) | VARCHAR(n) |
837
+ | `z.number()` | REAL | DOUBLE PRECISION | REAL |
838
+ | `z.number().int()` | INTEGER | INTEGER | INTEGER |
839
+ | `z.boolean()` | INTEGER | BOOLEAN | BOOLEAN |
840
+ | `z.date()` | TEXT | TIMESTAMPTZ | DATETIME |
841
+ | `z.enum([...])` | TEXT | TEXT | TEXT |
842
+ | `z.object({...})` | TEXT | JSONB | TEXT |
843
+ | `z.array(...)` | TEXT | JSONB | TEXT |
844
+
845
+ Override with `.db.type("CUSTOM")` when using custom encode/decode.
829
846
 
830
847
  ## Public API Reference
831
848
 
@@ -961,6 +978,9 @@ Users.pick("id", "email"); // PartialTable with subset of fields
961
978
  Users.derive("hasEmail", z.boolean())`
962
979
  ${Users.cols.email} IS NOT NULL
963
980
  `;
981
+
982
+ // Views
983
+ Users.active; // View excluding soft-deleted rows (read-only)
964
984
  ```
965
985
 
966
986
  ### Database Methods
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  createTemplate,
3
3
  getTableMeta,
4
+ getViewMeta,
4
5
  ident,
5
6
  makeTemplate
6
- } from "./chunk-56M5Z3A6.js";
7
+ } from "./chunk-XHXMCOSW.js";
7
8
 
8
9
  // src/impl/ddl.ts
9
10
  import { z } from "zod";
@@ -303,8 +304,32 @@ CREATE INDEX ${indexExists}`;
303
304
  }
304
305
  return createTemplate(makeTemplate(strings), values);
305
306
  }
307
+ function generateViewDDL(viewObj, _options = {}) {
308
+ const viewMeta = getViewMeta(viewObj);
309
+ const strings = [];
310
+ const values = [];
311
+ strings.push("DROP VIEW IF EXISTS ");
312
+ values.push(ident(viewObj.name));
313
+ strings.push(";\n\n");
314
+ strings[strings.length - 1] += "CREATE VIEW ";
315
+ values.push(ident(viewObj.name));
316
+ strings.push(" AS SELECT * FROM ");
317
+ values.push(ident(viewMeta.baseTable.name));
318
+ strings.push(" ");
319
+ const whereTemplate = viewMeta.whereTemplate;
320
+ const whereStrings = whereTemplate[0];
321
+ const whereValues = whereTemplate.slice(1);
322
+ strings[strings.length - 1] += whereStrings[0];
323
+ for (let i = 0; i < whereValues.length; i++) {
324
+ values.push(whereValues[i]);
325
+ strings.push(whereStrings[i + 1]);
326
+ }
327
+ strings[strings.length - 1] += ";";
328
+ return createTemplate(makeTemplate(strings), values);
329
+ }
306
330
 
307
331
  export {
308
332
  generateColumnDDL,
309
- generateDDL
333
+ generateDDL,
334
+ generateViewDDL
310
335
  };
@@ -0,0 +1,63 @@
1
+ import {
2
+ isSQLBuiltin,
3
+ isSQLIdentifier,
4
+ resolveSQLBuiltin
5
+ } from "./chunk-XHXMCOSW.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
+ };
@@ -668,6 +668,120 @@ var TABLE_META = Symbol.for("@b9g/zen:table-meta");
668
668
  function getTableMeta(table2) {
669
669
  return table2[TABLE_META];
670
670
  }
671
+ var VIEW_MARKER = Symbol("view");
672
+ var VIEW_META = Symbol("viewMeta");
673
+ function isView(value) {
674
+ return typeof value === "object" && value !== null && value[VIEW_MARKER] === true;
675
+ }
676
+ function getViewMeta(view2) {
677
+ return view2[VIEW_META];
678
+ }
679
+ function view(name, baseTable) {
680
+ validateIdentifier(name, "table");
681
+ if (name.includes(".")) {
682
+ throw new TableDefinitionError(
683
+ `Invalid view name "${name}": view names cannot contain "." as it conflicts with normalization prefixes`,
684
+ name
685
+ );
686
+ }
687
+ return (strings, ...templateValues) => {
688
+ const resultStrings = [];
689
+ const resultValues = [];
690
+ for (let i = 0; i < strings.length; i++) {
691
+ if (i === 0) {
692
+ resultStrings.push(strings[i]);
693
+ }
694
+ if (i < templateValues.length) {
695
+ const value = templateValues[i];
696
+ if (isSQLTemplate(value)) {
697
+ mergeFragment(resultStrings, resultValues, value, strings[i + 1]);
698
+ } else {
699
+ resultValues.push(value);
700
+ resultStrings.push(strings[i + 1]);
701
+ }
702
+ }
703
+ }
704
+ if (resultStrings.length > 0) {
705
+ resultStrings[0] = resultStrings[0].trimStart();
706
+ resultStrings[resultStrings.length - 1] = resultStrings[resultStrings.length - 1].trimEnd();
707
+ }
708
+ const whereTemplate = createTemplate(
709
+ makeTemplate(resultStrings),
710
+ resultValues
711
+ );
712
+ return createViewObject(name, baseTable, whereTemplate);
713
+ };
714
+ }
715
+ function createViewObject(name, baseTable, whereTemplate) {
716
+ const baseMeta = getTableMeta(baseTable);
717
+ const cols = new Proxy({}, {
718
+ get(_target, prop) {
719
+ if (prop in baseTable.schema.shape) {
720
+ return createTemplate(makeTemplate(["", ".", ""]), [
721
+ ident(name),
722
+ ident(prop)
723
+ ]);
724
+ }
725
+ return void 0;
726
+ },
727
+ has(_target, prop) {
728
+ return prop in baseTable.schema.shape;
729
+ },
730
+ ownKeys() {
731
+ return Object.keys(baseTable.schema.shape);
732
+ },
733
+ getOwnPropertyDescriptor(_target, prop) {
734
+ if (prop in baseTable.schema.shape) {
735
+ return { enumerable: true, configurable: true };
736
+ }
737
+ return void 0;
738
+ }
739
+ });
740
+ const primary = baseMeta.primary ? createTemplate(makeTemplate(["", ".", ""]), [
741
+ ident(name),
742
+ ident(baseMeta.primary)
743
+ ]) : null;
744
+ const viewMeta = {
745
+ baseTable,
746
+ whereTemplate
747
+ };
748
+ const tableMeta = {
749
+ ...baseMeta,
750
+ isView: true,
751
+ viewOf: baseTable.name,
752
+ derivedExprs: void 0,
753
+ derivedFields: void 0,
754
+ isDerived: void 0
755
+ };
756
+ const viewObj = {
757
+ name,
758
+ schema: baseTable.schema,
759
+ meta: tableMeta,
760
+ cols,
761
+ primary,
762
+ baseTable,
763
+ fields() {
764
+ return baseTable.fields();
765
+ },
766
+ references() {
767
+ return baseTable.references();
768
+ }
769
+ };
770
+ Object.defineProperty(viewObj, VIEW_MARKER, { value: true, enumerable: false });
771
+ Object.defineProperty(viewObj, VIEW_META, {
772
+ value: viewMeta,
773
+ enumerable: false
774
+ });
775
+ Object.defineProperty(viewObj, TABLE_MARKER, {
776
+ value: true,
777
+ enumerable: false
778
+ });
779
+ Object.defineProperty(viewObj, TABLE_META, {
780
+ value: tableMeta,
781
+ enumerable: false
782
+ });
783
+ return viewObj;
784
+ }
671
785
  function table(name, shape, options = {}) {
672
786
  validateIdentifier(name, "table");
673
787
  if (name.includes(".")) {
@@ -886,7 +1000,7 @@ function createTableObject(name, schema, zodShape, meta, options) {
886
1000
  `Table "${name}" does not have a soft delete field. Use softDelete() wrapper to mark a field.`
887
1001
  );
888
1002
  }
889
- return createTemplate(makeTemplate(["", ".", " IS NOT NULL"]), [
1003
+ return createTemplate(makeTemplate(["(", ".", " IS NOT NULL)"]), [
890
1004
  ident(name),
891
1005
  ident(softDeleteField)
892
1006
  ]);
@@ -1113,6 +1227,28 @@ function createTableObject(name, schema, zodShape, meta, options) {
1113
1227
  }
1114
1228
  }
1115
1229
  return createTemplate(makeTemplate(strings), templateValues);
1230
+ },
1231
+ get active() {
1232
+ const softDeleteField = meta.softDeleteField;
1233
+ if (!softDeleteField) {
1234
+ throw new Error(
1235
+ `Table "${name}" does not have a soft delete field. Use .db.softDelete() to mark a field for soft delete.`
1236
+ );
1237
+ }
1238
+ const whereTemplate = createTemplate(
1239
+ makeTemplate(["WHERE ", ".", " IS NULL"]),
1240
+ [ident(name), ident(softDeleteField)]
1241
+ );
1242
+ const activeViewName = `${name}_active`;
1243
+ const activeView = createViewObject(
1244
+ activeViewName,
1245
+ table2,
1246
+ whereTemplate
1247
+ );
1248
+ if (!internalMeta.activeView) {
1249
+ internalMeta.activeView = activeView;
1250
+ }
1251
+ return activeView;
1116
1252
  }
1117
1253
  };
1118
1254
  return table2;
@@ -1250,7 +1386,25 @@ function extractFieldMeta(name, zodType, dbMeta) {
1250
1386
  }
1251
1387
  return meta;
1252
1388
  }
1253
- function decodeData(table2, data) {
1389
+ function inferFieldType(schema) {
1390
+ let core = schema;
1391
+ while (typeof core.unwrap === "function") {
1392
+ if (core instanceof z.ZodArray || core instanceof z.ZodObject || core instanceof z.ZodDate || core instanceof z.ZodBoolean || core instanceof z.ZodNumber) {
1393
+ break;
1394
+ }
1395
+ core = core.unwrap();
1396
+ }
1397
+ if (core instanceof z.ZodDate)
1398
+ return "datetime";
1399
+ if (core instanceof z.ZodBoolean)
1400
+ return "boolean";
1401
+ if (core instanceof z.ZodObject || core instanceof z.ZodArray)
1402
+ return "json";
1403
+ if (core instanceof z.ZodNumber)
1404
+ return "real";
1405
+ return "text";
1406
+ }
1407
+ function decodeData(table2, data, driver) {
1254
1408
  if (!data)
1255
1409
  return data;
1256
1410
  const decoded = {};
@@ -1260,10 +1414,13 @@ function decodeData(table2, data) {
1260
1414
  const fieldSchema = shape?.[key];
1261
1415
  if (fieldMeta?.decode && typeof fieldMeta.decode === "function") {
1262
1416
  decoded[key] = fieldMeta.decode(value);
1417
+ } else if (driver?.decodeValue && fieldSchema) {
1418
+ const fieldType = inferFieldType(fieldSchema);
1419
+ decoded[key] = driver.decodeValue(value, fieldType);
1263
1420
  } else if (fieldSchema) {
1264
1421
  let core = fieldSchema;
1265
1422
  while (typeof core.unwrap === "function") {
1266
- if (core instanceof z.ZodArray || core instanceof z.ZodObject) {
1423
+ if (core instanceof z.ZodArray || core instanceof z.ZodObject || core instanceof z.ZodBoolean || core instanceof z.ZodDate) {
1267
1424
  break;
1268
1425
  }
1269
1426
  core = core.unwrap();
@@ -1280,9 +1437,20 @@ function decodeData(table2, data) {
1280
1437
  } else {
1281
1438
  decoded[key] = value;
1282
1439
  }
1440
+ } else if (core instanceof z.ZodBoolean) {
1441
+ if (typeof value === "number") {
1442
+ decoded[key] = value !== 0;
1443
+ } else if (typeof value === "string") {
1444
+ decoded[key] = value !== "0" && value !== "";
1445
+ } else if (typeof value === "boolean") {
1446
+ decoded[key] = value;
1447
+ } else {
1448
+ decoded[key] = value;
1449
+ }
1283
1450
  } else if (core instanceof z.ZodDate) {
1284
1451
  if (typeof value === "string") {
1285
- const date = new Date(value);
1452
+ const normalized = value.includes("T") ? value : value.replace(" ", "T") + "Z";
1453
+ const date = new Date(normalized);
1286
1454
  if (isNaN(date.getTime())) {
1287
1455
  throw new Error(
1288
1456
  `Invalid date value for field "${key}": "${value}" cannot be parsed as a valid date`
@@ -1341,6 +1509,10 @@ export {
1341
1509
  validateWithStandardSchema,
1342
1510
  extendZod,
1343
1511
  getTableMeta,
1512
+ isView,
1513
+ getViewMeta,
1514
+ view,
1344
1515
  table,
1516
+ inferFieldType,
1345
1517
  decodeData
1346
1518
  };
@@ -0,0 +1,11 @@
1
+ import {
2
+ generateColumnDDL,
3
+ generateDDL,
4
+ generateViewDDL
5
+ } from "./chunk-CHF7L5PC.js";
6
+ import "./chunk-XHXMCOSW.js";
7
+ export {
8
+ generateColumnDDL,
9
+ generateDDL,
10
+ generateViewDDL
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b9g/zen",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The simple database client. Define tables. Write SQL. Get objects.",
5
5
  "keywords": [
6
6
  "database",
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-2IEEEMRN.js";
6
+ } from "../chunk-W7JTNEM4.js";
7
7
  import {
8
- generateDDL
9
- } from "../chunk-QXGEP5PB.js";
8
+ generateDDL,
9
+ generateViewDDL
10
+ } from "../chunk-CHF7L5PC.js";
10
11
  import {
12
+ getTableMeta,
11
13
  resolveSQLBuiltin
12
- } from "../chunk-56M5Z3A6.js";
14
+ } from "../chunk-XHXMCOSW.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-NAJM37GQ.js");
745
+ const { generateColumnDDL } = await import("../ddl-2A2UFUR3.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(", ");