@moltendb-web/react 1.7.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 ADDED
@@ -0,0 +1,290 @@
1
+ # @moltendb-web/react
2
+
3
+ Official React hooks wrapper for [MoltenDb](https://github.com/maximilian27/moltendb-web).
4
+
5
+ ## React Version Support
6
+
7
+ | React Version | Supported |
8
+ |---|---|
9
+ | 16.8+ | ✅ |
10
+ | 17.x | ✅ |
11
+ | 18.x | ✅ |
12
+ | 19.x | ✅ |
13
+
14
+ The package uses only stable React hooks (`useState`, `useEffect`, `useRef`, `useContext`, `createContext`) available since React 16.8. No concurrent features or React 18+ APIs are used in the library itself.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @moltendb-web/react
20
+ ```
21
+
22
+ `@moltendb-web/core` and `@moltendb-web/query` are automatically installed as dependencies — no need to install them separately.
23
+
24
+ ## Setup
25
+
26
+ ### Root-level provider (recommended)
27
+
28
+ Wrap your entire app with `MoltenDbProvider` so all components share a single database instance:
29
+
30
+ **React 18+ (`createRoot`)**
31
+ ```tsx
32
+ import React from 'react';
33
+ import ReactDOM from 'react-dom/client';
34
+ import { MoltenDbProvider } from '@moltendb-web/react';
35
+ import App from './App';
36
+
37
+ ReactDOM.createRoot(document.getElementById('root')!).render(
38
+ <React.StrictMode>
39
+ <MoltenDbProvider config={{ name: 'mydb' }}>
40
+ <App />
41
+ </MoltenDbProvider>
42
+ </React.StrictMode>
43
+ );
44
+ ```
45
+
46
+ **React 16/17 (`ReactDOM.render`)**
47
+ ```tsx
48
+ import React from 'react';
49
+ import ReactDOM from 'react-dom';
50
+ import { MoltenDbProvider } from '@moltendb-web/react';
51
+ import App from './App';
52
+
53
+ ReactDOM.render(
54
+ <React.StrictMode>
55
+ <MoltenDbProvider config={{ name: 'mydb' }}>
56
+ <App />
57
+ </MoltenDbProvider>
58
+ </React.StrictMode>,
59
+ document.getElementById('root')
60
+ );
61
+ ```
62
+
63
+ ### Component-level provider
64
+
65
+ You can also scope a `MoltenDbProvider` to a specific subtree or feature area. Each provider creates its own isolated database instance:
66
+
67
+ ```tsx
68
+ import { MoltenDbProvider } from '@moltendb-web/react';
69
+
70
+ function InventoryFeature() {
71
+ return (
72
+ <MoltenDbProvider config={{ name: 'inventory_db', inMemory: false }}>
73
+ <InventoryList />
74
+ <InventoryStats />
75
+ </MoltenDbProvider>
76
+ );
77
+ }
78
+ ```
79
+
80
+ This is useful for:
81
+ - **Lazy-loaded routes** — only initialise the DB when the route is visited
82
+ - **Isolated feature modules** — each feature uses its own database
83
+ - **Testing** — wrap individual components in a provider with `inMemory: true`
84
+
85
+ ```tsx
86
+ // Lazy-loaded route example
87
+ import { lazy, Suspense } from 'react';
88
+ import { MoltenDbProvider } from '@moltendb-web/react';
89
+
90
+ const Dashboard = lazy(() => import('./Dashboard'));
91
+
92
+ function App() {
93
+ return (
94
+ <Suspense fallback={<p>Loading…</p>}>
95
+ <MoltenDbProvider config={{ name: 'dashboard_db' }}>
96
+ <Dashboard />
97
+ </MoltenDbProvider>
98
+ </Suspense>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Hooks
104
+
105
+ ### `useMoltenDb()`
106
+
107
+ Returns the `MoltenDbClient` instance for manual queries and mutations. Must be used inside `<MoltenDbProvider>`.
108
+
109
+ ```tsx
110
+ import { useMoltenDb } from '@moltendb-web/react';
111
+
112
+ function AddTodoButton() {
113
+ const client = useMoltenDb();
114
+
115
+ const handleClick = async () => {
116
+ await client.collection('todos').set({
117
+ todo_1: { text: 'Hello MoltenDb!', done: false }
118
+ }).exec();
119
+ };
120
+
121
+ return <button onClick={handleClick}>Add Todo</button>;
122
+ }
123
+ ```
124
+
125
+ ### `useMoltenDbResource<T>(collection, queryFn)`
126
+
127
+ Reactively fetches data from a collection. Automatically re-fetches whenever the collection is mutated. Returns `{ value, isLoading, error }`.
128
+
129
+ ```tsx
130
+ import { useMoltenDbResource } from '@moltendb-web/react';
131
+
132
+ interface Todo {
133
+ text: string;
134
+ done: boolean;
135
+ }
136
+
137
+ function TodoList() {
138
+ const { value: todos, isLoading, error } = useMoltenDbResource<Record<string, Todo>>(
139
+ 'todos',
140
+ (col) => col.get().exec()
141
+ );
142
+
143
+ if (isLoading) return <p>Loading…</p>;
144
+ if (error) return <p>Error: {error.message}</p>;
145
+ if (!todos) return <p>No todos yet.</p>;
146
+
147
+ return (
148
+ <ul>
149
+ {Object.entries(todos).map(([id, todo]) => (
150
+ <li key={id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
151
+ {todo.text}
152
+ </li>
153
+ ))}
154
+ </ul>
155
+ );
156
+ }
157
+ ```
158
+
159
+ #### With query builder options
160
+
161
+ ```tsx
162
+ const { value: expensiveLaptops } = useMoltenDbResource(
163
+ 'laptops',
164
+ (col) => col.get()
165
+ .where({ price: { $gt: 2000 }, in_stock: true })
166
+ .sort([{ field: 'price', order: 'asc' }])
167
+ .exec()
168
+ );
169
+ ```
170
+
171
+ ### `useMoltenDbReady()`
172
+
173
+ Returns `true` once MoltenDb has finished initialising. Useful for gating UI until the database is ready. Must be used inside `<MoltenDbProvider>`.
174
+
175
+ ```tsx
176
+ import { useMoltenDbReady } from '@moltendb-web/react';
177
+
178
+ function AppShell({ children }: { children: React.ReactNode }) {
179
+ const isReady = useMoltenDbReady();
180
+
181
+ if (!isReady) return <p>⚙ Initialising database…</p>;
182
+
183
+ return <>{children}</>;
184
+ }
185
+ ```
186
+
187
+ ### `useMoltenDbIsLeader()`
188
+
189
+ Returns `true` if the current tab is the **Leader** — the tab running the WASM worker and performing actual writes. Other tabs act as follower proxies that forward operations to the leader. Must be used inside `<MoltenDbProvider>`.
190
+
191
+ ```tsx
192
+ import { useMoltenDbIsLeader } from '@moltendb-web/react';
193
+
194
+ function TabBadge() {
195
+ const isLeader = useMoltenDbIsLeader();
196
+
197
+ return (
198
+ <span className="badge">
199
+ {isLeader ? '👑 Leader' : '🔗 Follower'}
200
+ </span>
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### `useMoltenDbTerminate()`
206
+
207
+ Returns a function that terminates the MoltenDb worker. You must call this before clearing OPFS storage to avoid file-lock conflicts. Must be used inside `<MoltenDbProvider>`.
208
+
209
+ ```tsx
210
+ import { useMoltenDbTerminate } from '@moltendb-web/react';
211
+
212
+ function ResetButton() {
213
+ const terminate = useMoltenDbTerminate();
214
+
215
+ const handleReset = async () => {
216
+ if (!confirm('Delete all local data?')) return;
217
+ terminate();
218
+ const root = await navigator.storage.getDirectory();
219
+ await root.removeEntry('mydb', { recursive: true });
220
+ location.reload();
221
+ };
222
+
223
+ return <button onClick={handleReset}>🗑 Reset All Data</button>;
224
+ }
225
+ ```
226
+
227
+ ### `useMoltenDbEvents(listener)`
228
+
229
+ Subscribes to real-time mutation events from the database. The `listener` is called with a `DbEvent` whenever any document is created, updated, deleted, or a collection is dropped. Must be used inside `<MoltenDbProvider>`.
230
+
231
+ ```tsx
232
+ import { useCallback, useState } from 'react';
233
+ import { useMoltenDbEvents } from '@moltendb-web/react';
234
+ import type { DbEvent } from '@moltendb-web/react';
235
+
236
+ function LiveFeed() {
237
+ const [events, setEvents] = useState<DbEvent[]>([]);
238
+
239
+ useMoltenDbEvents(useCallback((evt: DbEvent) => {
240
+ setEvents((prev) => [evt, ...prev].slice(0, 50));
241
+ }, []));
242
+
243
+ return (
244
+ <ul>
245
+ {events.map((e, i) => (
246
+ <li key={i}>{e.event} — {e.collection}/{e.key}</li>
247
+ ))}
248
+ </ul>
249
+ );
250
+ }
251
+ ```
252
+
253
+ > **Tip:** Wrap the listener in `useCallback` with an empty dependency array to keep it stable and avoid re-subscribing on every render.
254
+
255
+ ## API Reference
256
+
257
+ | Export | Type | Description |
258
+ |---|---|---|
259
+ | `MoltenDbProvider` | Component | Context provider — initializes MoltenDb and exposes the client to the subtree |
260
+ | `useMoltenDb()` | Hook | Returns the `MoltenDbClient` instance |
261
+ | `useMoltenDbReady()` | Hook | Returns `true` once MoltenDb has finished initialising |
262
+ | `useMoltenDbIsLeader()` | Hook | Returns `true` if the current tab is the Leader (running the WASM worker) |
263
+ | `useMoltenDbTerminate()` | Hook | Returns a function that terminates the MoltenDb worker — call before clearing OPFS storage |
264
+ | `useMoltenDbResource(collection, queryFn)` | Hook | Reactive data fetching with `value`, `isLoading`, `error` and auto-refresh on mutations |
265
+ | `useMoltenDbEvents(listener)` | Hook | Subscribe to real-time `DbEvent` mutation events |
266
+ | `DbEvent` | Type | Event object emitted on mutations: `{ event, collection, key, new_v }` |
267
+ | `MoltenDbProviderProps` | Interface | Props for `MoltenDbProvider`: `{ config: ReactMoltenDbOptions, children }` |
268
+ | `ReactMoltenDbOptions` | Interface | Config passed to the provider — extends `MoltenDbOptions` with a required `name` field |
269
+ | `MoltenDbResourceResult<T>` | Interface | Return type of `useMoltenDbResource`: `{ value, isLoading, error }` |
270
+
271
+ ## Configuration
272
+
273
+ `ReactMoltenDbOptions` extends the core `MoltenDbOptions` with one required field:
274
+
275
+ | Option | Type | Default | Description |
276
+ |---|---|---|---|
277
+ | `name` | `string` | **required** | Database name (used as the OPFS directory name) |
278
+ | `inMemory` | `boolean` | `false` | Run entirely in RAM — no OPFS writes. Data persists as long as at least one tab is open; any tab refresh or close wipes the shared store for all tabs |
279
+ | `encryptionKey` | `string` | `undefined` | Password for at-rest encryption. If omitted, data is stored as plain JSON |
280
+ | `writeMode` | `'async' \| 'sync'` | `'async'` | Storage write mode: `'async'` for high throughput or `'sync'` for durable writes |
281
+ | `hotThreshold` | `number` | `50000` | Maximum documents per collection to keep in RAM |
282
+ | `maxBodySize` | `number` | `undefined` | Maximum request body size in bytes |
283
+ | `maxKeysPerRequest` | `number` | `1000` | Maximum number of keys allowed per JSON request |
284
+ | `workerUrl` | `string \| URL` | `undefined` | Custom URL or path to `moltendb-worker.js` |
285
+
286
+ ## Notes
287
+
288
+ - `MoltenDbProvider` initialises the database asynchronously. Hooks will not return data until `isReady` is `true` — `useMoltenDbResource` handles this automatically by waiting before fetching.
289
+ - Multiple `MoltenDbProvider` instances with the **same `name`** will share the same underlying OPFS storage but maintain separate in-memory instances. Use the same `name` across tabs for cross-tab sync via the built-in leader/follower mechanism.
290
+ - The library ships both **ESM** (`dist/index.js`) and **CommonJS** (`dist/index.cjs`) builds with full TypeScript declarations.
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { MoltenDb, MoltenDbOptions } from '@moltendb-web/core';
3
+ import { MoltenDbClient } from '@moltendb-web/query';
4
+ export interface ReactMoltenDbOptions extends MoltenDbOptions {
5
+ name: string;
6
+ }
7
+ export interface MoltenDbContextValue {
8
+ db: MoltenDb;
9
+ client: MoltenDbClient;
10
+ isReady: boolean;
11
+ }
12
+ export declare const MoltenDbContext: React.Context<MoltenDbContextValue | null>;
13
+ export interface MoltenDbProviderProps {
14
+ config: ReactMoltenDbOptions;
15
+ children: React.ReactNode;
16
+ }
17
+ export declare function MoltenDbProvider({ config, children }: MoltenDbProviderProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function useMoltenDbContext(): MoltenDbContextValue;
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`react`);c=s(c,1);let l=require(`@moltendb-web/core`),u=require(`@moltendb-web/query`),d=require(`react/jsx-runtime`);var f=(0,c.createContext)(null);function p({config:e,children:t}){let[n,r]=(0,c.useState)(!1),i=(0,c.useRef)(null),a=(0,c.useRef)(null);return i.current||(i.current=new l.MoltenDb(e.name,e),a.current=new u.MoltenDbClient(i.current)),(0,c.useEffect)(()=>{i.current.init().then(()=>r(!0)).catch(e=>console.error(`[MoltenDb] Failed to initialize`,e))},[]),(0,d.jsx)(f.Provider,{value:{db:i.current,client:a.current,isReady:n},children:t})}function m(){let e=(0,c.useContext)(f);if(!e)throw Error(`[MoltenDb] useMoltenDbContext must be used inside <MoltenDbProvider>`);return e}function h(){return m().client}function g(){return m().isReady}function _(){return m().db.isLeader}function v(){let{db:e}=m();return()=>e.terminate()}function y(e){let{db:t,isReady:n}=m();(0,c.useEffect)(()=>{if(n)return t.subscribe(e)},[t,n,e])}function b(e,t){let{db:n,client:r,isReady:i}=m(),[a,o]=(0,c.useState)(void 0),[s,l]=(0,c.useState)(!1),[u,d]=(0,c.useState)(null),f=(0,c.useRef)(t);return f.current=t,(0,c.useEffect)(()=>{if(!i)return;let t=!1,a=async()=>{l(!0);try{let n=await f.current(r.collection(e),r);t||(o(n),d(null))}catch(e){t||(e.message?.includes(`404`)?(o([]),d(null)):d(e))}finally{t||l(!1)}};a();let s=n.subscribe(t=>{t.collection===e&&a()});return()=>{t=!0,s()}},[i,e,n,r]),{value:a,isLoading:s,error:u}}exports.MoltenDbProvider=p,exports.useMoltenDb=h,exports.useMoltenDbEvents=y,exports.useMoltenDbIsLeader=_,exports.useMoltenDbReady=g,exports.useMoltenDbResource=b,exports.useMoltenDbTerminate=v;
@@ -0,0 +1,6 @@
1
+ export { MoltenDbProvider } from './MoltenDbContext';
2
+ export type { ReactMoltenDbOptions, MoltenDbProviderProps } from './MoltenDbContext';
3
+ export { useMoltenDb, useMoltenDbReady, useMoltenDbIsLeader, useMoltenDbTerminate, useMoltenDbEvents } from './useMoltenDb';
4
+ export type { DbEvent } from '@moltendb-web/core';
5
+ export { useMoltenDbResource } from './useMoltenDbResource';
6
+ export type { MoltenDbResourceResult } from './useMoltenDbResource';
package/dist/index.js ADDED
@@ -0,0 +1,86 @@
1
+ import { createContext as e, useContext as t, useEffect as n, useRef as r, useState as i } from "react";
2
+ import { MoltenDb as a } from "@moltendb-web/core";
3
+ import { MoltenDbClient as o } from "@moltendb-web/query";
4
+ import { jsx as s } from "react/jsx-runtime";
5
+ //#region src/MoltenDbContext.tsx
6
+ var c = e(null);
7
+ function l({ config: e, children: t }) {
8
+ let [l, u] = i(!1), d = r(null), f = r(null);
9
+ return d.current || (d.current = new a(e.name, e), f.current = new o(d.current)), n(() => {
10
+ d.current.init().then(() => u(!0)).catch((e) => console.error("[MoltenDb] Failed to initialize", e));
11
+ }, []), /* @__PURE__ */ s(c.Provider, {
12
+ value: {
13
+ db: d.current,
14
+ client: f.current,
15
+ isReady: l
16
+ },
17
+ children: t
18
+ });
19
+ }
20
+ function u() {
21
+ let e = t(c);
22
+ if (!e) throw Error("[MoltenDb] useMoltenDbContext must be used inside <MoltenDbProvider>");
23
+ return e;
24
+ }
25
+ //#endregion
26
+ //#region src/useMoltenDb.ts
27
+ function d() {
28
+ return u().client;
29
+ }
30
+ function f() {
31
+ return u().isReady;
32
+ }
33
+ function p() {
34
+ return u().db.isLeader;
35
+ }
36
+ function m() {
37
+ let { db: e } = u();
38
+ return () => e.terminate();
39
+ }
40
+ function h(e) {
41
+ let { db: t, isReady: r } = u();
42
+ n(() => {
43
+ if (r) return t.subscribe(e);
44
+ }, [
45
+ t,
46
+ r,
47
+ e
48
+ ]);
49
+ }
50
+ //#endregion
51
+ //#region src/useMoltenDbResource.ts
52
+ function g(e, t) {
53
+ let { db: a, client: o, isReady: s } = u(), [c, l] = i(void 0), [d, f] = i(!1), [p, m] = i(null), h = r(t);
54
+ return h.current = t, n(() => {
55
+ if (!s) return;
56
+ let t = !1, n = async () => {
57
+ f(!0);
58
+ try {
59
+ let n = await h.current(o.collection(e), o);
60
+ t || (l(n), m(null));
61
+ } catch (e) {
62
+ t || (e.message?.includes("404") ? (l([]), m(null)) : m(e));
63
+ } finally {
64
+ t || f(!1);
65
+ }
66
+ };
67
+ n();
68
+ let r = a.subscribe((t) => {
69
+ t.collection === e && n();
70
+ });
71
+ return () => {
72
+ t = !0, r();
73
+ };
74
+ }, [
75
+ s,
76
+ e,
77
+ a,
78
+ o
79
+ ]), {
80
+ value: c,
81
+ isLoading: d,
82
+ error: p
83
+ };
84
+ }
85
+ //#endregion
86
+ export { l as MoltenDbProvider, d as useMoltenDb, h as useMoltenDbEvents, p as useMoltenDbIsLeader, f as useMoltenDbReady, g as useMoltenDbResource, m as useMoltenDbTerminate };
@@ -0,0 +1,16 @@
1
+ import { DbEvent } from '@moltendb-web/core';
2
+ import { MoltenDbClient } from '@moltendb-web/query';
3
+ /** Hook to access the MoltenDb Query Client directly. Must be used inside <MoltenDbProvider>. */
4
+ export declare function useMoltenDb(): MoltenDbClient;
5
+ /** Returns true once MoltenDb has finished initialising. Must be used inside <MoltenDbProvider>. */
6
+ export declare function useMoltenDbReady(): boolean;
7
+ /** Returns true if this tab is the Leader (running the WASM worker). Must be used inside <MoltenDbProvider>. */
8
+ export declare function useMoltenDbIsLeader(): boolean;
9
+ /** Returns a function that terminates the MoltenDb worker. Call before clearing OPFS storage. Must be used inside <MoltenDbProvider>. */
10
+ export declare function useMoltenDbTerminate(): () => void;
11
+ /**
12
+ * Hook to subscribe to real-time MoltenDb mutation events.
13
+ * The callback is called whenever any document in the database changes.
14
+ * Must be used inside <MoltenDbProvider>.
15
+ */
16
+ export declare function useMoltenDbEvents(listener: (event: DbEvent) => void): void;
@@ -0,0 +1,15 @@
1
+ import { MoltenDbClient } from '@moltendb-web/query';
2
+ export interface MoltenDbResourceResult<T> {
3
+ value: T | undefined;
4
+ isLoading: boolean;
5
+ error: any | null;
6
+ }
7
+ /**
8
+ * Hook to reactively fetch data from a MoltenDb collection.
9
+ * Automatically re-fetches when the collection changes.
10
+ * Must be used inside <MoltenDbProvider>.
11
+ *
12
+ * @param collection - The collection name to query.
13
+ * @param queryFn - A function receiving the pre-bound collection accessor and the full client.
14
+ */
15
+ export declare function useMoltenDbResource<T>(collection: string, queryFn: (collection: ReturnType<MoltenDbClient['collection']>, client: MoltenDbClient) => Promise<T>): MoltenDbResourceResult<T>;
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@moltendb-web/react",
3
+ "version": "1.7.0",
4
+ "description": "Official React hooks wrapper for MoltenDb",
5
+ "type": "module",
6
+ "author": "Maximilian Both <maximilian.both27@outlook.com>",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/maximilian27/moltendb-web/tree/master/packages/react"
11
+ },
12
+ "main": "./dist/index.cjs",
13
+ "module": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "require": "./dist/index.cjs",
19
+ "types": "./dist/index.d.ts"
20
+ }
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "build": "vite build && tsc --emitDeclarationOnly --declaration --declarationDir dist",
27
+ "dev": "vite build --watch",
28
+ "prepublishOnly": "npm run build"
29
+ },
30
+ "peerDependencies": {
31
+ "react": ">=16.8.0"
32
+ },
33
+ "dependencies": {
34
+ "@moltendb-web/core": "^1.7.0",
35
+ "@moltendb-web/query": "^1.7.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/react": "^19.2.14",
39
+ "react": "^19.2.5",
40
+ "typescript": "^5.5.3",
41
+ "vite": "^8.0.10",
42
+ "vite-plugin-dts": "^3.0.0"
43
+ }
44
+ }