web-sqlite-js 2.1.0 → 2.3.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
@@ -14,7 +14,7 @@
14
14
  <img src="https://img.shields.io/npm/v/web-sqlite-js.svg" alt="NPM Version" />
15
15
  </a>
16
16
  <a href="https://github.com/wuchuheng/web-sqlite-js/discussions" target="_blank">
17
- <img src="https://img.shields.io/badge/v2.0.0-new%20features-blue" alt="v2.0.0" />
17
+ <img src="https://img.shields.io/badge/v2.2.0-two--tier%20validation-brightgreen" alt="v2.2.0" />
18
18
  </a>
19
19
  <a href="https://github.com/wuchuheng/web-sqlite-js/blob/main/LICENSE" target="_blank">
20
20
  <img src="https://img.shields.io/github/license/wuchuheng/web-sqlite-js.svg" alt="License" />
@@ -36,11 +36,10 @@ Designed to be truly effortless, it allows you to get a high-performance relatio
36
36
  - [Quick start](#quick-start)
37
37
  - [Setup HTTP headers](#setup-http-headers)
38
38
  - [Usage](#usage)
39
- - [Debug mode](#debug-mode)
40
39
  - [Transactions](#transactions)
41
- - [Structured Logging (v2.0.0)](#structured-logging-v200)
42
- - [Global Database Access (v2.0.0)](#global-database-access-v200)
43
- - [Database Events (v2.0.0)](#database-events-v200)
40
+ - [Schema Migrations](#schema-migrations)
41
+ - [Debug mode](#debug-mode)
42
+ - [Structured Logging](#structured-logging)
44
43
 
45
44
  ## Features
46
45
 
@@ -49,10 +48,10 @@ Designed to be truly effortless, it allows you to get a high-performance relatio
49
48
  - **Concurrency Safe**: Built-in mutex ensures safe, sequential execution of commands.
50
49
  - **Type-Safe**: Written in TypeScript with full type definitions.
51
50
  - **Transactions**: Supports atomic transactions with automatic rollback on error.
52
- - **Structured Logging** (v2.0.0): Subscribe to SQL execution logs via `onLog()`.
53
- - **Global Namespace** (v2.0.0): Access databases from anywhere via `window.__web_sqlite`.
54
- - **Database Events** (v2.0.0): Listen to database open/close events for UI synchronization.
55
- - **Database Registry** (v2.0.0): Prevents duplicate database opens with automatic tracking.
51
+ - **Schema Migrations**: Built-in versioning system for database schema changes.
52
+ - **Two-Tier Validation** (v2.2.0): Distinguishes between whitespace-only changes and actual SQL changes.
53
+ - **Enhanced Error Messages** (v2.2.0): SQL diffs and truncation for better debugging.
54
+ - **Structured Logging**: Subscribe to SQL execution logs via `onLog()`.
56
55
 
57
56
  ## Quick start
58
57
 
@@ -76,7 +75,7 @@ For quick demos or plain HTML pages you can load the prebuilt module directly:
76
75
 
77
76
  ```html
78
77
  <script type="module">
79
- import openDB from "https://cdn.jsdelivr.net/npm/web-sqlite-js@1.0.9/dist/index.js";
78
+ import openDB from "https://cdn.jsdelivr.net/npm/web-sqlite-js@2.2.0/dist/index.js";
80
79
  // ...
81
80
  </script>
82
81
  ```
@@ -286,7 +285,120 @@ await db.transaction(async (tx) => {
286
285
  });
287
286
  ```
288
287
 
289
- ## Structured Logging (v2.0.0)
288
+ ## Schema Migrations
289
+
290
+ Manage database schema changes across releases using the built-in versioning system. Define releases with migration SQL and optional seed data.
291
+
292
+ ### Basic Usage
293
+
294
+ ```typescript
295
+ const db = await openDB("myapp", {
296
+ releases: [
297
+ {
298
+ version: "1.0.0",
299
+ migrationSQL: `
300
+ CREATE TABLE users (
301
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
302
+ name TEXT NOT NULL,
303
+ email TEXT UNIQUE
304
+ );
305
+ `,
306
+ seedSQL: `
307
+ INSERT INTO users (name, email) VALUES
308
+ ('Alice', 'alice@example.com'),
309
+ ('Bob', 'bob@example.com');
310
+ `,
311
+ },
312
+ {
313
+ version: "1.1.0",
314
+ migrationSQL: `
315
+ ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT (datetime('now'));
316
+ `,
317
+ },
318
+ ],
319
+ });
320
+
321
+ // Database is now at version 1.1.0 with all migrations applied
322
+ const users = await db.query("SELECT * FROM users");
323
+ console.log(users);
324
+ // Output: [{ id: 1, name: 'Alice', email: 'alice@example.com', created_at: '...' }, ...]
325
+ ```
326
+
327
+ ### Two-Tier Validation (v2.2.0)
328
+
329
+ Starting with v2.2.0, web-sqlite-js uses a two-tier validation system that intelligently handles SQL formatting changes:
330
+
331
+ **Tier 1: Fast Path** (< 0.1ms)
332
+
333
+ - Trims whitespace and compares hashes
334
+ - Passes immediately if SQL hasn't changed
335
+
336
+ **Tier 2: Slow Path** (1-5ms, only on hash mismatch)
337
+
338
+ - Normalizes SQL using SQLite prepare
339
+ - Auto-updates hash for whitespace-only changes
340
+ - Throws enhanced error for actual SQL changes
341
+
342
+ **What this means for you:**
343
+
344
+ ```typescript
345
+ // You can reformat your SQL without breaking migrations!
346
+ const db1 = await openDB("myapp", {
347
+ releases: [
348
+ {
349
+ version: "1.0.0",
350
+ migrationSQL: "CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT);",
351
+ },
352
+ ],
353
+ });
354
+ await db1.close();
355
+
356
+ // Reopen with reformatted SQL (whitespace-only change)
357
+ const db2 = await openDB("myapp", {
358
+ releases: [
359
+ {
360
+ version: "1.0.0",
361
+ migrationSQL: `
362
+ CREATE TABLE items (
363
+ id INTEGER PRIMARY KEY,
364
+ name TEXT
365
+ );
366
+ `,
367
+ },
368
+ ],
369
+ });
370
+ // ✅ Works! Hash auto-updated (no error)
371
+ ```
372
+
373
+ **Enhanced Error Messages:**
374
+
375
+ If you accidentally change the SQL semantics, you'll get detailed error messages:
376
+
377
+ ```
378
+ Hash mismatch for 1.0.0 migrationSQL:
379
+ Expected: abc123...
380
+ Actual: def456...
381
+ SQL has changed:
382
+ - Original: CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT);
383
+ + Current: CREATE TABLE items (id INTEGER PRIMARY KEY, email TEXT);
384
+ ```
385
+
386
+ ### How It Works
387
+
388
+ 1. **Version Tracking**: Each release has a semantic version (e.g., "1.0.0")
389
+ 2. **Automatic Migration**: When opening a database, new releases are applied in order
390
+ 3. **Hash Verification**: Migration SQL is hashed to prevent tampering
391
+ 4. **Two-Tier Validation**: Distinguishes whitespace-only changes from actual SQL changes
392
+ 5. **OPFS Storage**: Each version is stored as a separate file (`1.0.0.sqlite3`, `1.1.0.sqlite3`)
393
+
394
+ ### Best Practices
395
+
396
+ - **Use Semantic Versioning**: Follow `MAJOR.MINOR.PATCH` format
397
+ - **Idempotent Migrations**: Each migration should handle re-runs safely
398
+ - **Test Migrations**: Always test migrations on a clean database
399
+ - **Incremental Changes**: Keep migrations focused on single schema changes
400
+
401
+ ## Structured Logging
290
402
 
291
403
  Subscribe to structured log events for monitoring, debugging, and analytics. The `onLog()` API allows you to capture SQL execution details, errors, and application events.
292
404
 
@@ -330,97 +442,6 @@ const cancel2 = db.onLog((log) => {
330
442
  });
331
443
  ```
332
444
 
333
- ---
334
-
335
- ## Global Database Access (v2.0.0)
336
-
337
- Access opened databases from anywhere in your application without imports. The `window.__web_sqlite` global namespace provides direct references to all opened database instances.
338
-
339
- ```typescript
340
- // Open database in module A
341
- const db = await openDB("app");
342
-
343
- // In module B (no import needed):
344
- const db = window.__web_sqlite.databases["app.sqlite3"];
345
- const users = await db.query("SELECT * FROM users");
346
-
347
- // List all opened databases
348
- console.log(Object.keys(window.__web_sqlite.databases));
349
- // Output: ["app.sqlite3", "users.sqlite3"]
350
- ```
351
-
352
- **Use Cases**:
353
-
354
- - **DevTools Integration**: Access databases from browser console for debugging
355
- - **Cross-Module Communication**: Share database state without prop drilling
356
- - **Debugging**: Inspect and query databases directly from DevTools console
357
-
358
- **Browser Console Example**:
359
-
360
- ```javascript
361
- // From browser DevTools console:
362
- window.__web_sqlite.databases["app.sqlite3"]
363
- .query("SELECT * FROM users")
364
- .then((users) => console.table(users));
365
- ```
366
-
367
- ---
368
-
369
- ## Database Events (v2.0.0)
370
-
371
- Subscribe to database open/close events for UI synchronization and monitoring. The `onDatabaseChange()` API notifies you when databases are opened or closed.
372
-
373
- ```typescript
374
- // Subscribe to database changes
375
- const unsubscribe = window.__web_sqlite.onDatabaseChange((event) => {
376
- if (event.action === "opened") {
377
- console.log(`Database opened: ${event.dbName}`);
378
- updateDatabaseList(event.databases);
379
- } else if (event.action === "closed") {
380
- console.log(`Database closed: ${event.dbName}`);
381
- updateDatabaseList(event.databases);
382
- }
383
- console.log("Current databases:", event.databases);
384
- });
385
-
386
- // Open a database
387
- await openDB("app");
388
- // Output: Database opened: app.sqlite3
389
- // Output: Current databases: ["app.sqlite3"]
390
-
391
- // Open another database
392
- await openDB("users");
393
- // Output: Database opened: users.sqlite3
394
- // Output: Current databases: ["app.sqlite3", "users.sqlite3"]
395
-
396
- // Close first database
397
- await window.__web_sqlite.databases["app.sqlite3"].close();
398
- // Output: Database closed: app.sqlite3
399
- // Output: Current databases: ["users.sqlite3"]
400
-
401
- // Unsubscribe when done
402
- // unsubscribe();
403
- ```
404
-
405
- **Event Structure**:
406
-
407
- ```typescript
408
- interface DatabaseChangeEvent {
409
- action: "opened" | "closed"; // What happened
410
- dbName: string; // Which database (normalized name)
411
- databases: string[]; // All currently opened database names
412
- }
413
- ```
414
-
415
- **Use Cases**:
416
-
417
- - **DevTools Panels**: Show active databases in browser DevTools
418
- - **UI Updates**: Refresh database list when databases open/close
419
- - **Monitoring**: Track database lifecycle for debugging
420
- - **Multi-Window Sync**: Coordinate database access across browser windows
421
-
422
- ---
423
-
424
445
  ## Star History
425
446
 
426
447
  <p align="left">
package/dist/index.d.ts CHANGED
@@ -3,45 +3,96 @@ declare interface DBInterface {
3
3
  /**
4
4
  * Execute a SQL script (one or more statements) without returning rows.
5
5
  * Intended for migrations, schema setup, or bulk SQL execution.
6
+ *
6
7
  * @param sql - SQL string to execute.
7
8
  * @param params - Optional bind parameters for the statement.
9
+ * @returns Result metadata (changes, lastInsertRowid).
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * await db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
14
+ * await db.exec("INSERT INTO users (name) VALUES (?)", ["Alice"]);
15
+ * ```
8
16
  */
9
17
  exec(sql: string, params?: SQLParams): Promise<ExecResult>;
10
18
  /**
11
19
  * Execute a query and return all result rows as an array of objects.
20
+ *
12
21
  * @param sql - SELECT SQL to execute.
13
22
  * @param params - Optional bind parameters for the query.
23
+ * @returns Array of result rows.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * const users = await db.query<{ id: number; name: string }>(
28
+ * "SELECT id, name FROM users WHERE id = ?",
29
+ * [1]
30
+ * );
31
+ * ```
14
32
  */
15
33
  query<T = unknown>(sql: string, params?: SQLParams): Promise<T[]>;
16
34
  /**
17
- * Run a callback inside a transaction. The implementation should BEGIN before calling `fn`
18
- * and COMMIT on success or ROLLBACK on error.
19
- * @param fn - Callback that receives a DBInterface and performs transactional work.
35
+ * Execute a transaction with automatic rollback on error.
36
+ *
37
+ * @param fn - Transaction callback receiving transaction interface.
38
+ * @returns Result of the transaction callback.
39
+ *
40
+ * @example
41
+ * ```ts
42
+ * await db.transaction(async (tx) => {
43
+ * await tx.exec("INSERT INTO users (name) VALUES (?)", ["Bob"]);
44
+ * await tx.exec("INSERT INTO posts (title) VALUES (?)", ["Hello"]);
45
+ * });
46
+ * ```
20
47
  */
21
48
  transaction<T>(fn: transactionCallback<T>): Promise<T>;
22
- /** Close the database and release resources. */
49
+ /**
50
+ * Close the database and release worker resources.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * await db.close();
55
+ * ```
56
+ */
23
57
  close(): Promise<void>;
24
58
  /**
25
- * Subscribe to log events
26
- * Logs include SQL execution, timing, errors, and application events
59
+ * Register a callback for database logs (worker SQL execution logs).
27
60
  *
28
- * @param callback - Called for each log entry
29
- * @returns Unsubscribe function
61
+ * @param callback - Function to receive log entries.
62
+ * @returns Unregister function.
30
63
  *
31
64
  * @example
32
- * const unsubscribe = db.onLog((log) => {
33
- * console.log(`[${log.level}]`, log.data);
65
+ * ```ts
66
+ * const unregister = db.onLog((log) => {
67
+ * console.log(`[${log.level}]`, log.data);
34
68
  * });
35
- * // Later: unsubscribe();
69
+ * // Later: unregister();
70
+ * ```
36
71
  */
37
72
  onLog(callback: (log: LogEntry) => void): () => void;
38
- /** Dev tooling APIs for release testing. */
73
+ /**
74
+ * Dev tooling for creating and managing dev versions.
75
+ */
39
76
  devTool: DevTool;
40
77
  }
41
78
 
79
+ /**
80
+ * Dev tooling interface for creating and rolling back dev versions.
81
+ */
42
82
  declare type DevTool = {
43
83
  /**
44
- * Create a new dev version using migration and seed SQL.
84
+ * Create a new dev version with migration and seed SQL.
85
+ *
86
+ * @param input - Release config with version, migration SQL, and optional seed SQL.
87
+ *
88
+ * @example
89
+ * ```ts
90
+ * await db.devTool.release({
91
+ * version: "1.0.1",
92
+ * migrationSQL: "ALTER TABLE users ADD COLUMN email TEXT",
93
+ * seedSQL: "UPDATE users SET email = 'test@example.com' WHERE email IS NULL",
94
+ * });
95
+ * ```
45
96
  */
46
97
  release(input: ReleaseConfig): Promise<void>;
47
98
  /**
@@ -118,6 +169,25 @@ declare type SQLParams = SqlValue[] | Record<string, SqlValue>;
118
169
  */
119
170
  declare type SqlValue = null | number | string | boolean | bigint | Uint8Array | ArrayBuffer;
120
171
 
121
- declare type transactionCallback<T> = (db: Pick<DBInterface, "exec" | "query">) => Promise<T>;
172
+ /**
173
+ * Transaction interface passed to transaction callbacks.
174
+ * All operations execute within the same transaction.
175
+ */
176
+ declare interface Transaction {
177
+ /**
178
+ * Execute a SQL statement within the transaction.
179
+ */
180
+ exec(sql: string, params?: SQLParams): Promise<ExecResult>;
181
+ /**
182
+ * Execute a query within the transaction.
183
+ */
184
+ query<T = unknown>(sql: string, params?: SQLParams): Promise<T[]>;
185
+ }
186
+
187
+ /**
188
+ * Transaction callback interface.
189
+ * Provides exec and query methods scoped to the transaction.
190
+ */
191
+ declare type transactionCallback<T> = (tx: Transaction) => Promise<T>;
122
192
 
123
193
  export { }