@virtuoso.dev/reactive-engine-react 0.0.4 → 0.2.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/dist/index.d.ts CHANGED
@@ -56,8 +56,16 @@ export declare interface EngineProviderProps {
56
56
  children: React_2.ReactNode;
57
57
  /**
58
58
  * Optional stable ID for storage namespacing. Use this for multi-engine apps to prevent storage key conflicts.
59
+ * Also registers the engine in the global registry for access via `useRemote*` hooks with a string ID.
59
60
  */
60
61
  engineId?: string;
62
+ /**
63
+ * Optional reactive ref to expose the engine instance. Created by {@link useEngineRef}.
64
+ * Pass to `useRemote*` hooks to access the engine from sibling or ancestor components.
65
+ *
66
+ * @remarks An `EngineRef` should only be used with a single `EngineProvider`.
67
+ */
68
+ engineRef?: EngineRef;
61
69
  /**
62
70
  * A callback invoked once when the engine is created. Use this to register nodes and set up subscriptions.
63
71
  */
@@ -76,6 +84,40 @@ export declare interface EngineProviderProps {
76
84
  updateFn?: (engine: Engine) => void;
77
85
  }
78
86
 
87
+ /**
88
+ * A reactive ref that holds an engine instance. Created by {@link useEngineRef}.
89
+ * Pass it to {@link EngineProvider} via the `engineRef` prop, then use it with
90
+ * the `useRemote*` hooks to access the engine from anywhere in the component tree.
91
+ *
92
+ * @remarks An `EngineRef` should only be used with a single `EngineProvider`.
93
+ * Using the same ref with multiple providers is unsupported.
94
+ *
95
+ * @category React Hooks and Components
96
+ */
97
+ export declare interface EngineRef {
98
+ /** The current engine instance, or null if not yet available. */
99
+ readonly current: Engine | null;
100
+ }
101
+
102
+ /**
103
+ * Union type for the engine source parameter accepted by `useRemote*` hooks.
104
+ * Can be either a string engine ID (for global registry lookup) or an {@link EngineRef}.
105
+ *
106
+ * @category React Hooks and Components
107
+ */
108
+ export declare type EngineSource = EngineRef | string;
109
+
110
+ /**
111
+ * Options for the {@link useRemoteCellValues} hook.
112
+ * @category React Hooks and Components
113
+ */
114
+ export declare interface RemoteCellValuesOptions<T extends unknown[]> {
115
+ cells: {
116
+ [K in keyof T]: Out<T[K]>;
117
+ };
118
+ engineSource: EngineSource;
119
+ }
120
+
79
121
  /**
80
122
  * Returns a tuple of the current value of a cell and a publisher function (similar to `useState`).
81
123
  * The component re-renderes when the cell value changes.
@@ -187,6 +229,29 @@ declare function useCellValueWithStore<T>(cell: Out<T>): T;
187
229
  */
188
230
  export declare function useEngine(): Engine;
189
231
 
232
+ /**
233
+ * Creates a stable, memoized {@link EngineRef} for use inside a component.
234
+ * Pass the returned ref to an {@link EngineProvider} via the `engineRef` prop,
235
+ * and to `useRemote*` hooks to access the engine reactively.
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * function App() {
240
+ * const engineRef = useEngineRef()
241
+ * return (
242
+ * <>
243
+ * <SiblingComponent engineRef={engineRef} />
244
+ * <EngineProvider engineRef={engineRef} initFn={initFn}>
245
+ * <NestedComponent />
246
+ * </EngineProvider>
247
+ * </>
248
+ * )
249
+ * }
250
+ * ```
251
+ * @category React Hooks and Components
252
+ */
253
+ export declare function useEngineRef(): EngineRef;
254
+
190
255
  export declare const useIsomorphicLayoutEffect: typeof React_2.useLayoutEffect;
191
256
 
192
257
  /**
@@ -213,4 +278,135 @@ export declare const useIsomorphicLayoutEffect: typeof React_2.useLayoutEffect;
213
278
  */
214
279
  export declare function usePublisher<T>(node$: Inp<T>): (value: T) => void;
215
280
 
281
+ /**
282
+ * Returns a tuple of the current value and a publisher function for a cell in an engine identified by `engineSource`.
283
+ * Returns `[undefined, noop]` when the engine is not available yet.
284
+ *
285
+ * @param cell - The cell to use.
286
+ * @param engineSource - A string engine ID or an {@link EngineRef}.
287
+ * @returns A tuple of the current value (or undefined) and a publisher function.
288
+ * @typeParam T - The type of values that the cell emits/accepts.
289
+ *
290
+ * @example
291
+ * ```tsx
292
+ * const cell$ = Cell(0)
293
+ * // ...
294
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
295
+ * const [value, setValue] = useRemoteCell(cell$, engineRef)
296
+ * if (value === undefined) return <div>Loading...</div>
297
+ * return <button onClick={() => setValue(value + 1)}>{value}</button>
298
+ * }
299
+ * ```
300
+ * @category React Hooks and Components
301
+ */
302
+ export declare function useRemoteCell<T>(cell: NodeRef<T>, engineSource: EngineSource): [T | undefined, (value: T) => void];
303
+
304
+ /**
305
+ * Gets the current value of the cell from an engine identified by `engineSource`.
306
+ * Returns `undefined` when the engine is not available yet.
307
+ *
308
+ * @param cell - The cell to read from.
309
+ * @param engineSource - A string engine ID or an {@link EngineRef} to read from.
310
+ * @returns The current value of the cell, or `undefined` if the engine is not available.
311
+ * @typeParam T - The type of the value that the cell carries.
312
+ *
313
+ * @example
314
+ * ```tsx
315
+ * const cell$ = Cell(0)
316
+ * // ...
317
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
318
+ * const value = useRemoteCellValue(cell$, engineRef)
319
+ * if (value === undefined) return <div>Loading...</div>
320
+ * return <div>{value}</div>
321
+ * }
322
+ * ```
323
+ * @category React Hooks and Components
324
+ */
325
+ export declare function useRemoteCellValue<T>(cell: Out<T>, engineSource: EngineSource): T | undefined;
326
+
327
+ /**
328
+ * Returns the up-to-date values of the passed cells from an engine identified by `engineId`.
329
+ * Returns `undefined` when the engine is not available yet.
330
+ *
331
+ * @param options - An object containing the cells array and engineId.
332
+ * @returns An array with the current values of the cells, or `undefined` if the engine is not available.
333
+ *
334
+ * @example
335
+ * ```tsx
336
+ * const foo$ = Cell('foo')
337
+ * const bar$ = Cell('bar')
338
+ * // ...
339
+ * function SiblingComponent() {
340
+ * const values = useRemoteCellValues({ cells: [foo$, bar$], engineId: 'my-engine' })
341
+ * if (values === undefined) return <div>Loading...</div>
342
+ * const [foo, bar] = values
343
+ * return <div>{foo} - {bar}</div>
344
+ * }
345
+ * ```
346
+ * @category React Hooks and Components
347
+ */
348
+ /** @hidden */
349
+ export declare function useRemoteCellValues<T1>(options: RemoteCellValuesOptions<[T1]>): [T1] | undefined;
350
+
351
+ /** @hidden */
352
+ export declare function useRemoteCellValues<T1, T2>(options: RemoteCellValuesOptions<[T1, T2]>): [T1, T2] | undefined;
353
+
354
+ /** @hidden */
355
+ export declare function useRemoteCellValues<T1, T2, T3>(options: RemoteCellValuesOptions<[T1, T2, T3]>): [T1, T2, T3] | undefined;
356
+
357
+ /** @hidden */
358
+ export declare function useRemoteCellValues<T1, T2, T3, T4>(options: RemoteCellValuesOptions<[T1, T2, T3, T4]>): [T1, T2, T3, T4] | undefined;
359
+
360
+ /** @hidden */
361
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5]>): [T1, T2, T3, T4, T5] | undefined;
362
+
363
+ /** @hidden */
364
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6]>): [T1, T2, T3, T4, T5, T6] | undefined;
365
+
366
+ /** @hidden */
367
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7]>): [T1, T2, T3, T4, T5, T6, T7] | undefined;
368
+
369
+ /** @hidden */
370
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8]>): [T1, T2, T3, T4, T5, T6, T7, T8] | undefined;
371
+
372
+ /** @hidden */
373
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>): [T1, T2, T3, T4, T5, T6, T7, T8, T9] | undefined;
374
+
375
+ /** @hidden */
376
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>): [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] | undefined;
377
+
378
+ /** @hidden */
379
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]>): [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] | undefined;
380
+
381
+ /** @hidden */
382
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]>): [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] | undefined;
383
+
384
+ /** @hidden */
385
+ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>(options: RemoteCellValuesOptions<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]>): [T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] | undefined;
386
+
387
+ /** @hidden */
388
+ export declare function useRemoteCellValues(options: RemoteCellValuesOptions<unknown[]>): undefined | unknown[];
389
+
390
+ /**
391
+ * Returns a function that publishes values to a node in an engine identified by `engineSource`.
392
+ * Returns a no-op function when the engine is not available yet.
393
+ *
394
+ * @param node$ - The node to publish to.
395
+ * @param engineSource - A string engine ID or an {@link EngineRef} to publish to.
396
+ * @returns A publisher function that accepts values of type T.
397
+ * @typeParam T - The type of values that the node accepts.
398
+ *
399
+ * @example
400
+ * ```tsx
401
+ * const trigger$ = Trigger()
402
+ * // ...
403
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
404
+ * const publish = useRemotePublisher(trigger$, engineRef)
405
+ * return <button onClick={() => publish()}>Trigger</button>
406
+ * }
407
+ * ```
408
+ * @category React Hooks and Components
409
+ */
410
+ export declare function useRemotePublisher<T>(node$: Inp<T>, engineSource: EngineSource): (value: T) => void;
411
+
216
412
  export { }
package/dist/index.js CHANGED
@@ -1,63 +1,185 @@
1
- import { jsx as C } from "react/jsx-runtime";
2
- import { Engine as E } from "@virtuoso.dev/reactive-engine-core";
3
- import * as u from "react";
4
- import b from "tiny-invariant";
5
- const a = typeof document < "u" ? u.useLayoutEffect : u.useEffect, g = u.createContext(null);
6
- function o() {
7
- const e = u.useContext(g);
8
- return b(e !== null, "useEngine must be used within an EngineProvider"), e;
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import { Engine as d } from "@virtuoso.dev/reactive-engine-core";
3
+ import * as s from "react";
4
+ import h from "tiny-invariant";
5
+ const c = typeof document < "u" ? s.useLayoutEffect : s.useEffect, y = Symbol("engineRefInternal");
6
+ function x() {
7
+ let e = null;
8
+ const n = /* @__PURE__ */ new Set();
9
+ return {
10
+ get current() {
11
+ return e;
12
+ },
13
+ [y]: {
14
+ set(r) {
15
+ e = r, n.forEach((u) => {
16
+ u();
17
+ });
18
+ },
19
+ subscribe(r) {
20
+ return n.add(r), () => {
21
+ n.delete(r);
22
+ };
23
+ }
24
+ }
25
+ };
26
+ }
27
+ function j() {
28
+ const [e] = s.useState(() => x());
29
+ return e;
30
+ }
31
+ function E(e) {
32
+ return e[y];
9
33
  }
34
+ const f = /* @__PURE__ */ new Map();
10
35
  function S(e) {
11
- const n = o();
36
+ let n = f.get(e);
37
+ return n || (n = { engine: null, subscribers: /* @__PURE__ */ new Set() }, f.set(e, n)), n;
38
+ }
39
+ function R(e, n) {
40
+ const t = S(e);
41
+ t.engine = n, t.subscribers.forEach((r) => {
42
+ r();
43
+ }), t.subscribers.size === 0 && n === null && f.delete(e);
44
+ }
45
+ function I(e, n) {
46
+ const t = S(e);
47
+ return t.subscribers.add(n), () => {
48
+ t.subscribers.delete(n), t.subscribers.size === 0 && t.engine === null && f.delete(e);
49
+ };
50
+ }
51
+ function m(e) {
52
+ var n;
53
+ return ((n = f.get(e)) == null ? void 0 : n.engine) ?? null;
54
+ }
55
+ const V = s.createContext(null);
56
+ function a() {
57
+ const e = s.useContext(V);
58
+ return h(e !== null, "useEngine must be used within an EngineProvider"), e;
59
+ }
60
+ function w(e) {
61
+ const n = a();
12
62
  n.register(e);
13
- const t = u.useCallback((r) => n.sub(e, r), [n, e]);
14
- return u.useSyncExternalStore(
63
+ const t = s.useCallback((r) => n.sub(e, r), [n, e]);
64
+ return s.useSyncExternalStore(
15
65
  t,
16
66
  () => n.getValue(e),
17
67
  () => n.getValue(e)
18
68
  );
19
69
  }
20
- function V(e) {
21
- const n = o();
70
+ function P(e) {
71
+ const n = a();
22
72
  n.register(e);
23
- const [t, r] = u.useState(() => n.getValue(e));
24
- return a(() => n.sub(e, r), [n, e]), t;
73
+ const [t, r] = s.useState(() => n.getValue(e));
74
+ return c(() => n.sub(e, r), [n, e]), t;
25
75
  }
26
- const f = "useSyncExternalStore" in u ? S : V;
27
- function h(...e) {
28
- const n = o(), t = u.useMemo(() => n.combineCells(e), [n, ...e]);
29
- return f(t);
76
+ const v = "useSyncExternalStore" in s ? w : P;
77
+ function k(...e) {
78
+ const n = a(), t = s.useMemo(() => n.combineCells(e), [n, ...e]);
79
+ return v(t);
30
80
  }
31
- function x(e) {
32
- const n = o();
33
- return n.register(e), u.useCallback(
81
+ function N(e) {
82
+ const n = a();
83
+ return n.register(e), s.useCallback(
34
84
  (t) => {
35
85
  n.pub(e, t);
36
86
  },
37
87
  [n, e]
38
88
  );
39
89
  }
40
- function P(e) {
41
- return [f(e), x(e)];
42
- }
43
- const d = ({ children: e, engineId: n, initFn: t, initWith: r, updateDeps: l, updateFn: i }) => {
44
- const [s, m] = u.useState(null);
45
- return a(() => {
46
- const c = new E(r, n);
47
- return m(c), t == null || t(c), () => {
48
- c.dispose();
90
+ function A(e) {
91
+ return [v(e), N(e)];
92
+ }
93
+ function C(e) {
94
+ const n = typeof e != "string", t = n ? null : e, r = n ? e : null, [u, l] = s.useState(() => t ? m(t) : null);
95
+ c(() => {
96
+ if (!t) {
97
+ l(null);
98
+ return;
99
+ }
100
+ return l(m(t)), I(t, () => {
101
+ l(m(t));
102
+ });
103
+ }, [t]);
104
+ const [i, o] = s.useState(() => r ? r.current : null);
105
+ return c(() => {
106
+ if (!r) {
107
+ o(null);
108
+ return;
109
+ }
110
+ const b = E(r);
111
+ return o(r.current), b.subscribe(() => {
112
+ o(r.current);
113
+ });
114
+ }, [r]), n ? i : u;
115
+ }
116
+ function L(e, n) {
117
+ const t = C(n), [r, u] = s.useState(() => t ? t.getValue(e) : void 0);
118
+ return c(() => {
119
+ if (!t) {
120
+ u(void 0);
121
+ return;
122
+ }
123
+ return t.register(e), u(t.getValue(e)), t.sub(e, u);
124
+ }, [t, e]), r;
125
+ }
126
+ function M(e, n) {
127
+ const t = C(n);
128
+ return c(() => {
129
+ t && t.register(e);
130
+ }, [t, e]), s.useCallback(
131
+ (r) => {
132
+ t && t.pub(e, r);
133
+ },
134
+ [t, e]
135
+ );
136
+ }
137
+ function G(e, n) {
138
+ return [L(e, n), M(e, n)];
139
+ }
140
+ function O(e) {
141
+ const { cells: n, engineSource: t } = e, r = C(t), u = s.useMemo(() => r ? r.combineCells(n) : null, [r, ...n]), [l, i] = s.useState(
142
+ () => r && u ? r.getValue(u) : void 0
143
+ );
144
+ return c(() => {
145
+ if (!r || !u) {
146
+ i(void 0);
147
+ return;
148
+ }
149
+ return r.register(u), i(r.getValue(u)), r.sub(u, i);
150
+ }, [r, u]), l;
151
+ }
152
+ const W = ({
153
+ children: e,
154
+ engineId: n,
155
+ engineRef: t,
156
+ initFn: r,
157
+ initWith: u,
158
+ updateDeps: l,
159
+ updateFn: i
160
+ }) => {
161
+ const [o, b] = s.useState(null);
162
+ return c(() => {
163
+ const g = new d(u, n);
164
+ return b(g), r == null || r(g), n && R(n, g), t && E(t).set(g), () => {
165
+ n && R(n, null), t && E(t).set(null), g.dispose();
49
166
  };
50
- }, [r, n]), a(() => {
51
- s && (i == null || i(s));
52
- }, [s, ...l ?? []]), s && /* @__PURE__ */ C(g.Provider, { value: s, children: e });
167
+ }, [u, n, t]), c(() => {
168
+ o && (i == null || i(o));
169
+ }, [o, ...l ?? []]), o && /* @__PURE__ */ p(V.Provider, { value: o, children: e });
53
170
  };
54
171
  export {
55
- g as EngineContext,
56
- d as EngineProvider,
57
- P as useCell,
58
- f as useCellValue,
59
- h as useCellValues,
60
- o as useEngine,
61
- a as useIsomorphicLayoutEffect,
62
- x as usePublisher
172
+ V as EngineContext,
173
+ W as EngineProvider,
174
+ A as useCell,
175
+ v as useCellValue,
176
+ k as useCellValues,
177
+ a as useEngine,
178
+ j as useEngineRef,
179
+ c as useIsomorphicLayoutEffect,
180
+ N as usePublisher,
181
+ G as useRemoteCell,
182
+ L as useRemoteCellValue,
183
+ O as useRemoteCellValues,
184
+ M as useRemotePublisher
63
185
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
- "version": "0.0.4",
6
+ "version": "0.2.0",
7
7
  "module": "dist/index.js",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
@@ -21,13 +21,11 @@
21
21
  "tiny-invariant": "^1.3.3"
22
22
  },
23
23
  "peerDependencies": {
24
- "@virtuoso.dev/reactive-engine-core": ">=0.0.3",
24
+ "@virtuoso.dev/reactive-engine-core": ">=0.0.4",
25
25
  "react": ">= 18",
26
26
  "react-dom": ">= 18"
27
27
  },
28
28
  "devDependencies": {
29
- "@testing-library/jest-dom": "^6.9.1",
30
- "@testing-library/react": "^16.3.0",
31
29
  "@types/node": "^22.10.1",
32
30
  "@types/react": "^19.2.7",
33
31
  "@types/react-dom": "^19.2.3",
@@ -35,7 +33,6 @@
35
33
  "@vitest/browser-playwright": "^4.0.16",
36
34
  "@vitejs/plugin-react": "^4.5.0",
37
35
  "eslint": "^9.24.0",
38
- "jsdom": "^27.4.0",
39
36
  "playwright": "^1.33.0",
40
37
  "prettier": "^3.5.3",
41
38
  "react": "^19.2.3",
@@ -46,7 +43,7 @@
46
43
  "vitest": "^4.0.16",
47
44
  "vitest-browser-react": "^2.0.2",
48
45
  "@virtuoso.dev/tooling": "0.1.0",
49
- "@virtuoso.dev/reactive-engine-core": "0.0.3"
46
+ "@virtuoso.dev/reactive-engine-core": "0.0.4"
50
47
  },
51
48
  "files": [
52
49
  "dist"