@moltendb-web/core 1.7.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 +164 -126
- package/dist/index.d.ts +21 -11
- package/dist/index.js +184 -125
- package/dist/moltendb-worker.js +8 -8
- package/dist/wasm/moltendb_core.d.ts +4 -29
- package/dist/wasm/moltendb_core.js +9 -64
- package/dist/wasm/moltendb_core_bg.wasm +0 -0
- package/dist/wasm/moltendb_core_bg.wasm.d.ts +3 -4
- package/package.json +3 -2
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
|
-
|
|
7
|
-
**High-performance Rust engine compiled to WASM. Persistent storage via OPFS.**
|
|
6
|
+
### π The Embedded Database for the Modern Web
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
**High-performance Rust engine compiled to WASM. Persistent storage via OPFS.**
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
+
[](https://www.npmjs.com/package/@moltendb-web/core)
|
|
13
|
+
[](LICENSE.md)
|
|
14
|
+
[](https://webassembly.org/)
|
|
15
|
+
[]()
|
|
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
|
-
|
|
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
|
|
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: {
|
|
87
|
+
optimizeDeps: {exclude: ['@moltendb-web/core']}
|
|
73
88
|
});
|
|
74
89
|
```
|
|
75
90
|
|
|
@@ -79,23 +94,25 @@ 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: [{
|
|
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 {
|
|
95
|
-
import {
|
|
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
|
-
hotThreshold: 25000, // Keep 25k docs in RAM per collection
|
|
99
116
|
encryptionKey: 'my-secret', // Enable transparent at-rest encryption
|
|
100
117
|
writeMode: 'sync' // Ensure every write is flushed to OPFS
|
|
101
118
|
});
|
|
@@ -103,36 +120,38 @@ await db.init();
|
|
|
103
120
|
|
|
104
121
|
// Connect the query builder to the WASM worker
|
|
105
122
|
const client = new MoltenDbClient(db);
|
|
123
|
+
```
|
|
106
124
|
|
|
107
|
-
|
|
125
|
+
2. Insert and Query
|
|
108
126
|
|
|
109
|
-
|
|
127
|
+
Use the `@moltendb-web/query` builder for a type-safe experience.
|
|
110
128
|
|
|
129
|
+
```ts
|
|
111
130
|
// Insert data
|
|
112
131
|
await client.collection('laptops').set({
|
|
113
132
|
lp1: {
|
|
114
133
|
brand: "Apple",
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
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,
|
|
127
145
|
}
|
|
146
|
+
}
|
|
128
147
|
},
|
|
129
148
|
lp2: {
|
|
130
149
|
brand: "Apple",
|
|
131
150
|
model: "MacBook Air",
|
|
132
151
|
price: 900,
|
|
133
152
|
in_stock: true,
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
memory_id: 'mem2',
|
|
154
|
+
specs: {
|
|
136
155
|
cpu: {
|
|
137
156
|
cores: 4,
|
|
138
157
|
clock_speed: 3.5,
|
|
@@ -145,29 +164,29 @@ await client.collection('laptops').set({
|
|
|
145
164
|
}).exec();
|
|
146
165
|
|
|
147
166
|
await client.collection('memory').set({
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
+
},
|
|
160
179
|
}).exec();
|
|
161
180
|
|
|
162
181
|
// Query with field selection
|
|
163
182
|
const results = await client.collection('laptops')
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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();
|
|
169
188
|
|
|
170
|
-
console.log(results);
|
|
189
|
+
console.log(results);
|
|
171
190
|
// [
|
|
172
191
|
// {
|
|
173
192
|
// "_key": "lp1",
|
|
@@ -180,50 +199,57 @@ console.log(results);
|
|
|
180
199
|
// "price": 900
|
|
181
200
|
// }
|
|
182
201
|
// ]
|
|
202
|
+
```
|
|
183
203
|
|
|
184
|
-
|
|
185
|
-
|
|
204
|
+
Powerful Query Capabilities
|
|
205
|
+
GraphQL-style Field Selection
|
|
186
206
|
|
|
187
|
-
|
|
207
|
+
Never over-fetch data again. Use dot-notation to extract deeply nested values.
|
|
188
208
|
|
|
209
|
+
```ts
|
|
189
210
|
await client.collection('laptops')
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
211
|
+
.get()
|
|
212
|
+
.fields(["brand", "specs.cpu.cores", "specs.display.refresh_hz"])
|
|
213
|
+
.exec();
|
|
214
|
+
```
|
|
193
215
|
|
|
194
|
-
|
|
216
|
+
Inline Joins
|
|
195
217
|
|
|
196
|
-
|
|
218
|
+
Resolve relationships between collections at query time.
|
|
197
219
|
|
|
220
|
+
```ts
|
|
198
221
|
await client.collection('laptops')
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
---
|
|
207
233
|
|
|
208
|
-
|
|
234
|
+
## Supported Query Operators
|
|
209
235
|
|
|
210
236
|
MoltenDb supports a variety of operators in the `where` clause:
|
|
211
237
|
|
|
212
|
-
| Operator
|
|
213
|
-
|
|
214
|
-
| `$eq`
|
|
215
|
-
| `$ne`
|
|
216
|
-
| `$gt`
|
|
217
|
-
| `$gte`
|
|
218
|
-
| `$lt`
|
|
219
|
-
| `$lte`
|
|
220
|
-
| `$contains` | `$ct`
|
|
221
|
-
| `$in`
|
|
222
|
-
| `$nin`
|
|
223
|
-
| `$or`
|
|
224
|
-
| `$and`
|
|
225
|
-
|
|
226
|
-
|
|
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`)
|
|
227
253
|
|
|
228
254
|
The `extends` key embeds data from another collection directly into the stored document at insert time β no join needed on reads.
|
|
229
255
|
|
|
@@ -243,23 +269,24 @@ await client.collection('laptops')
|
|
|
243
269
|
.exec();
|
|
244
270
|
```
|
|
245
271
|
|
|
272
|
+
---
|
|
246
273
|
**When to use `extends` vs `joins`:**
|
|
247
274
|
|
|
248
|
-
|
|
|
249
|
-
|
|
250
|
-
| Resolved at
|
|
251
|
-
| Data freshness | Snapshot β may become stale
|
|
252
|
-
| Read cost
|
|
253
|
-
| Use when
|
|
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 |
|
|
254
281
|
|
|
255
282
|
---
|
|
283
|
+
|
|
256
284
|
## Configuration
|
|
257
285
|
|
|
258
286
|
You can customise the database behavior by passing an options object to the `MoltenDb` constructor.
|
|
259
287
|
|
|
260
288
|
```ts
|
|
261
289
|
const db = new MoltenDb('my-app', {
|
|
262
|
-
hotThreshold: 25000, // Page to disk after 25k docs
|
|
263
290
|
encryptionKey: 'user-secret', // Secure at-rest storage in OPFS
|
|
264
291
|
writeMode: 'sync' // Ensure durability on every write
|
|
265
292
|
});
|
|
@@ -268,25 +295,24 @@ await db.init();
|
|
|
268
295
|
|
|
269
296
|
### Options Reference
|
|
270
297
|
|
|
271
|
-
| Property
|
|
272
|
-
|
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
275
|
-
| `
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `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. |
|
|
280
306
|
|
|
281
307
|
---
|
|
308
|
+
|
|
282
309
|
## Storage Architecture
|
|
283
310
|
|
|
284
311
|
### How the Log Works
|
|
285
312
|
|
|
286
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.
|
|
287
314
|
|
|
288
|
-
- **
|
|
289
|
-
- **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.
|
|
290
316
|
|
|
291
317
|
### Multi-Tab Sync
|
|
292
318
|
|
|
@@ -326,7 +352,7 @@ unsubscribe();
|
|
|
326
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:
|
|
327
353
|
|
|
328
354
|
```ts
|
|
329
|
-
db.subscribe(({
|
|
355
|
+
db.subscribe(({event, collection, key}) => {
|
|
330
356
|
if (collection === 'laptops') {
|
|
331
357
|
refreshLaptopList(); // re-query and re-render
|
|
332
358
|
}
|
|
@@ -336,12 +362,13 @@ db.subscribe(({ event, collection, key }) => {
|
|
|
336
362
|
The `DbEvent` type is exported from the package for full TypeScript support:
|
|
337
363
|
|
|
338
364
|
```ts
|
|
339
|
-
import {
|
|
365
|
+
import {MoltenDb, DbEvent} from '@moltendb-web/core';
|
|
340
366
|
|
|
341
367
|
const db = new MoltenDb('my-app');
|
|
342
368
|
await db.init();
|
|
343
369
|
|
|
344
|
-
db.subscribe((e: DbEvent) => { /* fully typed */ })
|
|
370
|
+
db.subscribe((e: DbEvent) => { /* fully typed */ });
|
|
371
|
+
```
|
|
345
372
|
|
|
346
373
|
---
|
|
347
374
|
|
|
@@ -356,6 +383,22 @@ Cross-Origin-Embedder-Policy: require-corp
|
|
|
356
383
|
|
|
357
384
|
---
|
|
358
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
|
+
|
|
359
402
|
## Testing
|
|
360
403
|
|
|
361
404
|
The core package ships with a comprehensive test suite built on **Vitest**:
|
|
@@ -368,20 +411,20 @@ npm run test:coverage # with coverage report
|
|
|
368
411
|
|
|
369
412
|
### What's covered
|
|
370
413
|
|
|
371
|
-
| Suite
|
|
372
|
-
|
|
373
|
-
| `init()`
|
|
374
|
-
| CRUD β leader
|
|
375
|
-
| CRUD β follower
|
|
376
|
-
| Worker error handling
|
|
377
|
-
| Leader promotion
|
|
378
|
-
| `Pub/Sub (subscribe)`
|
|
379
|
-
| Follower timeout
|
|
380
|
-
| `terminate` / `disconnect` | 3
|
|
381
|
-
| Stress β rapid writes
|
|
382
|
-
| BC name isolation
|
|
383
|
-
| Bulk insert stress
|
|
384
|
-
| Multi-tab parallel stress
|
|
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 |
|
|
385
428
|
|
|
386
429
|
**Total: 50 tests β all green.**
|
|
387
430
|
|
|
@@ -397,16 +440,13 @@ This monorepo contains the following packages:
|
|
|
397
440
|
## Roadmap
|
|
398
441
|
|
|
399
442
|
- [x] **Multi-Tab Sync:** Leader election for multiple tabs to share a single OPFS instance.
|
|
400
|
-
- [x] **Automatic Compaction:** Log compacts automatically at 500 entries or 5 MB.
|
|
401
443
|
- [x] **Rich Test Suite:** 50 unit, integration, and stress tests via Vitest.
|
|
402
|
-
- [
|
|
444
|
+
- [x] **React Adapter:** Official `@moltendb-web/react` package with `useQuery` hooks and real-time context providers.
|
|
403
445
|
- [x] **Angular Adapter:** Official `@moltendb-web/angular` package featuring Signal-based data fetching.
|
|
404
446
|
- [ ] **Delta Sync:** Automatic two-way sync with the MoltenDb Rust server.
|
|
405
447
|
- [x] **Data Encryption:** Transparent encryption-at-rest using hardware-backed keys (Argon2id + XChaCha20).
|
|
406
|
-
- [x] **Hybrid Bitcask:** Seamlessly handle datasets larger than RAM by paging docs to OPFS.
|
|
407
448
|
- [ ] **Analytics Functionality:** Run complex analytics queries straight in the browser without blocking the UI.
|
|
408
|
-
- [x] **Configurable Limits:** User-defined
|
|
409
|
-
|
|
449
|
+
- [x] **Configurable Limits:** User-defined request body sizes for edge and browser environments.
|
|
410
450
|
|
|
411
451
|
## Contributing & Feedback
|
|
412
452
|
|
|
@@ -418,6 +458,4 @@ Found a bug or have a feature request? Please open an issue on the [GitHub issue
|
|
|
418
458
|
|
|
419
459
|
The MoltenDb Web packages (`@moltendb-web/core` and `@moltendb-web/query`) are licensed under the MIT License.
|
|
420
460
|
|
|
421
|
-
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).
|
|
422
|
-
|
|
423
|
-
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
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
export interface MoltenDbOptions {
|
|
2
2
|
/** URL or path to moltendb-worker.js. */
|
|
3
3
|
workerUrl?: string | URL;
|
|
4
|
-
/** Maximum documents per collection to keep in RAM. Default: 50,000. */
|
|
5
|
-
hotThreshold?: number;
|
|
6
4
|
/** Password for at-rest encryption. If not provided, data is stored as plain JSON. */
|
|
7
5
|
encryptionKey?: string;
|
|
8
6
|
/** Storage write mode: 'async' (default, high throughput) or 'sync' (durable). */
|
|
9
|
-
writeMode?:
|
|
7
|
+
writeMode?: "async" | "sync";
|
|
10
8
|
/** Maximum request body size in bytes. */
|
|
11
9
|
maxBodySize?: number;
|
|
12
10
|
/** Maximum number of keys allowed per JSON request. Default: 1000. */
|
|
@@ -23,8 +21,8 @@ export interface MoltenDbOptions {
|
|
|
23
21
|
inMemory?: boolean;
|
|
24
22
|
}
|
|
25
23
|
export interface DbEvent {
|
|
26
|
-
type:
|
|
27
|
-
event:
|
|
24
|
+
type: "event";
|
|
25
|
+
event: "change" | "delete" | "drop" | "kill" | "ttl_expiry";
|
|
28
26
|
collection: string;
|
|
29
27
|
key: string;
|
|
30
28
|
new_v: number | null;
|
|
@@ -34,12 +32,12 @@ export declare class MoltenDb {
|
|
|
34
32
|
readonly workerUrl?: string | URL;
|
|
35
33
|
readonly options: MoltenDbOptions;
|
|
36
34
|
worker: Worker | null;
|
|
37
|
-
private initPromise;
|
|
38
|
-
private pendingRequests;
|
|
39
35
|
isLeader: boolean;
|
|
40
|
-
private bc;
|
|
41
36
|
/** Legacy global hook. Use `subscribe()` for multi-component listeners. */
|
|
42
37
|
onEvent?: (event: DbEvent) => void;
|
|
38
|
+
private initPromise;
|
|
39
|
+
private pendingRequests;
|
|
40
|
+
private bc;
|
|
43
41
|
private eventListeners;
|
|
44
42
|
constructor(dbName?: string, options?: MoltenDbOptions);
|
|
45
43
|
/**
|
|
@@ -49,16 +47,28 @@ export declare class MoltenDb {
|
|
|
49
47
|
subscribe(listener: (event: DbEvent) => void): () => void;
|
|
50
48
|
/** Manually remove a specific listener */
|
|
51
49
|
unsubscribe(listener: (event: DbEvent) => void): void;
|
|
52
|
-
private dispatchEvent;
|
|
53
50
|
init(): Promise<void>;
|
|
54
|
-
private startAsLeader;
|
|
55
|
-
private startAsFollower;
|
|
56
51
|
sendMessage(action: string, payload?: Record<string, unknown>): Promise<any>;
|
|
57
52
|
set(collection: string, key: string, value: any): Promise<void>;
|
|
58
53
|
get(collection: string, key: string): Promise<unknown>;
|
|
59
54
|
getAll(collection: string): Promise<unknown[]>;
|
|
60
55
|
delete(collection: string, key: string): Promise<void>;
|
|
61
56
|
compact(): Promise<unknown>;
|
|
57
|
+
/**
|
|
58
|
+
* Truncate and close the OPFS file handle, then remove the OPFS directory.
|
|
59
|
+
*
|
|
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.
|
|
64
|
+
*
|
|
65
|
+
* After this resolves, call the `terminate` method and `location.reload()` if needed or re-initialize the database.
|
|
66
|
+
*/
|
|
67
|
+
clearOpfs(): Promise<void>;
|
|
62
68
|
disconnect(): void;
|
|
69
|
+
/** Terminates the MoltenDb worker. Call after clearing OPFS storage. */
|
|
63
70
|
terminate(): void;
|
|
71
|
+
private dispatchEvent;
|
|
72
|
+
private startAsLeader;
|
|
73
|
+
private startAsFollower;
|
|
64
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 =
|
|
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(
|
|
40
|
+
window.addEventListener("beforeunload", () => {
|
|
53
41
|
try {
|
|
54
|
-
this.bc?.postMessage({ type:
|
|
42
|
+
this.bc?.postMessage({ type: "clear_all" });
|
|
55
43
|
}
|
|
56
44
|
catch { }
|
|
57
45
|
});
|
|
@@ -84,6 +72,134 @@ export class MoltenDb {
|
|
|
84
72
|
});
|
|
85
73
|
return this.initPromise;
|
|
86
74
|
}
|
|
75
|
+
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
76
|
+
async sendMessage(action, payload) {
|
|
77
|
+
// Wait for the engine to boot before routing the message.
|
|
78
|
+
// If the DB is already initialized, this resolves instantly.
|
|
79
|
+
if (action !== "init") {
|
|
80
|
+
if (this.initPromise) {
|
|
81
|
+
await this.initPromise;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
throw new Error("[MoltenDb] You must call db.init() before querying the database.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// 2. Generate a unique ID
|
|
88
|
+
const id = typeof crypto !== "undefined" && crypto.randomUUID
|
|
89
|
+
? crypto.randomUUID()
|
|
90
|
+
: Math.random().toString(36).substring(2, 9);
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
const successHandler = (res) => resolve(mapToObj(res));
|
|
93
|
+
// 3. We are now GUARANTEED that isLeader, worker, and bc are accurately set
|
|
94
|
+
if (this.isLeader && this.worker) {
|
|
95
|
+
this.pendingRequests.set(id, { resolve: successHandler, reject });
|
|
96
|
+
this.worker.postMessage({ id, action, ...payload });
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Follower routing via BroadcastChannel
|
|
100
|
+
const timer = setTimeout(() => {
|
|
101
|
+
if (this.pendingRequests.has(id)) {
|
|
102
|
+
this.pendingRequests.delete(id);
|
|
103
|
+
reject(new Error(`[MoltenDb] Request "${action}" timed out after 10s.`));
|
|
104
|
+
}
|
|
105
|
+
}, 10000);
|
|
106
|
+
this.pendingRequests.set(id, {
|
|
107
|
+
resolve: (res) => {
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
successHandler(res);
|
|
110
|
+
},
|
|
111
|
+
reject: (e) => {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
reject(e);
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
this.bc.postMessage({ type: "query", id, action, payload });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
} // ββ Convenience CRUD helpers βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
120
|
+
async set(collection, key, value) {
|
|
121
|
+
await this.sendMessage("set", { collection, data: { [key]: value } });
|
|
122
|
+
}
|
|
123
|
+
async get(collection, key) {
|
|
124
|
+
try {
|
|
125
|
+
return await this.sendMessage("get", { collection, keys: key });
|
|
126
|
+
}
|
|
127
|
+
catch (err) {
|
|
128
|
+
try {
|
|
129
|
+
const errorData = JSON.parse(err.message);
|
|
130
|
+
if (errorData.statusCode === 404)
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
catch { }
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async getAll(collection) {
|
|
138
|
+
try {
|
|
139
|
+
const result = await this.sendMessage("get", { collection });
|
|
140
|
+
return result || [];
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
try {
|
|
144
|
+
const errorData = JSON.parse(err.message);
|
|
145
|
+
if (errorData.statusCode === 404)
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
catch { }
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async delete(collection, key) {
|
|
153
|
+
await this.sendMessage("delete", { collection, keys: key });
|
|
154
|
+
}
|
|
155
|
+
compact() {
|
|
156
|
+
return this.sendMessage("compact");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Truncate and close the OPFS file handle, then remove the OPFS directory.
|
|
160
|
+
*
|
|
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.
|
|
165
|
+
*
|
|
166
|
+
* After this resolves, call the `terminate` method and `location.reload()` if needed or re-initialize the database.
|
|
167
|
+
*/
|
|
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" });
|
|
177
|
+
}
|
|
178
|
+
disconnect() {
|
|
179
|
+
if (this.bc)
|
|
180
|
+
this.bc.close();
|
|
181
|
+
}
|
|
182
|
+
/** Terminates the MoltenDb worker. Call after clearing OPFS storage. */
|
|
183
|
+
terminate() {
|
|
184
|
+
this.disconnect();
|
|
185
|
+
if (this.worker) {
|
|
186
|
+
this.worker.terminate();
|
|
187
|
+
this.worker = null;
|
|
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
|
+
}
|
|
87
203
|
async startAsLeader() {
|
|
88
204
|
// Guard: OPFS is required
|
|
89
205
|
if (!this.options.inMemory) {
|
|
@@ -91,18 +207,21 @@ export class MoltenDb {
|
|
|
91
207
|
await navigator.storage.getDirectory();
|
|
92
208
|
}
|
|
93
209
|
catch {
|
|
94
|
-
throw new Error(
|
|
95
|
-
|
|
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+).");
|
|
96
212
|
}
|
|
97
213
|
}
|
|
98
214
|
this.isLeader = true;
|
|
99
215
|
if (this.worker)
|
|
100
216
|
this.worker.terminate();
|
|
101
|
-
const url = this.workerUrl || new URL(
|
|
102
|
-
this.worker = new Worker(url, {
|
|
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
|
+
});
|
|
103
222
|
this.worker.onmessage = (e) => {
|
|
104
223
|
const data = e.data;
|
|
105
|
-
if (data.type ===
|
|
224
|
+
if (data.type === "event") {
|
|
106
225
|
this.dispatchEvent(data); // β¬
οΈ Trigger new dispatcher
|
|
107
226
|
this.bc.postMessage(data);
|
|
108
227
|
return;
|
|
@@ -117,10 +236,9 @@ export class MoltenDb {
|
|
|
117
236
|
}
|
|
118
237
|
};
|
|
119
238
|
// Wait for worker to boot
|
|
120
|
-
await this.sendMessage(
|
|
239
|
+
await this.sendMessage("init", {
|
|
121
240
|
dbName: this.dbName,
|
|
122
241
|
encryptionKey: this.options.encryptionKey,
|
|
123
|
-
hotThreshold: this.options.hotThreshold,
|
|
124
242
|
inMemory: this.options.inMemory,
|
|
125
243
|
maxBodySize: this.options.maxBodySize,
|
|
126
244
|
maxKeysPerRequest: this.options.maxKeysPerRequest,
|
|
@@ -128,25 +246,41 @@ export class MoltenDb {
|
|
|
128
246
|
});
|
|
129
247
|
this.bc.onmessage = async (e) => {
|
|
130
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
|
+
}
|
|
131
261
|
// Any tab unloading in in-memory mode broadcasts this β wipe the shared RAM store.
|
|
132
|
-
if (msg.type ===
|
|
262
|
+
if (msg.type === "clear_all") {
|
|
133
263
|
try {
|
|
134
|
-
await this.sendMessage(
|
|
135
|
-
this.bc.postMessage({ type:
|
|
136
|
-
console.log(
|
|
264
|
+
await this.sendMessage("clear", {});
|
|
265
|
+
this.bc.postMessage({ type: "cleared" });
|
|
266
|
+
console.log("[MoltenDb] In-memory store wiped (tab unloaded).");
|
|
137
267
|
}
|
|
138
268
|
catch (err) {
|
|
139
|
-
console.warn(
|
|
269
|
+
console.warn("[MoltenDb] Failed to clear in-memory store:", err);
|
|
140
270
|
}
|
|
141
271
|
return;
|
|
142
272
|
}
|
|
143
|
-
if (msg.type ===
|
|
273
|
+
if (msg.type === "query" && msg.action) {
|
|
144
274
|
try {
|
|
145
275
|
const result = await this.sendMessage(msg.action, msg.payload);
|
|
146
|
-
this.bc.postMessage({ type:
|
|
276
|
+
this.bc.postMessage({ type: "response", id: msg.id, result });
|
|
147
277
|
}
|
|
148
278
|
catch (err) {
|
|
149
|
-
this.bc.postMessage({
|
|
279
|
+
this.bc.postMessage({
|
|
280
|
+
type: "response",
|
|
281
|
+
id: msg.id,
|
|
282
|
+
error: err.message,
|
|
283
|
+
});
|
|
150
284
|
}
|
|
151
285
|
}
|
|
152
286
|
};
|
|
@@ -159,20 +293,32 @@ export class MoltenDb {
|
|
|
159
293
|
}
|
|
160
294
|
this.bc.onmessage = (e) => {
|
|
161
295
|
const data = e.data;
|
|
162
|
-
|
|
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") {
|
|
163
309
|
this.dispatchEvent(data); // β¬
οΈ Trigger new dispatcher
|
|
164
310
|
return;
|
|
165
311
|
}
|
|
166
312
|
// In-memory wipe notification from leader β reject all in-flight requests.
|
|
167
|
-
if (data.type ===
|
|
168
|
-
console.log(
|
|
313
|
+
if (data.type === "cleared") {
|
|
314
|
+
console.log("[MoltenDb] In-memory store was wiped by another tab.");
|
|
169
315
|
for (const [id, req] of this.pendingRequests) {
|
|
170
|
-
req.reject(new Error(
|
|
316
|
+
req.reject(new Error("[MoltenDb] In-memory store was cleared by a tab reload."));
|
|
171
317
|
this.pendingRequests.delete(id);
|
|
172
318
|
}
|
|
173
319
|
return;
|
|
174
320
|
}
|
|
175
|
-
if (data.type ===
|
|
321
|
+
if (data.type === "response") {
|
|
176
322
|
const req = this.pendingRequests.get(data.id);
|
|
177
323
|
if (req) {
|
|
178
324
|
if (data.error)
|
|
@@ -184,91 +330,4 @@ export class MoltenDb {
|
|
|
184
330
|
}
|
|
185
331
|
};
|
|
186
332
|
}
|
|
187
|
-
async sendMessage(action, payload) {
|
|
188
|
-
// Wait for the engine to boot before routing the message.
|
|
189
|
-
// If the DB is already initialized, this resolves instantly.
|
|
190
|
-
if (action !== 'init') {
|
|
191
|
-
if (this.initPromise) {
|
|
192
|
-
await this.initPromise;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
throw new Error('[MoltenDb] You must call db.init() before querying the database.');
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
// 2. Generate a unique ID
|
|
199
|
-
const id = (typeof crypto !== 'undefined' && crypto.randomUUID)
|
|
200
|
-
? crypto.randomUUID()
|
|
201
|
-
: Math.random().toString(36).substring(2, 9);
|
|
202
|
-
return new Promise((resolve, reject) => {
|
|
203
|
-
const successHandler = (res) => resolve(mapToObj(res));
|
|
204
|
-
// 3. We are now GUARANTEED that isLeader, worker, and bc are accurately set
|
|
205
|
-
if (this.isLeader && this.worker) {
|
|
206
|
-
this.pendingRequests.set(id, { resolve: successHandler, reject });
|
|
207
|
-
this.worker.postMessage({ id, action, ...payload });
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
// Follower routing via BroadcastChannel
|
|
211
|
-
const timer = setTimeout(() => {
|
|
212
|
-
if (this.pendingRequests.has(id)) {
|
|
213
|
-
this.pendingRequests.delete(id);
|
|
214
|
-
reject(new Error(`[MoltenDb] Request "${action}" timed out after 10s.`));
|
|
215
|
-
}
|
|
216
|
-
}, 10000);
|
|
217
|
-
this.pendingRequests.set(id, {
|
|
218
|
-
resolve: (res) => { clearTimeout(timer); successHandler(res); },
|
|
219
|
-
reject: (e) => { clearTimeout(timer); reject(e); }
|
|
220
|
-
});
|
|
221
|
-
this.bc.postMessage({ type: 'query', id, action, payload });
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
} // ββ Convenience CRUD helpers βββββββββββββββββββββββββββββββββββββββββββββββ
|
|
225
|
-
async set(collection, key, value) {
|
|
226
|
-
await this.sendMessage('set', { collection, data: { [key]: value } });
|
|
227
|
-
}
|
|
228
|
-
async get(collection, key) {
|
|
229
|
-
try {
|
|
230
|
-
return await this.sendMessage('get', { collection, keys: key });
|
|
231
|
-
}
|
|
232
|
-
catch (err) {
|
|
233
|
-
try {
|
|
234
|
-
const errorData = JSON.parse(err.message);
|
|
235
|
-
if (errorData.statusCode === 404)
|
|
236
|
-
return null;
|
|
237
|
-
}
|
|
238
|
-
catch { }
|
|
239
|
-
throw err;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
async getAll(collection) {
|
|
243
|
-
try {
|
|
244
|
-
const result = await this.sendMessage('get', { collection });
|
|
245
|
-
return result || [];
|
|
246
|
-
}
|
|
247
|
-
catch (err) {
|
|
248
|
-
try {
|
|
249
|
-
const errorData = JSON.parse(err.message);
|
|
250
|
-
if (errorData.statusCode === 404)
|
|
251
|
-
return [];
|
|
252
|
-
}
|
|
253
|
-
catch { }
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
async delete(collection, key) {
|
|
258
|
-
await this.sendMessage('delete', { collection, keys: key });
|
|
259
|
-
}
|
|
260
|
-
compact() {
|
|
261
|
-
return this.sendMessage('compact');
|
|
262
|
-
}
|
|
263
|
-
disconnect() {
|
|
264
|
-
if (this.bc)
|
|
265
|
-
this.bc.close();
|
|
266
|
-
}
|
|
267
|
-
terminate() {
|
|
268
|
-
this.disconnect();
|
|
269
|
-
if (this.worker) {
|
|
270
|
-
this.worker.terminate();
|
|
271
|
-
this.worker = null;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
333
|
}
|
package/dist/moltendb-worker.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import init, { WorkerDb } from
|
|
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 ===
|
|
7
|
+
if (action === "init") {
|
|
8
8
|
if (!initPromise) {
|
|
9
9
|
initPromise = (async () => {
|
|
10
10
|
await init();
|
|
11
11
|
// Pass all config flags to Rust
|
|
12
|
-
const instance = await WorkerDb.create(payload.dbName, payload.
|
|
12
|
+
const instance = await WorkerDb.create(payload.dbName, payload.encryptionKey, payload.writeMode, payload.maxBodySize, payload.maxKeysPerRequest, payload.inMemory);
|
|
13
13
|
// Listen to Rust and broadcast events
|
|
14
14
|
instance.subscribe((eventStr) => {
|
|
15
15
|
try {
|
|
16
16
|
const eventData = JSON.parse(eventStr);
|
|
17
|
-
self.postMessage({ type:
|
|
17
|
+
self.postMessage({ type: "event", ...eventData });
|
|
18
18
|
}
|
|
19
19
|
catch (err) {
|
|
20
|
-
console.error(
|
|
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:
|
|
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 =
|
|
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 =
|
|
50
|
+
const errorMsg = error instanceof Map
|
|
51
51
|
? JSON.stringify(Object.fromEntries(error))
|
|
52
52
|
: String(error);
|
|
53
53
|
self.postMessage({ id, error: errorMsg });
|
|
@@ -17,28 +17,6 @@ export class WorkerDb {
|
|
|
17
17
|
private constructor();
|
|
18
18
|
free(): void;
|
|
19
19
|
[Symbol.dispose](): void;
|
|
20
|
-
/**
|
|
21
|
-
* Execute an analytics query and return the result as a JSON string.
|
|
22
|
-
*
|
|
23
|
-
* This is the method called by the dashboard's auto-refresh loop:
|
|
24
|
-
* `const resultStr = db.analytics(JSON.stringify(query))`
|
|
25
|
-
*
|
|
26
|
-
* Takes a JSON string (not a JsValue) because the analytics query format
|
|
27
|
-
* is complex and easier to pass as a pre-serialized string from JavaScript.
|
|
28
|
-
*
|
|
29
|
-
* Returns a JSON string (not a JsValue) so JavaScript can parse it with
|
|
30
|
-
* `JSON.parse(resultStr)` and access `result` and `metadata`.
|
|
31
|
-
*
|
|
32
|
-
* Example input:
|
|
33
|
-
* `'{"collection":"events","metric":{"type":"COUNT"},"where":{"event_type":"button_click"}}'`
|
|
34
|
-
*
|
|
35
|
-
* Example output:
|
|
36
|
-
* `'{"result":42,"metadata":{"execution_time_ms":0,"rows_scanned":42}}'`
|
|
37
|
-
*
|
|
38
|
-
* `#[wasm_bindgen(js_name = analytics)]` sets the JavaScript method name to
|
|
39
|
-
* "analytics" (matching the call in analytics-worker.js).
|
|
40
|
-
*/
|
|
41
|
-
analytics(query_json: string): string;
|
|
42
20
|
/**
|
|
43
21
|
* Initialize the database and open (or create) the OPFS storage file.
|
|
44
22
|
*
|
|
@@ -56,7 +34,6 @@ export class WorkerDb {
|
|
|
56
34
|
* # Arguments
|
|
57
35
|
* * `db_name` β The name of the OPFS file to open (e.g. "click_analytics_db").
|
|
58
36
|
* Each unique name is a separate database file in the browser's OPFS storage.
|
|
59
|
-
* * `hot_threshold` β Optional maximum documents per collection to keep in RAM (default: 50,000).
|
|
60
37
|
* * `encryption_key` β Optional password for at-rest encryption.
|
|
61
38
|
* * `write_mode` β Optional write mode: "async" (default) or "sync".
|
|
62
39
|
* * `max_body_size` β Optional maximum request body size in bytes (default: 10MB).
|
|
@@ -65,7 +42,7 @@ export class WorkerDb {
|
|
|
65
42
|
* When `true`, all data is lost when the worker is terminated β useful for ephemeral
|
|
66
43
|
* session caches or testing without touching OPFS storage.
|
|
67
44
|
*/
|
|
68
|
-
static create(db_name: string,
|
|
45
|
+
static create(db_name: string, encryption_key?: string | null, write_mode?: string | null, max_body_size?: number | null, max_keys_per_request?: number | null, in_memory?: boolean | null): Promise<WorkerDb>;
|
|
69
46
|
/**
|
|
70
47
|
* Route an incoming message from the JavaScript worker to the correct handler.
|
|
71
48
|
*
|
|
@@ -81,7 +58,6 @@ export class WorkerDb {
|
|
|
81
58
|
* - "update" β patch/merge documents: { collection, data: { key: patch, ... } }
|
|
82
59
|
* - "delete" β delete documents or drop: { collection, keys: ... } or { drop: true }
|
|
83
60
|
* - "compact" β compact the OPFS log file
|
|
84
|
-
* - "get_size" β return current OPFS file size in bytes
|
|
85
61
|
* - "clear" β wipe all in-memory state (in-memory mode only)
|
|
86
62
|
*
|
|
87
63
|
* Returns a JsValue result on success, or a JsValue error string on failure.
|
|
@@ -100,12 +76,11 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
100
76
|
export interface InitOutput {
|
|
101
77
|
readonly memory: WebAssembly.Memory;
|
|
102
78
|
readonly __wbg_workerdb_free: (a: number, b: number) => void;
|
|
103
|
-
readonly
|
|
104
|
-
readonly workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => number;
|
|
79
|
+
readonly workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => number;
|
|
105
80
|
readonly workerdb_handle_message: (a: number, b: number, c: number) => void;
|
|
106
81
|
readonly workerdb_subscribe: (a: number, b: number) => void;
|
|
107
|
-
readonly
|
|
108
|
-
readonly
|
|
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;
|
|
109
84
|
readonly __wbindgen_export: (a: number, b: number) => number;
|
|
110
85
|
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
111
86
|
readonly __wbindgen_export3: (a: number) => void;
|
|
@@ -30,47 +30,6 @@ export class WorkerDb {
|
|
|
30
30
|
const ptr = this.__destroy_into_raw();
|
|
31
31
|
wasm.__wbg_workerdb_free(ptr, 0);
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* Execute an analytics query and return the result as a JSON string.
|
|
35
|
-
*
|
|
36
|
-
* This is the method called by the dashboard's auto-refresh loop:
|
|
37
|
-
* `const resultStr = db.analytics(JSON.stringify(query))`
|
|
38
|
-
*
|
|
39
|
-
* Takes a JSON string (not a JsValue) because the analytics query format
|
|
40
|
-
* is complex and easier to pass as a pre-serialized string from JavaScript.
|
|
41
|
-
*
|
|
42
|
-
* Returns a JSON string (not a JsValue) so JavaScript can parse it with
|
|
43
|
-
* `JSON.parse(resultStr)` and access `result` and `metadata`.
|
|
44
|
-
*
|
|
45
|
-
* Example input:
|
|
46
|
-
* `'{"collection":"events","metric":{"type":"COUNT"},"where":{"event_type":"button_click"}}'`
|
|
47
|
-
*
|
|
48
|
-
* Example output:
|
|
49
|
-
* `'{"result":42,"metadata":{"execution_time_ms":0,"rows_scanned":42}}'`
|
|
50
|
-
*
|
|
51
|
-
* `#[wasm_bindgen(js_name = analytics)]` sets the JavaScript method name to
|
|
52
|
-
* "analytics" (matching the call in analytics-worker.js).
|
|
53
|
-
* @param {string} query_json
|
|
54
|
-
* @returns {string}
|
|
55
|
-
*/
|
|
56
|
-
analytics(query_json) {
|
|
57
|
-
let deferred2_0;
|
|
58
|
-
let deferred2_1;
|
|
59
|
-
try {
|
|
60
|
-
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
61
|
-
const ptr0 = passStringToWasm0(query_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
62
|
-
const len0 = WASM_VECTOR_LEN;
|
|
63
|
-
wasm.workerdb_analytics(retptr, this.__wbg_ptr, ptr0, len0);
|
|
64
|
-
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
65
|
-
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
66
|
-
deferred2_0 = r0;
|
|
67
|
-
deferred2_1 = r1;
|
|
68
|
-
return getStringFromWasm0(r0, r1);
|
|
69
|
-
} finally {
|
|
70
|
-
wasm.__wbindgen_add_to_stack_pointer(16);
|
|
71
|
-
wasm.__wbindgen_export4(deferred2_0, deferred2_1, 1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
33
|
/**
|
|
75
34
|
* Initialize the database and open (or create) the OPFS storage file.
|
|
76
35
|
*
|
|
@@ -88,7 +47,6 @@ export class WorkerDb {
|
|
|
88
47
|
* # Arguments
|
|
89
48
|
* * `db_name` β The name of the OPFS file to open (e.g. "click_analytics_db").
|
|
90
49
|
* Each unique name is a separate database file in the browser's OPFS storage.
|
|
91
|
-
* * `hot_threshold` β Optional maximum documents per collection to keep in RAM (default: 50,000).
|
|
92
50
|
* * `encryption_key` β Optional password for at-rest encryption.
|
|
93
51
|
* * `write_mode` β Optional write mode: "async" (default) or "sync".
|
|
94
52
|
* * `max_body_size` β Optional maximum request body size in bytes (default: 10MB).
|
|
@@ -97,7 +55,6 @@ export class WorkerDb {
|
|
|
97
55
|
* When `true`, all data is lost when the worker is terminated β useful for ephemeral
|
|
98
56
|
* session caches or testing without touching OPFS storage.
|
|
99
57
|
* @param {string} db_name
|
|
100
|
-
* @param {number | null} [hot_threshold]
|
|
101
58
|
* @param {string | null} [encryption_key]
|
|
102
59
|
* @param {string | null} [write_mode]
|
|
103
60
|
* @param {number | null} [max_body_size]
|
|
@@ -105,14 +62,14 @@ export class WorkerDb {
|
|
|
105
62
|
* @param {boolean | null} [in_memory]
|
|
106
63
|
* @returns {Promise<WorkerDb>}
|
|
107
64
|
*/
|
|
108
|
-
static create(db_name,
|
|
65
|
+
static create(db_name, encryption_key, write_mode, max_body_size, max_keys_per_request, in_memory) {
|
|
109
66
|
const ptr0 = passStringToWasm0(db_name, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
110
67
|
const len0 = WASM_VECTOR_LEN;
|
|
111
68
|
var ptr1 = isLikeNone(encryption_key) ? 0 : passStringToWasm0(encryption_key, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
112
69
|
var len1 = WASM_VECTOR_LEN;
|
|
113
70
|
var ptr2 = isLikeNone(write_mode) ? 0 : passStringToWasm0(write_mode, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
|
114
71
|
var len2 = WASM_VECTOR_LEN;
|
|
115
|
-
const ret = wasm.workerdb_create(ptr0, len0,
|
|
72
|
+
const ret = wasm.workerdb_create(ptr0, len0, ptr1, len1, ptr2, len2, isLikeNone(max_body_size) ? 0x100000001 : (max_body_size) >>> 0, isLikeNone(max_keys_per_request) ? 0x100000001 : (max_keys_per_request) >>> 0, isLikeNone(in_memory) ? 0xFFFFFF : in_memory ? 1 : 0);
|
|
116
73
|
return takeObject(ret);
|
|
117
74
|
}
|
|
118
75
|
/**
|
|
@@ -130,7 +87,6 @@ export class WorkerDb {
|
|
|
130
87
|
* - "update" β patch/merge documents: { collection, data: { key: patch, ... } }
|
|
131
88
|
* - "delete" β delete documents or drop: { collection, keys: ... } or { drop: true }
|
|
132
89
|
* - "compact" β compact the OPFS log file
|
|
133
|
-
* - "get_size" β return current OPFS file size in bytes
|
|
134
90
|
* - "clear" β wipe all in-memory state (in-memory mode only)
|
|
135
91
|
*
|
|
136
92
|
* Returns a JsValue result on success, or a JsValue error string on failure.
|
|
@@ -275,9 +231,6 @@ function __wbg_get_imports() {
|
|
|
275
231
|
const ret = Object.entries(getObject(arg0));
|
|
276
232
|
return addHeapObject(ret);
|
|
277
233
|
},
|
|
278
|
-
__wbg_error_2001591ad2463697: function(arg0) {
|
|
279
|
-
console.error(getObject(arg0));
|
|
280
|
-
},
|
|
281
234
|
__wbg_error_a6fa202b58aa1cd3: function(arg0, arg1) {
|
|
282
235
|
let deferred0_0;
|
|
283
236
|
let deferred0_1;
|
|
@@ -420,7 +373,7 @@ function __wbg_get_imports() {
|
|
|
420
373
|
const a = state0.a;
|
|
421
374
|
state0.a = 0;
|
|
422
375
|
try {
|
|
423
|
-
return
|
|
376
|
+
return __wasm_bindgen_func_elem_4178(a, state0.b, arg0, arg1);
|
|
424
377
|
} finally {
|
|
425
378
|
state0.a = a;
|
|
426
379
|
}
|
|
@@ -455,14 +408,6 @@ function __wbg_get_imports() {
|
|
|
455
408
|
const ret = Date.now();
|
|
456
409
|
return ret;
|
|
457
410
|
},
|
|
458
|
-
__wbg_now_e7c6795a7f81e10f: function(arg0) {
|
|
459
|
-
const ret = getObject(arg0).now();
|
|
460
|
-
return ret;
|
|
461
|
-
},
|
|
462
|
-
__wbg_performance_3fcf6e32a7e1ed0a: function(arg0) {
|
|
463
|
-
const ret = getObject(arg0).performance;
|
|
464
|
-
return addHeapObject(ret);
|
|
465
|
-
},
|
|
466
411
|
__wbg_process_44c7a14e11e9f69e: function(arg0) {
|
|
467
412
|
const ret = getObject(arg0).process;
|
|
468
413
|
return addHeapObject(ret);
|
|
@@ -567,8 +512,8 @@ function __wbg_get_imports() {
|
|
|
567
512
|
return ret;
|
|
568
513
|
}, arguments); },
|
|
569
514
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
570
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
571
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
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);
|
|
572
517
|
return addHeapObject(ret);
|
|
573
518
|
},
|
|
574
519
|
__wbindgen_cast_0000000000000002: function(arg0) {
|
|
@@ -610,10 +555,10 @@ function __wbg_get_imports() {
|
|
|
610
555
|
};
|
|
611
556
|
}
|
|
612
557
|
|
|
613
|
-
function
|
|
558
|
+
function __wasm_bindgen_func_elem_4166(arg0, arg1, arg2) {
|
|
614
559
|
try {
|
|
615
560
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
616
|
-
wasm.
|
|
561
|
+
wasm.__wasm_bindgen_func_elem_4166(retptr, arg0, arg1, addHeapObject(arg2));
|
|
617
562
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
618
563
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
619
564
|
if (r1) {
|
|
@@ -624,8 +569,8 @@ function __wasm_bindgen_func_elem_4220(arg0, arg1, arg2) {
|
|
|
624
569
|
}
|
|
625
570
|
}
|
|
626
571
|
|
|
627
|
-
function
|
|
628
|
-
wasm.
|
|
572
|
+
function __wasm_bindgen_func_elem_4178(arg0, arg1, arg2, arg3) {
|
|
573
|
+
wasm.__wasm_bindgen_func_elem_4178(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
629
574
|
}
|
|
630
575
|
|
|
631
576
|
const WorkerDbFinalization = (typeof FinalizationRegistry === 'undefined')
|
|
Binary file
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
export const memory: WebAssembly.Memory;
|
|
4
4
|
export const __wbg_workerdb_free: (a: number, b: number) => void;
|
|
5
|
-
export const
|
|
6
|
-
export const workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number) => number;
|
|
5
|
+
export const workerdb_create: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => number;
|
|
7
6
|
export const workerdb_handle_message: (a: number, b: number, c: number) => void;
|
|
8
7
|
export const workerdb_subscribe: (a: number, b: number) => void;
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
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;
|
|
11
10
|
export const __wbindgen_export: (a: number, b: number) => number;
|
|
12
11
|
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
|
13
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": "
|
|
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 <
|
|
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",
|