@rytejs/react 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ryte Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,361 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ createWorkflowClient: () => createWorkflowClient,
24
+ createWorkflowContext: () => createWorkflowContext,
25
+ createWorkflowStore: () => createWorkflowStore,
26
+ useWorkflow: () => useWorkflow
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/client.ts
31
+ function createWorkflowClient(transport) {
32
+ const cache = /* @__PURE__ */ new Map();
33
+ return {
34
+ connect(definition, id) {
35
+ const cacheKey = `${definition.name}:${id}`;
36
+ const existing = cache.get(cacheKey);
37
+ if (existing) {
38
+ return existing;
39
+ }
40
+ const store = createRemoteStore(transport, definition, id);
41
+ cache.set(cacheKey, store);
42
+ return store;
43
+ }
44
+ };
45
+ }
46
+ function createRemoteStore(transport, definition, id) {
47
+ let workflow = null;
48
+ let version = 0;
49
+ let isLoading = true;
50
+ let isDispatching = false;
51
+ let error = null;
52
+ let disposed = false;
53
+ const listeners = /* @__PURE__ */ new Set();
54
+ let snapshot = {
55
+ workflow,
56
+ isLoading,
57
+ isDispatching,
58
+ error
59
+ };
60
+ function notify() {
61
+ if (disposed) return;
62
+ snapshot = { workflow, isLoading, isDispatching, error };
63
+ for (const listener of listeners) {
64
+ listener();
65
+ }
66
+ }
67
+ transport.load(id).then((result) => {
68
+ if (disposed) return;
69
+ if (result !== null) {
70
+ const restored = definition.deserialize(result.snapshot);
71
+ if (restored.ok) {
72
+ workflow = restored.workflow;
73
+ version = result.version;
74
+ }
75
+ }
76
+ isLoading = false;
77
+ notify();
78
+ }).catch((err) => {
79
+ if (disposed) return;
80
+ error = {
81
+ category: "unexpected",
82
+ error: err,
83
+ message: err instanceof Error ? err.message : String(err)
84
+ // biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape for interface conformance
85
+ };
86
+ isLoading = false;
87
+ notify();
88
+ });
89
+ const subscription = transport.subscribe(id, (message) => {
90
+ if (disposed) return;
91
+ const restored = definition.deserialize(message.snapshot);
92
+ if (restored.ok) {
93
+ workflow = restored.workflow;
94
+ version = message.version;
95
+ error = null;
96
+ notify();
97
+ }
98
+ });
99
+ const dispatch = async (command, payload) => {
100
+ if (isLoading) {
101
+ return {
102
+ ok: false,
103
+ error: {
104
+ category: "unexpected",
105
+ error: new Error("Cannot dispatch while loading"),
106
+ message: "Cannot dispatch while loading"
107
+ }
108
+ };
109
+ }
110
+ isDispatching = true;
111
+ notify();
112
+ try {
113
+ const result = await transport.dispatch(id, { type: command, payload }, version);
114
+ if (result.ok) {
115
+ const restored = definition.deserialize(result.snapshot);
116
+ if (restored.ok) {
117
+ workflow = restored.workflow;
118
+ version = result.version;
119
+ error = null;
120
+ isDispatching = false;
121
+ notify();
122
+ return {
123
+ ok: true,
124
+ workflow: restored.workflow,
125
+ events: result.events
126
+ };
127
+ }
128
+ }
129
+ error = result.ok ? null : result.error;
130
+ isDispatching = false;
131
+ notify();
132
+ return {
133
+ ok: false,
134
+ error: error ?? {
135
+ category: "unexpected",
136
+ error: new Error("Transport error"),
137
+ message: "Transport error"
138
+ }
139
+ };
140
+ } catch (err) {
141
+ error = {
142
+ category: "unexpected",
143
+ error: err,
144
+ message: err instanceof Error ? err.message : String(err)
145
+ // biome-ignore lint/suspicious/noExplicitAny: network error mapped to PipelineError shape
146
+ };
147
+ isDispatching = false;
148
+ notify();
149
+ return {
150
+ ok: false,
151
+ // biome-ignore lint/style/noNonNullAssertion: error is always assigned in the catch block above
152
+ error
153
+ };
154
+ }
155
+ };
156
+ return {
157
+ getWorkflow: () => workflow,
158
+ getSnapshot: () => snapshot,
159
+ subscribe: (listener) => {
160
+ listeners.add(listener);
161
+ return () => {
162
+ listeners.delete(listener);
163
+ };
164
+ },
165
+ dispatch,
166
+ setWorkflow: (newWorkflow) => {
167
+ workflow = newWorkflow;
168
+ error = null;
169
+ notify();
170
+ },
171
+ cleanup() {
172
+ disposed = true;
173
+ subscription.unsubscribe();
174
+ }
175
+ };
176
+ }
177
+
178
+ // src/context.ts
179
+ var import_react2 = require("react");
180
+
181
+ // src/use-workflow.ts
182
+ var import_react = require("react");
183
+ function createReturn(snapshot, dispatch) {
184
+ const wf = snapshot.workflow;
185
+ return {
186
+ workflow: wf,
187
+ // biome-ignore lint/suspicious/noExplicitAny: state/data are undefined when workflow is null (loading) — consumers check isLoading first
188
+ state: wf?.state,
189
+ // biome-ignore lint/suspicious/noExplicitAny: see above
190
+ data: wf?.data,
191
+ isLoading: snapshot.isLoading,
192
+ isDispatching: snapshot.isDispatching,
193
+ error: snapshot.error,
194
+ dispatch,
195
+ match(matchers, fallback) {
196
+ if (!wf) {
197
+ if (fallback) return fallback(wf);
198
+ throw new Error("Cannot match on a loading workflow \u2014 check isLoading first");
199
+ }
200
+ const state = wf.state;
201
+ const matcher = matchers[state];
202
+ if (matcher) {
203
+ return matcher(wf.data, wf);
204
+ }
205
+ if (fallback) {
206
+ return fallback(wf);
207
+ }
208
+ throw new Error(`No match for state "${state}" and no fallback provided`);
209
+ }
210
+ };
211
+ }
212
+ function useWorkflow(store, selector, equalityFn) {
213
+ const selectorRef = (0, import_react.useRef)(selector);
214
+ const equalityFnRef = (0, import_react.useRef)(equalityFn);
215
+ const cachedRef = (0, import_react.useRef)(void 0);
216
+ const hasCachedRef = (0, import_react.useRef)(false);
217
+ selectorRef.current = selector;
218
+ equalityFnRef.current = equalityFn;
219
+ const selectorSnapshot = (0, import_react.useCallback)(() => {
220
+ const wf = store.getWorkflow();
221
+ if (!wf) {
222
+ return cachedRef.current;
223
+ }
224
+ const next = selectorRef.current(wf);
225
+ const eq = equalityFnRef.current ?? Object.is;
226
+ if (hasCachedRef.current && eq(cachedRef.current, next)) {
227
+ return cachedRef.current;
228
+ }
229
+ cachedRef.current = next;
230
+ hasCachedRef.current = true;
231
+ return next;
232
+ }, [store]);
233
+ const getSnapshot = selector ? selectorSnapshot : store.getSnapshot;
234
+ const result = (0, import_react.useSyncExternalStore)(store.subscribe, getSnapshot, getSnapshot);
235
+ if (!selector) {
236
+ return createReturn(result, store.dispatch);
237
+ }
238
+ return result;
239
+ }
240
+
241
+ // src/context.ts
242
+ function createWorkflowContext(_definition) {
243
+ const StoreContext = (0, import_react2.createContext)(null);
244
+ function Provider({
245
+ store,
246
+ children
247
+ }) {
248
+ return (0, import_react2.createElement)(StoreContext.Provider, { value: store }, children);
249
+ }
250
+ function useWorkflowFromContext(selector, equalityFn) {
251
+ const store = (0, import_react2.useContext)(StoreContext);
252
+ if (!store) {
253
+ throw new Error(
254
+ "useWorkflow must be used within a WorkflowProvider. Wrap your component tree with <Provider store={...}>."
255
+ );
256
+ }
257
+ if (selector) {
258
+ return useWorkflow(store, selector, equalityFn);
259
+ }
260
+ return useWorkflow(store);
261
+ }
262
+ return { Provider, useWorkflow: useWorkflowFromContext };
263
+ }
264
+
265
+ // src/store.ts
266
+ var import_core = require("@rytejs/core");
267
+ function createWorkflowStore(router, initialConfig, options) {
268
+ const definition = router.definition;
269
+ let workflow = loadOrCreate(definition, initialConfig, options);
270
+ let isDispatching = false;
271
+ let error = null;
272
+ let snapshot = {
273
+ workflow,
274
+ isLoading: false,
275
+ isDispatching,
276
+ error
277
+ };
278
+ const listeners = /* @__PURE__ */ new Set();
279
+ function notify() {
280
+ snapshot = { workflow, isLoading: false, isDispatching, error };
281
+ for (const listener of listeners) {
282
+ listener();
283
+ }
284
+ }
285
+ const dispatch = async (command, payload) => {
286
+ isDispatching = true;
287
+ notify();
288
+ const result = await router.dispatch(workflow, { type: command, payload });
289
+ if (result.ok) {
290
+ workflow = result.workflow;
291
+ error = null;
292
+ } else {
293
+ error = result.error;
294
+ }
295
+ isDispatching = false;
296
+ notify();
297
+ if (result.ok && options?.persist) {
298
+ const { key, storage } = options.persist;
299
+ const snap = definition.serialize(workflow);
300
+ storage.setItem(key, JSON.stringify(snap));
301
+ }
302
+ return result;
303
+ };
304
+ return {
305
+ getWorkflow: () => workflow,
306
+ getSnapshot: () => snapshot,
307
+ subscribe: (listener) => {
308
+ listeners.add(listener);
309
+ return () => {
310
+ listeners.delete(listener);
311
+ };
312
+ },
313
+ dispatch,
314
+ setWorkflow: (newWorkflow) => {
315
+ workflow = newWorkflow;
316
+ error = null;
317
+ notify();
318
+ },
319
+ cleanup() {
320
+ }
321
+ };
322
+ }
323
+ function loadOrCreate(definition, initialConfig, options) {
324
+ if (options?.persist) {
325
+ const { key, storage, migrations } = options.persist;
326
+ try {
327
+ const stored = storage.getItem(key);
328
+ if (stored) {
329
+ let parsed = JSON.parse(stored);
330
+ if (migrations) {
331
+ const migrated = (0, import_core.migrate)(migrations, parsed);
332
+ if (migrated.ok) {
333
+ parsed = migrated.snapshot;
334
+ } else {
335
+ return createFresh(definition, initialConfig);
336
+ }
337
+ }
338
+ const restored = definition.deserialize(parsed);
339
+ if (restored.ok) {
340
+ return restored.workflow;
341
+ }
342
+ }
343
+ } catch {
344
+ }
345
+ }
346
+ return createFresh(definition, initialConfig);
347
+ }
348
+ function createFresh(definition, initialConfig) {
349
+ return definition.createWorkflow(initialConfig.id ?? crypto.randomUUID(), {
350
+ initialState: initialConfig.state,
351
+ data: initialConfig.data
352
+ });
353
+ }
354
+ // Annotate the CommonJS export names for ESM import in node:
355
+ 0 && (module.exports = {
356
+ createWorkflowClient,
357
+ createWorkflowContext,
358
+ createWorkflowStore,
359
+ useWorkflow
360
+ });
361
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/context.ts","../src/use-workflow.ts","../src/store.ts"],"sourcesContent":["export { createWorkflowClient } from \"./client.js\";\nexport { createWorkflowContext } from \"./context.js\";\nexport { createWorkflowStore } from \"./store.js\";\nexport type {\n\tBroadcastMessage,\n\tTransport,\n\tTransportError,\n\tTransportResult,\n\tTransportSubscription,\n} from \"./transport.js\";\nexport type {\n\tUseWorkflowReturn,\n\tWorkflowStore,\n\tWorkflowStoreOptions,\n\tWorkflowStoreSnapshot,\n} from \"./types.js\";\nexport { useWorkflow } from \"./use-workflow.js\";\n","import type {\n\tCommandNames,\n\tCommandPayload,\n\tDispatchResult,\n\tPipelineError,\n\tWorkflow,\n\tWorkflowConfig,\n\tWorkflowDefinition,\n} from \"@rytejs/core\";\nimport type { BroadcastMessage, Transport } from \"./transport.js\";\nimport type { WorkflowStore, WorkflowStoreSnapshot } from \"./types.js\";\n\nexport function createWorkflowClient(transport: Transport) {\n\t// biome-ignore lint/suspicious/noExplicitAny: cache stores keyed by string, values are type-erased WorkflowStore instances\n\tconst cache = new Map<string, WorkflowStore<any>>();\n\n\treturn {\n\t\tconnect<TConfig extends WorkflowConfig>(\n\t\t\tdefinition: WorkflowDefinition<TConfig>,\n\t\t\tid: string,\n\t\t): WorkflowStore<TConfig> {\n\t\t\tconst cacheKey = `${definition.name}:${id}`;\n\t\t\tconst existing = cache.get(cacheKey);\n\t\t\tif (existing) {\n\t\t\t\treturn existing as WorkflowStore<TConfig>;\n\t\t\t}\n\n\t\t\tconst store = createRemoteStore(transport, definition, id);\n\t\t\tcache.set(cacheKey, store);\n\t\t\treturn store;\n\t\t},\n\t};\n}\n\nfunction createRemoteStore<TConfig extends WorkflowConfig>(\n\ttransport: Transport,\n\tdefinition: WorkflowDefinition<TConfig>,\n\tid: string,\n): WorkflowStore<TConfig> {\n\tlet workflow: Workflow<TConfig> | null = null;\n\tlet version = 0;\n\tlet isLoading = true;\n\tlet isDispatching = false;\n\tlet error: PipelineError<TConfig> | null = null;\n\tlet disposed = false;\n\n\tconst listeners = new Set<() => void>();\n\n\tlet snapshot: WorkflowStoreSnapshot<TConfig> = {\n\t\tworkflow,\n\t\tisLoading,\n\t\tisDispatching,\n\t\terror,\n\t};\n\n\tfunction notify() {\n\t\tif (disposed) return;\n\t\tsnapshot = { workflow, isLoading, isDispatching, error };\n\t\tfor (const listener of listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n\n\t// Eagerly load\n\ttransport\n\t\t.load(id)\n\t\t.then((result) => {\n\t\t\tif (disposed) return;\n\t\t\tif (result !== null) {\n\t\t\t\tconst restored = definition.deserialize(result.snapshot);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\tworkflow = restored.workflow;\n\t\t\t\t\tversion = result.version;\n\t\t\t\t}\n\t\t\t}\n\t\t\tisLoading = false;\n\t\t\tnotify();\n\t\t})\n\t\t.catch((err: unknown) => {\n\t\t\tif (disposed) return;\n\t\t\terror = {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: err,\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape for interface conformance\n\t\t\t} as any;\n\t\t\tisLoading = false;\n\t\t\tnotify();\n\t\t});\n\n\t// Subscribe to live broadcasts\n\tconst subscription = transport.subscribe(id, (message: BroadcastMessage) => {\n\t\tif (disposed) return;\n\t\tconst restored = definition.deserialize(message.snapshot);\n\t\tif (restored.ok) {\n\t\t\tworkflow = restored.workflow;\n\t\t\tversion = message.version;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t}\n\t});\n\n\tconst dispatch = async <C extends CommandNames<TConfig>>(\n\t\tcommand: C,\n\t\tpayload: CommandPayload<TConfig, C>,\n\t): Promise<DispatchResult<TConfig>> => {\n\t\tif (isLoading) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: new Error(\"Cannot dispatch while loading\"),\n\t\t\t\t\tmessage: \"Cannot dispatch while loading\",\n\t\t\t\t},\n\t\t\t} as DispatchResult<TConfig>;\n\t\t}\n\n\t\tisDispatching = true;\n\t\tnotify();\n\n\t\ttry {\n\t\t\tconst result = await transport.dispatch(id, { type: command as string, payload }, version);\n\n\t\t\tif (result.ok) {\n\t\t\t\tconst restored = definition.deserialize(result.snapshot);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\tworkflow = restored.workflow;\n\t\t\t\t\tversion = result.version;\n\t\t\t\t\terror = null;\n\t\t\t\t\tisDispatching = false;\n\t\t\t\t\tnotify();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tworkflow: restored.workflow,\n\t\t\t\t\t\tevents: result.events,\n\t\t\t\t\t} as DispatchResult<TConfig>;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Error path\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape\n\t\t\terror = (result.ok ? null : result.error) as any;\n\t\t\tisDispatching = false;\n\t\t\tnotify();\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: error ?? {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: new Error(\"Transport error\"),\n\t\t\t\t\tmessage: \"Transport error\",\n\t\t\t\t},\n\t\t\t} as DispatchResult<TConfig>;\n\t\t} catch (err: unknown) {\n\t\t\terror = {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: err,\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: network error mapped to PipelineError shape\n\t\t\t} as any;\n\t\t\tisDispatching = false;\n\t\t\tnotify();\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: error is always assigned in the catch block above\n\t\t\t\terror: error!,\n\t\t\t} as DispatchResult<TConfig>;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetWorkflow: () => workflow,\n\t\tgetSnapshot: () => snapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tdispatch,\n\t\tsetWorkflow: (newWorkflow) => {\n\t\t\tworkflow = newWorkflow;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t},\n\t\tcleanup() {\n\t\t\tdisposed = true;\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t};\n}\n","import type { Workflow, WorkflowConfig, WorkflowDefinition } from \"@rytejs/core\";\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext } from \"react\";\nimport type { UseWorkflowReturn, WorkflowStore } from \"./types.js\";\nimport { useWorkflow } from \"./use-workflow.js\";\n\nexport function createWorkflowContext<TConfig extends WorkflowConfig>(\n\t_definition: WorkflowDefinition<TConfig>,\n): {\n\tProvider: (props: { store: WorkflowStore<TConfig>; children?: ReactNode }) => ReactNode;\n\tuseWorkflow: {\n\t\t(): UseWorkflowReturn<TConfig>;\n\t\t<R>(selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;\n\t};\n} {\n\tconst StoreContext = createContext<WorkflowStore<TConfig> | null>(null);\n\n\tfunction Provider({\n\t\tstore,\n\t\tchildren,\n\t}: {\n\t\tstore: WorkflowStore<TConfig>;\n\t\tchildren?: ReactNode;\n\t}): ReactNode {\n\t\treturn createElement(StoreContext.Provider, { value: store }, children);\n\t}\n\n\tfunction useWorkflowFromContext(): UseWorkflowReturn<TConfig>;\n\tfunction useWorkflowFromContext<R>(\n\t\tselector: (workflow: Workflow<TConfig>) => R,\n\t\tequalityFn?: (a: R, b: R) => boolean,\n\t): R;\n\tfunction useWorkflowFromContext<R>(\n\t\tselector?: (workflow: Workflow<TConfig>) => R,\n\t\tequalityFn?: (a: R, b: R) => boolean,\n\t): UseWorkflowReturn<TConfig> | R {\n\t\tconst store = useContext(StoreContext);\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t\"useWorkflow must be used within a WorkflowProvider. \" +\n\t\t\t\t\t\"Wrap your component tree with <Provider store={...}>.\",\n\t\t\t);\n\t\t}\n\t\tif (selector) {\n\t\t\t// biome-ignore lint/correctness/useHookAtTopLevel: selector presence is stable per render — callers must not change between selector and no-selector mode\n\t\t\treturn useWorkflow(store, selector, equalityFn);\n\t\t}\n\t\t// biome-ignore lint/correctness/useHookAtTopLevel: called on the non-selector path; mutually exclusive with the branch above but stable per component lifetime\n\t\treturn useWorkflow(store);\n\t}\n\n\treturn { Provider, useWorkflow: useWorkflowFromContext };\n}\n","import type { Workflow, WorkflowConfig } from \"@rytejs/core\";\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport type { UseWorkflowReturn, WorkflowStore, WorkflowStoreSnapshot } from \"./types.js\";\n\nfunction createReturn<TConfig extends WorkflowConfig>(\n\tsnapshot: WorkflowStoreSnapshot<TConfig>,\n\tdispatch: WorkflowStore<TConfig>[\"dispatch\"],\n): UseWorkflowReturn<TConfig> {\n\tconst wf = snapshot.workflow;\n\treturn {\n\t\tworkflow: wf,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: state/data are undefined when workflow is null (loading) — consumers check isLoading first\n\t\tstate: wf?.state as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: see above\n\t\tdata: wf?.data as any,\n\t\tisLoading: snapshot.isLoading,\n\t\tisDispatching: snapshot.isDispatching,\n\t\terror: snapshot.error,\n\t\tdispatch,\n\t\tmatch(\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t\tmatchers: Record<string, any>,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t\tfallback?: (workflow: Workflow<TConfig> | null) => any,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t): any {\n\t\t\tif (!wf) {\n\t\t\t\tif (fallback) return fallback(wf);\n\t\t\t\tthrow new Error(\"Cannot match on a loading workflow — check isLoading first\");\n\t\t\t}\n\t\t\tconst state = wf.state as string;\n\t\t\tconst matcher = matchers[state];\n\t\t\tif (matcher) {\n\t\t\t\treturn matcher(wf.data, wf);\n\t\t\t}\n\t\t\tif (fallback) {\n\t\t\t\treturn fallback(wf);\n\t\t\t}\n\t\t\tthrow new Error(`No match for state \"${state}\" and no fallback provided`);\n\t\t},\n\t} as UseWorkflowReturn<TConfig>;\n}\n\nexport function useWorkflow<TConfig extends WorkflowConfig>(\n\tstore: WorkflowStore<TConfig>,\n): UseWorkflowReturn<TConfig>;\nexport function useWorkflow<TConfig extends WorkflowConfig, R>(\n\tstore: WorkflowStore<TConfig>,\n\tselector: (workflow: Workflow<TConfig>) => R,\n\tequalityFn?: (a: R, b: R) => boolean,\n): R;\nexport function useWorkflow<TConfig extends WorkflowConfig, R>(\n\tstore: WorkflowStore<TConfig>,\n\tselector?: (workflow: Workflow<TConfig>) => R,\n\tequalityFn?: (a: R, b: R) => boolean,\n): UseWorkflowReturn<TConfig> | R {\n\t// Refs for selector caching — always allocated to maintain hook call order\n\tconst selectorRef = useRef(selector);\n\tconst equalityFnRef = useRef(equalityFn);\n\tconst cachedRef = useRef<R | undefined>(undefined);\n\tconst hasCachedRef = useRef(false);\n\tselectorRef.current = selector;\n\tequalityFnRef.current = equalityFn;\n\n\tconst selectorSnapshot = useCallback(() => {\n\t\tconst wf = store.getWorkflow();\n\t\tif (!wf) {\n\t\t\t// Workflow is loading — return cached value if available\n\t\t\treturn cachedRef.current;\n\t\t}\n\t\t// biome-ignore lint/style/noNonNullAssertion: selectorSnapshot is only used when selector is defined (checked at call site)\n\t\tconst next = selectorRef.current!(wf);\n\t\tconst eq = equalityFnRef.current ?? Object.is;\n\t\tif (hasCachedRef.current && eq(cachedRef.current as R, next)) {\n\t\t\treturn cachedRef.current;\n\t\t}\n\t\tcachedRef.current = next;\n\t\thasCachedRef.current = true;\n\t\treturn next;\n\t}, [store]);\n\n\t// biome-ignore lint/suspicious/noExplicitAny: return type varies by overload; TS can't narrow union of getSnapshot functions\n\tconst getSnapshot: () => any = selector ? selectorSnapshot : store.getSnapshot;\n\n\tconst result: unknown = useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n\n\tif (!selector) {\n\t\treturn createReturn(result as WorkflowStoreSnapshot<TConfig>, store.dispatch);\n\t}\n\treturn result as R;\n}\n","import type {\n\tCommandNames,\n\tCommandPayload,\n\tDispatchResult,\n\tPipelineError,\n\tStateData,\n\tStateNames,\n\tWorkflow,\n\tWorkflowConfig,\n\tWorkflowDefinition,\n} from \"@rytejs/core\";\nimport { migrate, type WorkflowRouter } from \"@rytejs/core\";\nimport type { WorkflowStore, WorkflowStoreOptions, WorkflowStoreSnapshot } from \"./types.js\";\n\nexport function createWorkflowStore<\n\tTConfig extends WorkflowConfig,\n\tTDeps,\n\tS extends StateNames<TConfig>,\n>(\n\trouter: WorkflowRouter<TConfig, TDeps>,\n\tinitialConfig: {\n\t\tstate: S;\n\t\tdata: StateData<TConfig, S>;\n\t\tid?: string;\n\t},\n\toptions?: WorkflowStoreOptions<TConfig>,\n): WorkflowStore<TConfig> {\n\tconst definition = router.definition;\n\n\tlet workflow: Workflow<TConfig> = loadOrCreate(definition, initialConfig, options);\n\tlet isDispatching = false;\n\tlet error: PipelineError<TConfig> | null = null;\n\tlet snapshot: WorkflowStoreSnapshot<TConfig> = {\n\t\tworkflow,\n\t\tisLoading: false,\n\t\tisDispatching,\n\t\terror,\n\t};\n\tconst listeners = new Set<() => void>();\n\n\tfunction notify() {\n\t\tsnapshot = { workflow, isLoading: false, isDispatching, error };\n\t\tfor (const listener of listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n\n\tconst dispatch = async <C extends CommandNames<TConfig>>(\n\t\tcommand: C,\n\t\tpayload: CommandPayload<TConfig, C>,\n\t): Promise<DispatchResult<TConfig>> => {\n\t\tisDispatching = true;\n\t\tnotify();\n\n\t\tconst result = await router.dispatch(workflow, { type: command, payload });\n\n\t\tif (result.ok) {\n\t\t\tworkflow = result.workflow;\n\t\t\terror = null;\n\t\t} else {\n\t\t\terror = result.error;\n\t\t}\n\t\tisDispatching = false;\n\t\tnotify();\n\n\t\tif (result.ok && options?.persist) {\n\t\t\tconst { key, storage } = options.persist;\n\t\t\tconst snap = definition.serialize(workflow);\n\t\t\tstorage.setItem(key, JSON.stringify(snap));\n\t\t}\n\n\t\treturn result;\n\t};\n\n\treturn {\n\t\tgetWorkflow: () => workflow,\n\t\tgetSnapshot: () => snapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tdispatch,\n\t\tsetWorkflow: (newWorkflow) => {\n\t\t\tworkflow = newWorkflow;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t},\n\t\tcleanup() {},\n\t};\n}\n\nfunction loadOrCreate<TConfig extends WorkflowConfig, S extends StateNames<TConfig>>(\n\tdefinition: WorkflowDefinition<TConfig>,\n\tinitialConfig: { state: S; data: StateData<TConfig, S>; id?: string },\n\toptions?: WorkflowStoreOptions<TConfig>,\n): Workflow<TConfig> {\n\tif (options?.persist) {\n\t\tconst { key, storage, migrations } = options.persist;\n\t\ttry {\n\t\t\tconst stored = storage.getItem(key);\n\t\t\tif (stored) {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: JSON.parse returns unknown structure from storage\n\t\t\t\tlet parsed: any = JSON.parse(stored);\n\t\t\t\tif (migrations) {\n\t\t\t\t\tconst migrated = migrate(migrations, parsed);\n\t\t\t\t\tif (migrated.ok) {\n\t\t\t\t\t\tparsed = migrated.snapshot;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn createFresh(definition, initialConfig);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst restored = definition.deserialize(parsed);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\treturn restored.workflow;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid JSON or deserialize failed — fall through to create fresh\n\t\t}\n\t}\n\n\treturn createFresh(definition, initialConfig);\n}\n\nfunction createFresh<TConfig extends WorkflowConfig, S extends StateNames<TConfig>>(\n\tdefinition: WorkflowDefinition<TConfig>,\n\tinitialConfig: { state: S; data: StateData<TConfig, S>; id?: string },\n): Workflow<TConfig> {\n\treturn definition.createWorkflow(initialConfig.id ?? crypto.randomUUID(), {\n\t\tinitialState: initialConfig.state,\n\t\tdata: initialConfig.data,\n\t});\n}\n"],"mappings":"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","names":["import_react"]}
@@ -0,0 +1,103 @@
1
+ import { WorkflowSnapshot, WorkflowConfig, Workflow, StateNames, StateData, PipelineError, CommandNames, CommandPayload, DispatchResult, WorkflowOf, MigrationPipeline, WorkflowDefinition, WorkflowRouter } from '@rytejs/core';
2
+ import { StoredWorkflow } from '@rytejs/core/store';
3
+ import { ReactNode } from 'react';
4
+
5
+ interface BroadcastMessage {
6
+ snapshot: WorkflowSnapshot;
7
+ version: number;
8
+ events: Array<{
9
+ type: string;
10
+ data: unknown;
11
+ }>;
12
+ }
13
+ interface Transport {
14
+ load(id: string): Promise<StoredWorkflow | null>;
15
+ dispatch(id: string, command: {
16
+ type: string;
17
+ payload: unknown;
18
+ }, expectedVersion: number): Promise<TransportResult>;
19
+ subscribe(id: string, callback: (message: BroadcastMessage) => void): TransportSubscription;
20
+ }
21
+ type TransportResult = {
22
+ ok: true;
23
+ snapshot: WorkflowSnapshot;
24
+ version: number;
25
+ events: Array<{
26
+ type: string;
27
+ data: unknown;
28
+ }>;
29
+ } | {
30
+ ok: false;
31
+ error: TransportError;
32
+ };
33
+ interface TransportError {
34
+ category: "transport";
35
+ code: "NETWORK" | "CONFLICT" | "NOT_FOUND" | "TIMEOUT";
36
+ message: string;
37
+ }
38
+ interface TransportSubscription {
39
+ unsubscribe(): void;
40
+ }
41
+
42
+ interface WorkflowStoreSnapshot<TConfig extends WorkflowConfig> {
43
+ readonly workflow: Workflow<TConfig> | null;
44
+ readonly isLoading: boolean;
45
+ readonly isDispatching: boolean;
46
+ readonly error: PipelineError<TConfig> | null;
47
+ }
48
+ interface WorkflowStore<TConfig extends WorkflowConfig> {
49
+ getWorkflow(): Workflow<TConfig> | null;
50
+ getSnapshot(): WorkflowStoreSnapshot<TConfig>;
51
+ subscribe(listener: () => void): () => void;
52
+ dispatch<C extends CommandNames<TConfig>>(command: C, payload: CommandPayload<TConfig, C>): Promise<DispatchResult<TConfig>>;
53
+ setWorkflow(workflow: Workflow<TConfig>): void;
54
+ cleanup(): void;
55
+ }
56
+ interface WorkflowStoreOptions<TConfig extends WorkflowConfig> {
57
+ persist?: {
58
+ key: string;
59
+ storage: Storage;
60
+ migrations?: MigrationPipeline<TConfig>;
61
+ };
62
+ }
63
+ interface UseWorkflowReturn<TConfig extends WorkflowConfig> {
64
+ readonly workflow: Workflow<TConfig> | null;
65
+ readonly state: StateNames<TConfig>;
66
+ readonly data: StateData<TConfig, StateNames<TConfig>>;
67
+ readonly isLoading: boolean;
68
+ readonly isDispatching: boolean;
69
+ readonly error: PipelineError<TConfig> | null;
70
+ dispatch<C extends CommandNames<TConfig>>(command: C, payload: CommandPayload<TConfig, C>): Promise<DispatchResult<TConfig>>;
71
+ match<R>(matchers: {
72
+ [S in StateNames<TConfig>]: (data: StateData<TConfig, S>, workflow: WorkflowOf<TConfig, S>) => R;
73
+ }): R;
74
+ match<R>(matchers: Partial<{
75
+ [S in StateNames<TConfig>]: (data: StateData<TConfig, S>, workflow: WorkflowOf<TConfig, S>) => R;
76
+ }>, fallback: (workflow: Workflow<TConfig> | null) => R): R;
77
+ }
78
+
79
+ declare function createWorkflowClient(transport: Transport): {
80
+ connect<TConfig extends WorkflowConfig>(definition: WorkflowDefinition<TConfig>, id: string): WorkflowStore<TConfig>;
81
+ };
82
+
83
+ declare function createWorkflowContext<TConfig extends WorkflowConfig>(_definition: WorkflowDefinition<TConfig>): {
84
+ Provider: (props: {
85
+ store: WorkflowStore<TConfig>;
86
+ children?: ReactNode;
87
+ }) => ReactNode;
88
+ useWorkflow: {
89
+ (): UseWorkflowReturn<TConfig>;
90
+ <R>(selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;
91
+ };
92
+ };
93
+
94
+ declare function createWorkflowStore<TConfig extends WorkflowConfig, TDeps, S extends StateNames<TConfig>>(router: WorkflowRouter<TConfig, TDeps>, initialConfig: {
95
+ state: S;
96
+ data: StateData<TConfig, S>;
97
+ id?: string;
98
+ }, options?: WorkflowStoreOptions<TConfig>): WorkflowStore<TConfig>;
99
+
100
+ declare function useWorkflow<TConfig extends WorkflowConfig>(store: WorkflowStore<TConfig>): UseWorkflowReturn<TConfig>;
101
+ declare function useWorkflow<TConfig extends WorkflowConfig, R>(store: WorkflowStore<TConfig>, selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;
102
+
103
+ export { type BroadcastMessage, type Transport, type TransportError, type TransportResult, type TransportSubscription, type UseWorkflowReturn, type WorkflowStore, type WorkflowStoreOptions, type WorkflowStoreSnapshot, createWorkflowClient, createWorkflowContext, createWorkflowStore, useWorkflow };
@@ -0,0 +1,103 @@
1
+ import { WorkflowSnapshot, WorkflowConfig, Workflow, StateNames, StateData, PipelineError, CommandNames, CommandPayload, DispatchResult, WorkflowOf, MigrationPipeline, WorkflowDefinition, WorkflowRouter } from '@rytejs/core';
2
+ import { StoredWorkflow } from '@rytejs/core/store';
3
+ import { ReactNode } from 'react';
4
+
5
+ interface BroadcastMessage {
6
+ snapshot: WorkflowSnapshot;
7
+ version: number;
8
+ events: Array<{
9
+ type: string;
10
+ data: unknown;
11
+ }>;
12
+ }
13
+ interface Transport {
14
+ load(id: string): Promise<StoredWorkflow | null>;
15
+ dispatch(id: string, command: {
16
+ type: string;
17
+ payload: unknown;
18
+ }, expectedVersion: number): Promise<TransportResult>;
19
+ subscribe(id: string, callback: (message: BroadcastMessage) => void): TransportSubscription;
20
+ }
21
+ type TransportResult = {
22
+ ok: true;
23
+ snapshot: WorkflowSnapshot;
24
+ version: number;
25
+ events: Array<{
26
+ type: string;
27
+ data: unknown;
28
+ }>;
29
+ } | {
30
+ ok: false;
31
+ error: TransportError;
32
+ };
33
+ interface TransportError {
34
+ category: "transport";
35
+ code: "NETWORK" | "CONFLICT" | "NOT_FOUND" | "TIMEOUT";
36
+ message: string;
37
+ }
38
+ interface TransportSubscription {
39
+ unsubscribe(): void;
40
+ }
41
+
42
+ interface WorkflowStoreSnapshot<TConfig extends WorkflowConfig> {
43
+ readonly workflow: Workflow<TConfig> | null;
44
+ readonly isLoading: boolean;
45
+ readonly isDispatching: boolean;
46
+ readonly error: PipelineError<TConfig> | null;
47
+ }
48
+ interface WorkflowStore<TConfig extends WorkflowConfig> {
49
+ getWorkflow(): Workflow<TConfig> | null;
50
+ getSnapshot(): WorkflowStoreSnapshot<TConfig>;
51
+ subscribe(listener: () => void): () => void;
52
+ dispatch<C extends CommandNames<TConfig>>(command: C, payload: CommandPayload<TConfig, C>): Promise<DispatchResult<TConfig>>;
53
+ setWorkflow(workflow: Workflow<TConfig>): void;
54
+ cleanup(): void;
55
+ }
56
+ interface WorkflowStoreOptions<TConfig extends WorkflowConfig> {
57
+ persist?: {
58
+ key: string;
59
+ storage: Storage;
60
+ migrations?: MigrationPipeline<TConfig>;
61
+ };
62
+ }
63
+ interface UseWorkflowReturn<TConfig extends WorkflowConfig> {
64
+ readonly workflow: Workflow<TConfig> | null;
65
+ readonly state: StateNames<TConfig>;
66
+ readonly data: StateData<TConfig, StateNames<TConfig>>;
67
+ readonly isLoading: boolean;
68
+ readonly isDispatching: boolean;
69
+ readonly error: PipelineError<TConfig> | null;
70
+ dispatch<C extends CommandNames<TConfig>>(command: C, payload: CommandPayload<TConfig, C>): Promise<DispatchResult<TConfig>>;
71
+ match<R>(matchers: {
72
+ [S in StateNames<TConfig>]: (data: StateData<TConfig, S>, workflow: WorkflowOf<TConfig, S>) => R;
73
+ }): R;
74
+ match<R>(matchers: Partial<{
75
+ [S in StateNames<TConfig>]: (data: StateData<TConfig, S>, workflow: WorkflowOf<TConfig, S>) => R;
76
+ }>, fallback: (workflow: Workflow<TConfig> | null) => R): R;
77
+ }
78
+
79
+ declare function createWorkflowClient(transport: Transport): {
80
+ connect<TConfig extends WorkflowConfig>(definition: WorkflowDefinition<TConfig>, id: string): WorkflowStore<TConfig>;
81
+ };
82
+
83
+ declare function createWorkflowContext<TConfig extends WorkflowConfig>(_definition: WorkflowDefinition<TConfig>): {
84
+ Provider: (props: {
85
+ store: WorkflowStore<TConfig>;
86
+ children?: ReactNode;
87
+ }) => ReactNode;
88
+ useWorkflow: {
89
+ (): UseWorkflowReturn<TConfig>;
90
+ <R>(selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;
91
+ };
92
+ };
93
+
94
+ declare function createWorkflowStore<TConfig extends WorkflowConfig, TDeps, S extends StateNames<TConfig>>(router: WorkflowRouter<TConfig, TDeps>, initialConfig: {
95
+ state: S;
96
+ data: StateData<TConfig, S>;
97
+ id?: string;
98
+ }, options?: WorkflowStoreOptions<TConfig>): WorkflowStore<TConfig>;
99
+
100
+ declare function useWorkflow<TConfig extends WorkflowConfig>(store: WorkflowStore<TConfig>): UseWorkflowReturn<TConfig>;
101
+ declare function useWorkflow<TConfig extends WorkflowConfig, R>(store: WorkflowStore<TConfig>, selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;
102
+
103
+ export { type BroadcastMessage, type Transport, type TransportError, type TransportResult, type TransportSubscription, type UseWorkflowReturn, type WorkflowStore, type WorkflowStoreOptions, type WorkflowStoreSnapshot, createWorkflowClient, createWorkflowContext, createWorkflowStore, useWorkflow };
package/dist/index.js ADDED
@@ -0,0 +1,331 @@
1
+ // src/client.ts
2
+ function createWorkflowClient(transport) {
3
+ const cache = /* @__PURE__ */ new Map();
4
+ return {
5
+ connect(definition, id) {
6
+ const cacheKey = `${definition.name}:${id}`;
7
+ const existing = cache.get(cacheKey);
8
+ if (existing) {
9
+ return existing;
10
+ }
11
+ const store = createRemoteStore(transport, definition, id);
12
+ cache.set(cacheKey, store);
13
+ return store;
14
+ }
15
+ };
16
+ }
17
+ function createRemoteStore(transport, definition, id) {
18
+ let workflow = null;
19
+ let version = 0;
20
+ let isLoading = true;
21
+ let isDispatching = false;
22
+ let error = null;
23
+ let disposed = false;
24
+ const listeners = /* @__PURE__ */ new Set();
25
+ let snapshot = {
26
+ workflow,
27
+ isLoading,
28
+ isDispatching,
29
+ error
30
+ };
31
+ function notify() {
32
+ if (disposed) return;
33
+ snapshot = { workflow, isLoading, isDispatching, error };
34
+ for (const listener of listeners) {
35
+ listener();
36
+ }
37
+ }
38
+ transport.load(id).then((result) => {
39
+ if (disposed) return;
40
+ if (result !== null) {
41
+ const restored = definition.deserialize(result.snapshot);
42
+ if (restored.ok) {
43
+ workflow = restored.workflow;
44
+ version = result.version;
45
+ }
46
+ }
47
+ isLoading = false;
48
+ notify();
49
+ }).catch((err) => {
50
+ if (disposed) return;
51
+ error = {
52
+ category: "unexpected",
53
+ error: err,
54
+ message: err instanceof Error ? err.message : String(err)
55
+ // biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape for interface conformance
56
+ };
57
+ isLoading = false;
58
+ notify();
59
+ });
60
+ const subscription = transport.subscribe(id, (message) => {
61
+ if (disposed) return;
62
+ const restored = definition.deserialize(message.snapshot);
63
+ if (restored.ok) {
64
+ workflow = restored.workflow;
65
+ version = message.version;
66
+ error = null;
67
+ notify();
68
+ }
69
+ });
70
+ const dispatch = async (command, payload) => {
71
+ if (isLoading) {
72
+ return {
73
+ ok: false,
74
+ error: {
75
+ category: "unexpected",
76
+ error: new Error("Cannot dispatch while loading"),
77
+ message: "Cannot dispatch while loading"
78
+ }
79
+ };
80
+ }
81
+ isDispatching = true;
82
+ notify();
83
+ try {
84
+ const result = await transport.dispatch(id, { type: command, payload }, version);
85
+ if (result.ok) {
86
+ const restored = definition.deserialize(result.snapshot);
87
+ if (restored.ok) {
88
+ workflow = restored.workflow;
89
+ version = result.version;
90
+ error = null;
91
+ isDispatching = false;
92
+ notify();
93
+ return {
94
+ ok: true,
95
+ workflow: restored.workflow,
96
+ events: result.events
97
+ };
98
+ }
99
+ }
100
+ error = result.ok ? null : result.error;
101
+ isDispatching = false;
102
+ notify();
103
+ return {
104
+ ok: false,
105
+ error: error ?? {
106
+ category: "unexpected",
107
+ error: new Error("Transport error"),
108
+ message: "Transport error"
109
+ }
110
+ };
111
+ } catch (err) {
112
+ error = {
113
+ category: "unexpected",
114
+ error: err,
115
+ message: err instanceof Error ? err.message : String(err)
116
+ // biome-ignore lint/suspicious/noExplicitAny: network error mapped to PipelineError shape
117
+ };
118
+ isDispatching = false;
119
+ notify();
120
+ return {
121
+ ok: false,
122
+ // biome-ignore lint/style/noNonNullAssertion: error is always assigned in the catch block above
123
+ error
124
+ };
125
+ }
126
+ };
127
+ return {
128
+ getWorkflow: () => workflow,
129
+ getSnapshot: () => snapshot,
130
+ subscribe: (listener) => {
131
+ listeners.add(listener);
132
+ return () => {
133
+ listeners.delete(listener);
134
+ };
135
+ },
136
+ dispatch,
137
+ setWorkflow: (newWorkflow) => {
138
+ workflow = newWorkflow;
139
+ error = null;
140
+ notify();
141
+ },
142
+ cleanup() {
143
+ disposed = true;
144
+ subscription.unsubscribe();
145
+ }
146
+ };
147
+ }
148
+
149
+ // src/context.ts
150
+ import { createContext, createElement, useContext } from "react";
151
+
152
+ // src/use-workflow.ts
153
+ import { useCallback, useRef, useSyncExternalStore } from "react";
154
+ function createReturn(snapshot, dispatch) {
155
+ const wf = snapshot.workflow;
156
+ return {
157
+ workflow: wf,
158
+ // biome-ignore lint/suspicious/noExplicitAny: state/data are undefined when workflow is null (loading) — consumers check isLoading first
159
+ state: wf?.state,
160
+ // biome-ignore lint/suspicious/noExplicitAny: see above
161
+ data: wf?.data,
162
+ isLoading: snapshot.isLoading,
163
+ isDispatching: snapshot.isDispatching,
164
+ error: snapshot.error,
165
+ dispatch,
166
+ match(matchers, fallback) {
167
+ if (!wf) {
168
+ if (fallback) return fallback(wf);
169
+ throw new Error("Cannot match on a loading workflow \u2014 check isLoading first");
170
+ }
171
+ const state = wf.state;
172
+ const matcher = matchers[state];
173
+ if (matcher) {
174
+ return matcher(wf.data, wf);
175
+ }
176
+ if (fallback) {
177
+ return fallback(wf);
178
+ }
179
+ throw new Error(`No match for state "${state}" and no fallback provided`);
180
+ }
181
+ };
182
+ }
183
+ function useWorkflow(store, selector, equalityFn) {
184
+ const selectorRef = useRef(selector);
185
+ const equalityFnRef = useRef(equalityFn);
186
+ const cachedRef = useRef(void 0);
187
+ const hasCachedRef = useRef(false);
188
+ selectorRef.current = selector;
189
+ equalityFnRef.current = equalityFn;
190
+ const selectorSnapshot = useCallback(() => {
191
+ const wf = store.getWorkflow();
192
+ if (!wf) {
193
+ return cachedRef.current;
194
+ }
195
+ const next = selectorRef.current(wf);
196
+ const eq = equalityFnRef.current ?? Object.is;
197
+ if (hasCachedRef.current && eq(cachedRef.current, next)) {
198
+ return cachedRef.current;
199
+ }
200
+ cachedRef.current = next;
201
+ hasCachedRef.current = true;
202
+ return next;
203
+ }, [store]);
204
+ const getSnapshot = selector ? selectorSnapshot : store.getSnapshot;
205
+ const result = useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
206
+ if (!selector) {
207
+ return createReturn(result, store.dispatch);
208
+ }
209
+ return result;
210
+ }
211
+
212
+ // src/context.ts
213
+ function createWorkflowContext(_definition) {
214
+ const StoreContext = createContext(null);
215
+ function Provider({
216
+ store,
217
+ children
218
+ }) {
219
+ return createElement(StoreContext.Provider, { value: store }, children);
220
+ }
221
+ function useWorkflowFromContext(selector, equalityFn) {
222
+ const store = useContext(StoreContext);
223
+ if (!store) {
224
+ throw new Error(
225
+ "useWorkflow must be used within a WorkflowProvider. Wrap your component tree with <Provider store={...}>."
226
+ );
227
+ }
228
+ if (selector) {
229
+ return useWorkflow(store, selector, equalityFn);
230
+ }
231
+ return useWorkflow(store);
232
+ }
233
+ return { Provider, useWorkflow: useWorkflowFromContext };
234
+ }
235
+
236
+ // src/store.ts
237
+ import { migrate } from "@rytejs/core";
238
+ function createWorkflowStore(router, initialConfig, options) {
239
+ const definition = router.definition;
240
+ let workflow = loadOrCreate(definition, initialConfig, options);
241
+ let isDispatching = false;
242
+ let error = null;
243
+ let snapshot = {
244
+ workflow,
245
+ isLoading: false,
246
+ isDispatching,
247
+ error
248
+ };
249
+ const listeners = /* @__PURE__ */ new Set();
250
+ function notify() {
251
+ snapshot = { workflow, isLoading: false, isDispatching, error };
252
+ for (const listener of listeners) {
253
+ listener();
254
+ }
255
+ }
256
+ const dispatch = async (command, payload) => {
257
+ isDispatching = true;
258
+ notify();
259
+ const result = await router.dispatch(workflow, { type: command, payload });
260
+ if (result.ok) {
261
+ workflow = result.workflow;
262
+ error = null;
263
+ } else {
264
+ error = result.error;
265
+ }
266
+ isDispatching = false;
267
+ notify();
268
+ if (result.ok && options?.persist) {
269
+ const { key, storage } = options.persist;
270
+ const snap = definition.serialize(workflow);
271
+ storage.setItem(key, JSON.stringify(snap));
272
+ }
273
+ return result;
274
+ };
275
+ return {
276
+ getWorkflow: () => workflow,
277
+ getSnapshot: () => snapshot,
278
+ subscribe: (listener) => {
279
+ listeners.add(listener);
280
+ return () => {
281
+ listeners.delete(listener);
282
+ };
283
+ },
284
+ dispatch,
285
+ setWorkflow: (newWorkflow) => {
286
+ workflow = newWorkflow;
287
+ error = null;
288
+ notify();
289
+ },
290
+ cleanup() {
291
+ }
292
+ };
293
+ }
294
+ function loadOrCreate(definition, initialConfig, options) {
295
+ if (options?.persist) {
296
+ const { key, storage, migrations } = options.persist;
297
+ try {
298
+ const stored = storage.getItem(key);
299
+ if (stored) {
300
+ let parsed = JSON.parse(stored);
301
+ if (migrations) {
302
+ const migrated = migrate(migrations, parsed);
303
+ if (migrated.ok) {
304
+ parsed = migrated.snapshot;
305
+ } else {
306
+ return createFresh(definition, initialConfig);
307
+ }
308
+ }
309
+ const restored = definition.deserialize(parsed);
310
+ if (restored.ok) {
311
+ return restored.workflow;
312
+ }
313
+ }
314
+ } catch {
315
+ }
316
+ }
317
+ return createFresh(definition, initialConfig);
318
+ }
319
+ function createFresh(definition, initialConfig) {
320
+ return definition.createWorkflow(initialConfig.id ?? crypto.randomUUID(), {
321
+ initialState: initialConfig.state,
322
+ data: initialConfig.data
323
+ });
324
+ }
325
+ export {
326
+ createWorkflowClient,
327
+ createWorkflowContext,
328
+ createWorkflowStore,
329
+ useWorkflow
330
+ };
331
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts","../src/context.ts","../src/use-workflow.ts","../src/store.ts"],"sourcesContent":["import type {\n\tCommandNames,\n\tCommandPayload,\n\tDispatchResult,\n\tPipelineError,\n\tWorkflow,\n\tWorkflowConfig,\n\tWorkflowDefinition,\n} from \"@rytejs/core\";\nimport type { BroadcastMessage, Transport } from \"./transport.js\";\nimport type { WorkflowStore, WorkflowStoreSnapshot } from \"./types.js\";\n\nexport function createWorkflowClient(transport: Transport) {\n\t// biome-ignore lint/suspicious/noExplicitAny: cache stores keyed by string, values are type-erased WorkflowStore instances\n\tconst cache = new Map<string, WorkflowStore<any>>();\n\n\treturn {\n\t\tconnect<TConfig extends WorkflowConfig>(\n\t\t\tdefinition: WorkflowDefinition<TConfig>,\n\t\t\tid: string,\n\t\t): WorkflowStore<TConfig> {\n\t\t\tconst cacheKey = `${definition.name}:${id}`;\n\t\t\tconst existing = cache.get(cacheKey);\n\t\t\tif (existing) {\n\t\t\t\treturn existing as WorkflowStore<TConfig>;\n\t\t\t}\n\n\t\t\tconst store = createRemoteStore(transport, definition, id);\n\t\t\tcache.set(cacheKey, store);\n\t\t\treturn store;\n\t\t},\n\t};\n}\n\nfunction createRemoteStore<TConfig extends WorkflowConfig>(\n\ttransport: Transport,\n\tdefinition: WorkflowDefinition<TConfig>,\n\tid: string,\n): WorkflowStore<TConfig> {\n\tlet workflow: Workflow<TConfig> | null = null;\n\tlet version = 0;\n\tlet isLoading = true;\n\tlet isDispatching = false;\n\tlet error: PipelineError<TConfig> | null = null;\n\tlet disposed = false;\n\n\tconst listeners = new Set<() => void>();\n\n\tlet snapshot: WorkflowStoreSnapshot<TConfig> = {\n\t\tworkflow,\n\t\tisLoading,\n\t\tisDispatching,\n\t\terror,\n\t};\n\n\tfunction notify() {\n\t\tif (disposed) return;\n\t\tsnapshot = { workflow, isLoading, isDispatching, error };\n\t\tfor (const listener of listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n\n\t// Eagerly load\n\ttransport\n\t\t.load(id)\n\t\t.then((result) => {\n\t\t\tif (disposed) return;\n\t\t\tif (result !== null) {\n\t\t\t\tconst restored = definition.deserialize(result.snapshot);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\tworkflow = restored.workflow;\n\t\t\t\t\tversion = result.version;\n\t\t\t\t}\n\t\t\t}\n\t\t\tisLoading = false;\n\t\t\tnotify();\n\t\t})\n\t\t.catch((err: unknown) => {\n\t\t\tif (disposed) return;\n\t\t\terror = {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: err,\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape for interface conformance\n\t\t\t} as any;\n\t\t\tisLoading = false;\n\t\t\tnotify();\n\t\t});\n\n\t// Subscribe to live broadcasts\n\tconst subscription = transport.subscribe(id, (message: BroadcastMessage) => {\n\t\tif (disposed) return;\n\t\tconst restored = definition.deserialize(message.snapshot);\n\t\tif (restored.ok) {\n\t\t\tworkflow = restored.workflow;\n\t\t\tversion = message.version;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t}\n\t});\n\n\tconst dispatch = async <C extends CommandNames<TConfig>>(\n\t\tcommand: C,\n\t\tpayload: CommandPayload<TConfig, C>,\n\t): Promise<DispatchResult<TConfig>> => {\n\t\tif (isLoading) {\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: new Error(\"Cannot dispatch while loading\"),\n\t\t\t\t\tmessage: \"Cannot dispatch while loading\",\n\t\t\t\t},\n\t\t\t} as DispatchResult<TConfig>;\n\t\t}\n\n\t\tisDispatching = true;\n\t\tnotify();\n\n\t\ttry {\n\t\t\tconst result = await transport.dispatch(id, { type: command as string, payload }, version);\n\n\t\t\tif (result.ok) {\n\t\t\t\tconst restored = definition.deserialize(result.snapshot);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\tworkflow = restored.workflow;\n\t\t\t\t\tversion = result.version;\n\t\t\t\t\terror = null;\n\t\t\t\t\tisDispatching = false;\n\t\t\t\t\tnotify();\n\t\t\t\t\treturn {\n\t\t\t\t\t\tok: true,\n\t\t\t\t\t\tworkflow: restored.workflow,\n\t\t\t\t\t\tevents: result.events,\n\t\t\t\t\t} as DispatchResult<TConfig>;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Error path\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: TransportError mapped to PipelineError shape\n\t\t\terror = (result.ok ? null : result.error) as any;\n\t\t\tisDispatching = false;\n\t\t\tnotify();\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\terror: error ?? {\n\t\t\t\t\tcategory: \"unexpected\",\n\t\t\t\t\terror: new Error(\"Transport error\"),\n\t\t\t\t\tmessage: \"Transport error\",\n\t\t\t\t},\n\t\t\t} as DispatchResult<TConfig>;\n\t\t} catch (err: unknown) {\n\t\t\terror = {\n\t\t\t\tcategory: \"unexpected\",\n\t\t\t\terror: err,\n\t\t\t\tmessage: err instanceof Error ? err.message : String(err),\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: network error mapped to PipelineError shape\n\t\t\t} as any;\n\t\t\tisDispatching = false;\n\t\t\tnotify();\n\t\t\treturn {\n\t\t\t\tok: false,\n\t\t\t\t// biome-ignore lint/style/noNonNullAssertion: error is always assigned in the catch block above\n\t\t\t\terror: error!,\n\t\t\t} as DispatchResult<TConfig>;\n\t\t}\n\t};\n\n\treturn {\n\t\tgetWorkflow: () => workflow,\n\t\tgetSnapshot: () => snapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tdispatch,\n\t\tsetWorkflow: (newWorkflow) => {\n\t\t\tworkflow = newWorkflow;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t},\n\t\tcleanup() {\n\t\t\tdisposed = true;\n\t\t\tsubscription.unsubscribe();\n\t\t},\n\t};\n}\n","import type { Workflow, WorkflowConfig, WorkflowDefinition } from \"@rytejs/core\";\nimport type { ReactNode } from \"react\";\nimport { createContext, createElement, useContext } from \"react\";\nimport type { UseWorkflowReturn, WorkflowStore } from \"./types.js\";\nimport { useWorkflow } from \"./use-workflow.js\";\n\nexport function createWorkflowContext<TConfig extends WorkflowConfig>(\n\t_definition: WorkflowDefinition<TConfig>,\n): {\n\tProvider: (props: { store: WorkflowStore<TConfig>; children?: ReactNode }) => ReactNode;\n\tuseWorkflow: {\n\t\t(): UseWorkflowReturn<TConfig>;\n\t\t<R>(selector: (workflow: Workflow<TConfig>) => R, equalityFn?: (a: R, b: R) => boolean): R;\n\t};\n} {\n\tconst StoreContext = createContext<WorkflowStore<TConfig> | null>(null);\n\n\tfunction Provider({\n\t\tstore,\n\t\tchildren,\n\t}: {\n\t\tstore: WorkflowStore<TConfig>;\n\t\tchildren?: ReactNode;\n\t}): ReactNode {\n\t\treturn createElement(StoreContext.Provider, { value: store }, children);\n\t}\n\n\tfunction useWorkflowFromContext(): UseWorkflowReturn<TConfig>;\n\tfunction useWorkflowFromContext<R>(\n\t\tselector: (workflow: Workflow<TConfig>) => R,\n\t\tequalityFn?: (a: R, b: R) => boolean,\n\t): R;\n\tfunction useWorkflowFromContext<R>(\n\t\tselector?: (workflow: Workflow<TConfig>) => R,\n\t\tequalityFn?: (a: R, b: R) => boolean,\n\t): UseWorkflowReturn<TConfig> | R {\n\t\tconst store = useContext(StoreContext);\n\t\tif (!store) {\n\t\t\tthrow new Error(\n\t\t\t\t\"useWorkflow must be used within a WorkflowProvider. \" +\n\t\t\t\t\t\"Wrap your component tree with <Provider store={...}>.\",\n\t\t\t);\n\t\t}\n\t\tif (selector) {\n\t\t\t// biome-ignore lint/correctness/useHookAtTopLevel: selector presence is stable per render — callers must not change between selector and no-selector mode\n\t\t\treturn useWorkflow(store, selector, equalityFn);\n\t\t}\n\t\t// biome-ignore lint/correctness/useHookAtTopLevel: called on the non-selector path; mutually exclusive with the branch above but stable per component lifetime\n\t\treturn useWorkflow(store);\n\t}\n\n\treturn { Provider, useWorkflow: useWorkflowFromContext };\n}\n","import type { Workflow, WorkflowConfig } from \"@rytejs/core\";\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport type { UseWorkflowReturn, WorkflowStore, WorkflowStoreSnapshot } from \"./types.js\";\n\nfunction createReturn<TConfig extends WorkflowConfig>(\n\tsnapshot: WorkflowStoreSnapshot<TConfig>,\n\tdispatch: WorkflowStore<TConfig>[\"dispatch\"],\n): UseWorkflowReturn<TConfig> {\n\tconst wf = snapshot.workflow;\n\treturn {\n\t\tworkflow: wf,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: state/data are undefined when workflow is null (loading) — consumers check isLoading first\n\t\tstate: wf?.state as any,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: see above\n\t\tdata: wf?.data as any,\n\t\tisLoading: snapshot.isLoading,\n\t\tisDispatching: snapshot.isDispatching,\n\t\terror: snapshot.error,\n\t\tdispatch,\n\t\tmatch(\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t\tmatchers: Record<string, any>,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t\tfallback?: (workflow: Workflow<TConfig> | null) => any,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: match overloads handled by UseWorkflowReturn type\n\t\t): any {\n\t\t\tif (!wf) {\n\t\t\t\tif (fallback) return fallback(wf);\n\t\t\t\tthrow new Error(\"Cannot match on a loading workflow — check isLoading first\");\n\t\t\t}\n\t\t\tconst state = wf.state as string;\n\t\t\tconst matcher = matchers[state];\n\t\t\tif (matcher) {\n\t\t\t\treturn matcher(wf.data, wf);\n\t\t\t}\n\t\t\tif (fallback) {\n\t\t\t\treturn fallback(wf);\n\t\t\t}\n\t\t\tthrow new Error(`No match for state \"${state}\" and no fallback provided`);\n\t\t},\n\t} as UseWorkflowReturn<TConfig>;\n}\n\nexport function useWorkflow<TConfig extends WorkflowConfig>(\n\tstore: WorkflowStore<TConfig>,\n): UseWorkflowReturn<TConfig>;\nexport function useWorkflow<TConfig extends WorkflowConfig, R>(\n\tstore: WorkflowStore<TConfig>,\n\tselector: (workflow: Workflow<TConfig>) => R,\n\tequalityFn?: (a: R, b: R) => boolean,\n): R;\nexport function useWorkflow<TConfig extends WorkflowConfig, R>(\n\tstore: WorkflowStore<TConfig>,\n\tselector?: (workflow: Workflow<TConfig>) => R,\n\tequalityFn?: (a: R, b: R) => boolean,\n): UseWorkflowReturn<TConfig> | R {\n\t// Refs for selector caching — always allocated to maintain hook call order\n\tconst selectorRef = useRef(selector);\n\tconst equalityFnRef = useRef(equalityFn);\n\tconst cachedRef = useRef<R | undefined>(undefined);\n\tconst hasCachedRef = useRef(false);\n\tselectorRef.current = selector;\n\tequalityFnRef.current = equalityFn;\n\n\tconst selectorSnapshot = useCallback(() => {\n\t\tconst wf = store.getWorkflow();\n\t\tif (!wf) {\n\t\t\t// Workflow is loading — return cached value if available\n\t\t\treturn cachedRef.current;\n\t\t}\n\t\t// biome-ignore lint/style/noNonNullAssertion: selectorSnapshot is only used when selector is defined (checked at call site)\n\t\tconst next = selectorRef.current!(wf);\n\t\tconst eq = equalityFnRef.current ?? Object.is;\n\t\tif (hasCachedRef.current && eq(cachedRef.current as R, next)) {\n\t\t\treturn cachedRef.current;\n\t\t}\n\t\tcachedRef.current = next;\n\t\thasCachedRef.current = true;\n\t\treturn next;\n\t}, [store]);\n\n\t// biome-ignore lint/suspicious/noExplicitAny: return type varies by overload; TS can't narrow union of getSnapshot functions\n\tconst getSnapshot: () => any = selector ? selectorSnapshot : store.getSnapshot;\n\n\tconst result: unknown = useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n\n\tif (!selector) {\n\t\treturn createReturn(result as WorkflowStoreSnapshot<TConfig>, store.dispatch);\n\t}\n\treturn result as R;\n}\n","import type {\n\tCommandNames,\n\tCommandPayload,\n\tDispatchResult,\n\tPipelineError,\n\tStateData,\n\tStateNames,\n\tWorkflow,\n\tWorkflowConfig,\n\tWorkflowDefinition,\n} from \"@rytejs/core\";\nimport { migrate, type WorkflowRouter } from \"@rytejs/core\";\nimport type { WorkflowStore, WorkflowStoreOptions, WorkflowStoreSnapshot } from \"./types.js\";\n\nexport function createWorkflowStore<\n\tTConfig extends WorkflowConfig,\n\tTDeps,\n\tS extends StateNames<TConfig>,\n>(\n\trouter: WorkflowRouter<TConfig, TDeps>,\n\tinitialConfig: {\n\t\tstate: S;\n\t\tdata: StateData<TConfig, S>;\n\t\tid?: string;\n\t},\n\toptions?: WorkflowStoreOptions<TConfig>,\n): WorkflowStore<TConfig> {\n\tconst definition = router.definition;\n\n\tlet workflow: Workflow<TConfig> = loadOrCreate(definition, initialConfig, options);\n\tlet isDispatching = false;\n\tlet error: PipelineError<TConfig> | null = null;\n\tlet snapshot: WorkflowStoreSnapshot<TConfig> = {\n\t\tworkflow,\n\t\tisLoading: false,\n\t\tisDispatching,\n\t\terror,\n\t};\n\tconst listeners = new Set<() => void>();\n\n\tfunction notify() {\n\t\tsnapshot = { workflow, isLoading: false, isDispatching, error };\n\t\tfor (const listener of listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n\n\tconst dispatch = async <C extends CommandNames<TConfig>>(\n\t\tcommand: C,\n\t\tpayload: CommandPayload<TConfig, C>,\n\t): Promise<DispatchResult<TConfig>> => {\n\t\tisDispatching = true;\n\t\tnotify();\n\n\t\tconst result = await router.dispatch(workflow, { type: command, payload });\n\n\t\tif (result.ok) {\n\t\t\tworkflow = result.workflow;\n\t\t\terror = null;\n\t\t} else {\n\t\t\terror = result.error;\n\t\t}\n\t\tisDispatching = false;\n\t\tnotify();\n\n\t\tif (result.ok && options?.persist) {\n\t\t\tconst { key, storage } = options.persist;\n\t\t\tconst snap = definition.serialize(workflow);\n\t\t\tstorage.setItem(key, JSON.stringify(snap));\n\t\t}\n\n\t\treturn result;\n\t};\n\n\treturn {\n\t\tgetWorkflow: () => workflow,\n\t\tgetSnapshot: () => snapshot,\n\t\tsubscribe: (listener) => {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t\tdispatch,\n\t\tsetWorkflow: (newWorkflow) => {\n\t\t\tworkflow = newWorkflow;\n\t\t\terror = null;\n\t\t\tnotify();\n\t\t},\n\t\tcleanup() {},\n\t};\n}\n\nfunction loadOrCreate<TConfig extends WorkflowConfig, S extends StateNames<TConfig>>(\n\tdefinition: WorkflowDefinition<TConfig>,\n\tinitialConfig: { state: S; data: StateData<TConfig, S>; id?: string },\n\toptions?: WorkflowStoreOptions<TConfig>,\n): Workflow<TConfig> {\n\tif (options?.persist) {\n\t\tconst { key, storage, migrations } = options.persist;\n\t\ttry {\n\t\t\tconst stored = storage.getItem(key);\n\t\t\tif (stored) {\n\t\t\t\t// biome-ignore lint/suspicious/noExplicitAny: JSON.parse returns unknown structure from storage\n\t\t\t\tlet parsed: any = JSON.parse(stored);\n\t\t\t\tif (migrations) {\n\t\t\t\t\tconst migrated = migrate(migrations, parsed);\n\t\t\t\t\tif (migrated.ok) {\n\t\t\t\t\t\tparsed = migrated.snapshot;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn createFresh(definition, initialConfig);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst restored = definition.deserialize(parsed);\n\t\t\t\tif (restored.ok) {\n\t\t\t\t\treturn restored.workflow;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Invalid JSON or deserialize failed — fall through to create fresh\n\t\t}\n\t}\n\n\treturn createFresh(definition, initialConfig);\n}\n\nfunction createFresh<TConfig extends WorkflowConfig, S extends StateNames<TConfig>>(\n\tdefinition: WorkflowDefinition<TConfig>,\n\tinitialConfig: { state: S; data: StateData<TConfig, S>; id?: string },\n): Workflow<TConfig> {\n\treturn definition.createWorkflow(initialConfig.id ?? crypto.randomUUID(), {\n\t\tinitialState: initialConfig.state,\n\t\tdata: initialConfig.data,\n\t});\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@rytejs/react",
3
+ "version": "0.7.0",
4
+ "description": "React bindings for @rytejs/core — use workflows as reactive state stores",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "sideEffects": false,
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/helico-tech/rytejs",
24
+ "directory": "packages/react"
25
+ },
26
+ "homepage": "https://helico-tech.github.io/rytejs",
27
+ "bugs": "https://github.com/helico-tech/rytejs/issues",
28
+ "keywords": [
29
+ "workflow",
30
+ "state-machine",
31
+ "react",
32
+ "hooks",
33
+ "state-management"
34
+ ],
35
+ "peerDependencies": {
36
+ "react": ">=18",
37
+ "@rytejs/core": "^0.7.0"
38
+ },
39
+ "devDependencies": {
40
+ "@testing-library/jest-dom": "^6.9.1",
41
+ "@testing-library/react": "^16.3.0",
42
+ "@testing-library/user-event": "^14.6.1",
43
+ "@types/react": "^19.2.0",
44
+ "jsdom": "^29.0.0",
45
+ "react": "^19.2.0",
46
+ "react-dom": "^19.2.0",
47
+ "tsup": "^8.5.0",
48
+ "typescript": "^5.9.0",
49
+ "vitest": "^4.0.0",
50
+ "zod": "^4.3.0",
51
+ "@rytejs/core": "0.7.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ },
56
+ "scripts": {
57
+ "build": "tsup",
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
60
+ "typecheck": "tsc --noEmit"
61
+ }
62
+ }