@virtuoso.dev/reactive-engine-react 0.1.0 → 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,29 @@ 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
+
79
110
  /**
80
111
  * Options for the {@link useRemoteCellValues} hook.
81
112
  * @category React Hooks and Components
@@ -84,7 +115,7 @@ export declare interface RemoteCellValuesOptions<T extends unknown[]> {
84
115
  cells: {
85
116
  [K in keyof T]: Out<T[K]>;
86
117
  };
87
- engineId: string;
118
+ engineSource: EngineSource;
88
119
  }
89
120
 
90
121
  /**
@@ -198,6 +229,29 @@ declare function useCellValueWithStore<T>(cell: Out<T>): T;
198
229
  */
199
230
  export declare function useEngine(): Engine;
200
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
+
201
255
  export declare const useIsomorphicLayoutEffect: typeof React_2.useLayoutEffect;
202
256
 
203
257
  /**
@@ -225,11 +279,11 @@ export declare const useIsomorphicLayoutEffect: typeof React_2.useLayoutEffect;
225
279
  export declare function usePublisher<T>(node$: Inp<T>): (value: T) => void;
226
280
 
227
281
  /**
228
- * Returns a tuple of the current value and a publisher function for a cell in an engine identified by `engineId`.
282
+ * Returns a tuple of the current value and a publisher function for a cell in an engine identified by `engineSource`.
229
283
  * Returns `[undefined, noop]` when the engine is not available yet.
230
284
  *
231
285
  * @param cell - The cell to use.
232
- * @param engineId - The ID of the engine (must match the `engineId` prop of an EngineProvider).
286
+ * @param engineSource - A string engine ID or an {@link EngineRef}.
233
287
  * @returns A tuple of the current value (or undefined) and a publisher function.
234
288
  * @typeParam T - The type of values that the cell emits/accepts.
235
289
  *
@@ -237,22 +291,22 @@ export declare function usePublisher<T>(node$: Inp<T>): (value: T) => void;
237
291
  * ```tsx
238
292
  * const cell$ = Cell(0)
239
293
  * // ...
240
- * function SiblingComponent() {
241
- * const [value, setValue] = useRemoteCell(cell$, 'my-engine')
294
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
295
+ * const [value, setValue] = useRemoteCell(cell$, engineRef)
242
296
  * if (value === undefined) return <div>Loading...</div>
243
297
  * return <button onClick={() => setValue(value + 1)}>{value}</button>
244
298
  * }
245
299
  * ```
246
300
  * @category React Hooks and Components
247
301
  */
248
- export declare function useRemoteCell<T>(cell: NodeRef<T>, engineId: string): [T | undefined, (value: T) => void];
302
+ export declare function useRemoteCell<T>(cell: NodeRef<T>, engineSource: EngineSource): [T | undefined, (value: T) => void];
249
303
 
250
304
  /**
251
- * Gets the current value of the cell from an engine identified by `engineId`.
305
+ * Gets the current value of the cell from an engine identified by `engineSource`.
252
306
  * Returns `undefined` when the engine is not available yet.
253
307
  *
254
308
  * @param cell - The cell to read from.
255
- * @param engineId - The ID of the engine to read from (must match the `engineId` prop of an EngineProvider).
309
+ * @param engineSource - A string engine ID or an {@link EngineRef} to read from.
256
310
  * @returns The current value of the cell, or `undefined` if the engine is not available.
257
311
  * @typeParam T - The type of the value that the cell carries.
258
312
  *
@@ -260,15 +314,15 @@ export declare function useRemoteCell<T>(cell: NodeRef<T>, engineId: string): [T
260
314
  * ```tsx
261
315
  * const cell$ = Cell(0)
262
316
  * // ...
263
- * function SiblingComponent() {
264
- * const value = useRemoteCellValue(cell$, 'my-engine')
317
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
318
+ * const value = useRemoteCellValue(cell$, engineRef)
265
319
  * if (value === undefined) return <div>Loading...</div>
266
320
  * return <div>{value}</div>
267
321
  * }
268
322
  * ```
269
323
  * @category React Hooks and Components
270
324
  */
271
- export declare function useRemoteCellValue<T>(cell: Out<T>, engineId: string): T | undefined;
325
+ export declare function useRemoteCellValue<T>(cell: Out<T>, engineSource: EngineSource): T | undefined;
272
326
 
273
327
  /**
274
328
  * Returns the up-to-date values of the passed cells from an engine identified by `engineId`.
@@ -334,11 +388,11 @@ export declare function useRemoteCellValues<T1, T2, T3, T4, T5, T6, T7, T8, T9,
334
388
  export declare function useRemoteCellValues(options: RemoteCellValuesOptions<unknown[]>): undefined | unknown[];
335
389
 
336
390
  /**
337
- * Returns a function that publishes values to a node in an engine identified by `engineId`.
391
+ * Returns a function that publishes values to a node in an engine identified by `engineSource`.
338
392
  * Returns a no-op function when the engine is not available yet.
339
393
  *
340
394
  * @param node$ - The node to publish to.
341
- * @param engineId - The ID of the engine to publish to (must match the `engineId` prop of an EngineProvider).
395
+ * @param engineSource - A string engine ID or an {@link EngineRef} to publish to.
342
396
  * @returns A publisher function that accepts values of type T.
343
397
  * @typeParam T - The type of values that the node accepts.
344
398
  *
@@ -346,13 +400,13 @@ export declare function useRemoteCellValues(options: RemoteCellValuesOptions<unk
346
400
  * ```tsx
347
401
  * const trigger$ = Trigger()
348
402
  * // ...
349
- * function SiblingComponent() {
350
- * const publish = useRemotePublisher(trigger$, 'my-engine')
403
+ * function SiblingComponent({ engineRef }: { engineRef: EngineRef }) {
404
+ * const publish = useRemotePublisher(trigger$, engineRef)
351
405
  * return <button onClick={() => publish()}>Trigger</button>
352
406
  * }
353
407
  * ```
354
408
  * @category React Hooks and Components
355
409
  */
356
- export declare function useRemotePublisher<T>(node$: Inp<T>, engineId: string): (value: T) => void;
410
+ export declare function useRemotePublisher<T>(node$: Inp<T>, engineSource: EngineSource): (value: T) => void;
357
411
 
358
412
  export { }
package/dist/index.js CHANGED
@@ -1,55 +1,84 @@
1
- import { jsx as v } from "react/jsx-runtime";
2
- import { Engine as S } from "@virtuoso.dev/reactive-engine-core";
1
+ import { jsx as p } from "react/jsx-runtime";
2
+ import { Engine as d } from "@virtuoso.dev/reactive-engine-core";
3
3
  import * as s from "react";
4
- import R from "tiny-invariant";
5
- const i = typeof document < "u" ? s.useLayoutEffect : s.useEffect, l = /* @__PURE__ */ new Map();
6
- function C(e) {
7
- let n = l.get(e);
8
- return n || (n = { engine: null, subscribers: /* @__PURE__ */ new Set() }, l.set(e, n)), n;
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;
9
30
  }
10
- function m(e, n) {
11
- const t = C(e);
12
- t.engine = n, t.subscribers.forEach((u) => {
13
- u();
14
- }), t.subscribers.size === 0 && n === null && l.delete(e);
31
+ function E(e) {
32
+ return e[y];
15
33
  }
16
- function p(e, n) {
17
- const t = C(e);
34
+ const f = /* @__PURE__ */ new Map();
35
+ function S(e) {
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);
18
47
  return t.subscribers.add(n), () => {
19
- t.subscribers.delete(n), t.subscribers.size === 0 && t.engine === null && l.delete(e);
48
+ t.subscribers.delete(n), t.subscribers.size === 0 && t.engine === null && f.delete(e);
20
49
  };
21
50
  }
22
- function f(e) {
51
+ function m(e) {
23
52
  var n;
24
- return ((n = l.get(e)) == null ? void 0 : n.engine) ?? null;
53
+ return ((n = f.get(e)) == null ? void 0 : n.engine) ?? null;
25
54
  }
26
- const E = s.createContext(null);
55
+ const V = s.createContext(null);
27
56
  function a() {
28
- const e = s.useContext(E);
29
- return R(e !== null, "useEngine must be used within an EngineProvider"), e;
57
+ const e = s.useContext(V);
58
+ return h(e !== null, "useEngine must be used within an EngineProvider"), e;
30
59
  }
31
- function x(e) {
60
+ function w(e) {
32
61
  const n = a();
33
62
  n.register(e);
34
- const t = s.useCallback((u) => n.sub(e, u), [n, e]);
63
+ const t = s.useCallback((r) => n.sub(e, r), [n, e]);
35
64
  return s.useSyncExternalStore(
36
65
  t,
37
66
  () => n.getValue(e),
38
67
  () => n.getValue(e)
39
68
  );
40
69
  }
41
- function h(e) {
70
+ function P(e) {
42
71
  const n = a();
43
72
  n.register(e);
44
- const [t, u] = s.useState(() => n.getValue(e));
45
- return i(() => n.sub(e, u), [n, e]), t;
73
+ const [t, r] = s.useState(() => n.getValue(e));
74
+ return c(() => n.sub(e, r), [n, e]), t;
46
75
  }
47
- const V = "useSyncExternalStore" in s ? x : h;
48
- function j(...e) {
76
+ const v = "useSyncExternalStore" in s ? w : P;
77
+ function k(...e) {
49
78
  const n = a(), t = s.useMemo(() => n.combineCells(e), [n, ...e]);
50
- return V(t);
79
+ return v(t);
51
80
  }
52
- function P(e) {
81
+ function N(e) {
53
82
  const n = a();
54
83
  return n.register(e), s.useCallback(
55
84
  (t) => {
@@ -58,73 +87,99 @@ function P(e) {
58
87
  [n, e]
59
88
  );
60
89
  }
61
- function O(e) {
62
- return [V(e), P(e)];
90
+ function A(e) {
91
+ return [v(e), N(e)];
63
92
  }
64
- function b(e) {
65
- const [n, t] = s.useState(() => f(e));
66
- return i(() => (t(f(e)), p(e, () => {
67
- t(f(e));
68
- })), [e]), n;
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;
69
115
  }
70
- function w(e, n) {
71
- const t = b(n), [u, r] = s.useState(() => t ? t.getValue(e) : void 0);
72
- return i(() => {
116
+ function L(e, n) {
117
+ const t = C(n), [r, u] = s.useState(() => t ? t.getValue(e) : void 0);
118
+ return c(() => {
73
119
  if (!t) {
74
- r(void 0);
120
+ u(void 0);
75
121
  return;
76
122
  }
77
- return t.register(e), r(t.getValue(e)), t.sub(e, r);
78
- }, [t, e]), u;
123
+ return t.register(e), u(t.getValue(e)), t.sub(e, u);
124
+ }, [t, e]), r;
79
125
  }
80
126
  function M(e, n) {
81
- const t = b(n);
82
- return i(() => {
127
+ const t = C(n);
128
+ return c(() => {
83
129
  t && t.register(e);
84
130
  }, [t, e]), s.useCallback(
85
- (u) => {
86
- t && t.pub(e, u);
131
+ (r) => {
132
+ t && t.pub(e, r);
87
133
  },
88
134
  [t, e]
89
135
  );
90
136
  }
91
- function T(e, n) {
92
- return [w(e, n), M(e, n)];
137
+ function G(e, n) {
138
+ return [L(e, n), M(e, n)];
93
139
  }
94
- function W(e) {
95
- const { cells: n, engineId: t } = e, u = b(t), r = s.useMemo(() => u ? u.combineCells(n) : null, [u, ...n]), [c, o] = s.useState(
96
- () => u && r ? u.getValue(r) : void 0
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
97
143
  );
98
- return i(() => {
99
- if (!u || !r) {
100
- o(void 0);
144
+ return c(() => {
145
+ if (!r || !u) {
146
+ i(void 0);
101
147
  return;
102
148
  }
103
- return u.register(r), o(u.getValue(r)), u.sub(r, o);
104
- }, [u, r]), c;
105
- }
106
- const q = ({ children: e, engineId: n, initFn: t, initWith: u, updateDeps: r, updateFn: c }) => {
107
- const [o, y] = s.useState(null);
108
- return i(() => {
109
- const g = new S(u, n);
110
- return y(g), t == null || t(g), n && m(n, g), () => {
111
- n && m(n, null), g.dispose();
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();
112
166
  };
113
- }, [u, n]), i(() => {
114
- o && (c == null || c(o));
115
- }, [o, ...r ?? []]), o && /* @__PURE__ */ v(E.Provider, { value: o, 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 });
116
170
  };
117
171
  export {
118
- E as EngineContext,
119
- q as EngineProvider,
120
- O as useCell,
121
- V as useCellValue,
122
- j as useCellValues,
172
+ V as EngineContext,
173
+ W as EngineProvider,
174
+ A as useCell,
175
+ v as useCellValue,
176
+ k as useCellValues,
123
177
  a as useEngine,
124
- i as useIsomorphicLayoutEffect,
125
- P as usePublisher,
126
- T as useRemoteCell,
127
- w as useRemoteCellValue,
128
- W as useRemoteCellValues,
178
+ j as useEngineRef,
179
+ c as useIsomorphicLayoutEffect,
180
+ N as usePublisher,
181
+ G as useRemoteCell,
182
+ L as useRemoteCellValue,
183
+ O as useRemoteCellValues,
129
184
  M as useRemotePublisher
130
185
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "private": false,
4
4
  "sideEffects": false,
5
5
  "type": "module",
6
- "version": "0.1.0",
6
+ "version": "0.2.0",
7
7
  "module": "dist/index.js",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
@@ -42,8 +42,8 @@
42
42
  "vite-plugin-dts": "^4.5.4",
43
43
  "vitest": "^4.0.16",
44
44
  "vitest-browser-react": "^2.0.2",
45
- "@virtuoso.dev/reactive-engine-core": "0.0.4",
46
- "@virtuoso.dev/tooling": "0.1.0"
45
+ "@virtuoso.dev/tooling": "0.1.0",
46
+ "@virtuoso.dev/reactive-engine-core": "0.0.4"
47
47
  },
48
48
  "files": [
49
49
  "dist"