@lobomfz/db 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ bun add @lobomfz/db
13
13
  ```typescript
14
14
  import { Database, generated, type } from "@lobomfz/db";
15
15
 
16
- const db = new Database({
16
+ const db = await Database.open({
17
17
  path: "data.db",
18
18
  schema: {
19
19
  tables: {
@@ -68,7 +68,7 @@ type("number.integer").configure({ references: "users.id", onDelete: "cascade" }
68
68
  JSON columns are validated against the schema on write by default. To also validate on read:
69
69
 
70
70
  ```typescript
71
- new Database({
71
+ await Database.open({
72
72
  // ...
73
73
  validation: { onRead: true }, // default: { onRead: false }
74
74
  });
@@ -76,7 +76,7 @@ new Database({
76
76
 
77
77
  ## Migrations
78
78
 
79
- Schema changes are applied automatically on startup. Every time `new Database(...)` runs, the library compares your Arktype schema against the actual SQLite database and applies the minimum set of operations to bring them in sync. No migration files, no version tracking — the database itself is the source of truth.
79
+ Schema changes are applied automatically on startup. `Database.open(...)` is async because migrations run before it resolves: the library compares your Arktype schema against the actual SQLite database and applies the minimum set of operations to bring them in sync. No migration files, no version tracking — the database itself is the source of truth.
80
80
 
81
81
  ### What's supported
82
82
 
@@ -102,7 +102,58 @@ Table rebuilds follow SQLite's [recommended procedure](https://www.sqlite.org/la
102
102
  - Adding a NOT NULL column without DEFAULT to a table **with data** throws an error
103
103
  - Changing a nullable column to NOT NULL without DEFAULT throws if any row has NULL in that column
104
104
  - Nullable-to-NOT-NULL with DEFAULT uses `COALESCE` to fill existing NULLs
105
- - Column renames are treated as drop + add (data in the old column is not preserved)
105
+ - Without a guided migration, column renames are treated as drop + add (data in the old column is not preserved)
106
+
107
+ ### Refusing destructive changes
108
+
109
+ By default the diff applies whatever brings the database in sync, including drops. Set `failOnDataLoss` to turn dropping a table, dropping a column, or a data-discarding type change into a thrown `DataLossError` instead — the database is left untouched.
110
+
111
+ ```typescript
112
+ await Database.open({
113
+ // ...
114
+ failOnDataLoss: true,
115
+ });
116
+ ```
117
+
118
+ Configuring a `migration` (see below) routes through the guided path instead, where the drops are intrinsic to the restructure. The gate does not apply there; the drops are logged via `console.warn` so they are still visible.
119
+
120
+ ### Guided data migrations
121
+
122
+ The automatic diff can rename or restructure a table only by dropping the old shape. When you need to preserve and transform the data — rename a column, split one column into two, convert a type — pass a `migration` with a stable `id` and a `run` function. It receives the same typed Kysely client and runs inside the migration transaction.
123
+
124
+ ```typescript
125
+ await Database.open({
126
+ path: "data.db",
127
+ schema: {
128
+ tables: {
129
+ users: type({ id: generated("autoincrement"), name: "string", email: "string" }),
130
+ },
131
+ },
132
+ migration: {
133
+ id: "rename-user-fields",
134
+ run: async ({ db }) => {
135
+ const rows = await db
136
+ .selectFrom("users")
137
+ .select(["id", sql<string>`full_name`.as("full_name")])
138
+ .execute();
139
+
140
+ for (const row of rows) {
141
+ await db.updateTable("users").set({ name: row.full_name }).where("id", "=", row.id).execute();
142
+ }
143
+ },
144
+ },
145
+ });
146
+ ```
147
+
148
+ The migration runs as expand then contract, all in one transaction:
149
+
150
+ 1. New tables and new columns are added (new columns nullable, so the backfill can write them).
151
+ 2. Your `run` backfills and transforms the data, reading old columns with raw `sql` and writing the new ones through the typed client.
152
+ 3. The schema is reconciled to the target: leftover columns and tables are dropped (logged via `console.warn`) and `NOT NULL` is tightened.
153
+
154
+ `run` already executes inside the migration transaction, so do not open your own with `db.transaction(...)`. Your reads and writes are already atomic with the rest of the migration; if `run` throws, the whole transaction rolls back and the database is left exactly as it was. Referential integrity is checked with `PRAGMA foreign_key_check` before the commit, even when `foreign_keys` is disabled.
155
+
156
+ Each `id` runs at most once. The applied set lives in a `__migrations` table; a fresh database records the `id` as a baseline without running it (the schema is created directly). Editing the body of an already-applied migration throws `MigrationDriftError` — migrations are immutable once applied, so give new logic a fresh `id`.
106
157
 
107
158
  ## License
108
159
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobomfz/db",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Bun SQLite database with Arktype schemas and typed Kysely client",
5
5
  "keywords": [
6
6
  "arktype",
@@ -32,15 +32,18 @@
32
32
  },
33
33
  "dependencies": {},
34
34
  "devDependencies": {
35
- "@types/bun": "^1.3.11",
36
- "@typescript/native-preview": "^7.0.0-dev.20260324.1",
37
- "oxfmt": "^0.42.0",
38
- "oxlint": "^1.57.0"
35
+ "@types/bun": "^1.3.12",
36
+ "@typescript/native-preview": "^7.0.0-dev.20260417.1",
37
+ "oxfmt": "^0.45.0",
38
+ "oxlint": "^1.60.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@ark/schema": ">=0.56.0",
42
42
  "@ark/util": ">=0.56.0",
43
43
  "arktype": "^2.1.29",
44
44
  "kysely": "^0.28.14"
45
+ },
46
+ "optionalDependencies": {
47
+ "sqlite-vec": "^0.1.9"
45
48
  }
46
49
  }