@procedures/sqlite 0.2.3 → 0.4.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +147 -17
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/index.d.ts +5 -3
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +8 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/module.d.ts +13 -2
  9. package/dist/module.d.ts.map +1 -1
  10. package/dist/module.js +135 -18
  11. package/dist/module.js.map +1 -1
  12. package/dist/procedures/db/backup.d.ts +19 -0
  13. package/dist/procedures/db/backup.d.ts.map +1 -0
  14. package/dist/procedures/db/backup.js +61 -0
  15. package/dist/procedures/db/backup.js.map +1 -0
  16. package/dist/procedures/db/index.d.ts +6 -0
  17. package/dist/procedures/db/index.d.ts.map +1 -0
  18. package/dist/procedures/db/index.js +6 -0
  19. package/dist/procedures/db/index.js.map +1 -0
  20. package/dist/procedures/db/migrate.d.ts +24 -0
  21. package/dist/procedures/db/migrate.d.ts.map +1 -0
  22. package/dist/procedures/db/migrate.js +110 -0
  23. package/dist/procedures/db/migrate.js.map +1 -0
  24. package/dist/procedures/db/schema.d.ts +13 -0
  25. package/dist/procedures/db/schema.d.ts.map +1 -0
  26. package/dist/procedures/db/schema.js +33 -0
  27. package/dist/procedures/db/schema.js.map +1 -0
  28. package/dist/procedures/db/shared.d.ts +25 -0
  29. package/dist/procedures/db/shared.d.ts.map +1 -0
  30. package/dist/procedures/db/shared.js +105 -0
  31. package/dist/procedures/db/shared.js.map +1 -0
  32. package/dist/procedures/db/tables.d.ts +12 -0
  33. package/dist/procedures/db/tables.d.ts.map +1 -0
  34. package/dist/procedures/db/tables.js +22 -0
  35. package/dist/procedures/db/tables.js.map +1 -0
  36. package/dist/procedures/db/transaction.d.ts +14 -0
  37. package/dist/procedures/db/transaction.d.ts.map +1 -0
  38. package/dist/procedures/db/transaction.js +44 -0
  39. package/dist/procedures/db/transaction.js.map +1 -0
  40. package/dist/storage.d.ts +123 -0
  41. package/dist/storage.d.ts.map +1 -0
  42. package/dist/storage.js +525 -0
  43. package/dist/storage.js.map +1 -0
  44. package/dist/types.d.ts +131 -0
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js +109 -0
  47. package/dist/types.js.map +1 -1
  48. package/package.json +9 -3
  49. package/src/index.ts +28 -2
  50. package/src/module.ts +167 -19
  51. package/src/procedures/db/backup.ts +62 -0
  52. package/src/procedures/db/index.ts +5 -0
  53. package/src/procedures/db/migrate.ts +121 -0
  54. package/src/procedures/db/schema.ts +71 -0
  55. package/src/procedures/db/shared.ts +109 -0
  56. package/src/procedures/db/tables.ts +27 -0
  57. package/src/procedures/db/transaction.ts +43 -0
  58. package/src/storage.ts +591 -0
  59. package/src/types.ts +270 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # @procedures/sqlite
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cc3f5f6: The SQLite procedures have the namespace `sqlite`, not the general namespace `db`: `sqlite.query`, `sqlite.execute`, `sqlite.tables`, `sqlite.schema`, `sqlite.transaction`, `sqlite.migrate` and `sqlite.backup`. The `logs.*` procedures keep their names.
8
+
9
+ The old names stay for one release as deprecated aliases (`db.query` and the others). Each alias has the same handler, the same schemas and the same effects. Its description starts with `Deprecated: use sqlite.<name>`, and its metadata has `deprecated: true`. The next release removes the aliases.
10
+
11
+ The description of the `dbPath` field does not show the home folder of the computer any more. It says `~/logs/cli/cli.db`.
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [d919678]
16
+ - Updated dependencies [057d69a]
17
+ - Updated dependencies [b2a1a1e]
18
+ - Updated dependencies [057d69a]
19
+ - Updated dependencies [057d69a]
20
+ - Updated dependencies [057d69a]
21
+ - Updated dependencies [057d69a]
22
+ - Updated dependencies [057d69a]
23
+ - Updated dependencies [afd6a19]
24
+ - Updated dependencies [057d69a]
25
+ - Updated dependencies [4e751da]
26
+ - @procedures/client@0.4.0
27
+
28
+ ## 0.3.0
29
+
30
+ ### Minor Changes
31
+
32
+ - 89d8a4a: Three procedures only read now (CAP-1, CAP-2, CAP-3, roadmap R7), so a read-only session (`mark mcp --read-only`) serves them. `db.query` refuses a statement that changes data (`PRAGMA query_only`), and the file keeps its bytes. `mongo.documents.aggregate` refuses `$out` and `$merge`, also in the pipelines of `$facet`, `$lookup` and `$unionWith`. `logs.query` gives no entries for a missing file or table, and does not make them.
33
+ - 94dd2a0: Five database procedures (roadmap R44). `db.tables` lists the tables and views, and `db.schema` gives the columns, indexes and foreign keys of a table. Both only read, and a missing file is an error. `db.query` stays read only.
34
+
35
+ `db.transaction` puts a list of statements in one transaction, and rolls it back when one fails. `db.migrate` applies numbered migrations from a folder or a list, in one transaction, with a table of the applied migrations and their sha256. A second call applies nothing. `db.backup` copies a database to a new file and checks the copy. It does not replace a file. With `vacuum: true`, it compacts the copy.
36
+ - 4ef0d44: The new collection storage `SqliteStorage` (roadmap R45) keeps the items of one collection in one table of a SQLite database: a file, or `:memory:`. It implements `CollectionStorage`, so a `ProcedureServer`, an `ApiStorage` server and a `HybridStorage` use it as they use `InMemoryStorage`. The new entry `@procedures/sqlite/storage` has it alone, and the package root exports it too.
37
+
38
+ The table has the columns `id` and `value` (the JSON text of the item). The name of the table starts with a letter and has only letters, digits and underscores. `getChangesSince()` reads a change log in the tables `_storage_log` and `_storage_versions`, with versions `<epoch>:<seq>` as `InMemoryStorage` has. The epoch stays in the file, so a version stays valid after a restart. The option `maxTombstones` limits the deletes that the log keeps.
39
+
40
+ Each write runs in a transaction, so a batch that fails changes nothing. The storage keeps the database in memory and writes the file after each write. One file write can hold the changes of several writes. The storages of one file in one process share one database. A storage that only reads does not make the file.
41
+
42
+ ### Patch Changes
43
+
44
+ - Updated dependencies [ddea529]
45
+ - Updated dependencies [b2a3e3a]
46
+ - Updated dependencies [a8c47f1]
47
+ - Updated dependencies [381ce3a]
48
+ - Updated dependencies [0c627f8]
49
+ - Updated dependencies [ae7f05f]
50
+ - Updated dependencies [7873940]
51
+ - Updated dependencies [a70d445]
52
+ - Updated dependencies [bc70357]
53
+ - Updated dependencies [07cc998]
54
+ - Updated dependencies [a6b01a9]
55
+ - Updated dependencies [c799cdd]
56
+ - Updated dependencies [540f8d9]
57
+ - Updated dependencies [b6daaec]
58
+ - Updated dependencies [5b9264e]
59
+ - Updated dependencies [956e88b]
60
+ - Updated dependencies [ff62281]
61
+ - Updated dependencies [d81d0ed]
62
+ - Updated dependencies [aa67cc7]
63
+ - Updated dependencies [061fa22]
64
+ - Updated dependencies [cfb9d69]
65
+ - Updated dependencies [43f7de5]
66
+ - Updated dependencies [b07bb50]
67
+ - Updated dependencies [4a3bfa1]
68
+ - Updated dependencies [bd0b626]
69
+ - Updated dependencies [a6be873]
70
+ - Updated dependencies [fb30718]
71
+ - Updated dependencies [c5528e4]
72
+ - Updated dependencies [fee5d9c]
73
+ - Updated dependencies [21f7743]
74
+ - Updated dependencies [31a0b46]
75
+ - @procedures/client@0.3.0
76
+
3
77
  ## 0.2.3
4
78
 
5
79
  ### Patch Changes
package/README.md CHANGED
@@ -15,11 +15,20 @@ SQLite database operations via client procedures. Execute queries and store stru
15
15
  - **Logging procedures**: Store and query structured application logs
16
16
  - **Migration support**: Schema versioning and migration utilities
17
17
  - **Default log database**: Pre-configured logging at `~/logs/cli/cli.db`
18
+ - **A collection storage**: `SqliteStorage` keeps a collection in a SQLite table, with a change log
18
19
 
19
- The package has 4 procedures. Its register entry (`import "@procedures/sqlite/register"`) registers them:
20
- - **Database**: `db.query`, `db.execute`
20
+ The package has 9 procedures. Its register entry (`import "@procedures/sqlite/register"`) registers them:
21
+ - **Database**: `sqlite.query`, `sqlite.execute`, `sqlite.tables`, `sqlite.schema`, `sqlite.transaction`, `sqlite.migrate`, `sqlite.backup`
21
22
  - **Logs**: `logs.store`, `logs.query`
22
23
 
24
+ ### The old names `db.*`
25
+
26
+ In version 0.3.0 and before, the database procedures had the namespace `db`. The general name `db` did not tell the database. Since version 0.4.0, the namespace is `sqlite`.
27
+
28
+ The old names stay for one release as deprecated aliases: `db.query`, `db.execute`, `db.tables`, `db.schema`, `db.transaction`, `db.migrate` and `db.backup`. Each alias has the same handler, the same schemas and the same effects as its new procedure. Its description starts with `Deprecated: use sqlite.<name>`. The next release removes the aliases.
29
+
30
+ The MCP server (`bundle-mcp`) does not expose the aliases. Its tools are `sqlite.query`, `sqlite.execute`, `sqlite.tables` and `sqlite.schema`. The `logs.*` procedures keep their names.
31
+
23
32
  ## Architecture
24
33
 
25
34
  ```mermaid
@@ -33,7 +42,7 @@ graph TB
33
42
  Register["register.ts<br/>(Auto-registers procedures)"]
34
43
 
35
44
  subgraph "Procedure Categories"
36
- DB["Database Procedures<br/>db.query, db.execute"]
45
+ DB["Database Procedures<br/>sqlite.query, sqlite.execute, sqlite.tables, sqlite.schema,<br/>sqlite.transaction, sqlite.migrate, sqlite.backup"]
37
46
  Logs["Log Procedures<br/>logs.store, logs.query"]
38
47
  end
39
48
 
@@ -147,7 +156,7 @@ You can override this path in each procedure call:
147
156
 
148
157
  ```typescript
149
158
  await client.exec(
150
- ["db", "query"],
159
+ ["sqlite", "query"],
151
160
  {
152
161
  sql: "SELECT * FROM logs",
153
162
  dbPath: "./custom/path/database.db"
@@ -159,13 +168,13 @@ await client.exec(
159
168
 
160
169
  ### Database Procedures
161
170
 
162
- #### `db.query`
171
+ #### `sqlite.query`
163
172
 
164
173
  Execute a SQL SELECT query and return rows.
165
174
 
166
175
  ```typescript
167
176
  const result = await client.exec(
168
- ["db", "query"],
177
+ ["sqlite", "query"],
169
178
  {
170
179
  sql: "SELECT * FROM logs WHERE level = ? ORDER BY timestamp DESC",
171
180
  params: ["error"]
@@ -196,7 +205,7 @@ interface DbQueryOutput<T = Record<string, unknown>> {
196
205
  **Example:**
197
206
  ```typescript
198
207
  const result = await client.exec(
199
- ["db", "query"],
208
+ ["sqlite", "query"],
200
209
  {
201
210
  sql: `
202
211
  SELECT level, COUNT(*) as count
@@ -211,13 +220,13 @@ const result = await client.exec(
211
220
  // result.rows = [{ level: "info", count: 45 }, { level: "error", count: 2 }]
212
221
  ```
213
222
 
214
- #### `db.execute`
223
+ #### `sqlite.execute`
215
224
 
216
225
  Execute a SQL statement (INSERT, UPDATE, DELETE) and return the number of affected rows.
217
226
 
218
227
  ```typescript
219
228
  const result = await client.exec(
220
- ["db", "execute"],
229
+ ["sqlite", "execute"],
221
230
  {
222
231
  sql: "DELETE FROM logs WHERE timestamp < ?",
223
232
  params: ["2024-01-01"]
@@ -247,7 +256,7 @@ interface DbExecuteOutput {
247
256
  ```typescript
248
257
  // INSERT
249
258
  await client.exec(
250
- ["db", "execute"],
259
+ ["sqlite", "execute"],
251
260
  {
252
261
  sql: "INSERT INTO users (name, email) VALUES (?, ?)",
253
262
  params: ["Alice", "alice@example.com"]
@@ -256,7 +265,7 @@ await client.exec(
256
265
 
257
266
  // UPDATE
258
267
  await client.exec(
259
- ["db", "execute"],
268
+ ["sqlite", "execute"],
260
269
  {
261
270
  sql: "UPDATE users SET active = ? WHERE id = ?",
262
271
  params: [true, 123]
@@ -265,7 +274,7 @@ await client.exec(
265
274
 
266
275
  // DELETE
267
276
  await client.exec(
268
- ["db", "execute"],
277
+ ["sqlite", "execute"],
269
278
  {
270
279
  sql: "DELETE FROM users WHERE active = ?",
271
280
  params: [false]
@@ -273,6 +282,65 @@ await client.exec(
273
282
  );
274
283
  ```
275
284
 
285
+ #### `sqlite.tables`
286
+
287
+ The procedure lists the tables and the views of a database, without the internal `sqlite_` tables. Each item has the `name`, the `type` and the `sql` that made it. With `views: false`, it gives only the tables. The procedure only reads. A missing file is an error (`ENOENT`), and the procedure does not make it.
288
+
289
+ ```typescript
290
+ const { tables } = await client.exec(["sqlite", "tables"], { dbPath: "./app.db" });
291
+ ```
292
+
293
+ #### `sqlite.schema`
294
+
295
+ The procedure gives the `columns`, the `indexes` and the `foreignKeys` of a table, or the columns of a view. The name of the table goes to SQLite as a bound value, not as SQL text. The procedure only reads.
296
+
297
+ ```typescript
298
+ const schema = await client.exec(["sqlite", "schema"], { dbPath: "./app.db", table: "users" });
299
+ // schema.columns: [{ name: "id", type: "INTEGER", notNull: false, defaultValue: null, primaryKey: 1 }, ...]
300
+ ```
301
+
302
+ #### `sqlite.transaction`
303
+
304
+ The procedure puts a list of statements in one transaction. When one statement fails, it rolls back the transaction, and the file stays as it was. The `params` of a statement bind to one statement. Without `params`, one `sql` can hold several statements. A statement that gives rows (`SELECT`, or `RETURNING`) gives its `rows` in its result. The procedure refuses `BEGIN`, `COMMIT`, `ROLLBACK`, `SAVEPOINT` and `RELEASE`, because it controls the transaction itself.
305
+
306
+ ```typescript
307
+ const { results, changes } = await client.exec(["sqlite", "transaction"], {
308
+ dbPath: "./app.db",
309
+ statements: [
310
+ { sql: "UPDATE accounts SET balance = balance - ? WHERE id = ?", params: [10, 1] },
311
+ { sql: "UPDATE accounts SET balance = balance + ? WHERE id = ?", params: [10, 2] },
312
+ { sql: "SELECT id, balance FROM accounts" },
313
+ ],
314
+ });
315
+ ```
316
+
317
+ #### `sqlite.migrate`
318
+
319
+ The procedure applies the numbered migrations that the database does not have, in the order of their numbers. The migrations come from a `folder` or from a list (`migrations`). In a folder, each migration is a file `<number>_<name>.sql`, for example `001_create_users.sql`. The procedure ignores the files that do not end in `.sql`.
320
+
321
+ - The table `schema_migrations` (or the `table` of the input) records each applied migration and the sha256 of its SQL.
322
+ - The pending migrations are in one transaction. When one fails, the procedure applies none of them.
323
+ - A second call applies nothing, and does not write the file.
324
+ - When the SQL of an applied migration changes, the procedure refuses the call. The sha256 reads CRLF line ends as LF.
325
+ - A migration must not hold `BEGIN`, `COMMIT` or `ROLLBACK`.
326
+
327
+ ```typescript
328
+ const { applied, skipped, version } = await client.exec(["sqlite", "migrate"], {
329
+ dbPath: "./app.db",
330
+ folder: "./migrations",
331
+ });
332
+ ```
333
+
334
+ #### `sqlite.backup`
335
+
336
+ The procedure copies a database to a new file (`to`). It does not replace a file, and it does not write the source. With `vacuum: true`, it compacts the copy.
337
+
338
+ This package uses SQLite in WebAssembly (sql.js). The database is in the memory of the module. `VACUUM INTO` and the backup API of SQLite write into the file system of the module, not to the disk. Thus the procedure writes the image of the database (its `export()`). It takes the image in the file queue of the database, where no other call of the process changes it. It writes the image to a file next to the target, checks it with `PRAGMA integrity_check`, and then renames it.
339
+
340
+ ```typescript
341
+ const { path, bytes } = await client.exec(["sqlite", "backup"], { dbPath: "./app.db", to: "./backups/app-2026-10-16.db" });
342
+ ```
343
+
276
344
  ### Log Procedures
277
345
 
278
346
  #### `logs.store`
@@ -425,6 +493,65 @@ const { logs } = await client.exec(
425
493
  );
426
494
  ```
427
495
 
496
+ ## The collection storage
497
+
498
+ `SqliteStorage` keeps the items of one collection in one table of a SQLite database. It implements `CollectionStorage` of `@procedures/client`. A `ProcedureServer`, an `ApiStorage` server and a `HybridStorage` use it as they use `InMemoryStorage`.
499
+
500
+ ```ts
501
+ import { SqliteStorage } from "@procedures/sqlite/storage";
502
+
503
+ const users = new SqliteStorage<User>({ path: "./data/app.db", table: "users" });
504
+ await users.set("u1", { name: "Ada" });
505
+ await users.setBatch([["u2", { name: "Bob" }], ["u3", { name: "Cy" }]]);
506
+ const changes = await users.getChangesSince(); // { version, full: true, changed, deleted: [] }
507
+ await users.close();
508
+ ```
509
+
510
+ The entry `@procedures/sqlite/storage` has the storage alone. The package root exports it too.
511
+
512
+ ### The table and the values
513
+
514
+ - The option `path` is a database file, or `:memory:` for a database that lives as long as the storage.
515
+ - The option `table` is the name of the table. It starts with a letter, and it has only letters, digits and underscores. The prefix `sqlite_` is not permitted.
516
+ - The table has the columns `id` and `value`. The value is the JSON text of the item.
517
+ - A value without JSON text, for example `undefined` or a `BigInt`, is a `TypeError`. The storage writes nothing then.
518
+ - A read gives a new copy of the item.
519
+ - The order of the items is the order of the first write of each id, as in a `Map`.
520
+
521
+ ### The change log and the versions
522
+
523
+ - `getChangesSince(version)` gives the items that changed and the ids that were deleted after a version. `HybridStorage` uses it for a delta sync.
524
+ - A version is `<epoch>:<seq>`, as in `InMemoryStorage`. The table `_storage_versions` keeps the epoch, so a version stays valid after a restart.
525
+ - The table `_storage_log` keeps the last change of each id. The option `maxTombstones` (10000 by default) limits the deletes that it keeps.
526
+ - A version that is older than the dropped deletes gives all items (`full: true`).
527
+ - Write the table only through the storage. A change from other SQL is not in the change log.
528
+
529
+ ### The transactions and the file
530
+
531
+ - Each write runs in a transaction with the change log. A batch that fails changes nothing.
532
+ - The storage keeps the database in memory, and it writes the file after each write. The write ends after the file write.
533
+ - When several writes wait, one file write holds their changes.
534
+ - A failed file write fails the write. The change stays in memory, and `flush()` or the next write writes it.
535
+ - A storage that only reads does not make the file.
536
+ - The storages of one file in one process share one database. Thus the storages of two tables in one file keep their items.
537
+ - Another process, and the procedures `sqlite.query` and `sqlite.execute`, do not share this database. Do not write the file from them while a storage has it open.
538
+
539
+ ### The durable local copy of `HybridStorage`
540
+
541
+ `HybridStorage` of `@procedures/store` takes the storage of its local copy in the option `local`. With a `SqliteStorage`, the local copy stays after a restart. A new storage in `local-only` mode then reads the items of the last run, without the remote.
542
+
543
+ ```ts
544
+ import { HybridStorage } from "@procedures/store";
545
+ import { FileQueueStore } from "@procedures/store/file-queue";
546
+ import { SqliteStorage } from "@procedures/sqlite/storage";
547
+
548
+ const users = new HybridStorage(remote, {
549
+ mode: "write-back",
550
+ local: new SqliteStorage({ path: "./users.db", table: "users" }),
551
+ queueStore: new FileQueueStore("./users.queue.jsonl"),
552
+ });
553
+ ```
554
+
428
555
  ## Usage Examples
429
556
 
430
557
  ### Basic Database Operations
@@ -437,7 +564,7 @@ const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
437
564
 
438
565
  // Create a table
439
566
  await client.exec(
440
- ["db", "execute"],
567
+ ["sqlite", "execute"],
441
568
  {
442
569
  sql: `
443
570
  CREATE TABLE IF NOT EXISTS users (
@@ -454,7 +581,7 @@ await client.exec(
454
581
 
455
582
  // Insert data
456
583
  await client.exec(
457
- ["db", "execute"],
584
+ ["sqlite", "execute"],
458
585
  {
459
586
  sql: "INSERT INTO users (name, email) VALUES (?, ?)",
460
587
  params: ["Alice Johnson", "alice@example.com"],
@@ -464,7 +591,7 @@ await client.exec(
464
591
 
465
592
  // Query data
466
593
  const result = await client.exec(
467
- ["db", "query"],
594
+ ["sqlite", "query"],
468
595
  {
469
596
  sql: "SELECT * FROM users WHERE active = ? ORDER BY created_at DESC",
470
597
  params: [1],
@@ -628,7 +755,7 @@ interface User {
628
755
  }
629
756
 
630
757
  const result = await client.call<DbQueryOutput<User>>(
631
- ["db", "query"],
758
+ ["sqlite", "query"],
632
759
  {
633
760
  sql: "SELECT * FROM users WHERE active = ?",
634
761
  params: [true],
@@ -708,7 +835,10 @@ export type {
708
835
  client-sqlite/
709
836
  ├── src/
710
837
  │ ├── types.ts # Type definitions
711
- │ ├── register.ts # Auto-registration + procedure implementations
838
+ │ ├── module.ts # The procedures as data, and the sqlite.query, sqlite.execute and logs.* handlers
839
+ │ ├── register.ts # The ./register entry: registers the module
840
+ │ ├── storage.ts # SqliteStorage, the collection storage (the ./storage entry)
841
+ │ ├── procedures/db/ # sqlite.tables, sqlite.schema, sqlite.transaction, sqlite.migrate, sqlite.backup
712
842
  │ └── index.ts # Main entry point
713
843
  ├── dist/ # Compiled output
714
844
  ├── package.json