picovolt 1.3.0 → 1.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
@@ -1,7 +1,7 @@
1
1
  # PicoVolt (PVDB)
2
2
 
3
3
  [![CI](https://github.com/MiniJe/picovolt/actions/workflows/ci.yml/badge.svg)](https://github.com/MiniJe/picovolt/actions/workflows/ci.yml)
4
- [![Version](https://img.shields.io/badge/version-1.1.0-blue.svg)](CHANGELOG.md)
4
+ [![Version](https://img.shields.io/badge/version-1.5.0-blue.svg)](CHANGELOG.md)
5
5
  [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
6
6
  ![Status: 1.0 stable](https://img.shields.io/badge/status-1.0%20stable-brightgreen.svg)
7
7
  [![GitHub stars](https://img.shields.io/github/stars/MiniJe/picovolt?style=social)](https://github.com/MiniJe/picovolt)
@@ -28,7 +28,7 @@ cache efficiency.
28
28
 
29
29
  ## Status
30
30
 
31
- The engine is built out across four phases, all implemented, with 103 unit and
31
+ The engine is built out across four phases, all implemented, with over 180 unit and
32
32
  integration tests plus doctests passing and a clean `cargo clippy -D warnings` on
33
33
  Linux and Windows. Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
34
34
 
@@ -52,11 +52,11 @@ Linux and Windows. Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
52
52
  | [`storage/compress.rs`](src/storage/compress.rs) | Delta-Z, LEB128 varints, dictionary bit-packing |
53
53
  | [`storage/index.rs`](src/storage/index.rs) | in-memory ordered secondary index (value to record addresses; point and range) |
54
54
  | [`storage/record.rs`](src/storage/record.rs) | row and record-body serialization with CAS interception |
55
- | [`storage/vle.rs`](src/storage/vle.rs) | dev directory store, prod mmap monolith, `bake` |
55
+ | [`storage/vle.rs`](src/storage/vle.rs) | dev directory store, owned prod snapshot, streamed reads, `bake` |
56
56
  | [`engine/mvcc.rs`](src/engine/mvcc.rs) | transaction clock and snapshot visibility |
57
57
  | [`engine/wasm.rs`](src/engine/wasm.rs) | sandboxed `wasmi` extension runtime and the `WasmExec` backend trait |
58
58
  | [`engine/interp.rs`](src/engine/interp.rs) | `pv-wasm`: a from-scratch WASM interpreter (integer subset) |
59
- | [`engine/query.rs`](src/engine/query.rs) | SQL front-end (CREATE/INSERT/UPDATE/DELETE/DROP, `SELECT` with projection, `AS` aliases, `DISTINCT`, aggregates, `GROUP BY`/`HAVING`, `WHERE` predicates incl. `IN`/`BETWEEN`/`IS NULL`/`LIKE`, `BEFORE`, multi-column `ORDER BY`, `LIMIT`) |
59
+ | [`engine/query.rs`](src/engine/query.rs) | SQL front-end (CREATE/INSERT/UPDATE/DELETE/DROP, `SELECT` with projection, `AS` aliases, `DISTINCT`, aggregates, `GROUP BY`/`HAVING`, `WHERE` predicates incl. `IN`/`BETWEEN`/`IS NULL`/`LIKE`, `BEFORE`, multi-column `ORDER BY`, `LIMIT`/`OFFSET`) |
60
60
  | [`engine/compliance.rs`](src/engine/compliance.rs) | optional, app-driven usage-policy hook (not a license requirement) |
61
61
  | [`db.rs`](src/db.rs) | the `Database` surface that ties it together |
62
62
  | [`ffi.rs`](src/ffi.rs) | C ABI (the `capi` feature): a panic-safe, C-callable surface wrapping the engine for Go, Python, and C bindings |
@@ -89,12 +89,15 @@ Linux and Windows. Changes are tracked in [CHANGELOG.md](CHANGELOG.md).
89
89
  - **Hardened against untrusted input.** Opening a `.pvdb` or workspace, or running
90
90
  a WASM module, validates manifest hashes (no path traversal), bounds-checks CAS
91
91
  offsets and page chains (no out-of-bounds reads or infinite loops on a crafted
92
- file), and caps WASM resource counts. The decoders are fuzzed (a cross-platform
92
+ file), and meters WASM instructions, memory, and output. The decoders are fuzzed (a cross-platform
93
93
  fuzz-lite test and a [`fuzz/`](fuzz) cargo-fuzz crate), and `cargo audit`
94
- reports no advisories. Both run in CI. See [SECURITY.md](SECURITY.md).
94
+ currently reports no vulnerability failures. Both run in CI. See
95
+ [SECURITY.md](SECURITY.md).
95
96
 
96
97
  ## Build
97
98
 
99
+ Rust 1.86 or newer is required.
100
+
98
101
  ```sh
99
102
  cargo build
100
103
  cargo test
@@ -109,14 +112,24 @@ cargo run --release --example repl # interactive SQL shell (pvsql)
109
112
  cargo run --release --example bench # evaluation harness across modes and workloads
110
113
  ```
111
114
 
112
- SQL supported: `CREATE TABLE`, `CREATE INDEX ON t (col)`, `INSERT`,
113
- `UPDATE ... SET ... WHERE`, `DELETE ... WHERE`, `DROP TABLE`, and
115
+ Install the first-class CLI with `cargo install picovolt`, then use `pv query`,
116
+ `pv inspect`, `pv history`, `pv import`, `pv export`, and `pv bake`. Copyable
117
+ Rust, Python, Go, Node, and browser projects are in [`starters/`](starters/README.md); supported adapters
118
+ are catalogued in [`docs/INTEGRATIONS.md`](docs/INTEGRATIONS.md).
119
+
120
+ SQL supported: `CREATE TABLE [IF NOT EXISTS]` with `PRIMARY KEY`, `UNIQUE`, and `NOT NULL`,
121
+ `CREATE [UNIQUE] INDEX ON t (col)`, single- and multi-row `INSERT`,
122
+ `UPDATE ... SET ... WHERE`, `DELETE ... WHERE`, `DROP TABLE [IF EXISTS]`, and
114
123
  `SELECT [DISTINCT] {* | col [AS alias], ... | COUNT/SUM/MIN/MAX/AVG(...) [AS alias]}
115
124
  FROM t [WHERE <pred>] [GROUP BY cols] [HAVING <pred>] [BEFORE tx]
116
- [ORDER BY col [ASC|DESC], ...] [LIMIT n]`, where `<pred>` combines
125
+ [ORDER BY col [ASC|DESC], ...] [LIMIT n] [OFFSET n]`, where `<pred>` combines
117
126
  `col <op> value` (`=`, `!=`, `<`, `<=`, `>`, `>=`, `LIKE`, `NOT LIKE`),
118
127
  `col [NOT] IN (...)`, `col [NOT] BETWEEN a AND b`, and `col IS [NOT] NULL` with
119
128
  `AND`, `OR`, and parentheses. Integer and decimal values compare by magnitude.
129
+ Two-table equality `INNER`/`LEFT JOIN` queries support qualified references,
130
+ projection, aliases, `DISTINCT`, filters, ordering, and pagination. Rust callers
131
+ can cache `Database::prepare(...)` templates and use
132
+ atomic `Database::transaction(...)` closures with in-memory databases.
120
133
  Durability is selectable via `Database::set_durability` (`Fast` OS-cache default,
121
134
  or crash-safe `Sync` with fsync and an atomic manifest).
122
135
 
@@ -126,9 +139,9 @@ around 33k rows/s, linear), larger-than-RAM reads through a bounded buffer pool
126
139
  667-page dataset serves from a 16-page pool), ordered secondary indexes (point
127
140
  lookups roughly 11,000 times faster than a scan, plus range predicates), MVCC
128
141
  time-travel, opt-in crash-safe durability (`Durability::Sync`), and a fast
129
- compile-and-publish path (CAS dedup, columnar compression, single-file mmap
130
- artifacts). Current limits: indexes are in-memory (rebuilt on open) and there is
131
- no concurrency.
142
+ compile-and-publish path (CAS dedup, columnar compression, memory-mappable
143
+ single-file artifacts). Current limits include no filesystem `BEGIN`/`COMMIT`
144
+ transactions, only basic two-table equality joins, and no concurrent writers.
132
145
 
133
146
  ## Install and distribution
134
147
 
@@ -136,10 +149,12 @@ no concurrency.
136
149
  |--------|-----|
137
150
  | **Rust** (crates.io) | `cargo add picovolt` |
138
151
  | **JavaScript / npm** (WebAssembly, browser and Node) | `npm install picovolt` |
139
- | **C / Go / Python** (native, via the C ABI) | `cargo build --release --features capi`, then see [`bindings/`](bindings) |
152
+ | **Python** (native wheels) | `python -m pip install picovolt` (after the first PyPI release) |
153
+ | **C / Go** (native, via the C ABI) | `cargo build --release --features capi`, then see [`bindings/`](bindings) |
140
154
  | **In-memory** (native, no filesystem) | `Database::open_memory()`, export with `bake_to_bytes()` |
141
155
 
142
- PicoVolt runs in the browser through its in-memory backend. Build the WebAssembly
156
+ PicoVolt runs in the browser through its in-memory backend plus an OPFS persistence
157
+ wrapper and Web Worker endpoint. Build the WebAssembly
143
158
  package with `wasm-pack build --target bundler --release -- --features wasm`, then
144
159
  `import { Db } from "picovolt"` and run SQL with `db.query(...)`. See
145
160
  [src/wasm_api.rs](src/wasm_api.rs) for the JavaScript surface.
@@ -155,9 +170,9 @@ All bindings accept positional `?` parameters
155
170
  a familiar surface, drop-in adapters are provided: a `better-sqlite3`-style
156
171
  JavaScript API (`import Database from "picovolt/sqlite"`), a Python DB-API 2.0
157
172
  module (`import picovolt.dbapi2 as sqlite`), and the Go `database/sql` driver
158
- ([`bindings/go/pvsql`](bindings/go/pvsql)). Shared limits across all of them:
159
- positional `?` only, no SQL transactions, no JOINs, and `CREATE TABLE` takes
160
- column names only.
173
+ ([`bindings/go/pvsql`](bindings/go/pvsql)). Shared limits include positional `?`
174
+ only and the intentionally compact SQL grammar; JavaScript and in-memory Rust
175
+ also expose rollback-capable transaction wrappers.
161
176
 
162
177
  ## Server mode
163
178
 
@@ -169,13 +184,22 @@ thread over a channel, so the single-threaded core is unchanged.
169
184
  ```sh
170
185
  cargo build --release --features server
171
186
  ./target/release/picovolt-server --memory --addr 127.0.0.1:8080
172
- curl -s localhost:8080/v1/query -d '{"sql":"SELECT 1 + 1","params":[]}'
187
+ curl -s localhost:8080/v1/query \
188
+ -H 'Content-Type: application/json' \
189
+ -d '{"sql":"CREATE TABLE demo (value)","params":[]}'
173
190
  ```
174
191
 
175
- Endpoints are `POST /v1/query`, `GET /v1/tx`, and `GET /v1/health`. There is no
176
- authentication or TLS, so run it behind a reverse proxy. See
192
+ Endpoints are `POST /v1/query`, `GET /v1/tx`, and `GET /v1/health`. Loopback use
193
+ may omit authentication. A non-loopback bind is refused unless a bearer token is
194
+ provided with `--token-file` or `PICOVOLT_SERVER_TOKEN`; send it as
195
+ `Authorization: Bearer ...`. Query bodies, queues, execution time, rows scanned,
196
+ result rows, and response size are bounded. TLS is not built in, so network
197
+ deployments still belong behind a TLS-terminating reverse proxy. See
177
198
  [src/bin/server.rs](src/bin/server.rs).
178
199
 
200
+ Applications accepting SQL from users can also call `Database::query_with_limits`
201
+ directly and choose their own scan, result, memory, and deadline budgets.
202
+
179
203
  ## Extending PicoVolt
180
204
 
181
205
  There are two extension paths: sandboxed WebAssembly user-defined functions, and
@@ -187,6 +211,8 @@ native modules built on the public API. Both are documented in
187
211
  | | |
188
212
  |--|--|
189
213
  | Roadmap | [ROADMAP.md](ROADMAP.md) |
214
+ | One-million-download plan | [docs/ROADMAP_1M_DOWNLOADS.md](docs/ROADMAP_1M_DOWNLOADS.md) |
215
+ | Platform and file support | [docs/SUPPORT.md](docs/SUPPORT.md) |
190
216
  | Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) |
191
217
  | Code of conduct | [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) |
192
218
  | Changelog | [CHANGELOG.md](CHANGELOG.md) |
package/browser.js ADDED
@@ -0,0 +1,40 @@
1
+ // Durable browser helper backed by the Origin Private File System (OPFS).
2
+ import { Db } from "./picovolt.js";
3
+
4
+ export class PersistentDb {
5
+ constructor(name, db) {
6
+ this.name = name;
7
+ this.db = db;
8
+ }
9
+
10
+ static async open(name = "picovolt.pvdb") {
11
+ if (!globalThis.navigator?.storage?.getDirectory) {
12
+ throw new Error("PicoVolt OPFS persistence is unavailable in this browser/context");
13
+ }
14
+ const root = await navigator.storage.getDirectory();
15
+ const handle = await root.getFileHandle(name, { create: true });
16
+ const file = await handle.getFile();
17
+ const bytes = new Uint8Array(await file.arrayBuffer());
18
+ const db = bytes.length ? Db.fromBytes(bytes) : new Db();
19
+ return new PersistentDb(name, db);
20
+ }
21
+
22
+ query(sql, params) {
23
+ const json = params === undefined ? this.db.query(sql) : this.db.query(sql, params);
24
+ return JSON.parse(json);
25
+ }
26
+
27
+ async save() {
28
+ const root = await navigator.storage.getDirectory();
29
+ const handle = await root.getFileHandle(this.name, { create: true });
30
+ const writable = await handle.createWritable();
31
+ await writable.write(this.db.export());
32
+ await writable.close();
33
+ }
34
+
35
+ async close() {
36
+ await this.save();
37
+ }
38
+ }
39
+
40
+ export default PersistentDb;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "picovolt",
3
3
  "type": "module",
4
4
  "description": "PicoVolt (PVDB): a polymorphic embedded database engine in Rust.",
5
- "version": "1.3.0",
5
+ "version": "1.5.0",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -13,7 +13,9 @@
13
13
  "picovolt.js",
14
14
  "picovolt_bg.js",
15
15
  "picovolt.d.ts",
16
- "sqlite.js"
16
+ "sqlite.js",
17
+ "browser.js",
18
+ "worker.js"
17
19
  ],
18
20
  "main": "picovolt.js",
19
21
  "types": "picovolt.d.ts",
@@ -30,6 +32,8 @@
30
32
  ],
31
33
  "exports": {
32
34
  ".": "./picovolt.js",
33
- "./sqlite": "./sqlite.js"
35
+ "./sqlite": "./sqlite.js",
36
+ "./browser": "./browser.js",
37
+ "./worker": "./worker.js"
34
38
  }
35
39
  }
package/picovolt_bg.js CHANGED
@@ -135,33 +135,33 @@ export class Db {
135
135
  }
136
136
  }
137
137
  if (Symbol.dispose) Db.prototype[Symbol.dispose] = Db.prototype.free;
138
- export function __wbg___wbindgen_boolean_get_fa956cfa2d1bd751(arg0) {
138
+ export function __wbg___wbindgen_boolean_get_c9c83ebd41b34df3(arg0) {
139
139
  const v = arg0;
140
140
  const ret = typeof(v) === 'boolean' ? v : undefined;
141
141
  return isLikeNone(ret) ? 0xFFFFFF : ret ? 1 : 0;
142
142
  }
143
- export function __wbg___wbindgen_debug_string_c25d447a39f5578f(arg0, arg1) {
143
+ export function __wbg___wbindgen_debug_string_a57024b9c6e4a48b(arg0, arg1) {
144
144
  const ret = debugString(arg1);
145
145
  const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
146
146
  const len1 = WASM_VECTOR_LEN;
147
147
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
148
148
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
149
149
  }
150
- export function __wbg___wbindgen_is_null_ea9085d691f535d3(arg0) {
150
+ export function __wbg___wbindgen_is_null_7d13f41e1a2d5140(arg0) {
151
151
  const ret = arg0 === null;
152
152
  return ret;
153
153
  }
154
- export function __wbg___wbindgen_is_undefined_c05833b95a3cf397(arg0) {
154
+ export function __wbg___wbindgen_is_undefined_6cff064c44e0d823(arg0) {
155
155
  const ret = arg0 === undefined;
156
156
  return ret;
157
157
  }
158
- export function __wbg___wbindgen_number_get_394265ed1e1b84ee(arg0, arg1) {
158
+ export function __wbg___wbindgen_number_get_136b9679cab35cfb(arg0, arg1) {
159
159
  const obj = arg1;
160
160
  const ret = typeof(obj) === 'number' ? obj : undefined;
161
161
  getDataViewMemory0().setFloat64(arg0 + 8 * 1, isLikeNone(ret) ? 0 : ret, true);
162
162
  getDataViewMemory0().setInt32(arg0 + 4 * 0, !isLikeNone(ret), true);
163
163
  }
164
- export function __wbg___wbindgen_string_get_b0ca35b86a603356(arg0, arg1) {
164
+ export function __wbg___wbindgen_string_get_d154f1e671052120(arg0, arg1) {
165
165
  const obj = arg1;
166
166
  const ret = typeof(obj) === 'string' ? obj : undefined;
167
167
  var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -169,14 +169,14 @@ export function __wbg___wbindgen_string_get_b0ca35b86a603356(arg0, arg1) {
169
169
  getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
170
170
  getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
171
171
  }
172
- export function __wbg___wbindgen_throw_344f42d3211c4765(arg0, arg1) {
172
+ export function __wbg___wbindgen_throw_bb96b2010945f0bc(arg0, arg1) {
173
173
  throw new Error(getStringFromWasm0(arg0, arg1));
174
174
  }
175
- export function __wbg_call_e3b662382210db98() { return handleError(function (arg0, arg1, arg2, arg3) {
175
+ export function __wbg_call_0f2a9af232c18fd2() { return handleError(function (arg0, arg1, arg2, arg3) {
176
176
  const ret = arg0.call(arg1, arg2, arg3);
177
177
  return ret;
178
178
  }, arguments); }
179
- export function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {
179
+ export function __wbg_error_757e9472f8410341(arg0, arg1) {
180
180
  let deferred0_0;
181
181
  let deferred0_1;
182
182
  try {
@@ -187,15 +187,15 @@ export function __wbg_error_a6fa202b58aa1cd3(arg0, arg1) {
187
187
  wasm.__wbindgen_free(deferred0_0, deferred0_1, 1);
188
188
  }
189
189
  }
190
- export function __wbg_from_13e323c65fc8f464(arg0) {
190
+ export function __wbg_from_74f3d90e0ff11240(arg0) {
191
191
  const ret = Array.from(arg0);
192
192
  return ret;
193
193
  }
194
- export function __wbg_get_unchecked_6e0ad6d2a41b06f6(arg0, arg1) {
194
+ export function __wbg_get_unchecked_e20b893aeafc3fca(arg0, arg1) {
195
195
  const ret = arg0[arg1 >>> 0];
196
196
  return ret;
197
197
  }
198
- export function __wbg_instanceof_Uint8Array_309b927aaf7a3fc7(arg0) {
198
+ export function __wbg_instanceof_Uint8Array_f935dbb0aa7cdeed(arg0) {
199
199
  let result;
200
200
  try {
201
201
  result = arg0 instanceof Uint8Array;
@@ -205,11 +205,11 @@ export function __wbg_instanceof_Uint8Array_309b927aaf7a3fc7(arg0) {
205
205
  const ret = result;
206
206
  return ret;
207
207
  }
208
- export function __wbg_length_1f0964f4a5e2c6d8(arg0) {
208
+ export function __wbg_length_36bd29c6848c2144(arg0) {
209
209
  const ret = arg0.length;
210
210
  return ret;
211
211
  }
212
- export function __wbg_length_370319915dc99107(arg0) {
212
+ export function __wbg_length_ecfa2c63d3d0d82c(arg0) {
213
213
  const ret = arg0.length;
214
214
  return ret;
215
215
  }
@@ -217,7 +217,7 @@ export function __wbg_new_227d7c05414eb861() {
217
217
  const ret = new Error();
218
218
  return ret;
219
219
  }
220
- export function __wbg_prototypesetcall_4770620bbe4688a0(arg0, arg1, arg2) {
220
+ export function __wbg_prototypesetcall_de8e0d9553586985(arg0, arg1, arg2) {
221
221
  Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
222
222
  }
223
223
  export function __wbg_stack_3b0d974bbf31e44f(arg0, arg1) {
package/picovolt_bg.wasm CHANGED
Binary file
package/sqlite.js CHANGED
@@ -9,12 +9,14 @@
9
9
  // // [ { id: 1, name: "alice" } ]
10
10
  //
11
11
  // Limitations: parameters are positional `?` only (named `:id` params are not
12
- // supported); there are no transactions; blob parameters are unsupported.
12
+ // supported); blob parameters are unsupported.
13
13
 
14
14
  import { Db } from "./picovolt.js";
15
15
 
16
16
  function rowToObject(columns, row) {
17
- const obj = {};
17
+ // A null prototype prevents hostile column names such as `__proto__` from
18
+ // changing the shape or prototype of the returned record.
19
+ const obj = Object.create(null);
18
20
  for (let i = 0; i < columns.length; i++) obj[columns[i]] = row[i];
19
21
  return obj;
20
22
  }
@@ -63,6 +65,7 @@ class Statement {
63
65
  class Database {
64
66
  constructor() {
65
67
  this._db = new Db();
68
+ this._inTransaction = false;
66
69
  }
67
70
 
68
71
  prepare(sql) {
@@ -91,8 +94,28 @@ class Database {
91
94
  throw new Error("picovolt: pragma is not supported");
92
95
  }
93
96
 
94
- transaction() {
95
- throw new Error("picovolt: transactions are not supported");
97
+ // Wrap a synchronous callback in an atomic unit. A compact PVDB snapshot is
98
+ // restored if the callback throws, matching better-sqlite3's common pattern.
99
+ transaction(fn) {
100
+ if (typeof fn !== "function") throw new TypeError("transaction expects a function");
101
+ const db = this;
102
+ function wrapped(...args) {
103
+ if (db._inTransaction) return fn(...args);
104
+ const snapshot = db.serialize();
105
+ db._inTransaction = true;
106
+ try {
107
+ return fn(...args);
108
+ } catch (error) {
109
+ db._db = Db.fromBytes(snapshot);
110
+ throw error;
111
+ } finally {
112
+ db._inTransaction = false;
113
+ }
114
+ }
115
+ wrapped.deferred = wrapped;
116
+ wrapped.immediate = wrapped;
117
+ wrapped.exclusive = wrapped;
118
+ return wrapped;
96
119
  }
97
120
 
98
121
  close() {
package/worker.js ADDED
@@ -0,0 +1,37 @@
1
+ // Module-worker RPC endpoint. Use `new Worker(url, { type: "module" })` and
2
+ // send `{ id, method, ... }`; every response echoes `id` and carries result/error.
3
+ import { PersistentDb } from "./browser.js";
4
+
5
+ let database;
6
+
7
+ self.addEventListener("message", async ({ data }) => {
8
+ const { id, method } = data ?? {};
9
+ try {
10
+ let result;
11
+ switch (method) {
12
+ case "open":
13
+ database = await PersistentDb.open(data.name);
14
+ result = true;
15
+ break;
16
+ case "query":
17
+ if (!database) throw new Error("open the database first");
18
+ result = database.query(data.sql, data.params);
19
+ break;
20
+ case "save":
21
+ if (!database) throw new Error("open the database first");
22
+ await database.save();
23
+ result = true;
24
+ break;
25
+ case "close":
26
+ if (database) await database.close();
27
+ database = undefined;
28
+ result = true;
29
+ break;
30
+ default:
31
+ throw new Error(`unknown PicoVolt worker method: ${method}`);
32
+ }
33
+ self.postMessage({ id, result });
34
+ } catch (error) {
35
+ self.postMessage({ id, error: error instanceof Error ? error.message : String(error) });
36
+ }
37
+ });