@moltendb-web/core 1.8.0 β†’ 2.0.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
@@ -3,15 +3,16 @@
3
3
  <div align="center">
4
4
  <img src="../../assets/logo.png" alt="MoltenDb Logo" width="64"/>
5
5
 
6
- ### πŸŒ‹ The Embedded Database for the Modern Web
7
- **High-performance Rust engine compiled to WASM. Persistent storage via OPFS.**
6
+ ### πŸŒ‹ The Embedded Database for the Modern Web
8
7
 
9
- [Interactive Demo](https://stackblitz.com/~/github.com/maximilian27/moltendb-wasm-demo?file=package.json) β€’ [Core Engine](https://www.npmjs.com/package/@moltendb-web/core) β€’ [Query Builder](https://www.npmjs.com/package/@moltendb-web/query) β€’ [Original Repository](https://github.com/maximilian27/MoltenDb) β€’ [License](LICENSE.md)
8
+ **High-performance Rust engine compiled to WASM. Persistent storage via OPFS.**
10
9
 
11
- [![NPM Version](https://img.shields.io/npm/v/@moltendb-web/core?style=flat-square&color=orange)](https://www.npmjs.com/package/@moltendb-web/core)
12
- [![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE.md)
13
- [![WASM](https://img.shields.io/badge/wasm-optimized-magenta?style=flat-square)](https://webassembly.org/)
14
- [![Status](https://img.shields.io/badge/status-stable-brightgreen?style=flat-square)]()
10
+ [Interactive Demo](https://stackblitz.com/~/github.com/maximilian27/moltendb-wasm-demo?file=package.json) β€’ [Core Engine](https://www.npmjs.com/package/@moltendb-web/core) β€’ [Query Builder](https://www.npmjs.com/package/@moltendb-web/query) β€’ [Original Repository](https://github.com/maximilian27/MoltenDb) β€’ [License](LICENSE.md)
11
+
12
+ [![NPM Version](https://img.shields.io/npm/v/@moltendb-web/core?style=flat-square&color=orange)](https://www.npmjs.com/package/@moltendb-web/core)
13
+ [![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)](LICENSE.md)
14
+ [![WASM](https://img.shields.io/badge/wasm-optimized-magenta?style=flat-square)](https://webassembly.org/)
15
+ [![Status](https://img.shields.io/badge/status-stable-brightgreen?style=flat-square)]()
15
16
 
16
17
  </div>
17
18
 
@@ -34,20 +35,33 @@ Prefer to run it in your own environment? You can **[clone the demo repository](
34
35
  **⚠️ Note for Online IDEs:** If you are viewing this on StackBlitz or CodeSandbox, the WASM engine may be blocked by iframe security restrictions. Please click the "Open in New Window/Tab" button in the preview pane to enable the full OPFS storage engine.
35
36
 
36
37
  ### Core Features
37
- - **Hybrid Bitcask Storage:** The same query logic used in our server binary, compiled to WebAssembly. Data is paged between RAM and OPFS to handle datasets larger than memory.
38
+
38
39
  - **At-Rest Encryption:** Transparently secure your data in the browser using XChaCha20-Poly1305 (Argon2id key derivation).
39
40
  - **OPFS Persistence:** Data persists across page reloads in a dedicated, high-speed sandbox.
40
41
  - **Worker-Threaded:** The database runs entirely inside a Web Workerβ€”zero impact on your UI thread.
41
- - **Multi-Tab Sync (stabilised):** Leader election via the Web Locks API ensures only one tab owns the OPFS handle. All other tabs proxy reads and writes through a `BroadcastChannel`. Seamless leader promotion when the active tab closes.
42
- - **Automatic Compaction:** The engine automatically compacts the append-only log when it exceeds **500 entries or 5 MB**, keeping storage lean without any manual intervention.
42
+ - **Multi-Tab Sync:** Leader election via the Web Locks API ensures only one tab owns the OPFS handle. All other tabs proxy reads and writes through a `BroadcastChannel`. Seamless leader promotion when the active tab closes.
43
43
  - **Real-Time Pub/Sub:** Every write and delete emits a typed `DbEvent` to all open tabs instantly. The `subscribe()` pattern supports multiple independent listeners per tab β€” perfect for modern UI frameworks like React and Angular.
44
44
  - **GraphQL-style Selection:** Request only the fields you need (even deeply nested ones) to save memory and CPU.
45
- - **Auto-Indexing:** The engine monitors your queries and automatically creates indexes for frequently filtered fields.
46
45
  - **Conflict Resolution:** Incoming writes with `_v ≀ stored _v` are silently skipped.
47
46
  - **Inline reference embedding (`extends`):** Embed data from another collection at insert time.
48
47
 
49
48
  ---
50
49
 
50
+ ## What's New in v2.0.0
51
+
52
+ ### Query Builder
53
+
54
+ - **Bulk Delete with `.where()`** β€” delete documents matching a filter clause without listing individual keys (see [`@moltendb-web/query`](../query/README.md)).
55
+ - **Capped Collections (`.maxSize()`)** β€” cap a collection to a maximum number of documents; oldest entries are evicted automatically when the limit is reached.
56
+ - **TTL Collections (`.ttl()`)** β€” set a time-to-live (in seconds) on a collection; documents are removed automatically after expiry.
57
+
58
+ ### Core Engine Performance
59
+
60
+ - **`Arc<str>` collection-key interning** β€” the outer `DashMap` key was changed from `String` to `Arc<str>`. During bulk insert and WAL replay all documents in the same collection share a single pointer instead of allocating a new `String` per document. Saves ~30 B per doc (~30 MB at 1 M docs) and reduces allocator pressure during startup.
61
+ - **MessagePack in-memory storage** β€” the hot document map was switched from `serde_json::Value` to `Box<[u8]>` (MessagePack bytes). Reduces steady-state RSS for 1 M docs from ~4 GB to ~500 MB (~8Γ— lower). Decoding to `Value` happens lazily on read; write paths encode via `rmp_serde`.
62
+
63
+ ---
64
+
51
65
  ## Installation
52
66
 
53
67
  MoltenDb is split into two packages: the core engine and the type-safe, chainable query builder.
@@ -59,6 +73,7 @@ npm install @moltendb-web/core
59
73
  # Install the chainable query builder
60
74
  npm install @moltendb-web/query
61
75
  ```
76
+
62
77
  πŸ“¦ **Bundler Setup**
63
78
 
64
79
  MoltenDb handles its own Web Workers and WASM loading automatically. However, depending on your build tool, you may need a tiny config tweak to ensure it serves the static files correctly.
@@ -67,9 +82,9 @@ MoltenDb handles its own Web Workers and WASM loading automatically. However, de
67
82
  Exclude the core package from pre-bundling in your vite.config.js:
68
83
 
69
84
  ```js
70
- // vite.config.js`
85
+ // vite.config.js
71
86
  export default defineConfig({
72
- optimizeDeps: { exclude: ['@moltendb-web/core'] }
87
+ optimizeDeps: {exclude: ['@moltendb-web/core']}
73
88
  });
74
89
  ```
75
90
 
@@ -79,20 +94,23 @@ Ensure Webpack treats the `.wasm` binary as a static resource in `webpack.config
79
94
  ```js
80
95
  module.exports = {
81
96
  module: {
82
- rules: [{ test: /\.wasm$/, type: 'asset/resource' }]
97
+ rules: [{test: /\.wasm$/, type: 'asset/resource'}]
83
98
  }
84
99
  };
85
100
  ```
101
+
86
102
  ---
87
103
 
88
104
  # Quick Start
105
+
89
106
  1. Initialize the Client
90
107
 
91
108
  MoltenDb handles the Web Worker and WASM instantiation for you.
92
109
  TypeScript
110
+
93
111
  ```ts
94
- import { MoltenDb } from '@moltendb-web/core';
95
- import { MoltenDbClient, WorkerTransport } from '@moltendb-web/query';
112
+ import {MoltenDb} from '@moltendb-web/core';
113
+ import {MoltenDbClient, WorkerTransport} from '@moltendb-web/query';
96
114
 
97
115
  const db = new MoltenDb('moltendb_demo', {
98
116
  encryptionKey: 'my-secret', // Enable transparent at-rest encryption
@@ -102,36 +120,38 @@ await db.init();
102
120
 
103
121
  // Connect the query builder to the WASM worker
104
122
  const client = new MoltenDbClient(db);
123
+ ```
105
124
 
106
- // 2. Insert and Query
125
+ 2. Insert and Query
107
126
 
108
- // Use the @moltendb-web/query builder for a type-safe experience.
127
+ Use the `@moltendb-web/query` builder for a type-safe experience.
109
128
 
129
+ ```ts
110
130
  // Insert data
111
131
  await client.collection('laptops').set({
112
132
  lp1: {
113
133
  brand: "Apple",
114
- model: "MacBook Pro",
115
- price: 1999,
116
- in_stock: true,
117
- memory_id: 'mem1',
118
- specs: {
119
- cpu: {
120
- cores: 8,
121
- clock_speed: 3.5,
122
- },
123
- display: {
124
- refresh_hz: 60,
125
- }
134
+ model: "MacBook Pro",
135
+ price: 1999,
136
+ in_stock: true,
137
+ memory_id: 'mem1',
138
+ specs: {
139
+ cpu: {
140
+ cores: 8,
141
+ clock_speed: 3.5,
142
+ },
143
+ display: {
144
+ refresh_hz: 60,
126
145
  }
146
+ }
127
147
  },
128
148
  lp2: {
129
149
  brand: "Apple",
130
150
  model: "MacBook Air",
131
151
  price: 900,
132
152
  in_stock: true,
133
- memory_id: 'mem2',
134
- specs: {
153
+ memory_id: 'mem2',
154
+ specs: {
135
155
  cpu: {
136
156
  cores: 4,
137
157
  clock_speed: 3.5,
@@ -144,29 +164,29 @@ await client.collection('laptops').set({
144
164
  }).exec();
145
165
 
146
166
  await client.collection('memory').set({
147
- mem1: {
148
- capacity_gb: 16,
149
- type: 'DDR4',
150
- speed_mhz: 4800,
151
- upgradeable: false
152
- },
153
- mem2: {
154
- capacity_gb: 64,
155
- type: 'DDR5',
156
- speed_mhz: 5600,
157
- upgradeable: true
158
- },
167
+ mem1: {
168
+ capacity_gb: 16,
169
+ type: 'DDR4',
170
+ speed_mhz: 4800,
171
+ upgradeable: false
172
+ },
173
+ mem2: {
174
+ capacity_gb: 64,
175
+ type: 'DDR5',
176
+ speed_mhz: 5600,
177
+ upgradeable: true
178
+ },
159
179
  }).exec();
160
180
 
161
181
  // Query with field selection
162
182
  const results = await client.collection('laptops')
163
- .get()
164
- .where({ brand: { $in: ["Apple", "Dell"] }, in_stock: true }) // Using $in operator
165
- .fields(['model', 'price']) // Only return these specific fields
166
- .sort([{ field: 'price', order: 'desc' }])
167
- .exec();
183
+ .get()
184
+ .where({brand: {$in: ["Apple", "Dell"]}, in_stock: true}) // Using $in operator
185
+ .fields(['model', 'price']) // Only return these specific fields
186
+ .sort([{field: 'price', order: 'desc'}])
187
+ .exec();
168
188
 
169
- console.log(results);
189
+ console.log(results);
170
190
  // [
171
191
  // {
172
192
  // "_key": "lp1",
@@ -179,50 +199,57 @@ console.log(results);
179
199
  // "price": 900
180
200
  // }
181
201
  // ]
202
+ ```
182
203
 
183
- // Powerful Query Capabilities
184
- // GraphQL-style Field Selection
204
+ Powerful Query Capabilities
205
+ GraphQL-style Field Selection
185
206
 
186
- // Never over-fetch data again. Use dot-notation to extract deeply nested values.
207
+ Never over-fetch data again. Use dot-notation to extract deeply nested values.
187
208
 
209
+ ```ts
188
210
  await client.collection('laptops')
189
- .get()
190
- .fields(["brand", "specs.cpu.cores", "specs.display.refresh_hz"])
191
- .exec();
211
+ .get()
212
+ .fields(["brand", "specs.cpu.cores", "specs.display.refresh_hz"])
213
+ .exec();
214
+ ```
192
215
 
193
- // Inline Joins
216
+ Inline Joins
194
217
 
195
- // Resolve relationships between collections at query time.
218
+ Resolve relationships between collections at query time.
196
219
 
220
+ ```ts
197
221
  await client.collection('laptops')
198
- .get()
199
- .joins([{
200
- alias: 'ram',
201
- from: 'memory',
202
- on: 'memory_id',
203
- fields: ['capacity_gb', 'type']
204
- }])
205
- .exec();
222
+ .get()
223
+ .joins([{
224
+ alias: 'ram',
225
+ from: 'memory',
226
+ on: 'memory_id',
227
+ fields: ['capacity_gb', 'type']
228
+ }])
229
+ .exec();
230
+ ```
231
+
232
+ ---
206
233
 
207
- // Supported Query Operators
234
+ ## Supported Query Operators
208
235
 
209
236
  MoltenDb supports a variety of operators in the `where` clause:
210
237
 
211
- | Operator | Aliases | Description |
212
- |---|---|---|
213
- | `$eq` | `$equals` | Exact equality |
214
- | `$ne` | `$notEquals` | Not equal |
215
- | `$gt` | `$greaterThan` | Greater than (numeric) |
216
- | `$gte` | | Greater than or equal |
217
- | `$lt` | `$lessThan` | Less than (numeric) |
218
- | `$lte` | | Less than or equal |
219
- | `$contains` | `$ct` | Substring check (string) or membership check (array) |
220
- | `$in` | `$oneOf` | Field value is one of a list |
221
- | `$nin` | `$notIn` | Field value is not in a list |
222
- | `$or` | | At least one of the sub-conditions must match (array of where-style objects) |
223
- | `$and` | | All sub-conditions must match (array of where-style objects) |
224
-
225
- // Inline reference embedding (`extends`)
238
+ | Operator | Aliases | Description |
239
+ | :---------- | :------------ | :---------------------------------------------------------- |
240
+ | `$eq` | `$equals` | Exact equality |
241
+ | `$ne` | `$notEquals` | Not equal |
242
+ | `$gt` | `$greaterThan`| Greater than (numeric) |
243
+ | `$gte` | | Greater than or equal |
244
+ | `$lt` | `$lessThan` | Less than (numeric) |
245
+ | `$lte` | | Less than or equal |
246
+ | `$contains` | `$ct` | Substring check (string) or membership check (array) |
247
+ | `$in` | `$oneOf` | Field value is one of a list |
248
+ | `$nin` | `$notIn` | Field value is not in a list |
249
+ | `$or` | | At least one of the sub-conditions must match (array of where-style objects) |
250
+ | `$and` | | All sub-conditions must match (array of where-style objects) |
251
+
252
+ ## Inline reference embedding (`extends`)
226
253
 
227
254
  The `extends` key embeds data from another collection directly into the stored document at insert time β€” no join needed on reads.
228
255
 
@@ -242,16 +269,18 @@ await client.collection('laptops')
242
269
  .exec();
243
270
  ```
244
271
 
272
+ ---
245
273
  **When to use `extends` vs `joins`:**
246
274
 
247
- | | `extends` | `joins` |
248
- |---|---|---|
249
- | Resolved at | Insert time (once) | Query time (every request) |
250
- | Data freshness | Snapshot β€” may become stale | Always live |
251
- | Read cost | O(1) β€” data already embedded | O(1) per join per document |
252
- | Use when | Data rarely changes, fast reads matter | Data changes frequently, freshness matters |
275
+ | | `extends` | `joins` |
276
+ | :------------- | :------------------------------------- | :----------------------------------------- |
277
+ | Resolved at | Insert time (once) | Query time (every request) |
278
+ | Data freshness | Snapshot β€” may become stale | Always live |
279
+ | Read cost | O(1) β€” data already embedded | O(1) per join per document |
280
+ | Use when | Data rarely changes, fast reads matter | Data changes frequently, freshness matters |
253
281
 
254
282
  ---
283
+
255
284
  ## Configuration
256
285
 
257
286
  You can customise the database behavior by passing an options object to the `MoltenDb` constructor.
@@ -266,24 +295,24 @@ await db.init();
266
295
 
267
296
  ### Options Reference
268
297
 
269
- | Property | Type | Default | Description |
270
- | :--- | :--- | :--- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
271
- | `encryptionKey` | `string` | `undefined` | **At-Rest Encryption:** If provided, all data written to OPFS is encrypted using XChaCha20-Poly1305. If omitted, data is stored as plain JSON. |
272
- | `writeMode` | `'async' \| 'sync'` | `'async'` | **Durability vs Speed:** `'async'` is blazing fast (high throughput), while `'sync'` ensures every write is flushed to disk before returning (safer but slower). **Note:** `async` is recommended for most web apps to avoid blocking during heavy write bursts. |
273
- | `workerUrl` | `string \| URL` | `undefined` | Custom path to the Web Worker script. |
274
- | `maxBodySize` | `number` | `10485760` | **Payload Limit:** Max body size in bytes. Prevents memory spikes from large messages. |
275
- | `maxKeysPerRequest` | `number` | `1000` | **Batch Limit:** Maximum number of keys allowed per JSON request. |
276
- | `inMemory` | `boolean` | `false` | **Ephemeral Mode:** Run entirely in RAM β€” no OPFS writes, no WAL. All data is lost when **any** tab refreshes or closes. Ideal for CI environments and ephemeral caches. |
298
+ | Property | Type | Default | Description |
299
+ | :------------------ | :------------------ | :---------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
300
+ | `encryptionKey` | `string` | `undefined` | **At-Rest Encryption:** If provided, all data written to OPFS is encrypted using XChaCha20-Poly1305. If omitted, data is stored as plain JSON. |
301
+ | `writeMode` | `'async' \| 'sync'` | `'async'` | **Durability vs Speed:** `'async'` is blazing fast (high throughput), while `'sync'` ensures every write is flushed to disk before returning (safer but slower). **Note:** `async` is recommended for most web apps to avoid blocking during heavy write bursts. |
302
+ | `workerUrl` | `string \| URL` | `undefined` | Custom path to the Web Worker script. |
303
+ | `maxBodySize` | `number` | `10485760` | **Payload Limit:** Max body size in bytes. Prevents memory spikes from large messages. |
304
+ | `maxKeysPerRequest` | `number` | `1000` | **Batch Limit:** Maximum number of keys allowed per JSON request. |
305
+ | `inMemory` | `boolean` | `false` | **Ephemeral Mode:** Run entirely in RAM β€” no OPFS writes, no WAL. All data is lost when **any** tab refreshes or closes. Ideal for CI environments and ephemeral caches. |
277
306
 
278
307
  ---
308
+
279
309
  ## Storage Architecture
280
310
 
281
311
  ### How the Log Works
282
312
 
283
313
  MoltenDb uses an append-only JSON log. Every write is a new line, ensuring your data is safe even if the tab is closed unexpectedly.
284
314
 
285
- - **Automatic Compaction:** When the log exceeds **500 entries or 5 MB**, the engine automatically "squashes" the log, removing superseded document versions to reclaim space. No manual `compact()` calls are needed in normal operation.
286
- - **Persistence:** All data is stored in the Origin Private File System (OPFS). This is a special file system for web apps that provides much higher performance than IndexedDB.
315
+ - **Persistence:** Unlese inMemory option is selected all data is stored in the Origin Private File System (OPFS). This is a special file system for web apps that provides much higher performance than IndexedDB.
287
316
 
288
317
  ### Multi-Tab Sync
289
318
 
@@ -323,7 +352,7 @@ unsubscribe();
323
352
  The event fires on the **leader tab** (directly from the WASM engine) and is automatically broadcast over the `BroadcastChannel` so every **follower tab** receives it too. This makes it trivial to keep your UI in sync across tabs without any extra infrastructure:
324
353
 
325
354
  ```ts
326
- db.subscribe(({ event, collection, key }) => {
355
+ db.subscribe(({event, collection, key}) => {
327
356
  if (collection === 'laptops') {
328
357
  refreshLaptopList(); // re-query and re-render
329
358
  }
@@ -333,12 +362,13 @@ db.subscribe(({ event, collection, key }) => {
333
362
  The `DbEvent` type is exported from the package for full TypeScript support:
334
363
 
335
364
  ```ts
336
- import { MoltenDb, DbEvent } from '@moltendb-web/core';
365
+ import {MoltenDb, DbEvent} from '@moltendb-web/core';
337
366
 
338
367
  const db = new MoltenDb('my-app');
339
368
  await db.init();
340
369
 
341
- db.subscribe((e: DbEvent) => { /* fully typed */ });```
370
+ db.subscribe((e: DbEvent) => { /* fully typed */ });
371
+ ```
342
372
 
343
373
  ---
344
374
 
@@ -353,6 +383,22 @@ Cross-Origin-Embedder-Policy: require-corp
353
383
 
354
384
  ---
355
385
 
386
+ ## Management Methods
387
+
388
+ ### `clearOpfs()`
389
+
390
+ Truncate and close the OPFS file handle, then remove the OPFS directory.
391
+
392
+ Works from any tab β€” followers automatically route the `clear_opfs` message through the leader via BroadcastChannel, so the leader worker (which holds the exclusive FileSystemSyncAccessHandle) is the one that actually closes the file before the directory is removed.
393
+
394
+ After this resolves, call the `terminate` method and `location.reload()` if needed or re-initialize the database.
395
+
396
+ ### `terminate()`
397
+
398
+ Terminates the MoltenDb worker. Call after clearing OPFS storage.
399
+
400
+ ---
401
+
356
402
  ## Testing
357
403
 
358
404
  The core package ships with a comprehensive test suite built on **Vitest**:
@@ -365,20 +411,20 @@ npm run test:coverage # with coverage report
365
411
 
366
412
  ### What's covered
367
413
 
368
- | Suite | Tests | What it verifies |
369
- |---|---|---|
370
- | `init()` | 5 | Leader election, idempotency, worker error propagation |
371
- | CRUD β€” leader | 9 | set/get/delete/getAll round-trips, collection isolation |
372
- | CRUD β€” follower | 3 | BroadcastChannel proxy path for all mutations |
373
- | Worker error handling | 3 | Transient errors, unknown actions, request isolation |
374
- | Leader promotion | 2 | Follower takes over when leader tab closes |
375
- | `Pub/Sub (subscribe)` | 2 | Multi-subscriber event delivery across tabs |
376
- | Follower timeout | 1 | Pending requests reject after 10 s if leader disappears |
377
- | `terminate` / `disconnect` | 3 | Worker cleanup, timer teardown |
378
- | Stress β€” rapid writes | 3 | 100 sequential, 50 concurrent, interleaved set/delete |
379
- | BC name isolation | 2 | Two databases on the same origin don't bleed data |
380
- | Bulk insert stress | 3 | 1 000 concurrent sets, 500 mixed ops, compact under pressure |
381
- | Multi-tab parallel stress | 4 | 3 tabs Γ— 100 writes, ID collision safety, follower reads after burst, promotion under load |
414
+ | Suite | Tests | What it verifies |
415
+ | :------------------------- | :---- | :----------------------------------------------------------------------------------------- |
416
+ | `init()` | 5 | Leader election, idempotency, worker error propagation |
417
+ | CRUD β€” leader | 9 | set/get/delete/getAll round-trips, collection isolation |
418
+ | CRUD β€” follower | 3 | BroadcastChannel proxy path for all mutations |
419
+ | Worker error handling | 3 | Transient errors, unknown actions, request isolation |
420
+ | Leader promotion | 2 | Follower takes over when leader tab closes |
421
+ | `Pub/Sub (subscribe)` | 2 | Multi-subscriber event delivery across tabs |
422
+ | Follower timeout | 1 | Pending requests reject after 10 s if leader disappears |
423
+ | `terminate` / `disconnect` | 3 | Worker cleanup, timer teardown |
424
+ | Stress β€” rapid writes | 3 | 100 sequential, 50 concurrent, interleaved set/delete |
425
+ | BC name isolation | 2 | Two databases on the same origin don't bleed data |
426
+ | Bulk insert stress | 3 | 1 000 concurrent sets, 500 mixed ops, compact under pressure |
427
+ | Multi-tab parallel stress | 4 | 3 tabs Γ— 100 writes, ID collision safety, follower reads after burst, promotion under load |
382
428
 
383
429
  **Total: 50 tests β€” all green.**
384
430
 
@@ -394,16 +440,13 @@ This monorepo contains the following packages:
394
440
  ## Roadmap
395
441
 
396
442
  - [x] **Multi-Tab Sync:** Leader election for multiple tabs to share a single OPFS instance.
397
- - [x] **Automatic Compaction:** Log compacts automatically at 500 entries or 5 MB.
398
443
  - [x] **Rich Test Suite:** 50 unit, integration, and stress tests via Vitest.
399
- - [ ] **React Adapter:** Official `@moltendb-web/react` package with `useQuery` hooks and real-time context providers.
444
+ - [x] **React Adapter:** Official `@moltendb-web/react` package with `useQuery` hooks and real-time context providers.
400
445
  - [x] **Angular Adapter:** Official `@moltendb-web/angular` package featuring Signal-based data fetching.
401
446
  - [ ] **Delta Sync:** Automatic two-way sync with the MoltenDb Rust server.
402
447
  - [x] **Data Encryption:** Transparent encryption-at-rest using hardware-backed keys (Argon2id + XChaCha20).
403
- - [x] **Hybrid Bitcask:** Seamlessly handle datasets larger than RAM by paging docs to OPFS.
404
448
  - [ ] **Analytics Functionality:** Run complex analytics queries straight in the browser without blocking the UI.
405
- - [x] **Configurable Limits:** User-defined RAM thresholds and request body sizes for edge and browser environments.
406
-
449
+ - [x] **Configurable Limits:** User-defined request body sizes for edge and browser environments.
407
450
 
408
451
  ## Contributing & Feedback
409
452
 
@@ -415,6 +458,4 @@ Found a bug or have a feature request? Please open an issue on the [GitHub issue
415
458
 
416
459
  The MoltenDb Web packages (`@moltendb-web/core` and `@moltendb-web/query`) are licensed under the MIT License.
417
460
 
418
- The **MoltenDb Server** (Rust backend) remains under the Business Source License 1.1 (Free for organizations under $5M revenue, requires a license for managed services).
419
-
420
- For commercial licensing or questions: [maximilian.both27@outlook.com](mailto:maximilian.both27@outlook.com)
461
+ The **MoltenDb Server** (Rust backend) remains under the Business Source License 1.1 (Free for organizations under $5M revenue, requires a license for managed services).
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export interface MoltenDbOptions {
4
4
  /** Password for at-rest encryption. If not provided, data is stored as plain JSON. */
5
5
  encryptionKey?: string;
6
6
  /** Storage write mode: 'async' (default, high throughput) or 'sync' (durable). */
7
- writeMode?: 'async' | 'sync';
7
+ writeMode?: "async" | "sync";
8
8
  /** Maximum request body size in bytes. */
9
9
  maxBodySize?: number;
10
10
  /** Maximum number of keys allowed per JSON request. Default: 1000. */
@@ -21,8 +21,8 @@ export interface MoltenDbOptions {
21
21
  inMemory?: boolean;
22
22
  }
23
23
  export interface DbEvent {
24
- type: 'event';
25
- event: 'change' | 'delete' | 'drop';
24
+ type: "event";
25
+ event: "change" | "delete" | "drop" | "kill" | "ttl_expiry";
26
26
  collection: string;
27
27
  key: string;
28
28
  new_v: number | null;
@@ -32,12 +32,12 @@ export declare class MoltenDb {
32
32
  readonly workerUrl?: string | URL;
33
33
  readonly options: MoltenDbOptions;
34
34
  worker: Worker | null;
35
- private initPromise;
36
- private pendingRequests;
37
35
  isLeader: boolean;
38
- private bc;
39
36
  /** Legacy global hook. Use `subscribe()` for multi-component listeners. */
40
37
  onEvent?: (event: DbEvent) => void;
38
+ private initPromise;
39
+ private pendingRequests;
40
+ private bc;
41
41
  private eventListeners;
42
42
  constructor(dbName?: string, options?: MoltenDbOptions);
43
43
  /**
@@ -47,10 +47,7 @@ export declare class MoltenDb {
47
47
  subscribe(listener: (event: DbEvent) => void): () => void;
48
48
  /** Manually remove a specific listener */
49
49
  unsubscribe(listener: (event: DbEvent) => void): void;
50
- private dispatchEvent;
51
50
  init(): Promise<void>;
52
- private startAsLeader;
53
- private startAsFollower;
54
51
  sendMessage(action: string, payload?: Record<string, unknown>): Promise<any>;
55
52
  set(collection: string, key: string, value: any): Promise<void>;
56
53
  get(collection: string, key: string): Promise<unknown>;
@@ -58,19 +55,20 @@ export declare class MoltenDb {
58
55
  delete(collection: string, key: string): Promise<void>;
59
56
  compact(): Promise<unknown>;
60
57
  /**
61
- * Truncate and close the OPFS file so the directory can be removed.
58
+ * Truncate and close the OPFS file handle, then remove the OPFS directory.
62
59
  *
63
- * Works from any tab β€” followers automatically route this through the leader
64
- * via BroadcastChannel, so the leader worker (which holds the exclusive
65
- * FileSystemSyncAccessHandle) is the one that actually closes the file.
60
+ * Works from any tab β€” followers automatically route the `clear_opfs` message
61
+ * through the leader via BroadcastChannel, so the leader worker (which holds
62
+ * the exclusive FileSystemSyncAccessHandle) is the one that actually closes
63
+ * the file before the directory is removed.
66
64
  *
67
- * After this resolves, call:
68
- * `const root = await navigator.storage.getDirectory();`
69
- * `const root = await navigator.storage.getDirectory();`
70
- * `await root.removeEntry(dbName, { recursive: true });`
71
- * then reload the page.
65
+ * After this resolves, call the `terminate` method and `location.reload()` if needed or re-initialize the database.
72
66
  */
73
- clearOpfs(): Promise<unknown>;
67
+ clearOpfs(): Promise<void>;
74
68
  disconnect(): void;
69
+ /** Terminates the MoltenDb worker. Call after clearing OPFS storage. */
75
70
  terminate(): void;
71
+ private dispatchEvent;
72
+ private startAsLeader;
73
+ private startAsFollower;
76
74
  }
package/dist/index.js CHANGED
@@ -4,16 +4,16 @@ export class MoltenDb {
4
4
  workerUrl;
5
5
  options;
6
6
  worker = null;
7
- initPromise = null;
8
- pendingRequests = new Map();
9
7
  // Multi-tab Sync State
10
8
  isLeader = false;
11
- bc;
12
9
  /** Legacy global hook. Use `subscribe()` for multi-component listeners. */
13
10
  onEvent;
11
+ initPromise = null;
12
+ pendingRequests = new Map();
13
+ bc;
14
14
  // ── Multi-Subscriber Event System ──────────────────────────────────────────
15
15
  eventListeners = new Set();
16
- constructor(dbName = 'moltendb', options = {}) {
16
+ constructor(dbName = "moltendb", options = {}) {
17
17
  this.dbName = dbName;
18
18
  this.workerUrl = options.workerUrl;
19
19
  this.options = options;
@@ -30,18 +30,6 @@ export class MoltenDb {
30
30
  unsubscribe(listener) {
31
31
  this.eventListeners.delete(listener);
32
32
  }
33
- dispatchEvent(event) {
34
- // Fire all subscribed component handlers
35
- for (const listener of this.eventListeners) {
36
- try {
37
- listener(event);
38
- }
39
- catch (err) {
40
- console.error('[MoltenDb] Error in subscribed listener', err);
41
- }
42
- }
43
- }
44
- // ───────────────────────────────────────────────────────────────────────────
45
33
  init() {
46
34
  // 1. If initialization has already started or finished, return the existing promise
47
35
  if (this.initPromise)
@@ -49,9 +37,9 @@ export class MoltenDb {
49
37
  // 2. When running in-memory, any tab refresh should wipe the shared RAM store.
50
38
  // Broadcast a clear_all signal on beforeunload so the leader can wipe the Rust DashMap.
51
39
  if (this.options.inMemory) {
52
- window.addEventListener('beforeunload', () => {
40
+ window.addEventListener("beforeunload", () => {
53
41
  try {
54
- this.bc?.postMessage({ type: 'clear_all' });
42
+ this.bc?.postMessage({ type: "clear_all" });
55
43
  }
56
44
  catch { }
57
45
  });
@@ -84,118 +72,20 @@ export class MoltenDb {
84
72
  });
85
73
  return this.initPromise;
86
74
  }
87
- async startAsLeader() {
88
- // Guard: OPFS is required
89
- if (!this.options.inMemory) {
90
- try {
91
- await navigator.storage.getDirectory();
92
- }
93
- catch {
94
- throw new Error('[MoltenDb] Origin Private File System (OPFS) is not available in this browser context. ' +
95
- 'Try a non-private window or a browser that supports OPFS (Chrome 102+, Firefox 111+, Safari 15.2+).');
96
- }
97
- }
98
- this.isLeader = true;
99
- if (this.worker)
100
- this.worker.terminate();
101
- const url = this.workerUrl || new URL('./moltendb-worker.js', import.meta.url);
102
- this.worker = new Worker(url, { type: 'module', name: `moltendb-${this.dbName}-leader` });
103
- this.worker.onmessage = (e) => {
104
- const data = e.data;
105
- if (data.type === 'event') {
106
- this.dispatchEvent(data); // ⬅️ Trigger new dispatcher
107
- this.bc.postMessage(data);
108
- return;
109
- }
110
- const req = this.pendingRequests.get(data.id);
111
- if (req) {
112
- if (data.error)
113
- req.reject(new Error(data.error));
114
- else
115
- req.resolve(data.result);
116
- this.pendingRequests.delete(data.id);
117
- }
118
- };
119
- // Wait for worker to boot
120
- await this.sendMessage('init', {
121
- dbName: this.dbName,
122
- encryptionKey: this.options.encryptionKey,
123
- inMemory: this.options.inMemory,
124
- maxBodySize: this.options.maxBodySize,
125
- maxKeysPerRequest: this.options.maxKeysPerRequest,
126
- writeMode: this.options.writeMode,
127
- });
128
- this.bc.onmessage = async (e) => {
129
- const msg = e.data;
130
- // Any tab unloading in in-memory mode broadcasts this β€” wipe the shared RAM store.
131
- if (msg.type === 'clear_all') {
132
- try {
133
- await this.sendMessage('clear', {});
134
- this.bc.postMessage({ type: 'cleared' });
135
- console.log('[MoltenDb] In-memory store wiped (tab unloaded).');
136
- }
137
- catch (err) {
138
- console.warn('[MoltenDb] Failed to clear in-memory store:', err);
139
- }
140
- return;
141
- }
142
- if (msg.type === 'query' && msg.action) {
143
- try {
144
- const result = await this.sendMessage(msg.action, msg.payload);
145
- this.bc.postMessage({ type: 'response', id: msg.id, result });
146
- }
147
- catch (err) {
148
- this.bc.postMessage({ type: 'response', id: msg.id, error: err.message });
149
- }
150
- }
151
- };
152
- }
153
- startAsFollower() {
154
- this.isLeader = false;
155
- if (this.worker) {
156
- this.worker.terminate();
157
- this.worker = null;
158
- }
159
- this.bc.onmessage = (e) => {
160
- const data = e.data;
161
- if (data.type === 'event') {
162
- this.dispatchEvent(data); // ⬅️ Trigger new dispatcher
163
- return;
164
- }
165
- // In-memory wipe notification from leader β€” reject all in-flight requests.
166
- if (data.type === 'cleared') {
167
- console.log('[MoltenDb] In-memory store was wiped by another tab.');
168
- for (const [id, req] of this.pendingRequests) {
169
- req.reject(new Error('[MoltenDb] In-memory store was cleared by a tab reload.'));
170
- this.pendingRequests.delete(id);
171
- }
172
- return;
173
- }
174
- if (data.type === 'response') {
175
- const req = this.pendingRequests.get(data.id);
176
- if (req) {
177
- if (data.error)
178
- req.reject(new Error(data.error));
179
- else
180
- req.resolve(data.result);
181
- this.pendingRequests.delete(data.id);
182
- }
183
- }
184
- };
185
- }
75
+ // ───────────────────────────────────────────────────────────────────────────
186
76
  async sendMessage(action, payload) {
187
77
  // Wait for the engine to boot before routing the message.
188
78
  // If the DB is already initialized, this resolves instantly.
189
- if (action !== 'init') {
79
+ if (action !== "init") {
190
80
  if (this.initPromise) {
191
81
  await this.initPromise;
192
82
  }
193
83
  else {
194
- throw new Error('[MoltenDb] You must call db.init() before querying the database.');
84
+ throw new Error("[MoltenDb] You must call db.init() before querying the database.");
195
85
  }
196
86
  }
197
87
  // 2. Generate a unique ID
198
- const id = (typeof crypto !== 'undefined' && crypto.randomUUID)
88
+ const id = typeof crypto !== "undefined" && crypto.randomUUID
199
89
  ? crypto.randomUUID()
200
90
  : Math.random().toString(36).substring(2, 9);
201
91
  return new Promise((resolve, reject) => {
@@ -214,19 +104,25 @@ export class MoltenDb {
214
104
  }
215
105
  }, 10000);
216
106
  this.pendingRequests.set(id, {
217
- resolve: (res) => { clearTimeout(timer); successHandler(res); },
218
- reject: (e) => { clearTimeout(timer); reject(e); }
107
+ resolve: (res) => {
108
+ clearTimeout(timer);
109
+ successHandler(res);
110
+ },
111
+ reject: (e) => {
112
+ clearTimeout(timer);
113
+ reject(e);
114
+ },
219
115
  });
220
- this.bc.postMessage({ type: 'query', id, action, payload });
116
+ this.bc.postMessage({ type: "query", id, action, payload });
221
117
  }
222
118
  });
223
119
  } // ── Convenience CRUD helpers ───────────────────────────────────────────────
224
120
  async set(collection, key, value) {
225
- await this.sendMessage('set', { collection, data: { [key]: value } });
121
+ await this.sendMessage("set", { collection, data: { [key]: value } });
226
122
  }
227
123
  async get(collection, key) {
228
124
  try {
229
- return await this.sendMessage('get', { collection, keys: key });
125
+ return await this.sendMessage("get", { collection, keys: key });
230
126
  }
231
127
  catch (err) {
232
128
  try {
@@ -240,7 +136,7 @@ export class MoltenDb {
240
136
  }
241
137
  async getAll(collection) {
242
138
  try {
243
- const result = await this.sendMessage('get', { collection });
139
+ const result = await this.sendMessage("get", { collection });
244
140
  return result || [];
245
141
  }
246
142
  catch (err) {
@@ -254,36 +150,184 @@ export class MoltenDb {
254
150
  }
255
151
  }
256
152
  async delete(collection, key) {
257
- await this.sendMessage('delete', { collection, keys: key });
153
+ await this.sendMessage("delete", { collection, keys: key });
258
154
  }
259
155
  compact() {
260
- return this.sendMessage('compact');
156
+ return this.sendMessage("compact");
261
157
  }
262
158
  /**
263
- * Truncate and close the OPFS file so the directory can be removed.
159
+ * Truncate and close the OPFS file handle, then remove the OPFS directory.
264
160
  *
265
- * Works from any tab β€” followers automatically route this through the leader
266
- * via BroadcastChannel, so the leader worker (which holds the exclusive
267
- * FileSystemSyncAccessHandle) is the one that actually closes the file.
161
+ * Works from any tab β€” followers automatically route the `clear_opfs` message
162
+ * through the leader via BroadcastChannel, so the leader worker (which holds
163
+ * the exclusive FileSystemSyncAccessHandle) is the one that actually closes
164
+ * the file before the directory is removed.
268
165
  *
269
- * After this resolves, call:
270
- * `const root = await navigator.storage.getDirectory();`
271
- * `const root = await navigator.storage.getDirectory();`
272
- * `await root.removeEntry(dbName, { recursive: true });`
273
- * then reload the page.
166
+ * After this resolves, call the `terminate` method and `location.reload()` if needed or re-initialize the database.
274
167
  */
275
- clearOpfs() {
276
- return this.sendMessage('clear_opfs');
168
+ async clearOpfs() {
169
+ // 1. Tell Rust to flush, truncate, and CLOSE the FileSystemSyncAccessHandle.
170
+ // Without this, removeEntry() throws "No modification allowed".
171
+ await this.sendMessage("clear_opfs");
172
+ // 2. Remove the OPFS directory β€” the handle is now closed, so this succeeds.
173
+ const root = await navigator.storage.getDirectory();
174
+ await root.removeEntry(this.dbName, { recursive: true });
175
+ // 3. Hit the Kill Switch for all other tabs
176
+ this.bc?.postMessage({ type: "kill_signal" });
277
177
  }
278
178
  disconnect() {
279
179
  if (this.bc)
280
180
  this.bc.close();
281
181
  }
182
+ /** Terminates the MoltenDb worker. Call after clearing OPFS storage. */
282
183
  terminate() {
283
184
  this.disconnect();
284
185
  if (this.worker) {
285
186
  this.worker.terminate();
286
187
  this.worker = null;
287
188
  }
189
+ this.initPromise = null;
190
+ this.isLeader = false;
191
+ }
192
+ dispatchEvent(event) {
193
+ // Fire all subscribed component handlers
194
+ for (const listener of this.eventListeners) {
195
+ try {
196
+ listener(event);
197
+ }
198
+ catch (err) {
199
+ console.error("[MoltenDb] Error in subscribed listener", err);
200
+ }
201
+ }
202
+ }
203
+ async startAsLeader() {
204
+ // Guard: OPFS is required
205
+ if (!this.options.inMemory) {
206
+ try {
207
+ await navigator.storage.getDirectory();
208
+ }
209
+ catch {
210
+ throw new Error("[MoltenDb] Origin Private File System (OPFS) is not available in this browser context. " +
211
+ "Try a non-private window or a browser that supports OPFS (Chrome 102+, Firefox 111+, Safari 15.2+).");
212
+ }
213
+ }
214
+ this.isLeader = true;
215
+ if (this.worker)
216
+ this.worker.terminate();
217
+ const url = this.workerUrl || new URL("./moltendb-worker.js", import.meta.url);
218
+ this.worker = new Worker(url, {
219
+ type: "module",
220
+ name: `moltendb-${this.dbName}-leader`,
221
+ });
222
+ this.worker.onmessage = (e) => {
223
+ const data = e.data;
224
+ if (data.type === "event") {
225
+ this.dispatchEvent(data); // ⬅️ Trigger new dispatcher
226
+ this.bc.postMessage(data);
227
+ return;
228
+ }
229
+ const req = this.pendingRequests.get(data.id);
230
+ if (req) {
231
+ if (data.error)
232
+ req.reject(new Error(data.error));
233
+ else
234
+ req.resolve(data.result);
235
+ this.pendingRequests.delete(data.id);
236
+ }
237
+ };
238
+ // Wait for worker to boot
239
+ await this.sendMessage("init", {
240
+ dbName: this.dbName,
241
+ encryptionKey: this.options.encryptionKey,
242
+ inMemory: this.options.inMemory,
243
+ maxBodySize: this.options.maxBodySize,
244
+ maxKeysPerRequest: this.options.maxKeysPerRequest,
245
+ writeMode: this.options.writeMode,
246
+ });
247
+ this.bc.onmessage = async (e) => {
248
+ const msg = e.data;
249
+ // --- THE KILL SWITCH ---
250
+ if (msg.type === "kill_signal") {
251
+ this.terminate(); // Kills zombie worker instantly
252
+ this.dispatchEvent({
253
+ type: "event",
254
+ event: "kill",
255
+ collection: "*",
256
+ key: "*",
257
+ new_v: null,
258
+ });
259
+ return;
260
+ }
261
+ // Any tab unloading in in-memory mode broadcasts this β€” wipe the shared RAM store.
262
+ if (msg.type === "clear_all") {
263
+ try {
264
+ await this.sendMessage("clear", {});
265
+ this.bc.postMessage({ type: "cleared" });
266
+ console.log("[MoltenDb] In-memory store wiped (tab unloaded).");
267
+ }
268
+ catch (err) {
269
+ console.warn("[MoltenDb] Failed to clear in-memory store:", err);
270
+ }
271
+ return;
272
+ }
273
+ if (msg.type === "query" && msg.action) {
274
+ try {
275
+ const result = await this.sendMessage(msg.action, msg.payload);
276
+ this.bc.postMessage({ type: "response", id: msg.id, result });
277
+ }
278
+ catch (err) {
279
+ this.bc.postMessage({
280
+ type: "response",
281
+ id: msg.id,
282
+ error: err.message,
283
+ });
284
+ }
285
+ }
286
+ };
287
+ }
288
+ startAsFollower() {
289
+ this.isLeader = false;
290
+ if (this.worker) {
291
+ this.worker.terminate();
292
+ this.worker = null;
293
+ }
294
+ this.bc.onmessage = (e) => {
295
+ const data = e.data;
296
+ // --- THE KILKILL SWITCH ---
297
+ if (data.type === "kill_signal") {
298
+ this.terminate(); // Kills zombie worker instantly
299
+ this.dispatchEvent({
300
+ type: "event",
301
+ event: "kill",
302
+ collection: "*",
303
+ key: "*",
304
+ new_v: null,
305
+ });
306
+ return;
307
+ }
308
+ if (data.type === "event") {
309
+ this.dispatchEvent(data); // ⬅️ Trigger new dispatcher
310
+ return;
311
+ }
312
+ // In-memory wipe notification from leader β€” reject all in-flight requests.
313
+ if (data.type === "cleared") {
314
+ console.log("[MoltenDb] In-memory store was wiped by another tab.");
315
+ for (const [id, req] of this.pendingRequests) {
316
+ req.reject(new Error("[MoltenDb] In-memory store was cleared by a tab reload."));
317
+ this.pendingRequests.delete(id);
318
+ }
319
+ return;
320
+ }
321
+ if (data.type === "response") {
322
+ const req = this.pendingRequests.get(data.id);
323
+ if (req) {
324
+ if (data.error)
325
+ req.reject(new Error(data.error));
326
+ else
327
+ req.resolve(data.result);
328
+ this.pendingRequests.delete(data.id);
329
+ }
330
+ }
331
+ };
288
332
  }
289
333
  }
@@ -1,10 +1,10 @@
1
- import init, { WorkerDb } from './wasm/moltendb_core.js';
1
+ import init, { WorkerDb } from "./wasm/moltendb_core.js";
2
2
  let db = null;
3
3
  let initPromise = null;
4
4
  self.onmessage = async (e) => {
5
5
  const { id, action, ...payload } = e.data;
6
6
  // --- 1. Initialization Phase ---
7
- if (action === 'init') {
7
+ if (action === "init") {
8
8
  if (!initPromise) {
9
9
  initPromise = (async () => {
10
10
  await init();
@@ -14,10 +14,10 @@ self.onmessage = async (e) => {
14
14
  instance.subscribe((eventStr) => {
15
15
  try {
16
16
  const eventData = JSON.parse(eventStr);
17
- self.postMessage({ type: 'event', ...eventData });
17
+ self.postMessage({ type: "event", ...eventData });
18
18
  }
19
19
  catch (err) {
20
- console.error('[MoltenDb Worker] Event parse error', err);
20
+ console.error("[MoltenDb Worker] Event parse error", err);
21
21
  }
22
22
  });
23
23
  db = instance;
@@ -26,11 +26,11 @@ self.onmessage = async (e) => {
26
26
  }
27
27
  try {
28
28
  await initPromise;
29
- self.postMessage({ id, result: { status: 'ok' } });
29
+ self.postMessage({ id, result: { status: "ok" } });
30
30
  }
31
31
  catch (error) {
32
32
  // FIX: Handle Map-based errors from Rust correctly
33
- const errorMsg = (error instanceof Map)
33
+ const errorMsg = error instanceof Map
34
34
  ? JSON.stringify(Object.fromEntries(error))
35
35
  : String(error);
36
36
  self.postMessage({ id, error: errorMsg });
@@ -47,7 +47,7 @@ self.onmessage = async (e) => {
47
47
  }
48
48
  catch (error) {
49
49
  // FIX: Handle Map-based errors here too
50
- const errorMsg = (error instanceof Map)
50
+ const errorMsg = error instanceof Map
51
51
  ? JSON.stringify(Object.fromEntries(error))
52
52
  : String(error);
53
53
  self.postMessage({ id, error: errorMsg });
@@ -58,7 +58,6 @@ export class WorkerDb {
58
58
  * - "update" β†’ patch/merge documents: { collection, data: { key: patch, ... } }
59
59
  * - "delete" β†’ delete documents or drop: { collection, keys: ... } or { drop: true }
60
60
  * - "compact" β†’ compact the OPFS log file
61
- * - "get_size" β†’ return current OPFS file size in bytes
62
61
  * - "clear" β†’ wipe all in-memory state (in-memory mode only)
63
62
  *
64
63
  * Returns a JsValue result on success, or a JsValue error string on failure.
@@ -80,8 +79,8 @@ export interface InitOutput {
80
79
  readonly workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => number;
81
80
  readonly workerdb_handle_message: (a: number, b: number, c: number) => void;
82
81
  readonly workerdb_subscribe: (a: number, b: number) => void;
83
- readonly __wasm_bindgen_func_elem_3769: (a: number, b: number, c: number, d: number) => void;
84
- readonly __wasm_bindgen_func_elem_3781: (a: number, b: number, c: number, d: number) => void;
82
+ readonly __wasm_bindgen_func_elem_4166: (a: number, b: number, c: number, d: number) => void;
83
+ readonly __wasm_bindgen_func_elem_4178: (a: number, b: number, c: number, d: number) => void;
85
84
  readonly __wbindgen_export: (a: number, b: number) => number;
86
85
  readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
87
86
  readonly __wbindgen_export3: (a: number) => void;
@@ -87,7 +87,6 @@ export class WorkerDb {
87
87
  * - "update" β†’ patch/merge documents: { collection, data: { key: patch, ... } }
88
88
  * - "delete" β†’ delete documents or drop: { collection, keys: ... } or { drop: true }
89
89
  * - "compact" β†’ compact the OPFS log file
90
- * - "get_size" β†’ return current OPFS file size in bytes
91
90
  * - "clear" β†’ wipe all in-memory state (in-memory mode only)
92
91
  *
93
92
  * Returns a JsValue result on success, or a JsValue error string on failure.
@@ -232,9 +231,6 @@ function __wbg_get_imports() {
232
231
  const ret = Object.entries(getObject(arg0));
233
232
  return addHeapObject(ret);
234
233
  },
235
- __wbg_error_2001591ad2463697: function(arg0) {
236
- console.error(getObject(arg0));
237
- },
238
234
  __wbg_error_a6fa202b58aa1cd3: function(arg0, arg1) {
239
235
  let deferred0_0;
240
236
  let deferred0_1;
@@ -377,7 +373,7 @@ function __wbg_get_imports() {
377
373
  const a = state0.a;
378
374
  state0.a = 0;
379
375
  try {
380
- return __wasm_bindgen_func_elem_3781(a, state0.b, arg0, arg1);
376
+ return __wasm_bindgen_func_elem_4178(a, state0.b, arg0, arg1);
381
377
  } finally {
382
378
  state0.a = a;
383
379
  }
@@ -516,8 +512,8 @@ function __wbg_get_imports() {
516
512
  return ret;
517
513
  }, arguments); },
518
514
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
519
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 724, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
520
- const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_3769);
515
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 735, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
516
+ const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_4166);
521
517
  return addHeapObject(ret);
522
518
  },
523
519
  __wbindgen_cast_0000000000000002: function(arg0) {
@@ -559,10 +555,10 @@ function __wbg_get_imports() {
559
555
  };
560
556
  }
561
557
 
562
- function __wasm_bindgen_func_elem_3769(arg0, arg1, arg2) {
558
+ function __wasm_bindgen_func_elem_4166(arg0, arg1, arg2) {
563
559
  try {
564
560
  const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
565
- wasm.__wasm_bindgen_func_elem_3769(retptr, arg0, arg1, addHeapObject(arg2));
561
+ wasm.__wasm_bindgen_func_elem_4166(retptr, arg0, arg1, addHeapObject(arg2));
566
562
  var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
567
563
  var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
568
564
  if (r1) {
@@ -573,8 +569,8 @@ function __wasm_bindgen_func_elem_3769(arg0, arg1, arg2) {
573
569
  }
574
570
  }
575
571
 
576
- function __wasm_bindgen_func_elem_3781(arg0, arg1, arg2, arg3) {
577
- wasm.__wasm_bindgen_func_elem_3781(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
572
+ function __wasm_bindgen_func_elem_4178(arg0, arg1, arg2, arg3) {
573
+ wasm.__wasm_bindgen_func_elem_4178(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
578
574
  }
579
575
 
580
576
  const WorkerDbFinalization = (typeof FinalizationRegistry === 'undefined')
Binary file
@@ -5,8 +5,8 @@ export const __wbg_workerdb_free: (a: number, b: number) => void;
5
5
  export const workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => number;
6
6
  export const workerdb_handle_message: (a: number, b: number, c: number) => void;
7
7
  export const workerdb_subscribe: (a: number, b: number) => void;
8
- export const __wasm_bindgen_func_elem_3769: (a: number, b: number, c: number, d: number) => void;
9
- export const __wasm_bindgen_func_elem_3781: (a: number, b: number, c: number, d: number) => void;
8
+ export const __wasm_bindgen_func_elem_4166: (a: number, b: number, c: number, d: number) => void;
9
+ export const __wasm_bindgen_func_elem_4178: (a: number, b: number, c: number, d: number) => void;
10
10
  export const __wbindgen_export: (a: number, b: number) => number;
11
11
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
12
12
  export const __wbindgen_export3: (a: number) => void;
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@moltendb-web/core",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "MoltenDb WASM runtime β€” the database engine, Web Worker, and main-thread client in one package.",
5
5
  "type": "module",
6
- "author": "Maximilian Both <maximilian.both27@outlook.com>",
6
+ "author": "Maximilian Both <admin@moltendb.dev",
7
7
  "license": "MIT",
8
8
  "repository": {
9
9
  "type": "git",
@@ -43,6 +43,7 @@
43
43
  "devDependencies": {
44
44
  "@playwright/test": "^1.58.2",
45
45
  "@vitest/coverage-v8": "^4.1.1",
46
+ "@vitest/utils": "^4.1.5",
46
47
  "copyfiles": "^2.4.1",
47
48
  "happy-dom": "^20.8.7",
48
49
  "typescript": "^6.0.2",