@rxova/journey-core 1.0.0-rc.2 → 1.0.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.
Files changed (158) hide show
  1. package/README.md +182 -186
  2. package/dist/connectors/immer/immer.cjs +2 -0
  3. package/dist/connectors/immer/immer.cjs.map +7 -0
  4. package/dist/connectors/immer/immer.d.cts +11 -0
  5. package/dist/connectors/immer/immer.d.ts +11 -0
  6. package/dist/connectors/immer/immer.js +2 -0
  7. package/dist/connectors/immer/immer.js.map +7 -0
  8. package/dist/connectors/immer/immer.types.d.cts +9 -0
  9. package/dist/connectors/immer/immer.types.d.ts +9 -0
  10. package/dist/core/errors.d.cts +57 -0
  11. package/dist/core/errors.d.ts +57 -0
  12. package/dist/core/helpers.d.cts +46 -0
  13. package/dist/core/helpers.d.ts +46 -0
  14. package/dist/core/machine.d.cts +9 -0
  15. package/dist/core/machine.d.ts +9 -0
  16. package/dist/core/runtime.d.cts +115 -0
  17. package/dist/core/runtime.d.ts +115 -0
  18. package/dist/core/runtime.types.d.cts +112 -0
  19. package/dist/core/runtime.types.d.ts +112 -0
  20. package/dist/core/store.d.cts +31 -0
  21. package/dist/core/store.d.ts +31 -0
  22. package/dist/core/types.d.cts +481 -0
  23. package/dist/core/types.d.ts +481 -0
  24. package/dist/diagnostics/diagnostics.helpers.d.cts +17 -0
  25. package/dist/diagnostics/diagnostics.helpers.d.ts +17 -0
  26. package/dist/diagnostics/diagnostics.types.d.cts +22 -0
  27. package/dist/diagnostics/diagnostics.types.d.ts +22 -0
  28. package/dist/graph/bag.types.d.cts +91 -0
  29. package/dist/graph/bag.types.d.ts +91 -0
  30. package/dist/graph/graph.d.cts +63 -0
  31. package/dist/graph/graph.d.ts +63 -0
  32. package/dist/graph/graph.types.d.cts +212 -0
  33. package/dist/graph/graph.types.d.ts +212 -0
  34. package/dist/graph/work.d.cts +81 -0
  35. package/dist/graph/work.d.ts +81 -0
  36. package/dist/index.cjs +1 -1
  37. package/dist/index.cjs.map +4 -4
  38. package/dist/index.d.cts +13 -5
  39. package/dist/index.d.ts +13 -5
  40. package/dist/index.js +1 -1
  41. package/dist/index.js.map +4 -4
  42. package/dist/linear/linear.d.cts +33 -0
  43. package/dist/linear/linear.d.ts +33 -0
  44. package/dist/linear/linear.types.d.cts +28 -0
  45. package/dist/linear/linear.types.d.ts +28 -0
  46. package/dist/plugins/analytics/analytics.d.cts +8 -0
  47. package/dist/plugins/analytics/analytics.d.ts +8 -0
  48. package/dist/plugins/analytics/analytics.helpers.d.cts +2 -0
  49. package/dist/plugins/analytics/analytics.helpers.d.ts +2 -0
  50. package/dist/plugins/analytics/analytics.types.d.cts +27 -0
  51. package/dist/plugins/analytics/analytics.types.d.ts +27 -0
  52. package/dist/plugins/execution-paths/execution-paths.d.cts +7 -4
  53. package/dist/plugins/execution-paths/execution-paths.d.ts +7 -4
  54. package/dist/plugins/execution-paths/execution-paths.types.d.cts +21 -0
  55. package/dist/plugins/execution-paths/execution-paths.types.d.ts +21 -0
  56. package/dist/plugins/index.cjs +2 -0
  57. package/dist/plugins/index.cjs.map +7 -0
  58. package/dist/plugins/index.d.cts +19 -0
  59. package/dist/plugins/index.d.ts +19 -0
  60. package/dist/plugins/index.js +2 -0
  61. package/dist/plugins/index.js.map +7 -0
  62. package/dist/plugins/persistence/persistence.d.cts +16 -0
  63. package/dist/plugins/persistence/persistence.d.ts +16 -0
  64. package/dist/plugins/persistence/persistence.helpers.d.cts +34 -0
  65. package/dist/plugins/persistence/persistence.helpers.d.ts +34 -0
  66. package/dist/plugins/persistence/persistence.types.d.cts +63 -0
  67. package/dist/plugins/persistence/persistence.types.d.ts +63 -0
  68. package/dist/plugins/replay/replay.d.cts +7 -0
  69. package/dist/plugins/replay/replay.d.ts +7 -0
  70. package/dist/plugins/replay/replay.helpers.d.cts +22 -0
  71. package/dist/plugins/replay/replay.helpers.d.ts +22 -0
  72. package/dist/plugins/replay/replay.types.d.cts +28 -0
  73. package/dist/plugins/replay/replay.types.d.ts +28 -0
  74. package/package.json +78 -79
  75. package/dist/journey-builder/index.d.cts +0 -5
  76. package/dist/journey-builder/index.d.ts +0 -5
  77. package/dist/journey-builder/types.d.cts +0 -126
  78. package/dist/journey-builder/types.d.ts +0 -126
  79. package/dist/journey-machine/async-state.d.cts +0 -12
  80. package/dist/journey-machine/async-state.d.ts +0 -12
  81. package/dist/journey-machine/computed.d.cts +0 -2
  82. package/dist/journey-machine/computed.d.ts +0 -2
  83. package/dist/journey-machine/controls.d.cts +0 -17
  84. package/dist/journey-machine/controls.d.ts +0 -17
  85. package/dist/journey-machine/helpers.d.cts +0 -52
  86. package/dist/journey-machine/helpers.d.ts +0 -52
  87. package/dist/journey-machine/index.d.cts +0 -3
  88. package/dist/journey-machine/index.d.ts +0 -3
  89. package/dist/journey-machine/navigation.d.cts +0 -32
  90. package/dist/journey-machine/navigation.d.ts +0 -32
  91. package/dist/journey-machine/plugin-controller.d.cts +0 -15
  92. package/dist/journey-machine/plugin-controller.d.ts +0 -15
  93. package/dist/journey-machine/resolve-journey-definition.d.cts +0 -2
  94. package/dist/journey-machine/resolve-journey-definition.d.ts +0 -2
  95. package/dist/journey-machine/runtime.d.cts +0 -33
  96. package/dist/journey-machine/runtime.d.ts +0 -33
  97. package/dist/journey-machine/send.d.cts +0 -9
  98. package/dist/journey-machine/send.d.ts +0 -9
  99. package/dist/plugins/analytics/index.cjs +0 -2
  100. package/dist/plugins/analytics/index.cjs.map +0 -7
  101. package/dist/plugins/analytics/index.d.cts +0 -25
  102. package/dist/plugins/analytics/index.d.ts +0 -25
  103. package/dist/plugins/analytics/index.js +0 -2
  104. package/dist/plugins/analytics/index.js.map +0 -7
  105. package/dist/plugins/autosave/index.cjs +0 -2
  106. package/dist/plugins/autosave/index.cjs.map +0 -7
  107. package/dist/plugins/autosave/index.d.cts +0 -17
  108. package/dist/plugins/autosave/index.d.ts +0 -17
  109. package/dist/plugins/autosave/index.js +0 -2
  110. package/dist/plugins/autosave/index.js.map +0 -7
  111. package/dist/plugins/diagnostics/diagnostics.d.cts +0 -3
  112. package/dist/plugins/diagnostics/diagnostics.d.ts +0 -3
  113. package/dist/plugins/diagnostics/index.cjs +0 -2
  114. package/dist/plugins/diagnostics/index.cjs.map +0 -7
  115. package/dist/plugins/diagnostics/index.d.cts +0 -15
  116. package/dist/plugins/diagnostics/index.d.ts +0 -15
  117. package/dist/plugins/diagnostics/index.js +0 -2
  118. package/dist/plugins/diagnostics/index.js.map +0 -7
  119. package/dist/plugins/execution-paths/index.cjs +0 -2
  120. package/dist/plugins/execution-paths/index.cjs.map +0 -7
  121. package/dist/plugins/execution-paths/index.d.cts +0 -18
  122. package/dist/plugins/execution-paths/index.d.ts +0 -18
  123. package/dist/plugins/execution-paths/index.js +0 -2
  124. package/dist/plugins/execution-paths/index.js.map +0 -7
  125. package/dist/plugins/persistence/controller.d.cts +0 -17
  126. package/dist/plugins/persistence/controller.d.ts +0 -17
  127. package/dist/plugins/persistence/index.cjs +0 -2
  128. package/dist/plugins/persistence/index.cjs.map +0 -7
  129. package/dist/plugins/persistence/index.d.cts +0 -12
  130. package/dist/plugins/persistence/index.d.ts +0 -12
  131. package/dist/plugins/persistence/index.js +0 -2
  132. package/dist/plugins/persistence/index.js.map +0 -7
  133. package/dist/plugins/replay/index.cjs +0 -2
  134. package/dist/plugins/replay/index.cjs.map +0 -7
  135. package/dist/plugins/replay/index.d.cts +0 -27
  136. package/dist/plugins/replay/index.d.ts +0 -27
  137. package/dist/plugins/replay/index.js +0 -2
  138. package/dist/plugins/replay/index.js.map +0 -7
  139. package/dist/types/analytics.types.d.cts +0 -33
  140. package/dist/types/analytics.types.d.ts +0 -33
  141. package/dist/types/autosave.types.d.cts +0 -23
  142. package/dist/types/autosave.types.d.ts +0 -23
  143. package/dist/types/diagnostics.types.d.cts +0 -38
  144. package/dist/types/diagnostics.types.d.ts +0 -38
  145. package/dist/types/index.d.cts +0 -9
  146. package/dist/types/index.d.ts +0 -9
  147. package/dist/types/journey.types.d.cts +0 -191
  148. package/dist/types/journey.types.d.ts +0 -191
  149. package/dist/types/machine.types.d.cts +0 -104
  150. package/dist/types/machine.types.d.ts +0 -104
  151. package/dist/types/observation.types.d.cts +0 -83
  152. package/dist/types/observation.types.d.ts +0 -83
  153. package/dist/types/persistence.types.d.cts +0 -34
  154. package/dist/types/persistence.types.d.ts +0 -34
  155. package/dist/types/replay.types.d.cts +0 -35
  156. package/dist/types/replay.types.d.ts +0 -35
  157. package/dist/types/transitions.types.d.cts +0 -122
  158. package/dist/types/transitions.types.d.ts +0 -122
@@ -0,0 +1,31 @@
1
+ import type { JourneyEventPayloads, JourneySnapshot, JourneySubscriptionEvent, Unsubscribe } from "./types.js";
2
+ /**
3
+ * Snapshot holder + subscription hub. Snapshots are immutable and rebuilt by
4
+ * the runtime; the store only distributes them.
5
+ */
6
+ export declare class JourneyStore<TContext, TStepId extends string> {
7
+ private snapshot;
8
+ private readonly listeners;
9
+ private readonly eventListeners;
10
+ private disposed;
11
+ private readonly onListenerError;
12
+ constructor(initial: JourneySnapshot<TContext, TStepId>, onListenerError?: (error: unknown) => void);
13
+ /**
14
+ * Isolation stays unconditional; the configured reporter only routes the
15
+ * report. Internal, but reachable from the runtime so plugin-boundary
16
+ * failures land in the same place as subscriber failures.
17
+ */
18
+ report(error: unknown): void;
19
+ getSnapshot(): JourneySnapshot<TContext, TStepId>;
20
+ /** Replaces the snapshot and notifies every subscriber. */
21
+ publish(next: JourneySnapshot<TContext, TStepId>): void;
22
+ /**
23
+ * Notifies on every committed snapshot. Deriving a slice and skipping
24
+ * unchanged values is the caller's job — in React that is `useSelector`,
25
+ * which has to own the comparison anyway to keep render identity stable.
26
+ */
27
+ subscribe(listener: () => void): Unsubscribe;
28
+ subscribeEvent<TEvent extends JourneySubscriptionEvent>(event: TEvent, listener: (payload: JourneyEventPayloads<TContext, TStepId>[TEvent]) => void): Unsubscribe;
29
+ emit<TEvent extends JourneySubscriptionEvent>(event: TEvent, payload: JourneyEventPayloads<TContext, TStepId>[TEvent]): void;
30
+ dispose(): void;
31
+ }
@@ -0,0 +1,481 @@
1
+ /**
2
+ * Shared core contracts for `@rxova/journey-core`.
3
+ *
4
+ * The machine object holds only stable methods; every piece of changing state
5
+ * lives in an immutable {@link JourneySnapshot} rebuilt at defined emission
6
+ * points (navigation accepted → commit → settle, plus lifecycle and context
7
+ * changes) and delivered to subscribers.
8
+ */
9
+ import type { JourneyStorage } from "../plugins/persistence/persistence.types.js";
10
+ /** Journey lifecycle status (meta-state-machine, enforced in core). */
11
+ export type JourneyStatus = "idle" | "running" | "paused" | "completed" | "terminated";
12
+ /** An event declaration: discriminated union member `{ type; payload? }`. */
13
+ export type JourneyEventObject = {
14
+ readonly type: string;
15
+ readonly payload?: unknown;
16
+ };
17
+ /** Extracts the payload type of event `TType` from a declared event union. */
18
+ export type JourneyEventPayload<TEvents extends JourneyEventObject, TType extends TEvents["type"]> = Extract<TEvents, {
19
+ type: TType;
20
+ }> extends {
21
+ payload: infer P;
22
+ } ? P : undefined;
23
+ /**
24
+ * Why a navigation was rejected.
25
+ *
26
+ * - `error` — navigation work threw, rejected, timed out, or failed to commit.
27
+ * - `transitioning` — another navigation's hook chain is still pending.
28
+ * - `not-running` — the machine is idle, paused, completed, or terminated.
29
+ * - `invalid-target` — unknown step id, or (graph) no transition targets it.
30
+ * - `no-enabled-transition` — the event matched no enabled candidate.
31
+ * - `out-of-bounds` — timeline move past its edge (back at index 0, forward at tip).
32
+ * - `no-op` — navigation to the current position.
33
+ * - `disposed` — the machine was disposed; all methods are safe no-ops.
34
+ */
35
+ export type NavigationFailureReason = "error" | "transitioning" | "not-running" | "invalid-target" | "no-enabled-transition" | "out-of-bounds" | "no-op" | "disposed";
36
+ /**
37
+ * Result of every navigation verb (and of graph `send`).
38
+ *
39
+ * The failure arm carries `from`/`to` so a caller awaiting the result can log
40
+ * *which* target was rejected without also subscribing to `navigationBlocked`.
41
+ * `to` is null when the machine never resolved one — a graph `send` with no
42
+ * enabled candidate, for instance.
43
+ */
44
+ export type NavigationResult<TStepId extends string = string> = {
45
+ readonly ok: true;
46
+ readonly from: TStepId | null;
47
+ readonly to: TStepId;
48
+ } | {
49
+ readonly ok: false;
50
+ readonly reason: NavigationFailureReason;
51
+ readonly from: TStepId | null;
52
+ readonly to: TStepId | null;
53
+ readonly error?: unknown;
54
+ };
55
+ /** Journey outcome recorded by `complete(payload?)` / `terminate(payload?)`. */
56
+ export type JourneyOutcome<TCompletePayload = unknown, TTerminatePayload = unknown> = {
57
+ readonly type: "completed";
58
+ readonly payload: TCompletePayload | undefined;
59
+ } | {
60
+ readonly type: "terminated";
61
+ readonly payload: TTerminatePayload | undefined;
62
+ };
63
+ /** Machine-level async transition state (a snapshot source of truth). */
64
+ export type TransitionState<TStepId extends string = string> = {
65
+ readonly pending: boolean;
66
+ readonly phase: "working" | "leaving" | "entering" | null;
67
+ readonly from: TStepId | null;
68
+ readonly to: TStepId | null;
69
+ };
70
+ /** Async state of the current navigation work or lifecycle effect chain. */
71
+ export type StepAsyncState = {
72
+ readonly isLoading: boolean;
73
+ readonly isSuccess: boolean;
74
+ readonly isError: boolean;
75
+ readonly error: unknown | null;
76
+ };
77
+ /** Fields present on `currentStep` for every journey kind. */
78
+ export type CurrentStepBase<TStepId extends string, TMeta> = {
79
+ readonly id: TStepId;
80
+ readonly metadata: TMeta;
81
+ /** True while this is the first ever entry of the step in this run. */
82
+ readonly isFirstTimeVisit: boolean;
83
+ readonly async: StepAsyncState;
84
+ };
85
+ /** Browser-like timeline state shared by every journey kind. */
86
+ export type JourneyHistoryState<TStepId extends string> = {
87
+ readonly timeline: readonly TStepId[];
88
+ /** Pointer into `timeline`; -1 while idle. */
89
+ readonly currentIndex: number;
90
+ readonly visited: Readonly<Record<TStepId, boolean>>;
91
+ readonly canGoBack: boolean;
92
+ readonly canGoForward: boolean;
93
+ };
94
+ /**
95
+ * How the run ended, once it has.
96
+ *
97
+ * This used to also carry six derived booleans — `isLoading` and one per
98
+ * status. They were each another spelling of something the snapshot already
99
+ * said: the five status flags were `status === x`, and `isLoading` was
100
+ * `transition.pending` computed a second time. `outcome` is the only value here
101
+ * that is not derivable from another field.
102
+ */
103
+ export type MachineState<TCompletePayload = unknown, TTerminatePayload = unknown> = {
104
+ readonly outcome: JourneyOutcome<TCompletePayload, TTerminatePayload> | null;
105
+ };
106
+ /** Fields shared by every snapshot kind (discriminated on `type`). */
107
+ export type JourneySnapshotBase<TContext, TStepId extends string, TMeta, TCompletePayload = unknown, TTerminatePayload = unknown> = {
108
+ readonly status: JourneyStatus;
109
+ readonly context: TContext;
110
+ readonly transition: TransitionState<TStepId>;
111
+ readonly history: JourneyHistoryState<TStepId>;
112
+ readonly machine: MachineState<TCompletePayload, TTerminatePayload>;
113
+ readonly plugins: Readonly<Record<string, unknown>>;
114
+ readonly currentStep: CurrentStepBase<TStepId, TMeta> | null;
115
+ };
116
+ /** Linear snapshots carry declared-order derivations; graph fields don't exist. */
117
+ export type LinearSnapshot<TContext, TStepId extends string, TMeta, TCompletePayload = unknown, TTerminatePayload = unknown> = Omit<JourneySnapshotBase<TContext, TStepId, TMeta, TCompletePayload, TTerminatePayload>, "currentStep"> & {
118
+ readonly type: "linear";
119
+ readonly currentStep: (CurrentStepBase<TStepId, TMeta> & {
120
+ /** Index in declared order. */
121
+ readonly index: number;
122
+ readonly isFirstStep: boolean;
123
+ readonly isLastStep: boolean;
124
+ }) | null;
125
+ readonly steps: {
126
+ readonly totalSteps: number;
127
+ readonly stepOrder: readonly TStepId[];
128
+ readonly visitedStepCount: number;
129
+ };
130
+ };
131
+ /** Guard evaluation recorded for an outgoing graph transition candidate. */
132
+ export type GraphGuardState = "none" | "passed" | "failed";
133
+ /** Serializable routing state for one candidate declared from the current graph step. */
134
+ export type GraphTransitionSnapshot<TStepId extends string, TEventType extends string> = {
135
+ /** Event that evaluates this candidate. */
136
+ readonly event: TEventType;
137
+ /** Candidate destination. */
138
+ readonly to: TStepId;
139
+ /** Zero-based declaration order among candidates for the same event. */
140
+ readonly priority: number;
141
+ /** Current result of the candidate's guard, or `none` when no guard is declared. */
142
+ readonly guard: GraphGuardState;
143
+ /** Whether the candidate's guard currently permits routing. */
144
+ readonly enabled: boolean;
145
+ /** Whether sending this event would select this candidate under first-enabled semantics. */
146
+ readonly selected: boolean;
147
+ };
148
+ /** Graph snapshots carry declared and enabled transition derivations; order fields don't exist. */
149
+ export type GraphSnapshot<TContext, TStepId extends string, TMeta, TEvents extends JourneyEventObject = JourneyEventObject, TCompletePayload = unknown, TTerminatePayload = unknown> = Omit<JourneySnapshotBase<TContext, TStepId, TMeta, TCompletePayload, TTerminatePayload>, "currentStep"> & {
150
+ readonly type: "graph";
151
+ readonly currentStep: (CurrentStepBase<TStepId, TMeta> & {
152
+ /** No outgoing transitions are defined from this step. */
153
+ readonly isTerminal: boolean;
154
+ }) | null;
155
+ readonly steps: {
156
+ readonly totalSteps: number;
157
+ readonly visitedStepCount: number;
158
+ };
159
+ /** Event names declared by outgoing candidates from the current step. */
160
+ readonly declaredEvents: readonly TEvents["type"][];
161
+ /** Events with at least one enabled candidate from the current step. */
162
+ readonly availableEvents: readonly TEvents["type"][];
163
+ /** Targets of enabled candidates from the current step. */
164
+ readonly availableSteps: readonly TStepId[];
165
+ /** Guard and selection state for every outgoing candidate from the current step. */
166
+ readonly outgoingTransitions: readonly GraphTransitionSnapshot<TStepId, TEvents["type"]>[];
167
+ };
168
+ export type JourneySnapshot<TContext = unknown, TStepId extends string = string, TMeta = unknown, TEvents extends JourneyEventObject = JourneyEventObject, TCompletePayload = unknown, TTerminatePayload = unknown> = LinearSnapshot<TContext, TStepId, TMeta, TCompletePayload, TTerminatePayload> | GraphSnapshot<TContext, TStepId, TMeta, TEvents, TCompletePayload, TTerminatePayload>;
169
+ /** Context updater used by `machine.context.update` and hook args. */
170
+ export type ContextUpdater<TContext> = (previous: TContext) => TContext;
171
+ /**
172
+ * Which edge a hook is running for.
173
+ *
174
+ * `event` and `to` alone do not identify it: several candidates on one event
175
+ * can share a target and differ only by guard. `label` is the edge's declared
176
+ * name; `index` is its position among the candidates the origin step declares
177
+ * for that event, so an anonymous edge is still identifiable.
178
+ */
179
+ export type TransitionInfo = {
180
+ readonly event: string;
181
+ readonly from: string;
182
+ readonly to: string;
183
+ readonly label: string | null;
184
+ readonly index: number;
185
+ };
186
+ /**
187
+ * Arguments passed to step hooks.
188
+ *
189
+ * `event` is the graph event that caused the transition; `null` for timeline
190
+ * moves, linear navigation, and the initial entry on `start()`.
191
+ * Hooks run after navigation commits. Context updates made inside hooks apply
192
+ * immediately and are side effects; hook failures never roll navigation back.
193
+ */
194
+ export type StepHookArgs<TContext, TStepId extends string, TEvents extends JourneyEventObject = never, TSnap = JourneySnapshot<TContext, TStepId>> = {
195
+ readonly snapshot: TSnap;
196
+ readonly from: TStepId | null;
197
+ readonly to: TStepId;
198
+ readonly event: TEvents | null;
199
+ /**
200
+ * The edge that caused this move, or `null` for timeline moves, linear
201
+ * navigation, and the initial entry. Present on every step hook, not just
202
+ * `onTransition`, so `onEnter` can tell which candidate routed into it.
203
+ */
204
+ readonly transition: TransitionInfo | null;
205
+ readonly updateContext: (updater: ContextUpdater<TContext>) => void;
206
+ /**
207
+ * Queues an event for processing after the current transition fully settles
208
+ * (FIFO). Only meaningful on graph journeys; linear hooks receive a no-op.
209
+ */
210
+ readonly raise: (event: TEvents) => void;
211
+ };
212
+ /** `onLeave` is an awaited post-commit side effect and cannot block navigation. */
213
+ export type OnLeaveHook<TContext, TStepId extends string, TEvents extends JourneyEventObject = never, TSnap = JourneySnapshot<TContext, TStepId>> = (args: StepHookArgs<TContext, TStepId, TEvents, TSnap>) => void | Promise<void>;
214
+ /** `onEnter` is an awaited post-commit side effect and cannot block navigation. */
215
+ export type OnEnterHook<TContext, TStepId extends string, TEvents extends JourneyEventObject = never, TSnap = JourneySnapshot<TContext, TStepId>> = (args: StepHookArgs<TContext, TStepId, TEvents, TSnap>) => void | Promise<void>;
216
+ /**
217
+ * Completion and termination payload types supplied to a journey factory.
218
+ *
219
+ * Shared by both tiers: linear takes it as a generic parameter, graph names it
220
+ * through the definition's `$payloads` phantom carrier.
221
+ */
222
+ export type JourneyTerminationPayloads = {
223
+ readonly complete?: unknown;
224
+ readonly terminate?: unknown;
225
+ };
226
+ export type CompletePayloadOf<TTypes extends JourneyTerminationPayloads> = TTypes extends {
227
+ readonly complete: infer TPayload;
228
+ } ? TPayload : unknown;
229
+ export type TerminatePayloadOf<TTypes extends JourneyTerminationPayloads> = TTypes extends {
230
+ readonly terminate: infer TPayload;
231
+ } ? TPayload : unknown;
232
+ /** Subscription event names. */
233
+ export type JourneySubscriptionEvent = "stepEnter" | "stepLeave" | "statusChange" | "contextChange" | "navigationBlocked" | "error";
234
+ export type JourneyEventPayloads<TContext, TStepId extends string, TSnap = JourneySnapshot<TContext, TStepId>> = {
235
+ stepEnter: {
236
+ readonly snapshot: TSnap;
237
+ readonly from: TStepId | null;
238
+ readonly to: TStepId;
239
+ readonly direction: StepEnterDirection;
240
+ };
241
+ stepLeave: {
242
+ readonly snapshot: TSnap;
243
+ readonly from: TStepId;
244
+ readonly to: TStepId;
245
+ };
246
+ statusChange: {
247
+ readonly snapshot: TSnap;
248
+ readonly previous: JourneyStatus;
249
+ readonly current: JourneyStatus;
250
+ };
251
+ contextChange: {
252
+ readonly snapshot: TSnap;
253
+ readonly previous: TContext;
254
+ readonly current: TContext;
255
+ };
256
+ navigationBlocked: {
257
+ readonly snapshot: TSnap;
258
+ readonly reason: NavigationFailureReason;
259
+ readonly from: TStepId | null;
260
+ readonly to: TStepId | null;
261
+ readonly error?: unknown;
262
+ };
263
+ error: {
264
+ readonly snapshot: TSnap;
265
+ readonly error: unknown;
266
+ /** Which pipeline stage threw. */
267
+ readonly phase: "work" | "leave" | "enter" | "transition" | "raise";
268
+ readonly stepId: TStepId | null;
269
+ };
270
+ };
271
+ export type Unsubscribe = () => void;
272
+ export type JourneySubscriptions<TContext, TStepId extends string, TSnap = JourneySnapshot<TContext, TStepId>> = {
273
+ /**
274
+ * Fires on every committed snapshot; read the current one with
275
+ * `getSnapshot()`. Deriving a slice and skipping unchanged values belongs to
276
+ * the caller — React's `useSelector` owns that comparison because it must
277
+ * anyway, to keep render identity stable.
278
+ */
279
+ subscribe(listener: () => void): Unsubscribe;
280
+ subscribeEvent<TEvent extends JourneySubscriptionEvent>(event: TEvent, listener: (payload: JourneyEventPayloads<TContext, TStepId, TSnap>[TEvent]) => void): Unsubscribe;
281
+ };
282
+ /**
283
+ * Lifecycle verbs. Each returns `true` when the lifecycle change applied and
284
+ * `false` when rejected (wrong source status, pending transition, disposed).
285
+ */
286
+ export type JourneyControls<TCompletePayload = unknown, TTerminatePayload = unknown> = {
287
+ /** idle → running; enters the first/initial step. */
288
+ start(): boolean;
289
+ /** running → paused; navigation is rejected while paused. */
290
+ pause(): boolean;
291
+ /** paused → running. */
292
+ resume(): boolean;
293
+ /** running → completed. Explicit only — never fired by navigation. */
294
+ complete(payload?: TCompletePayload): boolean;
295
+ /** any → terminated. Wins over a pending transition. */
296
+ terminate(payload?: TTerminatePayload): boolean;
297
+ /** completed | terminated → running; resets timeline + context to initial. */
298
+ restart(): boolean;
299
+ };
300
+ export type NavigationDirection = "forward" | "backward";
301
+ /**
302
+ * How a step was entered, by intent rather than index math: only
303
+ * `goToNextStep`/`goToPreviousStep` report `"forward"`/`"backward"`; the
304
+ * initial entry, absolute verbs (`goToStepById`, `goToStepByIndex`,
305
+ * `goToLastVisitedStep`), and graph `send` report `"jump"`.
306
+ */
307
+ export type StepEnterDirection = NavigationDirection | "jump";
308
+ /** Read-only arguments for work that must succeed before navigation commits. */
309
+ export type NavigationWorkArgs<TStepId extends string, TSnap> = {
310
+ readonly snapshot: TSnap;
311
+ readonly from: TStepId;
312
+ readonly to: TStepId;
313
+ readonly direction: NavigationDirection;
314
+ };
315
+ /**
316
+ * Transactional work attached to next/previous navigation.
317
+ *
318
+ * `run` is awaited before movement. If it throws, rejects, or times out, the
319
+ * machine stays on the current step. `commit` runs synchronously after `run`
320
+ * succeeds; its context updates are staged and published atomically with the
321
+ * position change.
322
+ */
323
+ export type NavigationWork<TContext, TStepId extends string, TSnap, TResult = void> = {
324
+ readonly run: (args: NavigationWorkArgs<TStepId, TSnap>) => TResult | Promise<TResult>;
325
+ readonly commit?: (args: NavigationWorkArgs<TStepId, TSnap> & {
326
+ readonly result: TResult;
327
+ readonly updateContext: (updater: ContextUpdater<TContext>) => void;
328
+ }) => void;
329
+ /**
330
+ * Budget for this `run`, overriding `defaultTimeoutMs`. Left unset, the
331
+ * global default applies; set it when this one call is slower than the rest.
332
+ */
333
+ readonly timeoutMs?: number;
334
+ };
335
+ export type JourneyNavigation<TContext, TStepId extends string, TSnap> = {
336
+ goToStepById(id: TStepId): Promise<NavigationResult<TStepId>>;
337
+ /** Timeline pointer back `n`; clamps to start; fails only at index 0. */
338
+ goToPreviousStep(n?: number): Promise<NavigationResult<TStepId>>;
339
+ /** Timeline forward; linear falls back to next-in-declared-order at tip. */
340
+ goToNextStep<TResult = void>(work?: NavigationWork<TContext, TStepId, TSnap, TResult>): Promise<NavigationResult<TStepId>>;
341
+ /** Pointer → timeline tip; fails if already there. */
342
+ goToLastVisitedStep(): Promise<NavigationResult<TStepId>>;
343
+ };
344
+ /** Machine surface shared by linear and graph journeys. */
345
+ export type JourneyMachineBase<TContext, TStepId extends string, TSnap = JourneySnapshot<TContext, TStepId>, TCompletePayload = unknown, TTerminatePayload = unknown> = {
346
+ getSnapshot(): TSnap;
347
+ controls: JourneyControls<TCompletePayload, TTerminatePayload>;
348
+ navigate: JourneyNavigation<TContext, TStepId, TSnap>;
349
+ subscriptions: JourneySubscriptions<TContext, TStepId, TSnap>;
350
+ context: {
351
+ update(updater: ContextUpdater<TContext>): void;
352
+ };
353
+ async: {
354
+ clearError(): void;
355
+ };
356
+ /** Irreversible teardown: drops listeners; all methods become safe no-ops. */
357
+ dispose(): void;
358
+ };
359
+ /** A read-only structural view of the running definition, for analysis plugins. */
360
+ export type JourneyStructure = {
361
+ readonly kind: "linear" | "graph";
362
+ readonly stepIds: readonly string[];
363
+ readonly initial: string;
364
+ /** Flattened transitions in declaration order (empty for linear). */
365
+ readonly transitions: readonly {
366
+ readonly event: string;
367
+ readonly from: string;
368
+ readonly to: string;
369
+ readonly guarded: boolean;
370
+ /** The edge's declared name, or `null` when it was left anonymous. */
371
+ readonly label: string | null;
372
+ /** Position among the candidates this step declares for this event. */
373
+ readonly index: number;
374
+ }[];
375
+ };
376
+ /** Observe-only taps + reads — no interception in v1. */
377
+ export type PluginHost<TContext = unknown, TStepId extends string = string> = {
378
+ getSnapshot(): JourneySnapshot<TContext, TStepId>;
379
+ readonly structure: JourneyStructure;
380
+ /** Fires after commit + settle of every successful navigation (incl. initial entry). */
381
+ onTransition(callback: (info: {
382
+ readonly from: TStepId | null;
383
+ readonly to: TStepId;
384
+ readonly snapshot: JourneySnapshot<TContext, TStepId>;
385
+ }) => void): Unsubscribe;
386
+ onNavigationBlocked(callback: (info: JourneyEventPayloads<TContext, TStepId>["navigationBlocked"]) => void): Unsubscribe;
387
+ onStatusChange(callback: (info: JourneyEventPayloads<TContext, TStepId>["statusChange"]) => void): Unsubscribe;
388
+ onContextChange(callback: (info: JourneyEventPayloads<TContext, TStepId>["contextChange"]) => void): Unsubscribe;
389
+ onError(callback: (info: JourneyEventPayloads<TContext, TStepId>["error"]) => void): Unsubscribe;
390
+ onDispose(callback: () => void): void;
391
+ /**
392
+ * Routes a plugin's own failure to the machine's `onListenerError`.
393
+ *
394
+ * A plugin tap that throws synchronously is already isolated and reported.
395
+ * Work that outlives the tap — an awaited storage write, a debounced flush —
396
+ * has no such path, and would otherwise have to choose between an unhandled
397
+ * rejection and a `console.error` that ignores the configured reporter.
398
+ */
399
+ reportError(error: unknown): void;
400
+ };
401
+ /**
402
+ * A journey plugin: observes via {@link PluginHost} taps and extends the
403
+ * machine (`machine.plugins[name]`) and snapshot (`snapshot.plugins[name]`).
404
+ * Extensions are always namespaced, never merged into core fields.
405
+ */
406
+ export type JourneyPlugin<TName extends string = string, TApi = unknown, TSnapExt = unknown> = {
407
+ readonly name: TName;
408
+ setup(host: PluginHost): {
409
+ api?: TApi;
410
+ deriveSnapshot?: (snapshot: JourneySnapshot, previousExtension: TSnapExt | undefined) => TSnapExt;
411
+ };
412
+ };
413
+ /**
414
+ * Variance-friendly plugin bound: `deriveSnapshot`'s `previousExtension`
415
+ * parameter is contravariant, so concrete `JourneyPlugin<N, Api, Ext>` types
416
+ * are not assignable to bare `JourneyPlugin`. Constraints use this instead.
417
+ */
418
+ export type AnyJourneyPlugin = {
419
+ readonly name: string;
420
+ setup(host: PluginHost): {
421
+ api?: unknown;
422
+ deriveSnapshot?: (snapshot: JourneySnapshot, previousExtension: never) => unknown;
423
+ };
424
+ };
425
+ /** Maps a plugin tuple to the `machine.plugins` record. */
426
+ export type PluginApis<TPlugins extends readonly AnyJourneyPlugin[]> = {
427
+ readonly [P in TPlugins[number] as P["name"]]: P extends JourneyPlugin<string, infer TApi, infer TSnapExt> ? TApi : never;
428
+ };
429
+ /** Sugar over the persistence plugin, expanded at creation. */
430
+ export type JourneyPersistOption = {
431
+ readonly key: string;
432
+ /** Defaults to `globalThis.localStorage`; creation throws when neither is available. */
433
+ readonly storage?: JourneyStorage;
434
+ };
435
+ /** Runtime options shared by linear and graph journeys. */
436
+ export type JourneyRuntimeOptions<TPlugins extends readonly AnyJourneyPlugin[] = readonly [], TStepId extends string = string> = {
437
+ /**
438
+ * Defaults to `true`: creating a journey starts it, so the common case needs
439
+ * no second call.
440
+ *
441
+ * The trade this makes, stated plainly because it cannot be worked around:
442
+ * starting happens inside the constructor and the initial entry commits
443
+ * synchronously, so the first `stepEnter` has already fired by the time the
444
+ * factory returns. A subscriber attached afterwards cannot see it.
445
+ *
446
+ * Pass `false` when that first event matters — it is the subscribe-then-start
447
+ * order, and saying so out loud is better than a default that silently
448
+ * assumes it:
449
+ *
450
+ * ```ts
451
+ * const machine = createLinearJourney(definition, { autoStart: false });
452
+ * machine.subscriptions.subscribeEvent("stepEnter", onEnter);
453
+ * machine.controls.start();
454
+ * ```
455
+ */
456
+ autoStart?: boolean;
457
+ /**
458
+ * Start (and restart) directly at this step instead of the first/initial
459
+ * one: only the target's `onEnter` fires, earlier steps are neither entered
460
+ * nor visited, and the timeline begins as `[startAt]`. An unknown id throws
461
+ * at creation.
462
+ */
463
+ startAt?: TStepId;
464
+ /**
465
+ * Expands into the persistence plugin, prepended to `plugins`. Combining it
466
+ * with an explicit persistence plugin fails as a duplicate plugin name.
467
+ */
468
+ persist?: JourneyPersistOption;
469
+ /**
470
+ * Applied to navigation work and every async hook invocation. Work timeouts
471
+ * block movement; post-commit hook timeouts surface as step errors.
472
+ */
473
+ defaultTimeoutMs?: number;
474
+ /**
475
+ * Called when a subscriber (selector or event listener) throws. Listener
476
+ * failures are always isolated — this hook only routes the report. Defaults
477
+ * to a `console.error`; a throwing reporter falls back to that default.
478
+ */
479
+ onListenerError?: (error: unknown) => void;
480
+ plugins?: TPlugins;
481
+ };