web-sqlite-js 1.1.2 → 2.2.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,6 +13,9 @@
13
13
  <a href="https://www.npmjs.com/package/web-sqlite-js" target="_blank">
14
14
  <img src="https://img.shields.io/npm/v/web-sqlite-js.svg" alt="NPM Version" />
15
15
  </a>
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" />
18
+ </a>
16
19
  <a href="https://github.com/wuchuheng/web-sqlite-js/blob/main/LICENSE" target="_blank">
17
20
  <img src="https://img.shields.io/github/license/wuchuheng/web-sqlite-js.svg" alt="License" />
18
21
  </a>
@@ -33,8 +36,10 @@ Designed to be truly effortless, it allows you to get a high-performance relatio
33
36
  - [Quick start](#quick-start)
34
37
  - [Setup HTTP headers](#setup-http-headers)
35
38
  - [Usage](#usage)
36
- - [Debug mode](#debug-mode)
37
39
  - [Transactions](#transactions)
40
+ - [Schema Migrations](#schema-migrations)
41
+ - [Debug mode](#debug-mode)
42
+ - [Structured Logging](#structured-logging)
38
43
 
39
44
  ## Features
40
45
 
@@ -43,6 +48,8 @@ Designed to be truly effortless, it allows you to get a high-performance relatio
43
48
  - **Concurrency Safe**: Built-in mutex ensures safe, sequential execution of commands.
44
49
  - **Type-Safe**: Written in TypeScript with full type definitions.
45
50
  - **Transactions**: Supports atomic transactions with automatic rollback on error.
51
+ - **Schema Migrations**: Built-in versioning system for database schema changes.
52
+ - **Structured Logging**: Subscribe to SQL execution logs via `onLog()`.
46
53
 
47
54
  ## Quick start
48
55
 
@@ -66,8 +73,8 @@ For quick demos or plain HTML pages you can load the prebuilt module directly:
66
73
 
67
74
  ```html
68
75
  <script type="module">
69
- import openDB from "https://cdn.jsdelivr.net/npm/web-sqlite-js@1.0.9/dist/index.js";
70
- // ...
76
+ import openDB from "https://cdn.jsdelivr.net/npm/web-sqlite-js@1.0.9/dist/index.js";
77
+ // ...
71
78
  </script>
72
79
  ```
73
80
 
@@ -95,18 +102,18 @@ Update your `vite.config.ts`:
95
102
  import { defineConfig } from "vite";
96
103
 
97
104
  export default defineConfig({
98
- server: {
99
- headers: {
100
- "Cross-Origin-Opener-Policy": "same-origin",
101
- "Cross-Origin-Embedder-Policy": "require-corp",
102
- },
105
+ server: {
106
+ headers: {
107
+ "Cross-Origin-Opener-Policy": "same-origin",
108
+ "Cross-Origin-Embedder-Policy": "require-corp",
103
109
  },
104
- preview: {
105
- headers: {
106
- "Cross-Origin-Opener-Policy": "same-origin",
107
- "Cross-Origin-Embedder-Policy": "require-corp",
108
- },
110
+ },
111
+ preview: {
112
+ headers: {
113
+ "Cross-Origin-Opener-Policy": "same-origin",
114
+ "Cross-Origin-Embedder-Policy": "require-corp",
109
115
  },
116
+ },
110
117
  });
111
118
  ```
112
119
 
@@ -120,23 +127,23 @@ Update your `next.config.js`:
120
127
  ```javascript
121
128
  /** @type {import('next').NextConfig} */
122
129
  const nextConfig = {
123
- async headers() {
124
- return [
125
- {
126
- source: "/(.*)",
127
- headers: [
128
- {
129
- key: "Cross-Origin-Opener-Policy",
130
- value: "same-origin",
131
- },
132
- {
133
- key: "Cross-Origin-Embedder-Policy",
134
- value: "require-corp",
135
- },
136
- ],
137
- },
138
- ];
139
- },
130
+ async headers() {
131
+ return [
132
+ {
133
+ source: "/(.*)",
134
+ headers: [
135
+ {
136
+ key: "Cross-Origin-Opener-Policy",
137
+ value: "same-origin",
138
+ },
139
+ {
140
+ key: "Cross-Origin-Embedder-Policy",
141
+ value: "require-corp",
142
+ },
143
+ ],
144
+ },
145
+ ];
146
+ },
140
147
  };
141
148
 
142
149
  module.exports = nextConfig;
@@ -151,13 +158,13 @@ Update your `webpack.config.js`:
151
158
 
152
159
  ```javascript
153
160
  module.exports = {
154
- // ...
155
- devServer: {
156
- headers: {
157
- "Cross-Origin-Opener-Policy": "same-origin",
158
- "Cross-Origin-Embedder-Policy": "require-corp",
159
- },
161
+ // ...
162
+ devServer: {
163
+ headers: {
164
+ "Cross-Origin-Opener-Policy": "same-origin",
165
+ "Cross-Origin-Embedder-Policy": "require-corp",
160
166
  },
167
+ },
161
168
  };
162
169
  ```
163
170
 
@@ -189,9 +196,9 @@ const express = require("express");
189
196
  const app = express();
190
197
 
191
198
  app.use((req, res, next) => {
192
- res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
193
- res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
194
- next();
199
+ res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
200
+ res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
201
+ next();
195
202
  });
196
203
 
197
204
  // ...
@@ -227,12 +234,12 @@ await db.exec(`
227
234
 
228
235
  // 3. Insert data (Parameterized)
229
236
  await db.exec("INSERT INTO users (name, email) VALUES (?, ?)", [
230
- "Alice",
231
- "alice@example.com",
237
+ "Alice",
238
+ "alice@example.com",
232
239
  ]);
233
240
  await db.exec("INSERT INTO users (name, email) VALUES ($name, $email)", {
234
- $name: "Bob",
235
- $email: "bob@example.com",
241
+ $name: "Bob",
242
+ $email: "bob@example.com",
236
243
  });
237
244
 
238
245
  // 4. Query data
@@ -266,13 +273,110 @@ Transactions are atomic. If any command inside the callback fails, the entire tr
266
273
 
267
274
  ```typescript
268
275
  await db.transaction(async (tx) => {
269
- await tx.exec("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
276
+ await tx.exec("INSERT INTO users (name) VALUES (?)", ["Charlie"]);
277
+
278
+ // You can perform multiple operations safely
279
+ await tx.exec("INSERT INTO logs (action) VALUES (?)", ["User Created"]);
280
+
281
+ // If you throw an error here, both INSERTs will be rolled back!
282
+ // throw new Error('Something went wrong');
283
+ });
284
+ ```
285
+
286
+ ## Schema Migrations
270
287
 
271
- // You can perform multiple operations safely
272
- await tx.exec("INSERT INTO logs (action) VALUES (?)", ["User Created"]);
288
+ Manage database schema changes across releases using the built-in versioning system. Define releases with migration SQL and optional seed data.
273
289
 
274
- // If you throw an error here, both INSERTs will be rolled back!
275
- // throw new Error('Something went wrong');
290
+ ### Basic Usage
291
+
292
+ ```typescript
293
+ const db = await openDB("myapp", {
294
+ releases: [
295
+ {
296
+ version: "1.0.0",
297
+ migrationSQL: `
298
+ CREATE TABLE users (
299
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
300
+ name TEXT NOT NULL,
301
+ email TEXT UNIQUE
302
+ );
303
+ `,
304
+ seedSQL: `
305
+ INSERT INTO users (name, email) VALUES
306
+ ('Alice', 'alice@example.com'),
307
+ ('Bob', 'bob@example.com');
308
+ `,
309
+ },
310
+ {
311
+ version: "1.1.0",
312
+ migrationSQL: `
313
+ ALTER TABLE users ADD COLUMN created_at TEXT DEFAULT (datetime('now'));
314
+ `,
315
+ },
316
+ ],
317
+ });
318
+
319
+ // Database is now at version 1.1.0 with all migrations applied
320
+ const users = await db.query("SELECT * FROM users");
321
+ console.log(users);
322
+ // Output: [{ id: 1, name: 'Alice', email: 'alice@example.com', created_at: '...' }, ...]
323
+ ```
324
+
325
+ ### How It Works
326
+
327
+ 1. **Version Tracking**: Each release has a semantic version (e.g., "1.0.0")
328
+ 2. **Automatic Migration**: When opening a database, new releases are applied in order
329
+ 3. **Hash Verification**: Migration SQL is hashed to prevent tampering
330
+ 4. **OPFS Storage**: Each version is stored as a separate file (`1.0.0.sqlite3`, `1.1.0.sqlite3`)
331
+
332
+ ### Best Practices
333
+
334
+ - **Use Semantic Versioning**: Follow `MAJOR.MINOR.PATCH` format
335
+ - **Idempotent Migrations**: Each migration should handle re-runs safely
336
+ - **Test Migrations**: Always test migrations on a clean database
337
+ - **Incremental Changes**: Keep migrations focused on single schema changes
338
+
339
+ ## Structured Logging
340
+
341
+ Subscribe to structured log events for monitoring, debugging, and analytics. The `onLog()` API allows you to capture SQL execution details, errors, and application events.
342
+
343
+ ```typescript
344
+ const db = await openDB("myapp");
345
+
346
+ // Register log listener
347
+ const cancelLog = db.onLog((log) => {
348
+ if (log.level === "error") {
349
+ // Send errors to tracking service
350
+ errorTracking.capture(log.data);
351
+ } else if (log.level === "debug") {
352
+ // Log SQL execution details
353
+ console.log(`SQL: ${log.data.sql}, Duration: ${log.data.duration}ms`);
354
+ } else if (log.level === "info") {
355
+ // Track application events (open, close, transactions)
356
+ console.log(`Event: ${log.data.action}`);
357
+ }
358
+ });
359
+
360
+ // Execute some SQL to generate logs
361
+ await db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
362
+ await db.exec("INSERT INTO users (name) VALUES (?)", ["Alice"]);
363
+
364
+ // Later: stop listening
365
+ cancelLog();
366
+ ```
367
+
368
+ **Log Levels**:
369
+
370
+ - `"debug"` - SQL execution details (sql, duration, bind parameters)
371
+ - `"info"` - Application events (open, close, commit, rollback)
372
+ - `"error"` - SQL errors and exceptions
373
+
374
+ **Multiple Callbacks**: You can register multiple log listeners simultaneously:
375
+
376
+ ```typescript
377
+ const cancel1 = db.onLog((log) => console.log("Logger 1:", log));
378
+ const cancel2 = db.onLog((log) => {
379
+ if (log.level === "error") sendToAlerting(log.data);
276
380
  });
277
381
  ```
278
382
 
package/dist/index.d.ts CHANGED
@@ -21,6 +21,20 @@ declare interface DBInterface {
21
21
  transaction<T>(fn: transactionCallback<T>): Promise<T>;
22
22
  /** Close the database and release resources. */
23
23
  close(): Promise<void>;
24
+ /**
25
+ * Subscribe to log events
26
+ * Logs include SQL execution, timing, errors, and application events
27
+ *
28
+ * @param callback - Called for each log entry
29
+ * @returns Unsubscribe function
30
+ *
31
+ * @example
32
+ * const unsubscribe = db.onLog((log) => {
33
+ * console.log(`[${log.level}]`, log.data);
34
+ * });
35
+ * // Later: unsubscribe();
36
+ */
37
+ onLog(callback: (log: LogEntry) => void): () => void;
24
38
  /** Dev tooling APIs for release testing. */
25
39
  devTool: DevTool;
26
40
  }
@@ -46,6 +60,20 @@ declare type ExecResult = {
46
60
  lastInsertRowid?: number | bigint;
47
61
  };
48
62
 
63
+ /**
64
+ * Log entry with level and structured data
65
+ */
66
+ declare type LogEntry = {
67
+ /**
68
+ * Log level: 'info' | 'debug' | 'error'
69
+ */
70
+ level: "info" | "debug" | "error";
71
+ /**
72
+ * Log data (SQL, timing, errors, events, etc.)
73
+ */
74
+ data: unknown;
75
+ };
76
+
49
77
  /**
50
78
  * Opens a SQLite database connection with release-versioning support.
51
79
  *