@daltonr/pathwrite-core 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +158 -7
- package/dist/index.js +297 -91
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +404 -101
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,27 @@ export type PathData = Record<string, unknown>;
|
|
|
9
9
|
* ```
|
|
10
10
|
*/
|
|
11
11
|
export type FieldErrors = Record<string, string | undefined>;
|
|
12
|
+
/**
|
|
13
|
+
* The return type of a `canMoveNext` or `canMovePrevious` guard.
|
|
14
|
+
*
|
|
15
|
+
* - `true` — allow navigation
|
|
16
|
+
* - `{ allowed: false }` — block silently (no message)
|
|
17
|
+
* - `{ allowed: false, reason: "..." }` — block with a message; the shell surfaces
|
|
18
|
+
* this as `snapshot.blockingError` between the step content and the nav buttons
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* canMoveNext: async ({ data }) => {
|
|
23
|
+
* const result = await checkEligibility(data.applicantId);
|
|
24
|
+
* if (!result.eligible) return { allowed: false, reason: result.reason };
|
|
25
|
+
* return true;
|
|
26
|
+
* }
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export type GuardResult = true | {
|
|
30
|
+
allowed: false;
|
|
31
|
+
reason?: string;
|
|
32
|
+
};
|
|
12
33
|
export interface SerializedPathState {
|
|
13
34
|
version: 1;
|
|
14
35
|
pathId: string;
|
|
@@ -27,7 +48,7 @@ export interface SerializedPathState {
|
|
|
27
48
|
stepEntryData?: PathData;
|
|
28
49
|
stepEnteredAt?: number;
|
|
29
50
|
}>;
|
|
30
|
-
|
|
51
|
+
_status: PathStatus;
|
|
31
52
|
}
|
|
32
53
|
/**
|
|
33
54
|
* The interface every path state store must implement.
|
|
@@ -99,8 +120,8 @@ export interface PathStep<TData extends PathData = PathData> {
|
|
|
99
120
|
title?: string;
|
|
100
121
|
meta?: Record<string, unknown>;
|
|
101
122
|
shouldSkip?: (ctx: PathStepContext<TData>) => boolean | Promise<boolean>;
|
|
102
|
-
canMoveNext?: (ctx: PathStepContext<TData>) =>
|
|
103
|
-
canMovePrevious?: (ctx: PathStepContext<TData>) =>
|
|
123
|
+
canMoveNext?: (ctx: PathStepContext<TData>) => GuardResult | Promise<GuardResult>;
|
|
124
|
+
canMovePrevious?: (ctx: PathStepContext<TData>) => GuardResult | Promise<GuardResult>;
|
|
104
125
|
/**
|
|
105
126
|
* Returns a map of field ID → error message explaining why the step is not
|
|
106
127
|
* yet valid. The shell displays these messages below the step content (labeled
|
|
@@ -176,6 +197,24 @@ export interface PathDefinition<TData extends PathData = PathData> {
|
|
|
176
197
|
onCancel?: (data: TData) => void | Promise<void>;
|
|
177
198
|
}
|
|
178
199
|
export type StepStatus = "completed" | "current" | "upcoming";
|
|
200
|
+
/**
|
|
201
|
+
* The engine's current operational state. Exposed as `snapshot.status`.
|
|
202
|
+
*
|
|
203
|
+
* | Status | Meaning |
|
|
204
|
+
* |---------------|--------------------------------------------------------------|
|
|
205
|
+
* | `idle` | On a step, waiting for user input |
|
|
206
|
+
* | `entering` | `onEnter` hook is running |
|
|
207
|
+
* | `validating` | `canMoveNext` / `canMovePrevious` guard is running |
|
|
208
|
+
* | `leaving` | `onLeave` hook is running |
|
|
209
|
+
* | `completing` | `PathDefinition.onComplete` is running |
|
|
210
|
+
* | `error` | An async operation failed — see `snapshot.error` for details |
|
|
211
|
+
*/
|
|
212
|
+
export type PathStatus = "idle" | "entering" | "validating" | "leaving" | "completing" | "error";
|
|
213
|
+
/**
|
|
214
|
+
* The subset of `PathStatus` values that identify which phase was active
|
|
215
|
+
* when an error occurred. Used by `snapshot.error.phase`.
|
|
216
|
+
*/
|
|
217
|
+
export type ErrorPhase = Exclude<PathStatus, "idle" | "error">;
|
|
179
218
|
export interface StepSummary {
|
|
180
219
|
id: string;
|
|
181
220
|
title?: string;
|
|
@@ -231,8 +270,36 @@ export interface PathSnapshot<TData extends PathData = PathData> {
|
|
|
231
270
|
* progress bar above the sub-path's own progress bar.
|
|
232
271
|
*/
|
|
233
272
|
rootProgress?: RootProgress;
|
|
234
|
-
/**
|
|
235
|
-
|
|
273
|
+
/**
|
|
274
|
+
* The engine's current operational state. Use this instead of multiple boolean flags.
|
|
275
|
+
*
|
|
276
|
+
* Common patterns:
|
|
277
|
+
* - `status !== "idle"` — disable all navigation buttons (engine is busy or errored)
|
|
278
|
+
* - `status === "entering"` — show a skeleton/spinner inside the step area
|
|
279
|
+
* - `status === "validating"` — show "Checking…" on the Next button
|
|
280
|
+
* - `status === "error"` — show the retry / suspend error UI
|
|
281
|
+
*/
|
|
282
|
+
status: PathStatus;
|
|
283
|
+
/**
|
|
284
|
+
* Structured error set when an engine-invoked async operation throws. `null` when no error is active.
|
|
285
|
+
*
|
|
286
|
+
* `phase` identifies which operation failed so shells can adapt the message.
|
|
287
|
+
* `retryCount` counts how many times the user has explicitly called `retry()` — starts at 0 on
|
|
288
|
+
* first failure, increments on each retry. Use this to escalate from "Try again" to "Come back later".
|
|
289
|
+
*
|
|
290
|
+
* Cleared automatically when navigation succeeds or when `retry()` is called.
|
|
291
|
+
*/
|
|
292
|
+
error: {
|
|
293
|
+
message: string;
|
|
294
|
+
phase: ErrorPhase;
|
|
295
|
+
retryCount: number;
|
|
296
|
+
} | null;
|
|
297
|
+
/**
|
|
298
|
+
* `true` when a `PathStore` is attached via `PathEngineOptions.hasPersistence`.
|
|
299
|
+
* Shells use this to decide whether to promise the user that progress is saved
|
|
300
|
+
* when showing the "come back later" escalation message.
|
|
301
|
+
*/
|
|
302
|
+
hasPersistence: boolean;
|
|
236
303
|
/** Whether the current step's `canMoveNext` guard allows advancing. Async guards default to `true`. Auto-derived as `true` when `fieldErrors` is defined and returns no messages, and `canMoveNext` is not explicitly defined. */
|
|
237
304
|
canMoveNext: boolean;
|
|
238
305
|
/** Whether the current step's `canMovePrevious` guard allows going back. Async guards default to `true`. */
|
|
@@ -284,6 +351,15 @@ export interface PathSnapshot<TData extends PathData = PathData> {
|
|
|
284
351
|
* navigating back to a previously visited step).
|
|
285
352
|
*/
|
|
286
353
|
stepEnteredAt: number;
|
|
354
|
+
/**
|
|
355
|
+
* A guard-level blocking message set when `canMoveNext` returns
|
|
356
|
+
* `{ allowed: false, reason: "..." }`. `null` when there is no blocking message.
|
|
357
|
+
*
|
|
358
|
+
* Distinct from `fieldErrors` (field-attached) and `error` (async crash).
|
|
359
|
+
* Shells render this between the step content and the navigation buttons.
|
|
360
|
+
* Cleared automatically when the user successfully navigates to a new step.
|
|
361
|
+
*/
|
|
362
|
+
blockingError: string | null;
|
|
287
363
|
/**
|
|
288
364
|
* Field-keyed validation messages for the current step. Empty object when there are none.
|
|
289
365
|
* Use in step templates to render inline per-field errors: `snapshot.fieldErrors['email']`.
|
|
@@ -303,7 +379,7 @@ export interface PathSnapshot<TData extends PathData = PathData> {
|
|
|
303
379
|
/**
|
|
304
380
|
* Identifies the public method that triggered a `stateChanged` event.
|
|
305
381
|
*/
|
|
306
|
-
export type StateChangeCause = "start" | "next" | "previous" | "goToStep" | "goToStepChecked" | "setData" | "resetStep" | "cancel" | "restart";
|
|
382
|
+
export type StateChangeCause = "start" | "next" | "previous" | "goToStep" | "goToStepChecked" | "setData" | "resetStep" | "cancel" | "restart" | "retry" | "suspend";
|
|
307
383
|
export type PathEvent = {
|
|
308
384
|
type: "stateChanged";
|
|
309
385
|
cause: StateChangeCause;
|
|
@@ -316,6 +392,10 @@ export type PathEvent = {
|
|
|
316
392
|
type: "cancelled";
|
|
317
393
|
pathId: string;
|
|
318
394
|
data: PathData;
|
|
395
|
+
} | {
|
|
396
|
+
type: "suspended";
|
|
397
|
+
pathId: string;
|
|
398
|
+
data: PathData;
|
|
319
399
|
} | {
|
|
320
400
|
type: "resumed";
|
|
321
401
|
resumedPathId: string;
|
|
@@ -372,17 +452,48 @@ export interface PathEngineOptions {
|
|
|
372
452
|
* listeners use `engine.subscribe()`.
|
|
373
453
|
*/
|
|
374
454
|
observers?: PathObserver[];
|
|
455
|
+
/**
|
|
456
|
+
* Set to `true` when a `PathStore` is attached and will persist path state.
|
|
457
|
+
* Exposed as `snapshot.hasPersistence` so shells can honestly tell the user
|
|
458
|
+
* their progress is saved when showing the "come back later" escalation message.
|
|
459
|
+
*/
|
|
460
|
+
hasPersistence?: boolean;
|
|
375
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* Converts a camelCase or lowercase field key to a display label.
|
|
464
|
+
* `"firstName"` → `"First Name"`, `"email"` → `"Email"`.
|
|
465
|
+
* Used by shells to render labeled field-error summaries.
|
|
466
|
+
*/
|
|
467
|
+
export declare function formatFieldKey(key: string): string;
|
|
468
|
+
/**
|
|
469
|
+
* Returns a human-readable description of which operation failed, keyed by
|
|
470
|
+
* the `ErrorPhase` value on `snapshot.error.phase`. Used by shells to render
|
|
471
|
+
* the error panel message.
|
|
472
|
+
*/
|
|
473
|
+
export declare function errorPhaseMessage(phase: string): string;
|
|
376
474
|
export declare class PathEngine {
|
|
377
475
|
private activePath;
|
|
378
476
|
private readonly pathStack;
|
|
379
477
|
private readonly listeners;
|
|
380
|
-
private
|
|
478
|
+
private _status;
|
|
381
479
|
/** True after the user has called next() on the current step at least once. Resets on step entry. */
|
|
382
480
|
private _hasAttemptedNext;
|
|
481
|
+
/** Blocking message from canMoveNext returning { allowed: false, reason }. Cleared on step entry. */
|
|
482
|
+
private _blockingError;
|
|
383
483
|
/** The path and initial data from the most recent top-level start() call. Used by restart(). */
|
|
384
484
|
private _rootPath;
|
|
385
485
|
private _rootInitialData;
|
|
486
|
+
/** Structured error from the most recent failed async operation. Null when no error is active. */
|
|
487
|
+
private _error;
|
|
488
|
+
/** Stored retry function. Null when no error is pending. */
|
|
489
|
+
private _pendingRetry;
|
|
490
|
+
/**
|
|
491
|
+
* Counts how many times `retry()` has been called for the current error sequence.
|
|
492
|
+
* Reset to 0 by `next()` (fresh navigation). Incremented by `retry()`.
|
|
493
|
+
*/
|
|
494
|
+
private _retryCount;
|
|
495
|
+
private _hasPersistence;
|
|
496
|
+
private _hasWarnedAsyncShouldSkip;
|
|
386
497
|
constructor(options?: PathEngineOptions);
|
|
387
498
|
/**
|
|
388
499
|
* Restores a PathEngine from previously exported state.
|
|
@@ -428,6 +539,29 @@ export declare class PathEngine {
|
|
|
428
539
|
startSubPath(path: PathDefinition<any>, initialData?: PathData, meta?: Record<string, unknown>): Promise<void>;
|
|
429
540
|
next(): Promise<void>;
|
|
430
541
|
previous(): Promise<void>;
|
|
542
|
+
/**
|
|
543
|
+
* Re-runs the operation that caused the most recent `snapshot.error`.
|
|
544
|
+
* Increments `snapshot.error.retryCount` so shells can escalate from
|
|
545
|
+
* "Try again" to "Come back later" after repeated failures.
|
|
546
|
+
*
|
|
547
|
+
* No-op if there is no pending error or if navigation is in progress.
|
|
548
|
+
*/
|
|
549
|
+
retry(): Promise<void>;
|
|
550
|
+
/**
|
|
551
|
+
* Pauses the path with intent to return. Preserves all state and data.
|
|
552
|
+
*
|
|
553
|
+
* - Clears any active error state
|
|
554
|
+
* - Emits a `suspended` event that the application can listen for to dismiss
|
|
555
|
+
* the wizard UI (close a modal, navigate away, etc.)
|
|
556
|
+
* - The engine remains in its current state — call `start()` / `restoreOrStart()`
|
|
557
|
+
* to resume when the user returns
|
|
558
|
+
*
|
|
559
|
+
* Use in the "Come back later" escalation path when `snapshot.error.retryCount`
|
|
560
|
+
* has crossed `retryThreshold`. The `suspended` event signals the app to dismiss
|
|
561
|
+
* the UI; Pathwrite's persistence layer handles saving progress automatically via
|
|
562
|
+
* the configured store and observer strategy.
|
|
563
|
+
*/
|
|
564
|
+
suspend(): Promise<void>;
|
|
431
565
|
/** Cancel is synchronous for top-level paths (no hooks). Sub-path cancellation
|
|
432
566
|
* is async when an `onSubPathCancel` hook is present. Returns a Promise for
|
|
433
567
|
* API consistency. */
|
|
@@ -473,6 +607,22 @@ export declare class PathEngine {
|
|
|
473
607
|
private _goToStepCheckedAsync;
|
|
474
608
|
private _cancelSubPathAsync;
|
|
475
609
|
private finishActivePath;
|
|
610
|
+
/**
|
|
611
|
+
* Wraps `finishActivePath` with error handling for the `completing` phase.
|
|
612
|
+
* On failure: sets `_error`, stores a retry that re-calls `finishActivePath`,
|
|
613
|
+
* resets status to `"error"`, and emits `stateChanged`.
|
|
614
|
+
* On success: resets status to `"idle"` (finishActivePath sets activePath = null,
|
|
615
|
+
* so no stateChanged is needed — the `completed` event is the terminal signal).
|
|
616
|
+
*/
|
|
617
|
+
private _finishActivePathWithErrorHandling;
|
|
618
|
+
/**
|
|
619
|
+
* Wraps `enterCurrentStep` with error handling for the `entering` phase.
|
|
620
|
+
* Called by both `_startAsync` and `_nextAsync` after advancing to a new step.
|
|
621
|
+
* On failure: sets `_error`, stores a retry that re-calls this method,
|
|
622
|
+
* resets status to `"error"`, and emits `stateChanged` with the given `cause`.
|
|
623
|
+
*/
|
|
624
|
+
private _enterCurrentStepWithErrorHandling;
|
|
625
|
+
private static errorMessage;
|
|
476
626
|
private requireActivePath;
|
|
477
627
|
private assertPathHasSteps;
|
|
478
628
|
private emit;
|
|
@@ -497,6 +647,7 @@ export declare class PathEngine {
|
|
|
497
647
|
private leaveCurrentStep;
|
|
498
648
|
private canMoveNext;
|
|
499
649
|
private canMovePrevious;
|
|
650
|
+
private static normaliseGuardResult;
|
|
500
651
|
/**
|
|
501
652
|
* Evaluates a guard function synchronously for inclusion in the snapshot.
|
|
502
653
|
* If the guard is absent, returns `true`.
|