@modular-react/journeys 0.1.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.
@@ -0,0 +1,754 @@
1
+ import { AbandonCtx } from '@modular-react/core';
2
+ import { ComponentType } from 'react';
3
+ import { EntryInputOf } from '@modular-react/core';
4
+ import { EntryNamesOf } from '@modular-react/core';
5
+ import { EntryTransitions } from '@modular-react/core';
6
+ import { ExitCtx } from '@modular-react/core';
7
+ import { ExitNamesOf } from '@modular-react/core';
8
+ import { ExitOutputOf } from '@modular-react/core';
9
+ import { InstanceId } from '@modular-react/core';
10
+ import { JourneyDefinitionSummary } from '@modular-react/core';
11
+ import { JourneyHandleRef } from '@modular-react/core';
12
+ import { JourneyInstance } from '@modular-react/core';
13
+ import { JourneyPersistence } from '@modular-react/core';
14
+ import { JourneyRuntime } from '@modular-react/core';
15
+ import { JourneyStatus } from '@modular-react/core';
16
+ import { JourneyStep } from '@modular-react/core';
17
+ import { MaybePromise } from '@modular-react/core';
18
+ import { ModuleDescriptor } from '@modular-react/core';
19
+ import { ModuleExitEvent } from '@modular-react/react';
20
+ import { ModuleTypeMap } from '@modular-react/core';
21
+ import { NavigationItemBase } from '@modular-react/core';
22
+ import { ReactNode } from 'react';
23
+ import { RegistryPlugin } from '@modular-react/core';
24
+ import { SerializedJourney } from '@modular-react/core';
25
+ import { StepSpec } from '@modular-react/core';
26
+ import { TerminalCtx } from '@modular-react/core';
27
+ import { TerminalOutcome } from '@modular-react/core';
28
+ import { TransitionEvent as TransitionEvent_2 } from '@modular-react/core';
29
+ import { TransitionMap } from '@modular-react/core';
30
+ import { TransitionResult } from '@modular-react/core';
31
+
32
+ export { AbandonCtx }
33
+
34
+ /** Erased shape used by the registry — `any` on the generics lets the
35
+ * registry store definitions from different journeys side-by-side.
36
+ * Tightening to `unknown` breaks variance: `initialState: (input: TInput)
37
+ * => TState` for a specific journey is not assignable to
38
+ * `(input: unknown) => unknown` because function parameters are
39
+ * contravariant, so the registry would reject any concrete definition. */
40
+ export declare type AnyJourneyDefinition = JourneyDefinition<ModuleTypeMap, any, any>;
41
+
42
+ /**
43
+ * Create a journey runtime bound to a set of registered journeys. The
44
+ * registry integration assembles this once at resolve time; the runtime is
45
+ * owned by the manifest and exposed as `manifest.journeys`.
46
+ *
47
+ * Passing an empty `registered` array yields a no-op runtime: every public
48
+ * method is safe to call, and `start()` will throw "unknown journey id" —
49
+ * matching the normal "not registered" failure mode and letting shells skip
50
+ * null-guards on `manifest.journeys`.
51
+ */
52
+ export declare function createJourneyRuntime(registered: readonly RegisteredJourney[], options?: JourneyRuntimeOptions): JourneyRuntime;
53
+
54
+ /**
55
+ * Map-backed `JourneyPersistence` for tests and SSR. Gives tests a
56
+ * canonical isolated store (no bleed between cases, no `localStorage`
57
+ * mocking) and keeps the runtime's persistence code paths exercised.
58
+ *
59
+ * On SSR, it's a safe "persistence is configured but nothing survives
60
+ * the request" mode — every start mints a fresh instance, and save /
61
+ * remove are no-ops from the client's perspective.
62
+ *
63
+ * ```ts
64
+ * const store = createMemoryPersistence<MyInput, MyState>({
65
+ * keyFor: ({ journeyId, input }) => `${journeyId}:${input.id}`,
66
+ * });
67
+ *
68
+ * const runtime = createJourneyRuntime(
69
+ * [{ definition: myJourney, options: { persistence: store } }],
70
+ * { modules },
71
+ * );
72
+ *
73
+ * // Tests can assert directly against the store:
74
+ * expect(store.size()).toBe(1);
75
+ * ```
76
+ */
77
+ export declare function createMemoryPersistence<TInput, TState>(options: MemoryPersistenceOptions<TInput, TState>): MemoryPersistence<TInput, TState>;
78
+
79
+ /**
80
+ * `JourneyPersistence` backed by the Web Storage API
81
+ * (`localStorage` / `sessionStorage`). Covers the 80% case: a few KB of
82
+ * JSON per journey-per-customer, read on mount, written on every transition.
83
+ *
84
+ * SSR-safe — when `storage` resolves to `null` (server rendering, private
85
+ * modes where storage is disabled) all four methods no-op and `load`
86
+ * returns `null`, so the runtime mints a fresh instance as it would
87
+ * without persistence configured.
88
+ *
89
+ * Corrupt entries (invalid JSON) are removed lazily on `load` so a single
90
+ * bad write doesn't block future loads for the same key.
91
+ *
92
+ * ```ts
93
+ * export const journeyPersistence = createWebStoragePersistence<
94
+ * OnboardingInput,
95
+ * OnboardingState
96
+ * >({
97
+ * keyFor: ({ journeyId, input }) =>
98
+ * `journey:${input.customerId}:${journeyId}`,
99
+ * });
100
+ *
101
+ * // Tab-scoped, cleared when the tab closes:
102
+ * const sessionScoped = createWebStoragePersistence<MyInput, MyState>({
103
+ * keyFor: ({ journeyId, input }) => `s:${input.id}:${journeyId}`,
104
+ * storage: typeof sessionStorage !== "undefined" ? sessionStorage : null,
105
+ * });
106
+ * ```
107
+ *
108
+ * **Limits.** `localStorage` is synchronous and capped at ~5 MB per origin.
109
+ * Writes throw `QuotaExceededError` when full; the error bubbles so the
110
+ * app can surface it. If a journey holds large state or offline-first
111
+ * matters, write a custom adapter against IndexedDB.
112
+ */
113
+ export declare function createWebStoragePersistence<TInput, TState>(options: WebStoragePersistenceOptions<TInput>): SyncJourneyPersistence<TState, TInput>;
114
+
115
+ /**
116
+ * Declare a journey with full type inference on entry/exit contracts,
117
+ * transitions, and the journey's private state.
118
+ *
119
+ * **Why the empty parens?** TypeScript can't partially infer generics: if
120
+ * `defineJourney` took `<TModules, TState, TInput>` in a single call, you'd
121
+ * either have to spell all three — losing the ability to infer `TInput`
122
+ * from `initialState`'s parameter — or spell none, losing the ability to
123
+ * narrow `TModules` / `TState`. The two-call shape splits the generics
124
+ * so `TModules` + `TState` are explicit (first call) while `TInput` is
125
+ * inferred from the definition object (second call).
126
+ *
127
+ * ```ts
128
+ * defineJourney<OnboardingModules, OnboardingState>()({
129
+ * id: "customer-onboarding",
130
+ * version: "1.0.0",
131
+ * initialState: (input: { customerId: string }) => ({ ... }),
132
+ * // TInput is inferred as { customerId: string } here
133
+ * start: (state) => ({ module: "profile", entry: "review", input: { customerId: state.customerId } }),
134
+ * transitions: { ... },
135
+ * });
136
+ * ```
137
+ *
138
+ * Zero runtime cost — the definition is returned unchanged.
139
+ */
140
+ export declare const defineJourney: <TModules extends ModuleTypeMap, TState>() => <TInput = void>(definition: JourneyDefinition<TModules, TState, TInput>) => JourneyDefinition<TModules, TState, TInput>;
141
+
142
+ /**
143
+ * Build a handle from a journey definition. Runtime identity is just
144
+ * `{ id: def.id }`; the returned object is typed so callers get
145
+ * `input`-checking through the `start` overload.
146
+ */
147
+ export declare function defineJourneyHandle<TModules extends ModuleTypeMap, TState, TInput>(def: JourneyDefinition<TModules, TState, TInput>): JourneyHandle<string, TInput>;
148
+
149
+ /**
150
+ * Identity helper that ties a persistence adapter's `keyFor` input to a
151
+ * journey's `TInput` so callers get compile-time checking on per-customer /
152
+ * per-session keys. Zero runtime cost — the adapter is returned as-is.
153
+ *
154
+ * The return type preserves both `TInput` and `TState`, so shells calling
155
+ * `persistence.keyFor({ input })` *outside* the runtime (e.g. to probe
156
+ * storage before opening a journey tab) still see the journey's typed
157
+ * input shape — no `input: unknown` erasure at the boundary.
158
+ *
159
+ * ```ts
160
+ * interface CustomerInput { customerId: string }
161
+ *
162
+ * const journeyPersistence = defineJourneyPersistence<CustomerInput, MyState>({
163
+ * keyFor: ({ input }) => `journey:${input.customerId}:onboarding`,
164
+ * load: (k) => backend.load(k),
165
+ * save: (k, b) => backend.save(k, b),
166
+ * remove: (k) => backend.remove(k),
167
+ * });
168
+ *
169
+ * // Outside the runtime — `input` is typed as CustomerInput:
170
+ * const key = journeyPersistence.keyFor({
171
+ * journeyId: "onboarding",
172
+ * input: { customerId: "C-1" },
173
+ * });
174
+ * ```
175
+ */
176
+ export declare function defineJourneyPersistence<TInput, TState>(adapter: JourneyPersistence<TState, TInput>): JourneyPersistence<TState, TInput>;
177
+
178
+ export { EntryInputOf }
179
+
180
+ export { EntryNamesOf }
181
+
182
+ export { EntryTransitions }
183
+
184
+ export { ExitCtx }
185
+
186
+ export { ExitNamesOf }
187
+
188
+ export { ExitOutputOf }
189
+
190
+ export { InstanceId }
191
+
192
+ /**
193
+ * Default shape the journeys plugin emits for each `nav`-carrying journey.
194
+ * When {@link JourneysPluginOptions.buildNavItem} is provided, the plugin
195
+ * hands this default (plus the journey's id and buildInput factory) to the
196
+ * adapter so apps can reshape the item into their narrowed `TNavItem`.
197
+ */
198
+ export declare interface JourneyDefaultNavItem extends NavigationItemBase {
199
+ readonly label: string;
200
+ /**
201
+ * Always empty for a journey launcher — the dispatchable action lives in
202
+ * {@link JourneyDefaultNavItem.action}, so there is no URL to follow. An
203
+ * empty string keeps the structural `NavigationItemBase.to` satisfied
204
+ * without suggesting the shell should treat this item as a link.
205
+ */
206
+ readonly to: "";
207
+ readonly icon?: string | ComponentType<{
208
+ className?: string;
209
+ }>;
210
+ readonly group?: string;
211
+ readonly order?: number;
212
+ readonly hidden?: boolean;
213
+ readonly meta?: unknown;
214
+ readonly action: {
215
+ readonly kind: "journey-start";
216
+ readonly journeyId: string;
217
+ readonly buildInput?: (ctx?: unknown) => unknown;
218
+ };
219
+ }
220
+
221
+ export declare interface JourneyDefinition<TModules extends ModuleTypeMap, TState, TInput = void> {
222
+ readonly id: string;
223
+ readonly version: string;
224
+ readonly meta?: Readonly<Record<string, unknown>>;
225
+ readonly initialState: (input: TInput) => TState;
226
+ readonly start: (state: TState, input: TInput) => StepSpec<TModules>;
227
+ readonly transitions: TransitionMap<TModules, TState>;
228
+ readonly onTransition?: (ev: TransitionEvent_2<TModules, TState>) => void;
229
+ readonly onAbandon?: (ctx: AbandonCtx<TModules, TState>) => TransitionResult<TModules, TState>;
230
+ readonly onComplete?: (ctx: TerminalCtx<TState>, result: unknown) => void;
231
+ readonly onAbort?: (ctx: TerminalCtx<TState>, reason: unknown) => void;
232
+ readonly onHydrate?: (blob: SerializedJourney<TState>) => SerializedJourney<TState>;
233
+ }
234
+
235
+ export { JourneyDefinitionSummary }
236
+
237
+ /**
238
+ * Lightweight token a journey exports so modules and shells can open it
239
+ * with a typed `input` without pulling in the journey's runtime code.
240
+ * Structurally identical to `JourneyHandleRef` in `@modular-react/core` —
241
+ * re-exported here so authors have a single canonical name to import.
242
+ *
243
+ * The `__input` field is phantom: it never holds a value at runtime, it
244
+ * only carries the input type for the `start(handle, input)` overload.
245
+ */
246
+ export declare type JourneyHandle<TId extends string = string, TInput = unknown> = JourneyHandleRef<TId, TInput>;
247
+
248
+ export declare class JourneyHydrationError extends Error {
249
+ constructor(message: string, options?: ErrorOptions);
250
+ }
251
+
252
+ export { JourneyInstance }
253
+
254
+ /**
255
+ * Nav contribution attached to a `registerJourney` call. The journeys plugin
256
+ * collects these at manifest time and emits them as navigation items tagged
257
+ * with an `action.kind = "journey-start"` so the shell's navbar dispatcher
258
+ * can start the journey instead of following a URL.
259
+ *
260
+ * `TInput` is the journey's input type — `buildInput` produces that shape
261
+ * from whatever context the dispatcher passes at click time. Keeping the
262
+ * context loosely typed (`unknown`) matches how the journeys plugin surfaces
263
+ * contributions to the framework; apps that want to narrow the context can
264
+ * provide a typed `buildNavItem` adapter (see
265
+ * {@link JourneysPluginOptions}.`buildNavItem`).
266
+ */
267
+ export declare interface JourneyNavContribution<TInput = unknown> {
268
+ /** Display label. Apps that type-narrow labels should reshape via `buildNavItem`. */
269
+ readonly label: string;
270
+ /** Icon — string identifier or React component (matches `NavigationItem.icon`). */
271
+ readonly icon?: string | React.ComponentType<{
272
+ className?: string;
273
+ }>;
274
+ /** Grouping key for the navbar, same semantics as `NavigationItem.group`. */
275
+ readonly group?: string;
276
+ /** Sort order within the group (lower wins). */
277
+ readonly order?: number;
278
+ /** If true, registered but hidden from default navbar rendering. */
279
+ readonly hidden?: boolean;
280
+ /** App-owned metadata, opaque to the library. */
281
+ readonly meta?: unknown;
282
+ /**
283
+ * Optional factory that builds the journey's `input` at click time. The
284
+ * shell's navbar dispatcher calls this with whatever nav context the host
285
+ * provides (workspace id, current selection, etc.) and hands the result
286
+ * back to `runtime.start(handle, input)`. Omit when the journey has no
287
+ * input; pass a pure factory when it does.
288
+ */
289
+ readonly buildInput?: (ctx?: unknown) => TInput;
290
+ }
291
+
292
+ /**
293
+ * Signature for the optional typed adapter that reshapes the plugin's
294
+ * default nav item into the app's narrowed `TNavItem`. The adapter is
295
+ * called once per `nav`-carrying journey at manifest time.
296
+ */
297
+ export declare type JourneyNavItemBuilder<TNavItem extends NavigationItemBase> = (defaults: JourneyDefaultNavItem, raw: JourneyNavContribution<unknown> & {
298
+ readonly journeyId: string;
299
+ }) => TNavItem;
300
+
301
+ /**
302
+ * Renders the current step of a journey instance. Host-agnostic — works in
303
+ * a tab, modal, route element, or plain `<div>`. On unmount while active,
304
+ * the instance is abandoned (deferred by a microtask so React 18/19
305
+ * StrictMode's simulated mount/unmount/mount cycle does not tear the
306
+ * instance down on its first visit).
307
+ */
308
+ export declare function JourneyOutlet(props: JourneyOutletProps): ReactNode;
309
+
310
+ export declare interface JourneyOutletErrorProps {
311
+ readonly moduleId: string;
312
+ readonly error: unknown;
313
+ }
314
+
315
+ export declare interface JourneyOutletNotFoundProps {
316
+ readonly moduleId: string;
317
+ readonly entry: string;
318
+ }
319
+
320
+ export declare interface JourneyOutletProps {
321
+ /**
322
+ * Runtime to drive the outlet against. Optional when a `<JourneyProvider>`
323
+ * is mounted above — the outlet reads the runtime from context in that
324
+ * case. Explicit prop overrides context, so one outlet can reach a
325
+ * different runtime when needed.
326
+ */
327
+ readonly runtime?: JourneyRuntime;
328
+ readonly instanceId: InstanceId;
329
+ /**
330
+ * Module descriptors the outlet resolves step components against.
331
+ * Optional — when omitted, the outlet pulls the descriptors the runtime
332
+ * was constructed with (the common case).
333
+ */
334
+ readonly modules?: Readonly<Record<string, ModuleDescriptor<any, any, any, any>>>;
335
+ readonly loadingFallback?: ReactNode;
336
+ readonly onFinished?: (outcome: TerminalOutcome) => void;
337
+ readonly onStepError?: (err: unknown, ctx: {
338
+ step: JourneyStep;
339
+ }) => JourneyStepErrorPolicy;
340
+ /**
341
+ * Cap on `retry` responses before the outlet falls back to `abort`. The
342
+ * counter increments on every retry from `onStepError` and is never reset,
343
+ * so a step that causes a downstream step to also throw cannot bypass the
344
+ * cap by bumping the step token. Default: 2.
345
+ */
346
+ readonly retryLimit?: number;
347
+ /**
348
+ * Rendered when the current step points at a module/entry that is not
349
+ * registered with the runtime. Defaults to a plain red notice.
350
+ */
351
+ readonly notFoundComponent?: ComponentType<JourneyOutletNotFoundProps>;
352
+ /**
353
+ * Rendered when a step component throws. Defaults to a plain red notice
354
+ * with the error message. Receives the raw error so shells can route it
355
+ * through their own reporting.
356
+ */
357
+ readonly errorComponent?: ComponentType<JourneyOutletErrorProps>;
358
+ }
359
+
360
+ export { JourneyPersistence }
361
+
362
+ /**
363
+ * Provides the journey runtime to descendant `<JourneyOutlet>` nodes, and
364
+ * composes over `<ModuleExitProvider>` so module hosts (`<ModuleTab>`,
365
+ * `<ModuleRoute>`, anything using `useModuleExit`) see the shell's
366
+ * `onModuleExit` dispatcher without needing a second provider.
367
+ *
368
+ * Existing journey consumers do not need to change — `onModuleExit` keeps
369
+ * firing for every module exit emitted outside a journey step.
370
+ */
371
+ export declare function JourneyProvider(props: JourneyProviderProps): ReactNode;
372
+
373
+ export declare interface JourneyProviderProps {
374
+ readonly runtime: JourneyRuntime;
375
+ readonly onModuleExit?: JourneyProviderValue["onModuleExit"];
376
+ readonly children: ReactNode;
377
+ }
378
+
379
+ /**
380
+ * Shell-level context read by `<JourneyOutlet>` so callers don't have to
381
+ * thread `runtime` through every container that hosts a journey.
382
+ *
383
+ * `onModuleExit` is still surfaced here for backward compatibility with
384
+ * consumers that introspect the provider value. The actual dispatch now
385
+ * flows through `<ModuleExitProvider>` from `@modular-react/react`, which
386
+ * `<JourneyProvider>` mounts automatically. Prefer consuming
387
+ * `useModuleExit` / `useModuleExitDispatcher` from the react package
388
+ * directly in new code.
389
+ */
390
+ export declare interface JourneyProviderValue {
391
+ /** Journey runtime — usually `manifest.journeys`. */
392
+ readonly runtime: JourneyRuntime;
393
+ /**
394
+ * Optional fallback invoked by `<ModuleTab>` / `<ModuleRoute>` after any
395
+ * local `onExit` prop has run. Wiring this at the provider level gives a
396
+ * shell global telemetry / tab-close forwarding without threading the
397
+ * callback through every host.
398
+ */
399
+ readonly onModuleExit?: (event: ModuleExitEvent) => void;
400
+ }
401
+
402
+ export declare interface JourneyRegisterOptions<TState = unknown, TInput = unknown> {
403
+ /**
404
+ * Fires after every transition. Registration-level hook runs after the
405
+ * definition-level `onTransition`. Useful for shell telemetry that
406
+ * doesn't belong in journey authoring code.
407
+ */
408
+ onTransition?: (ev: TransitionEvent_2) => void;
409
+ /**
410
+ * Fires when the journey reaches a `{ complete }` transition. Runs after
411
+ * the definition-level `onComplete` (both fire). Use for shell-level
412
+ * completion analytics.
413
+ */
414
+ onComplete?: (ctx: TerminalCtx<TState>, result: unknown) => void;
415
+ /**
416
+ * Fires when the journey aborts — either via a `{ abort }` transition, a
417
+ * thrown transition handler, or `runtime.end(id)`. Runs after the
418
+ * definition-level `onAbort`.
419
+ */
420
+ onAbort?: (ctx: TerminalCtx<TState>, reason: unknown) => void;
421
+ /**
422
+ * Overrides the definition's `onAbandon` when `runtime.end(id)` is called
423
+ * on an active instance. When set, this handler supplies the transition
424
+ * result (typically `{ abort }`). When absent, the definition's handler
425
+ * is used. Use to swap out abandon behaviour for a specific deployment
426
+ * (e.g. a tab-close workflow that completes instead of aborting).
427
+ */
428
+ onAbandon?: (ctx: AbandonCtx<ModuleTypeMap, TState>) => TransitionResult<ModuleTypeMap, TState>;
429
+ /**
430
+ * Layered on top of the definition-level `onHydrate` — runs **after** the
431
+ * definition transforms the blob. Useful for shell-level migration of
432
+ * fields that the journey author doesn't know about (e.g. redacting
433
+ * environment-specific identifiers on load).
434
+ */
435
+ onHydrate?: (blob: SerializedJourney<TState>) => SerializedJourney<TState>;
436
+ /**
437
+ * Fires when a step component throws or a transition handler throws for
438
+ * an instance of this journey. Observation-only — the runtime still
439
+ * aborts / retries according to the outlet's `onStepError` policy.
440
+ */
441
+ onError?: (err: unknown, ctx: {
442
+ step: JourneyStep | null;
443
+ }) => void;
444
+ /**
445
+ * Optional. Without it, journeys live in memory only — every
446
+ * `runtime.start()` mints a fresh instance and nothing is written to
447
+ * storage. Add an adapter when you want reload recovery or idempotent
448
+ * `start` (same input → same `instanceId`).
449
+ *
450
+ * Typed against both `TState` (for `load` / `save` payloads) and the
451
+ * journey's `TInput` (for `keyFor`) — pass a typed adapter built with
452
+ * {@link defineJourneyPersistence} to get end-to-end checking.
453
+ */
454
+ persistence?: JourneyPersistence<TState, TInput>;
455
+ /**
456
+ * Maximum number of entries to keep in `history` (and the matching
457
+ * `rollbackSnapshots`). Oldest entries are dropped once the cap is
458
+ * exceeded. Omit — or pass `0` or a negative number — for unbounded
459
+ * history (the default).
460
+ *
461
+ * **Caveat with `allowBack`.** A cap smaller than the deepest
462
+ * back-enabled chain will silently break `goBack` past the trim
463
+ * point — the rollback snapshot that `goBack` would restore is among
464
+ * the dropped entries. Treat `maxHistory` as "prune old entries that
465
+ * no one will ever navigate back to" rather than a hard window on
466
+ * `goBack` distance. If an app needs both back-navigation and a tight
467
+ * cap, size the cap to at least the longest user-reachable back chain.
468
+ */
469
+ maxHistory?: number;
470
+ /**
471
+ * Optional nav contribution. When set, the journeys plugin emits a
472
+ * navigation item for this journey so pure launchers don't need a
473
+ * shadow module to host them. The contributed item is tagged with
474
+ * `action: { kind: "journey-start", journeyId, buildInput }`; the
475
+ * shell's navbar dispatcher starts the journey on click.
476
+ *
477
+ * Typed against the journey's `TInput` so `buildInput` returns the
478
+ * right shape end-to-end. Apps with a narrowed `TNavItem` should also
479
+ * pass a `buildNavItem` adapter on `journeysPlugin` to reshape the
480
+ * default item into the app's narrowed type.
481
+ */
482
+ nav?: JourneyNavContribution<TInput>;
483
+ }
484
+
485
+ export { JourneyRuntime }
486
+
487
+ export declare interface JourneyRuntimeOptions {
488
+ readonly debug?: boolean;
489
+ /**
490
+ * Module descriptors keyed by id — the runtime needs them to resolve
491
+ * `allowBack` mode ('preserve-state' | 'rollback' | false) at goBack time.
492
+ * When omitted, `goBack` falls back to 'preserve-state' for any journey
493
+ * transition that opts in via `allowBack: true`.
494
+ */
495
+ readonly modules?: Readonly<Record<string, ModuleDescriptor<any, any, any, any>>>;
496
+ }
497
+
498
+ /**
499
+ * Creates the journeys plugin. Pass to `registry.use(journeysPlugin())` to
500
+ * enable journey registration and outlet rendering without the runtime
501
+ * packages depending on `@modular-react/journeys` directly.
502
+ *
503
+ * The plugin:
504
+ * - contributes `registerJourney(...)` onto the registry (type-safe)
505
+ * - validates contracts against registered modules at resolve time
506
+ * - produces a `JourneyRuntime` on `manifest.extensions.journeys` (also
507
+ * surfaced as the `manifest.journeys` convenience alias)
508
+ * - wraps the provider stack in `<JourneyProvider runtime={...} />`
509
+ *
510
+ * **Instantiate per registry.** The returned object closes over a
511
+ * journey-registration list; passing the same instance to two
512
+ * `createRegistry()` calls causes them to share that list. Call
513
+ * `journeysPlugin()` once per registry.
514
+ */
515
+ export declare function journeysPlugin<TNavItem extends NavigationItemBase = JourneyDefaultNavItem>(options?: JourneysPluginOptions<TNavItem>): RegistryPlugin<"journeys", JourneysPluginExtension, JourneyRuntime>;
516
+
517
+ /**
518
+ * Methods the journeys plugin contributes to the registry. Registered
519
+ * plugins type-intersect with the base `ModuleRegistry` so shells call
520
+ * `registry.registerJourney(...)` with full type support.
521
+ */
522
+ export declare interface JourneysPluginExtension {
523
+ /**
524
+ * Register a journey definition. The structural shape is validated
525
+ * immediately (missing `id` / `version` / `transitions` etc.);
526
+ * module-level contracts are validated at `resolveManifest()` /
527
+ * `resolve()` time.
528
+ *
529
+ * `options.persistence` is typed against the journey's state, and
530
+ * `options.nav.buildInput` is typed against the journey's input — pass a
531
+ * typed definition and both are checked end-to-end.
532
+ */
533
+ registerJourney<TModules extends ModuleTypeMap, TState, TInput>(definition: JourneyDefinition<TModules, TState, TInput>, options?: JourneyRegisterOptions<TState, TInput>): void;
534
+ }
535
+
536
+ export declare interface JourneysPluginOptions<TNavItem extends NavigationItemBase = JourneyDefaultNavItem> {
537
+ /**
538
+ * Enable verbose transition / rollback logging in the runtime. Defaults to
539
+ * `false`; plugins propagate the registry-level debug flag when set.
540
+ */
541
+ readonly debug?: boolean;
542
+ /**
543
+ * Forwarded onto `<JourneyProvider>` as the shell-wide `onModuleExit`
544
+ * handler. Use it as a default place to close tabs / forward analytics
545
+ * when a module exit isn't consumed by an explicit prop.
546
+ */
547
+ readonly onModuleExit?: (event: {
548
+ readonly moduleId: string;
549
+ readonly entry: string;
550
+ readonly exit: string;
551
+ readonly output: unknown;
552
+ readonly tabId?: string;
553
+ }) => void;
554
+ /**
555
+ * Optional adapter that reshapes the plugin's default nav item into the
556
+ * app's narrowed `TNavItem`. Apps that use a typed `NavigationItem`
557
+ * alias (typed label union, typed action union, typed meta bag) should
558
+ * supply this so contributed items land in `manifest.navigation` with
559
+ * the correct narrowed type. When omitted, the plugin emits items as
560
+ * {@link JourneyDefaultNavItem} and the framework widens them to
561
+ * `TNavItem` at the assembly boundary.
562
+ */
563
+ readonly buildNavItem?: JourneyNavItemBuilder<TNavItem>;
564
+ }
565
+
566
+ export { JourneyStatus }
567
+
568
+ export { JourneyStep }
569
+
570
+ export declare type JourneyStepErrorPolicy = "abort" | "retry" | "ignore";
571
+
572
+ /**
573
+ * Aggregated error thrown when one or more registered journeys reference
574
+ * module ids, entry names, or exit names that do not exist (or that
575
+ * disagree on `allowBack`). Mirrors the style of core's
576
+ * `validateDependencies` — accumulate all issues, throw once.
577
+ */
578
+ export declare class JourneyValidationError extends Error {
579
+ readonly issues: readonly string[];
580
+ constructor(issues: readonly string[]);
581
+ }
582
+
583
+ export { MaybePromise }
584
+
585
+ /**
586
+ * `SyncJourneyPersistence` augmented with test-only inspection helpers
587
+ * (`size`, `entries`, `clear`). Methods are sync (no `MaybePromise`) so
588
+ * tests can `expect(store.load(k)).toEqual(blob)` without awaiting, and
589
+ * the value is still assignable to `JourneyPersistence<TState, TInput>`
590
+ * for `registerJourney({ persistence })`.
591
+ */
592
+ export declare interface MemoryPersistence<TInput, TState> extends SyncJourneyPersistence<TState, TInput> {
593
+ /** Number of entries currently stored. */
594
+ readonly size: () => number;
595
+ /** Snapshot of all `[key, blob]` pairs. Each blob is cloned if cloning is enabled. */
596
+ readonly entries: () => ReadonlyArray<readonly [string, SerializedJourney<TState>]>;
597
+ /** Drop all entries. */
598
+ readonly clear: () => void;
599
+ }
600
+
601
+ export declare interface MemoryPersistenceOptions<TInput, TState> {
602
+ /** Same contract as `WebStoragePersistenceOptions.keyFor`. */
603
+ readonly keyFor: (ctx: {
604
+ journeyId: string;
605
+ input: TInput;
606
+ }) => string;
607
+ /**
608
+ * Optional seed entries. Handy for tests that want the runtime to find a
609
+ * pre-persisted journey on first `start()` without walking through the
610
+ * flow to produce the blob.
611
+ */
612
+ readonly initial?: Iterable<readonly [string, SerializedJourney<TState>]>;
613
+ /**
614
+ * When true (default), stored blobs are deep-cloned on both `save` and
615
+ * `load` so callers mutating the returned object can't corrupt the
616
+ * backing store. Set to `false` to skip the clone in hot test loops
617
+ * where you've verified nobody mutates the blob.
618
+ */
619
+ readonly clone?: boolean;
620
+ }
621
+
622
+ /**
623
+ * Host for a single module instance rendered outside any route — in a tab,
624
+ * modal, or panel. Default exit behavior delegates to the `onExit` callback
625
+ * provided by the shell; the module itself stays journey-unaware.
626
+ */
627
+ export declare function ModuleTab<TInput = unknown>(props: ModuleTabProps<TInput>): ReactNode;
628
+
629
+ /**
630
+ * Exit event fired by a module rendered inside a `<ModuleTab>`.
631
+ *
632
+ * Alias for {@link ModuleExitEvent} from `@modular-react/react` — kept as a
633
+ * named export for the workspace-tab entry point so existing imports keep
634
+ * compiling. Both types have the same shape.
635
+ */
636
+ export declare type ModuleTabExitEvent = ModuleExitEvent;
637
+
638
+ export declare interface ModuleTabProps<TInput = unknown> {
639
+ /** Full module descriptor — the shell looks this up by id. */
640
+ readonly module: ModuleDescriptor<any, any, any, any>;
641
+ /**
642
+ * Entry point name on the module. If omitted and the module exposes
643
+ * exactly one entry, that entry is used automatically. If the module
644
+ * exposes several entries, the name must be supplied — passing an
645
+ * unknown name renders an error notice. If `entry` is omitted and the
646
+ * module has no entry points, the component falls back to the legacy
647
+ * `component` field; passing `entry` to such a module instead renders
648
+ * the error notice so misconfiguration is surfaced.
649
+ */
650
+ readonly entry?: string;
651
+ readonly input?: TInput;
652
+ /** Opaque tab id threaded through to `onExit` for the shell to close it. */
653
+ readonly tabId?: string;
654
+ /**
655
+ * Called when the module emits an exit. Runs *before* the provider's
656
+ * global `onExit` dispatcher (via `<ModuleExitProvider>`, typically
657
+ * composed under `<JourneyProvider>`), so the shell can close the tab
658
+ * first and let the provider hook forward to analytics / routing.
659
+ */
660
+ readonly onExit?: (event: ModuleTabExitEvent) => void;
661
+ }
662
+
663
+ export { ModuleTypeMap }
664
+
665
+ /** Internal registration record — definition + options kept together. */
666
+ export declare interface RegisteredJourney<TState = unknown, TInput = unknown> {
667
+ readonly definition: AnyJourneyDefinition;
668
+ readonly options: JourneyRegisterOptions<TState, TInput> | undefined;
669
+ }
670
+
671
+ export { SerializedJourney }
672
+
673
+ export { StepSpec }
674
+
675
+ /**
676
+ * Narrowed variant of {@link JourneyPersistence} whose methods are
677
+ * guaranteed synchronous — `load` returns `SerializedJourney<TState> | null`
678
+ * (not `MaybePromise<…>`), `save`/`remove` return `void` (not
679
+ * `MaybePromise<void>`). Stock adapters return this shape so direct
680
+ * `.load(key)` callers don't need to discriminate sync vs async or cast
681
+ * away the promise half of the union.
682
+ *
683
+ * Structurally assignable to `JourneyPersistence<TState, TInput>`, so the
684
+ * value can still be passed to `registerJourney({ persistence })` without
685
+ * widening.
686
+ */
687
+ export declare interface SyncJourneyPersistence<TState, TInput = unknown> {
688
+ readonly keyFor: (ctx: {
689
+ journeyId: string;
690
+ input: TInput;
691
+ }) => string;
692
+ readonly load: (key: string) => SerializedJourney<TState> | null;
693
+ readonly save: (key: string, blob: SerializedJourney<TState>) => void;
694
+ readonly remove: (key: string) => void;
695
+ }
696
+
697
+ export { TerminalCtx }
698
+
699
+ export { TerminalOutcome }
700
+
701
+ export { TransitionEvent_2 as TransitionEvent }
702
+
703
+ export { TransitionMap }
704
+
705
+ export { TransitionResult }
706
+
707
+ /**
708
+ * Thrown when `runtime.start()` / `runtime.hydrate()` is called with a
709
+ * journey id that is not registered. Distinct class so shells can
710
+ * discriminate "this journey is gone after an upgrade, drop the tab"
711
+ * from transient or validation failures.
712
+ */
713
+ export declare class UnknownJourneyError extends Error {
714
+ readonly journeyId: string;
715
+ constructor(journeyId: string, registered: readonly string[]);
716
+ }
717
+
718
+ /** Read the current provider value, or `null` when none is mounted. */
719
+ export declare function useJourneyContext(): JourneyProviderValue | null;
720
+
721
+ export declare function validateJourneyContracts(journeys: readonly RegisteredJourney[], modules: readonly ModuleDescriptor<any, any, any, any>[]): void;
722
+
723
+ /**
724
+ * Shallow sanity check on a journey definition's own shape. Use this for
725
+ * authoring ergonomics; structural contract checks live in
726
+ * {@link validateJourneyContracts}.
727
+ */
728
+ export declare function validateJourneyDefinition(def: AnyJourneyDefinition): readonly string[];
729
+
730
+ export declare interface WebStoragePersistenceOptions<TInput> {
731
+ /**
732
+ * Compute the persistence key from the journey id and starting input.
733
+ * Must be deterministic — `runtime.start()` probes this key to find an
734
+ * existing instance and achieve idempotency.
735
+ */
736
+ readonly keyFor: (ctx: {
737
+ journeyId: string;
738
+ input: TInput;
739
+ }) => string;
740
+ /**
741
+ * The `Storage` instance to read from and write to. Accepts either a
742
+ * direct reference or a lazy getter; the getter is invoked on every
743
+ * call, which keeps SSR safe (the default returns `null` when
744
+ * `localStorage` is not defined on the global).
745
+ *
746
+ * Defaults to `globalThis.localStorage` (or `null` under SSR) when
747
+ * omitted or explicitly `undefined`. Pass `sessionStorage` for
748
+ * tab-scoped persistence, `null` to force the SSR no-op path, or any
749
+ * `Storage`-shaped stub for custom backends.
750
+ */
751
+ readonly storage?: Storage | null | (() => Storage | null);
752
+ }
753
+
754
+ export { }