@moltendb-web/core 1.0.0-rc.3 โ 1.0.0-rc.5
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 +28 -28
- package/dist/index.d.ts +8 -8
- package/dist/index.js +29 -15
- package/dist/moltendb-worker.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# MoltenDb Web
|
|
2
2
|
|
|
3
3
|
<div align="center">
|
|
4
|
-
<img src="../../assets/logo.png" alt="
|
|
4
|
+
<img src="../../assets/logo.png" alt="MoltenDb Logo" width="64"/>
|
|
5
5
|
|
|
6
6
|
### ๐ The Embedded Database for the Modern Web
|
|
7
7
|
**High-performance Rust engine compiled to WASM. Persistent storage via OPFS.**
|
|
8
8
|
|
|
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/
|
|
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)
|
|
10
10
|
|
|
11
11
|
[](https://www.npmjs.com/package/@moltendb-web/core)
|
|
12
12
|
[](LICENSE.md)
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
## What is
|
|
20
|
+
## What is MoltenDb Web?
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
MoltenDb is a JSON document database written in Rust that runs directly in your browser. Unlike traditional browser databases limited by `localStorage` quotas or IndexedDB's complex API, MoltenDb leverages the **Origin Private File System (OPFS)** to provide a high-performance, append-only storage engine.
|
|
23
23
|
|
|
24
24
|
> **๐ Release Candidate** โ The core engine, multi-tab sync, and storage layer are feature-complete and stabilised for v1. Server sync, encryption and analytics are planned for a future release.
|
|
25
25
|
|
|
26
26
|
### ๐ฎ Explore the Full Functionality
|
|
27
27
|
|
|
28
|
-
The best way to experience
|
|
28
|
+
The best way to experience MoltenDb is through the **[Interactive Demo on StackBlitz](https://stackblitz.com/~/github.com/maximilian27/moltendb-wasm-demo?file=package.json)**. It provides a complete, live environment where you can test query builder expressions, perform mutations, and see real-time events with zero local setup.
|
|
29
29
|
|
|
30
30
|
Prefer to run it in your own environment? You can **[clone the demo repository](https://github.com/maximilian27/moltendb-wasm-demo)** to inspect the source code, run the explorers locally, and experiment with your own schema.
|
|
31
31
|
|
|
@@ -37,7 +37,7 @@ Prefer to run it in your own environment? You can **[clone the demo repository](
|
|
|
37
37
|
- **Worker-Threaded:** The database runs entirely inside a Web Workerโzero impact on your UI thread.
|
|
38
38
|
- **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.
|
|
39
39
|
- **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.
|
|
40
|
-
- **Real-Time Pub/Sub:** Every write and delete emits a typed `
|
|
40
|
+
- **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.
|
|
41
41
|
- **GraphQL-style Selection:** Request only the fields you need (even deeply nested ones) to save memory and CPU.
|
|
42
42
|
- **Auto-Indexing:** The engine monitors your queries and automatically creates indexes for frequently filtered fields.
|
|
43
43
|
- **Conflict Resolution:** Incoming writes with `_v โค stored _v` are silently skipped.
|
|
@@ -47,7 +47,7 @@ Prefer to run it in your own environment? You can **[clone the demo repository](
|
|
|
47
47
|
|
|
48
48
|
## Installation
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
MoltenDb is split into two packages: the core engine and the type-safe, chainable query builder.
|
|
51
51
|
|
|
52
52
|
```bash
|
|
53
53
|
# Install the core engine and WASM artifacts
|
|
@@ -58,7 +58,7 @@ npm install @moltendb-web/query
|
|
|
58
58
|
```
|
|
59
59
|
๐ฆ **Bundler Setup**
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
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.
|
|
62
62
|
|
|
63
63
|
**For Vite:**
|
|
64
64
|
Exclude the core package from pre-bundling in your vite.config.js:
|
|
@@ -85,17 +85,17 @@ module.exports = {
|
|
|
85
85
|
# Quick Start
|
|
86
86
|
1. Initialize the Client
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
MoltenDb handles the Web Worker and WASM instantiation for you.
|
|
89
89
|
TypeScript
|
|
90
90
|
```ts
|
|
91
|
-
import {
|
|
92
|
-
import {
|
|
91
|
+
import { MoltenDb } from '@moltendb-web/core';
|
|
92
|
+
import { MoltenDbClient, WorkerTransport } from '@moltendb-web/query';
|
|
93
93
|
|
|
94
|
-
const db = new
|
|
94
|
+
const db = new MoltenDb('moltendb_demo');
|
|
95
95
|
await db.init();
|
|
96
96
|
|
|
97
97
|
// Connect the query builder to the WASM worker
|
|
98
|
-
const client = new
|
|
98
|
+
const client = new MoltenDbClient(db);
|
|
99
99
|
|
|
100
100
|
// 2. Insert and Query
|
|
101
101
|
|
|
@@ -200,7 +200,7 @@ await client.collection('laptops')
|
|
|
200
200
|
|
|
201
201
|
// Supported Query Operators
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
MoltenDb supports a variety of operators in the `where` clause:
|
|
204
204
|
|
|
205
205
|
| Operator | Aliases | Description |
|
|
206
206
|
|---|---|---|
|
|
@@ -248,14 +248,14 @@ await client.collection('laptops')
|
|
|
248
248
|
|
|
249
249
|
### How the Log Works
|
|
250
250
|
|
|
251
|
-
|
|
251
|
+
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.
|
|
252
252
|
|
|
253
253
|
- **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.
|
|
254
254
|
- **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.
|
|
255
255
|
|
|
256
256
|
### Multi-Tab Sync
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
MoltenDb uses the **Web Locks API** for leader election. The first tab to acquire the lock becomes the *leader* and owns the OPFS file handle directly. Every subsequent tab becomes a *follower* and proxies all reads and writes through a `BroadcastChannel` to the leader.
|
|
259
259
|
|
|
260
260
|
When the leader tab is closed, the next queued follower automatically acquires the lock and promotes itself to leader โ no data loss, no manual reconnection required.
|
|
261
261
|
|
|
@@ -268,12 +268,12 @@ Tab 1 (Leader) โโownsโโโถ Web Worker โโโถ WASM Engine โโโถ OP
|
|
|
268
268
|
|
|
269
269
|
### Real-Time Events (Pub/Sub)
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
MoltenDb has a built-in pub/sub system that automatically notifies **all open tabs** whenever a document is created, updated, or deleted โ no polling required.
|
|
272
272
|
|
|
273
273
|
You can attach multiple independent listeners using the subscribe() method, making it trivial to keep different UI components (like React hooks or Angular signals) in sync without memory leaks:
|
|
274
274
|
|
|
275
275
|
```ts
|
|
276
|
-
const db = new
|
|
276
|
+
const db = new MoltenDb('my-app');
|
|
277
277
|
await db.init();
|
|
278
278
|
|
|
279
279
|
// Attach a listener (Returns an unsubscribe function)
|
|
@@ -298,21 +298,21 @@ db.subscribe(({ event, collection, key }) => {
|
|
|
298
298
|
});
|
|
299
299
|
```
|
|
300
300
|
|
|
301
|
-
The `
|
|
301
|
+
The `DbEvent` type is exported from the package for full TypeScript support:
|
|
302
302
|
|
|
303
303
|
```ts
|
|
304
|
-
import {
|
|
304
|
+
import { MoltenDb, DbEvent } from '@moltendb-web/core';
|
|
305
305
|
|
|
306
|
-
const db = new
|
|
306
|
+
const db = new MoltenDb('my-app');
|
|
307
307
|
await db.init();
|
|
308
308
|
|
|
309
|
-
db.subscribe((e:
|
|
309
|
+
db.subscribe((e: DbEvent) => { /* fully typed */ });```
|
|
310
310
|
|
|
311
311
|
---
|
|
312
312
|
|
|
313
313
|
### Performance Note
|
|
314
314
|
|
|
315
|
-
Because
|
|
315
|
+
Because MoltenDb uses OPFS, your browser must support `SharedArrayBuffer`. Most modern browsers support this, but your server must send the following headers:
|
|
316
316
|
|
|
317
317
|
```http
|
|
318
318
|
Cross-Origin-Opener-Policy: same-origin
|
|
@@ -356,7 +356,7 @@ npm run test:coverage # with coverage report
|
|
|
356
356
|
|
|
357
357
|
This monorepo contains the following packages:
|
|
358
358
|
|
|
359
|
-
- **`packages/core`:** The core WASM engine, Web Worker logic, and the
|
|
359
|
+
- **`packages/core`:** The core WASM engine, Web Worker logic, and the MoltenDb main client.
|
|
360
360
|
- **`packages/query`:** The type-safe, chainable Query Builder.
|
|
361
361
|
|
|
362
362
|
## Roadmap
|
|
@@ -366,7 +366,7 @@ This monorepo contains the following packages:
|
|
|
366
366
|
- [x] **Rich Test Suite:** 50 unit, integration, and stress tests via Vitest โ **stabilised in RC1**.
|
|
367
367
|
- [ ] **React Adapter:** Official `@moltendb-web/react` package with `useQuery` hooks and real-time context providers.
|
|
368
368
|
- [ ] **Angular Adapter:** Official `@moltendb-web/angular` package featuring RxJS observables and Signal-based data fetching.
|
|
369
|
-
- [ ] **Delta Sync:** Automatic two-way sync with the
|
|
369
|
+
- [ ] **Delta Sync:** Automatic two-way sync with the MoltenDb Rust server.
|
|
370
370
|
- [ ] **Data Encryption:** Transparent encryption-at-rest using hardware-backed keys (Web Crypto API).
|
|
371
371
|
- [ ] **Analytics Functionality:** Run complex analytics queries straight in the browser without blocking the UI.
|
|
372
372
|
|
|
@@ -379,8 +379,8 @@ Found a bug or have a feature request? Please open an issue on the [GitHub issue
|
|
|
379
379
|
|
|
380
380
|
## License
|
|
381
381
|
|
|
382
|
-
The
|
|
382
|
+
The MoltenDb Web packages (`@moltendb-web/core` and `@moltendb-web/query`) are licensed under the MIT License.
|
|
383
383
|
|
|
384
|
-
The **
|
|
384
|
+
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).
|
|
385
385
|
|
|
386
386
|
For commercial licensing or questions: [maximilian.both27@outlook.com](mailto:maximilian.both27@outlook.com)
|
package/dist/index.d.ts
CHANGED
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface MoltenDbOptions {
|
|
2
2
|
/** URL or path to moltendb-worker.js. */
|
|
3
3
|
workerUrl?: string | URL;
|
|
4
4
|
}
|
|
5
|
-
export interface
|
|
5
|
+
export interface DbEvent {
|
|
6
6
|
type: 'event';
|
|
7
7
|
event: 'change' | 'delete' | 'drop';
|
|
8
8
|
collection: string;
|
|
9
9
|
key: string;
|
|
10
10
|
new_v: number | null;
|
|
11
11
|
}
|
|
12
|
-
export declare class
|
|
12
|
+
export declare class MoltenDb {
|
|
13
13
|
readonly dbName: string;
|
|
14
14
|
readonly workerUrl?: string | URL;
|
|
15
15
|
worker: Worker | null;
|
|
16
|
+
private initPromise;
|
|
16
17
|
private pendingRequests;
|
|
17
18
|
isLeader: boolean;
|
|
18
19
|
private bc;
|
|
19
20
|
/** Legacy global hook. Use `subscribe()` for multi-component listeners. */
|
|
20
|
-
onEvent?: (event:
|
|
21
|
+
onEvent?: (event: DbEvent) => void;
|
|
21
22
|
private eventListeners;
|
|
22
|
-
constructor(dbName?: string, options?:
|
|
23
|
+
constructor(dbName?: string, options?: MoltenDbOptions);
|
|
23
24
|
/**
|
|
24
25
|
* โก Subscribe to real-time DB mutations.
|
|
25
26
|
* @returns An unsubscribe function to prevent memory leaks in UI frameworks.
|
|
26
27
|
*/
|
|
27
|
-
subscribe(listener: (event:
|
|
28
|
+
subscribe(listener: (event: DbEvent) => void): () => void;
|
|
28
29
|
/** Manually remove a specific listener */
|
|
29
|
-
unsubscribe(listener: (event:
|
|
30
|
+
unsubscribe(listener: (event: DbEvent) => void): void;
|
|
30
31
|
private dispatchEvent;
|
|
31
|
-
private initialized;
|
|
32
32
|
init(): Promise<void>;
|
|
33
33
|
private startAsLeader;
|
|
34
34
|
private startAsFollower;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mapToObj } from "./helpers";
|
|
2
|
-
export class
|
|
2
|
+
export class MoltenDb {
|
|
3
3
|
dbName;
|
|
4
4
|
workerUrl;
|
|
5
5
|
worker = null;
|
|
6
|
+
initPromise = null;
|
|
6
7
|
pendingRequests = new Map();
|
|
7
8
|
// Multi-tab Sync State
|
|
8
9
|
isLeader = false;
|
|
@@ -34,18 +35,18 @@ export class MoltenDB {
|
|
|
34
35
|
listener(event);
|
|
35
36
|
}
|
|
36
37
|
catch (err) {
|
|
37
|
-
console.error('[
|
|
38
|
+
console.error('[MoltenDb] Error in subscribed listener', err);
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if (this.
|
|
45
|
-
return;
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
43
|
+
init() {
|
|
44
|
+
// 1. If initialization has already started or finished, return the existing promise
|
|
45
|
+
if (this.initPromise)
|
|
46
|
+
return this.initPromise;
|
|
47
|
+
// 2. Otherwise, start the initialization and store the promise
|
|
48
|
+
this.initPromise = new Promise((resolveInit, rejectInit) => {
|
|
49
|
+
this.bc = new BroadcastChannel(`moltendb_channel_${this.dbName}`);
|
|
49
50
|
navigator.locks.request(`moltendb_lock_${this.dbName}`, { ifAvailable: true }, async (lock) => {
|
|
50
51
|
if (lock) {
|
|
51
52
|
try {
|
|
@@ -60,14 +61,16 @@ export class MoltenDB {
|
|
|
60
61
|
else {
|
|
61
62
|
this.startAsFollower();
|
|
62
63
|
resolveInit();
|
|
64
|
+
// Wait in the background to become leader if the current leader dies
|
|
63
65
|
navigator.locks.request(`moltendb_lock_${this.dbName}`, async () => {
|
|
64
|
-
console.log(`[
|
|
66
|
+
console.log(`[MoltenDb] Promoting this tab to Leader.`);
|
|
65
67
|
await this.startAsLeader();
|
|
66
68
|
return new Promise(() => { }); // Hold lock
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
71
|
});
|
|
70
72
|
});
|
|
73
|
+
return this.initPromise;
|
|
71
74
|
}
|
|
72
75
|
async startAsLeader() {
|
|
73
76
|
// Guard: OPFS is required
|
|
@@ -75,7 +78,7 @@ export class MoltenDB {
|
|
|
75
78
|
await navigator.storage.getDirectory();
|
|
76
79
|
}
|
|
77
80
|
catch {
|
|
78
|
-
throw new Error('[
|
|
81
|
+
throw new Error('[MoltenDb] Origin Private File System (OPFS) is not available in this browser context. ' +
|
|
79
82
|
'Try a non-private window or a browser that supports OPFS (Chrome 102+, Firefox 111+, Safari 15.2+).');
|
|
80
83
|
}
|
|
81
84
|
this.isLeader = true;
|
|
@@ -139,21 +142,33 @@ export class MoltenDB {
|
|
|
139
142
|
};
|
|
140
143
|
}
|
|
141
144
|
async sendMessage(action, payload) {
|
|
142
|
-
//
|
|
145
|
+
// Wait for the engine to boot before routing the message.
|
|
146
|
+
// If the DB is already initialized, this resolves instantly.
|
|
147
|
+
if (action !== 'init') {
|
|
148
|
+
if (this.initPromise) {
|
|
149
|
+
await this.initPromise;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw new Error('[MoltenDb] You must call db.init() before querying the database.');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// 2. Generate a unique ID
|
|
143
156
|
const id = (typeof crypto !== 'undefined' && crypto.randomUUID)
|
|
144
157
|
? crypto.randomUUID()
|
|
145
158
|
: Math.random().toString(36).substring(2, 9);
|
|
146
159
|
return new Promise((resolve, reject) => {
|
|
147
160
|
const successHandler = (res) => resolve(mapToObj(res));
|
|
161
|
+
// 3. We are now GUARANTEED that isLeader, worker, and bc are accurately set
|
|
148
162
|
if (this.isLeader && this.worker) {
|
|
149
163
|
this.pendingRequests.set(id, { resolve: successHandler, reject });
|
|
150
164
|
this.worker.postMessage({ id, action, ...payload });
|
|
151
165
|
}
|
|
152
166
|
else {
|
|
167
|
+
// Follower routing via BroadcastChannel
|
|
153
168
|
const timer = setTimeout(() => {
|
|
154
169
|
if (this.pendingRequests.has(id)) {
|
|
155
170
|
this.pendingRequests.delete(id);
|
|
156
|
-
reject(new Error(`[
|
|
171
|
+
reject(new Error(`[MoltenDb] Request "${action}" timed out after 10s.`));
|
|
157
172
|
}
|
|
158
173
|
}, 10000);
|
|
159
174
|
this.pendingRequests.set(id, {
|
|
@@ -163,8 +178,7 @@ export class MoltenDB {
|
|
|
163
178
|
this.bc.postMessage({ type: 'query', id, action, payload });
|
|
164
179
|
}
|
|
165
180
|
});
|
|
166
|
-
}
|
|
167
|
-
// โโ Convenience CRUD helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
181
|
+
} // โโ Convenience CRUD helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
168
182
|
async set(collection, key, value) {
|
|
169
183
|
await this.sendMessage('set', { collection, data: { [key]: value } });
|
|
170
184
|
}
|
package/dist/moltendb-worker.js
CHANGED
|
@@ -17,7 +17,7 @@ self.onmessage = async (e) => {
|
|
|
17
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;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moltendb-web/core",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0-rc.5",
|
|
4
|
+
"description": "MoltenDb WASM runtime โ the database engine, Web Worker, and main-thread client in one package.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Maximilian Both <maximilian.both27@outlook.com>",
|
|
7
7
|
"license": "MIT",
|