@secmia/openui-flow 4.0.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +24 -0
- package/README.md +188 -1
- package/dist/index.d.mts +141 -8
- package/dist/index.d.ts +141 -8
- package/dist/index.js +590 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +588 -146
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/AGENTS.md
CHANGED
|
@@ -30,14 +30,23 @@ Core evaluation APIs:
|
|
|
30
30
|
- createRequirementGraph(...)
|
|
31
31
|
- createDefaultRequirementGraph(...)
|
|
32
32
|
|
|
33
|
+
Graph safety guarantees:
|
|
34
|
+
|
|
35
|
+
- Dependency-aware topological ordering
|
|
36
|
+
- Deterministic tie-breaking for equal priority nodes
|
|
37
|
+
- Cycle and invalid dependency detection at graph creation
|
|
38
|
+
|
|
33
39
|
## Public API Surface (Current)
|
|
34
40
|
|
|
35
41
|
Primary component and types:
|
|
36
42
|
|
|
37
43
|
- AdaptiveFlow
|
|
44
|
+
- useAdaptiveFlow
|
|
38
45
|
- AdaptiveFlowProps
|
|
39
46
|
- AdaptiveFlowAdapter
|
|
40
47
|
- AdaptiveFlowValidators
|
|
48
|
+
- AdaptiveFlowSchemas
|
|
49
|
+
- AdaptiveFlowFieldErrors
|
|
41
50
|
- AdaptiveFlowPersistence
|
|
42
51
|
- AdaptiveFlowStyles
|
|
43
52
|
- AdaptiveFlowClassNames
|
|
@@ -67,6 +76,7 @@ Level 1: style/class slot overrides
|
|
|
67
76
|
|
|
68
77
|
- Use styles and classNames
|
|
69
78
|
- Use unstyled to disable built-in inline style defaults
|
|
79
|
+
- Configure default OAuth button list with oauthProviders
|
|
70
80
|
|
|
71
81
|
Level 2: per-step custom rendering
|
|
72
82
|
|
|
@@ -111,12 +121,26 @@ Persistence:
|
|
|
111
121
|
- Configure via persistence prop
|
|
112
122
|
- Supports session, local, or custom storage driver
|
|
113
123
|
- Use versioned keys (example: flow-state:v1)
|
|
124
|
+
- Use persistence.onError to capture read/write/clear failures
|
|
125
|
+
|
|
126
|
+
Retry policy:
|
|
127
|
+
|
|
128
|
+
- Configure via retryPolicy prop
|
|
129
|
+
- Supports maxAttempts, initialDelayMs, factor, maxDelayMs, jitter, delay, and shouldRetry
|
|
130
|
+
- Adapter calls use retryPolicy backoff; validation errors are not retried unless explicitly allowed
|
|
131
|
+
|
|
132
|
+
Context patching:
|
|
133
|
+
|
|
134
|
+
- mergeContext now recursively merges plain nested objects
|
|
135
|
+
- Arrays are replaced, not merged
|
|
114
136
|
|
|
115
137
|
Validation:
|
|
116
138
|
|
|
117
139
|
- Configure via validators prop
|
|
118
140
|
- Supports email, otp, password, profile, tos validators
|
|
119
141
|
- Validators may be sync or async
|
|
142
|
+
- Validators can return string or { field, message } for field-level error mapping
|
|
143
|
+
- Optional schemas prop supports schema objects with safeParse/parse (Zod-friendly)
|
|
120
144
|
|
|
121
145
|
## Recommended Agent Workflow
|
|
122
146
|
|
package/README.md
CHANGED
|
@@ -422,6 +422,18 @@ Keep built-in UI structure, but override styles/classes for all slots.
|
|
|
422
422
|
/>
|
|
423
423
|
```
|
|
424
424
|
|
|
425
|
+
Configure OAuth button list in default footer:
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
<AdaptiveFlow
|
|
429
|
+
oauthProviders={[
|
|
430
|
+
{ id: 'google', label: 'Continue with Google' },
|
|
431
|
+
{ id: 'github', label: 'Continue with GitHub' },
|
|
432
|
+
{ id: 'microsoft', label: 'Continue with Microsoft' },
|
|
433
|
+
]}
|
|
434
|
+
/>
|
|
435
|
+
```
|
|
436
|
+
|
|
425
437
|
Use `unstyled` to disable built-in inline defaults.
|
|
426
438
|
|
|
427
439
|
```tsx
|
|
@@ -462,6 +474,14 @@ Use `stepRegistry` to replace selected steps with your own components while leav
|
|
|
462
474
|
/>
|
|
463
475
|
```
|
|
464
476
|
|
|
477
|
+
`run(job)` semantics in custom renderers:
|
|
478
|
+
- sets `busy=true` during the async job
|
|
479
|
+
- clears previous global/field errors before execution
|
|
480
|
+
- catches and normalizes thrown errors into flow error state
|
|
481
|
+
- reverts `busy=false` on completion
|
|
482
|
+
|
|
483
|
+
Use it for custom step actions to keep consistent loading/error behavior.
|
|
484
|
+
|
|
465
485
|
### Level 3: custom renderer for all steps
|
|
466
486
|
|
|
467
487
|
Use `renderStep` for full step-body ownership. You can still reuse `defaultView` when useful.
|
|
@@ -489,7 +509,97 @@ Use `renderStep` for full step-body ownership. You can still reuse `defaultView`
|
|
|
489
509
|
|
|
490
510
|
### Level 4: fully custom UI for everything (headless mode)
|
|
491
511
|
|
|
492
|
-
If you want total control over shell, header, footer, button layout, and every interaction, use engine APIs directly.
|
|
512
|
+
If you want total control over shell, header, footer, button layout, and every interaction, use `useAdaptiveFlow` (or engine APIs directly).
|
|
513
|
+
|
|
514
|
+
### Headless hook (`useAdaptiveFlow`)
|
|
515
|
+
|
|
516
|
+
`useAdaptiveFlow` exposes the flow lifecycle without rendering any UI.
|
|
517
|
+
|
|
518
|
+
```tsx
|
|
519
|
+
'use client';
|
|
520
|
+
|
|
521
|
+
import { useAdaptiveFlow, type AdaptiveContextBase } from '@secmia/openui-flow';
|
|
522
|
+
|
|
523
|
+
type Ctx = AdaptiveContextBase & {
|
|
524
|
+
selectedPlan: 'starter' | 'pro' | null;
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
export default function FullyCustomFlow() {
|
|
528
|
+
const {
|
|
529
|
+
step,
|
|
530
|
+
context,
|
|
531
|
+
missingRequirements,
|
|
532
|
+
busy,
|
|
533
|
+
fieldErrors,
|
|
534
|
+
handleEmail,
|
|
535
|
+
handleOtp,
|
|
536
|
+
handlePassword,
|
|
537
|
+
handleProfile,
|
|
538
|
+
handleTos,
|
|
539
|
+
setContextPatch,
|
|
540
|
+
} = useAdaptiveFlow<Ctx>({
|
|
541
|
+
requirements: ['has_email', 'email_verified', 'has_password', 'has_first_name', 'accepted_tos', 'selected_plan'],
|
|
542
|
+
completeStep: 'COMPLETE',
|
|
543
|
+
initialValue: { selectedPlan: null },
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
return (
|
|
547
|
+
<div>
|
|
548
|
+
<h2>Step: {step}</h2>
|
|
549
|
+
<p>Pending: {missingRequirements.length}</p>
|
|
550
|
+
{fieldErrors.email ? <p>{fieldErrors.email}</p> : null}
|
|
551
|
+
|
|
552
|
+
<button disabled={busy} onClick={() => handleEmail('user@company.com')}>Submit Email</button>
|
|
553
|
+
<button disabled={busy} onClick={() => handleOtp('123456')}>Submit OTP</button>
|
|
554
|
+
<button disabled={busy} onClick={() => handlePassword('secure-password')}>Submit Password</button>
|
|
555
|
+
<button disabled={busy} onClick={() => handleProfile({ firstName: 'A', lastName: 'B', jobTitle: null })}>Save Profile</button>
|
|
556
|
+
<button disabled={busy} onClick={() => handleTos()}>Accept TOS</button>
|
|
557
|
+
<button onClick={() => setContextPatch({ selectedPlan: 'pro' })}>Select Plan</button>
|
|
558
|
+
<pre>{JSON.stringify(context, null, 2)}</pre>
|
|
559
|
+
</div>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
You can still bypass the hook and use pure engine APIs if you need lower-level control.
|
|
565
|
+
|
|
566
|
+
### Optional schema validation (Zod-friendly)
|
|
567
|
+
|
|
568
|
+
`AdaptiveFlow` and `useAdaptiveFlow` accept a `schemas` object. Any schema with `safeParse` or `parse` is supported (including Zod schemas).
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
import { z } from 'zod';
|
|
572
|
+
|
|
573
|
+
const emailSchema = z.string().email();
|
|
574
|
+
const profileSchema = z.object({
|
|
575
|
+
firstName: z.string().min(1),
|
|
576
|
+
lastName: z.string().min(1),
|
|
577
|
+
jobTitle: z.string().nullable(),
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
<AdaptiveFlow
|
|
581
|
+
schemas={{
|
|
582
|
+
email: emailSchema,
|
|
583
|
+
profile: profileSchema,
|
|
584
|
+
}}
|
|
585
|
+
/>
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Field-level validation errors
|
|
589
|
+
|
|
590
|
+
Validators can return either a string or `{ field, message }`.
|
|
591
|
+
|
|
592
|
+
```ts
|
|
593
|
+
const validators = {
|
|
594
|
+
password: (value: string) => {
|
|
595
|
+
if (value.length < 12) {
|
|
596
|
+
return { field: 'password', message: 'Password must be at least 12 characters.' };
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
Default UI now binds field errors to the matching input and sets `aria-invalid` automatically.
|
|
493
603
|
|
|
494
604
|
```tsx
|
|
495
605
|
'use client';
|
|
@@ -735,6 +845,11 @@ const missing = await getMissingRequirements(context, graph);
|
|
|
735
845
|
- Step: current UI state for unmet requirement.
|
|
736
846
|
- RequirementGraph: graph nodes with optional priority, condition, and dependency metadata.
|
|
737
847
|
|
|
848
|
+
Graph behavior:
|
|
849
|
+
- Dependency-aware ordering uses topological sorting.
|
|
850
|
+
- Priority is used as tie-breaker among currently available nodes.
|
|
851
|
+
- Circular dependencies and unknown dependencies are rejected during graph creation.
|
|
852
|
+
|
|
738
853
|
Evaluation loop:
|
|
739
854
|
1. Build graph.
|
|
740
855
|
2. Evaluate active nodes.
|
|
@@ -749,6 +864,7 @@ Evaluation loop:
|
|
|
749
864
|
|
|
750
865
|
Primary exports:
|
|
751
866
|
- `AdaptiveFlow`
|
|
867
|
+
- `useAdaptiveFlow`
|
|
752
868
|
- `defaultRequirements`
|
|
753
869
|
- `initialContext`
|
|
754
870
|
- `defaultRequirementResolvers`
|
|
@@ -762,8 +878,16 @@ Primary exports:
|
|
|
762
878
|
Important types:
|
|
763
879
|
- `AdaptiveFlowProps`
|
|
764
880
|
- `AdaptiveFlowAdapter`
|
|
881
|
+
- `UseAdaptiveFlowOptions`
|
|
882
|
+
- `UseAdaptiveFlowResult`
|
|
765
883
|
- `AdaptiveFlowValidators`
|
|
884
|
+
- `AdaptiveFlowValidationResult`
|
|
885
|
+
- `AdaptiveFlowValidationIssue`
|
|
886
|
+
- `AdaptiveFlowFieldErrors`
|
|
887
|
+
- `AdaptiveFlowSchemas`
|
|
888
|
+
- `AdaptiveFlowSchema`
|
|
766
889
|
- `AdaptiveFlowPersistence`
|
|
890
|
+
- `AdaptiveFlowOAuthProvider`
|
|
767
891
|
- `AdaptiveStepRegistry`
|
|
768
892
|
- `AdaptiveStepRendererArgs`
|
|
769
893
|
- `AdaptiveStepRenderArgs`
|
|
@@ -776,6 +900,49 @@ Important types:
|
|
|
776
900
|
|
|
777
901
|
---
|
|
778
902
|
|
|
903
|
+
## SSR and hydration notes
|
|
904
|
+
|
|
905
|
+
- Storage persistence only runs in the browser (`window` guard).
|
|
906
|
+
- On SSR, the flow starts from `initialValue`/defaults and then hydrates persisted browser state on mount.
|
|
907
|
+
- For App Router and other SSR setups, pass server-known session hints in `initialValue` to minimize client-side step flash.
|
|
908
|
+
|
|
909
|
+
Example:
|
|
910
|
+
|
|
911
|
+
```tsx
|
|
912
|
+
<AdaptiveFlow
|
|
913
|
+
initialValue={{
|
|
914
|
+
email: user.email ?? null,
|
|
915
|
+
isVerified: Boolean(user.emailVerifiedAt),
|
|
916
|
+
hasPassword: Boolean(user.hasPassword),
|
|
917
|
+
}}
|
|
918
|
+
persistence={{ key: 'flow-state:v1', storage: 'session' }}
|
|
919
|
+
/>
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
### Retry policy
|
|
923
|
+
|
|
924
|
+
Use `retryPolicy` to retry transient adapter failures with configurable backoff.
|
|
925
|
+
|
|
926
|
+
```tsx
|
|
927
|
+
<AdaptiveFlow
|
|
928
|
+
retryPolicy={{
|
|
929
|
+
maxAttempts: 4,
|
|
930
|
+
initialDelayMs: 200,
|
|
931
|
+
factor: 2,
|
|
932
|
+
maxDelayMs: 2000,
|
|
933
|
+
jitter: true,
|
|
934
|
+
shouldRetry: (error) => error.name !== 'FlowValidationError',
|
|
935
|
+
}}
|
|
936
|
+
/>
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
Default behavior:
|
|
940
|
+
- adapter calls are retried with exponential backoff
|
|
941
|
+
- non-adapter validation errors are not retried unless `shouldRetry` allows them
|
|
942
|
+
- retries use the browser/Node timer API, so no extra dependency is required
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
779
946
|
## Release checklist
|
|
780
947
|
|
|
781
948
|
- Ensure adapter methods return clear, user-safe errors.
|
|
@@ -801,6 +968,26 @@ OAuth return does not resume:
|
|
|
801
968
|
- Cause: missing `completeOAuth` or persistence disabled.
|
|
802
969
|
- Fix: implement `completeOAuth` and enable persistence.
|
|
803
970
|
|
|
971
|
+
Persistence write/read failures:
|
|
972
|
+
- Cause: storage quota, blocked storage, malformed persisted payload.
|
|
973
|
+
- Fix: pass `persistence.onError` to capture and report storage failures.
|
|
974
|
+
|
|
975
|
+
Custom nested context fields collapsing after patch:
|
|
976
|
+
- Cause: patching with a shallow object update.
|
|
977
|
+
- Fix: the flow now performs a recursive object merge for plain nested objects. Arrays are replaced, not merged.
|
|
978
|
+
|
|
979
|
+
```tsx
|
|
980
|
+
<AdaptiveFlow
|
|
981
|
+
persistence={{
|
|
982
|
+
key: 'flow-state:v1',
|
|
983
|
+
storage: 'local',
|
|
984
|
+
onError: (error, phase) => {
|
|
985
|
+
console.error('persistence failure', phase, error);
|
|
986
|
+
},
|
|
987
|
+
}}
|
|
988
|
+
/>
|
|
989
|
+
```
|
|
990
|
+
|
|
804
991
|
---
|
|
805
992
|
|
|
806
993
|
## License
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Built-in requirement keys that cover the default flow states supported by the package.
|
|
6
|
+
*/
|
|
4
7
|
declare const DefaultAppRequirements: {
|
|
5
8
|
readonly HAS_EMAIL: "has_email";
|
|
6
9
|
readonly EMAIL_VERIFIED: "email_verified";
|
|
@@ -10,6 +13,9 @@ declare const DefaultAppRequirements: {
|
|
|
10
13
|
readonly ACCEPTED_TOS: "accepted_tos";
|
|
11
14
|
readonly HAS_JOB_TITLE: "has_job_title";
|
|
12
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Built-in step keys used by the default flow UI and default requirement resolvers.
|
|
18
|
+
*/
|
|
13
19
|
declare const DefaultAdaptiveSteps: {
|
|
14
20
|
readonly COLLECT_EMAIL: "COLLECT_EMAIL";
|
|
15
21
|
readonly VERIFY_OTP: "VERIFY_OTP";
|
|
@@ -18,10 +24,15 @@ declare const DefaultAdaptiveSteps: {
|
|
|
18
24
|
readonly COLLECT_TOS: "COLLECT_TOS";
|
|
19
25
|
readonly COMPLETE: "COMPLETE";
|
|
20
26
|
};
|
|
27
|
+
/** Requirement key from the built-in default requirement set. */
|
|
21
28
|
type DefaultAppRequirement = (typeof DefaultAppRequirements)[keyof typeof DefaultAppRequirements];
|
|
29
|
+
/** Step key from the built-in default step set. */
|
|
22
30
|
type DefaultAdaptiveStep = (typeof DefaultAdaptiveSteps)[keyof typeof DefaultAdaptiveSteps];
|
|
31
|
+
/** Requirement key used by the engine, including custom app-specific requirements. */
|
|
23
32
|
type AppRequirement = DefaultAppRequirement | (string & {});
|
|
33
|
+
/** Step key used by the engine, including custom app-specific steps. */
|
|
24
34
|
type AdaptiveStep = DefaultAdaptiveStep | (string & {});
|
|
35
|
+
/** Base context shape managed by the default flow helpers and UI. */
|
|
25
36
|
type AdaptiveContextBase = {
|
|
26
37
|
email: string | null;
|
|
27
38
|
isVerified: boolean;
|
|
@@ -33,14 +44,23 @@ type AdaptiveContextBase = {
|
|
|
33
44
|
};
|
|
34
45
|
agreedToTos: boolean;
|
|
35
46
|
};
|
|
47
|
+
/** Alias for the default context shape used by the built-in component and hook. */
|
|
36
48
|
type AdaptiveContext = AdaptiveContextBase;
|
|
49
|
+
/** Default set of requirements used when a flow does not provide its own list. */
|
|
37
50
|
declare const defaultRequirements: DefaultAppRequirement[];
|
|
51
|
+
/** Default initial context used by the component and hook when no initialValue is provided. */
|
|
38
52
|
declare const initialContext: AdaptiveContextBase;
|
|
53
|
+
/** Utility type for APIs that may return either a synchronous or asynchronous value. */
|
|
39
54
|
type MaybePromise<T> = T | Promise<T>;
|
|
55
|
+
/** Resolver contract that maps one requirement to the step shown when it is unmet. */
|
|
40
56
|
type RequirementResolver<TContext = AdaptiveContextBase, TStep extends string = AdaptiveStep> = {
|
|
41
57
|
isMet: (context: TContext) => MaybePromise<boolean>;
|
|
42
58
|
step: TStep;
|
|
43
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Single requirement node in the requirement graph.
|
|
62
|
+
* The engine evaluates nodes by priority, dependency readiness, and custom conditions.
|
|
63
|
+
*/
|
|
44
64
|
type RequirementGraphNode<TContext, TRequirement extends string, TStep extends string> = {
|
|
45
65
|
requirement: TRequirement;
|
|
46
66
|
step: TStep;
|
|
@@ -49,13 +69,24 @@ type RequirementGraphNode<TContext, TRequirement extends string, TStep extends s
|
|
|
49
69
|
priority?: number;
|
|
50
70
|
dependsOn?: readonly TRequirement[];
|
|
51
71
|
};
|
|
72
|
+
/** Ordered requirement graph consumed by the engine evaluation helpers. */
|
|
52
73
|
type RequirementGraph<TContext, TRequirement extends string, TStep extends string> = readonly RequirementGraphNode<TContext, TRequirement, TStep>[];
|
|
74
|
+
/** Default requirement-to-step resolvers used by the built-in flow UI. */
|
|
53
75
|
declare const defaultRequirementResolvers: Record<DefaultAppRequirement, RequirementResolver<AdaptiveContextBase, AdaptiveStep>>;
|
|
76
|
+
/**
|
|
77
|
+
* Build a requirement graph from a requirement list, resolver map, and optional graph metadata.
|
|
78
|
+
*
|
|
79
|
+
* The function validates that dependencies refer to known resolver-backed requirements and
|
|
80
|
+
* rejects circular dependency chains at graph creation time.
|
|
81
|
+
*/
|
|
54
82
|
declare function createRequirementGraph<TContext, TRequirement extends string, TStep extends string>(requirements: readonly TRequirement[], resolvers: Partial<Record<TRequirement, RequirementResolver<TContext, TStep>>>, options?: {
|
|
55
83
|
priorities?: Partial<Record<TRequirement, number>>;
|
|
56
84
|
conditions?: Partial<Record<TRequirement, (context: TContext) => MaybePromise<boolean>>>;
|
|
57
85
|
dependencies?: Partial<Record<TRequirement, readonly TRequirement[]>>;
|
|
58
86
|
}): RequirementGraph<TContext, TRequirement, TStep>;
|
|
87
|
+
/**
|
|
88
|
+
* Build a requirement graph using the built-in default resolvers plus any app-specific overrides.
|
|
89
|
+
*/
|
|
59
90
|
declare function createDefaultRequirementGraph<TContext extends AdaptiveContextBase = AdaptiveContextBase, TRequirement extends string = DefaultAppRequirement, TStep extends string = AdaptiveStep>(options?: {
|
|
60
91
|
requirements?: readonly TRequirement[];
|
|
61
92
|
resolvers?: Partial<Record<TRequirement, RequirementResolver<TContext, TStep>>>;
|
|
@@ -63,10 +94,25 @@ declare function createDefaultRequirementGraph<TContext extends AdaptiveContextB
|
|
|
63
94
|
conditions?: Partial<Record<TRequirement, (context: TContext) => MaybePromise<boolean>>>;
|
|
64
95
|
dependencies?: Partial<Record<TRequirement, readonly TRequirement[]>>;
|
|
65
96
|
}): RequirementGraph<TContext, TRequirement, TStep>;
|
|
97
|
+
/**
|
|
98
|
+
* Evaluate the graph against the current context and return the next unmet step.
|
|
99
|
+
*
|
|
100
|
+
* Nodes are evaluated in dependency-aware order with deterministic tie-breaking.
|
|
101
|
+
*/
|
|
66
102
|
declare function evaluateNextStep<TContext, TRequirement extends string, TStep extends string>(context: TContext, graph: RequirementGraph<TContext, TRequirement, TStep>, completeStep: TStep): Promise<TStep>;
|
|
103
|
+
/**
|
|
104
|
+
* Return every unmet requirement for the current context in graph evaluation order.
|
|
105
|
+
*/
|
|
67
106
|
declare function getMissingRequirements<TContext, TRequirement extends string, TStep extends string>(context: TContext, graph: RequirementGraph<TContext, TRequirement, TStep>): Promise<TRequirement[]>;
|
|
68
107
|
|
|
69
|
-
|
|
108
|
+
/** Supported OAuth provider identifiers accepted by the adapter and default UI. */
|
|
109
|
+
type OAuthProvider = "google" | "apple" | (string & {});
|
|
110
|
+
/** Configurable OAuth provider label/id pair used by the default footer buttons. */
|
|
111
|
+
type AdaptiveFlowOAuthProvider = {
|
|
112
|
+
id: OAuthProvider | (string & {});
|
|
113
|
+
label: string;
|
|
114
|
+
};
|
|
115
|
+
/** Result returned by lookupByEmail to seed the flow with backend identity state. */
|
|
70
116
|
type IdentityResolution = {
|
|
71
117
|
accountExists: boolean;
|
|
72
118
|
hasPassword?: boolean;
|
|
@@ -74,6 +120,7 @@ type IdentityResolution = {
|
|
|
74
120
|
agreedToTos?: boolean;
|
|
75
121
|
profile?: Partial<AdaptiveContextBase["profile"]>;
|
|
76
122
|
};
|
|
123
|
+
/** Backend adapter contract used by the flow to delegate all side effects. */
|
|
77
124
|
type AdaptiveFlowAdapter<TContext extends AdaptiveContextBase = AdaptiveContextBase> = {
|
|
78
125
|
lookupByEmail?: (email: string) => Promise<IdentityResolution | null>;
|
|
79
126
|
requestOtp?: (email: string) => Promise<void>;
|
|
@@ -85,47 +132,100 @@ type AdaptiveFlowAdapter<TContext extends AdaptiveContextBase = AdaptiveContextB
|
|
|
85
132
|
startOAuth?: (provider: OAuthProvider, context: TContext) => Promise<void>;
|
|
86
133
|
completeOAuth?: (provider: OAuthProvider | null, context: TContext) => Promise<Partial<TContext> | void>;
|
|
87
134
|
};
|
|
135
|
+
/** Named style slots used by the built-in component shell and step renderers. */
|
|
88
136
|
type AdaptiveFlowStyleSlot = "shell" | "headerRow" | "eyebrow" | "title" | "badge" | "success" | "error" | "info" | "caption" | "formRow" | "formColumn" | "input" | "button" | "checkboxRow" | "complete" | "footer" | "oauthButton";
|
|
137
|
+
/** Inline style overrides keyed by the built-in style slots. */
|
|
89
138
|
type AdaptiveFlowStyles = Partial<Record<AdaptiveFlowStyleSlot, React.CSSProperties>>;
|
|
139
|
+
/** CSS class overrides keyed by the built-in style slots. */
|
|
90
140
|
type AdaptiveFlowClassNames = Partial<Record<AdaptiveFlowStyleSlot, string>>;
|
|
141
|
+
/** Minimal storage interface used by persistence drivers. */
|
|
91
142
|
type AdaptiveFlowStorageDriver = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
143
|
+
/** Persistence settings for storing and restoring flow state across reloads. */
|
|
92
144
|
type AdaptiveFlowPersistence<TContext extends AdaptiveContextBase = AdaptiveContextBase> = {
|
|
93
145
|
key: string;
|
|
94
146
|
storage?: "session" | "local" | AdaptiveFlowStorageDriver;
|
|
95
147
|
serialize?: (state: PersistedAdaptiveFlowState<TContext>) => string;
|
|
96
148
|
deserialize?: (value: string) => PersistedAdaptiveFlowState<TContext>;
|
|
97
149
|
clearOnComplete?: boolean;
|
|
150
|
+
onError?: (error: Error, phase: "read" | "write" | "clear") => void;
|
|
98
151
|
};
|
|
152
|
+
/** Persisted flow payload written to session/local/custom storage. */
|
|
99
153
|
type PersistedAdaptiveFlowState<TContext extends AdaptiveContextBase = AdaptiveContextBase> = {
|
|
100
154
|
context: TContext;
|
|
101
155
|
oauthPendingProvider: OAuthProvider | null;
|
|
102
156
|
};
|
|
157
|
+
/** Context wrapper passed into validators so they can inspect the current flow state. */
|
|
103
158
|
type AdaptiveFlowValidationContext<TContext extends AdaptiveContextBase = AdaptiveContextBase> = {
|
|
104
159
|
context: TContext;
|
|
105
160
|
};
|
|
161
|
+
/** Validator failure payload that can target a specific field or the whole form. */
|
|
162
|
+
type AdaptiveFlowValidationIssue = {
|
|
163
|
+
message: string;
|
|
164
|
+
field?: string;
|
|
165
|
+
};
|
|
166
|
+
/** Validator return type supported by the component and hook. */
|
|
167
|
+
type AdaptiveFlowValidationResult = string | AdaptiveFlowValidationIssue | void | Promise<string | AdaptiveFlowValidationIssue | void>;
|
|
168
|
+
/** Schema adapter contract used for optional Zod-friendly validation. */
|
|
169
|
+
type AdaptiveFlowSchema<TValue> = {
|
|
170
|
+
safeParse?: (value: TValue) => {
|
|
171
|
+
success: boolean;
|
|
172
|
+
error?: {
|
|
173
|
+
message?: string;
|
|
174
|
+
issues?: Array<{
|
|
175
|
+
message?: string;
|
|
176
|
+
path?: Array<string | number>;
|
|
177
|
+
}>;
|
|
178
|
+
};
|
|
179
|
+
};
|
|
180
|
+
parse?: (value: TValue) => unknown;
|
|
181
|
+
};
|
|
182
|
+
/** Optional schema map for built-in step fields. */
|
|
183
|
+
type AdaptiveFlowSchemas = {
|
|
184
|
+
email?: AdaptiveFlowSchema<string>;
|
|
185
|
+
otp?: AdaptiveFlowSchema<string>;
|
|
186
|
+
password?: AdaptiveFlowSchema<string>;
|
|
187
|
+
profile?: AdaptiveFlowSchema<AdaptiveContextBase["profile"]>;
|
|
188
|
+
tos?: AdaptiveFlowSchema<boolean>;
|
|
189
|
+
};
|
|
190
|
+
/** Backoff settings used to retry transient adapter calls and OAuth completion. */
|
|
191
|
+
type AdaptiveFlowRetryPolicy = {
|
|
192
|
+
maxAttempts?: number;
|
|
193
|
+
initialDelayMs?: number;
|
|
194
|
+
factor?: number;
|
|
195
|
+
maxDelayMs?: number;
|
|
196
|
+
jitter?: boolean;
|
|
197
|
+
shouldRetry?: (error: Error, attempt: number) => boolean;
|
|
198
|
+
delay?: (attempt: number) => number;
|
|
199
|
+
};
|
|
200
|
+
/** Field error bag keyed by field path or logical field name. */
|
|
201
|
+
type AdaptiveFlowFieldErrors = Partial<Record<string, string>>;
|
|
202
|
+
/** Validator map for the built-in flow fields. */
|
|
106
203
|
type AdaptiveFlowValidators<TContext extends AdaptiveContextBase = AdaptiveContextBase> = {
|
|
107
|
-
email?: (input: string, payload: AdaptiveFlowValidationContext<TContext>) =>
|
|
204
|
+
email?: (input: string, payload: AdaptiveFlowValidationContext<TContext>) => AdaptiveFlowValidationResult;
|
|
108
205
|
otp?: (input: string, payload: AdaptiveFlowValidationContext<TContext> & {
|
|
109
206
|
email: string;
|
|
110
|
-
}) =>
|
|
207
|
+
}) => AdaptiveFlowValidationResult;
|
|
111
208
|
password?: (input: string, payload: AdaptiveFlowValidationContext<TContext> & {
|
|
112
209
|
hasPassword: boolean;
|
|
113
|
-
}) =>
|
|
114
|
-
profile?: (input: AdaptiveContextBase["profile"], payload: AdaptiveFlowValidationContext<TContext>) =>
|
|
115
|
-
tos?: (accepted: boolean, payload: AdaptiveFlowValidationContext<TContext>) =>
|
|
210
|
+
}) => AdaptiveFlowValidationResult;
|
|
211
|
+
profile?: (input: AdaptiveContextBase["profile"], payload: AdaptiveFlowValidationContext<TContext>) => AdaptiveFlowValidationResult;
|
|
212
|
+
tos?: (accepted: boolean, payload: AdaptiveFlowValidationContext<TContext>) => AdaptiveFlowValidationResult;
|
|
116
213
|
};
|
|
214
|
+
/** Transition record emitted whenever the evaluated step changes. */
|
|
117
215
|
type AdaptiveStepTransition<TStep extends string = AdaptiveStep> = {
|
|
118
216
|
from: TStep | null;
|
|
119
217
|
to: TStep;
|
|
120
218
|
at: number;
|
|
121
219
|
attempt: number;
|
|
122
220
|
};
|
|
221
|
+
/** Arguments supplied to a stepRegistry renderer for a specific step. */
|
|
123
222
|
type AdaptiveStepRendererArgs<TContext extends AdaptiveContextBase, TRequirement extends string, TStep extends string> = {
|
|
124
223
|
step: TStep;
|
|
125
224
|
context: TContext;
|
|
126
225
|
busy: boolean;
|
|
127
226
|
message: string | null;
|
|
128
227
|
errorMessage: string | null;
|
|
228
|
+
fieldErrors: AdaptiveFlowFieldErrors;
|
|
129
229
|
missingRequirements: TRequirement[];
|
|
130
230
|
requirements: readonly TRequirement[];
|
|
131
231
|
setContextPatch: (patch: Partial<TContext>) => void;
|
|
@@ -133,13 +233,16 @@ type AdaptiveStepRendererArgs<TContext extends AdaptiveContextBase, TRequirement
|
|
|
133
233
|
adapter?: AdaptiveFlowAdapter<TContext>;
|
|
134
234
|
transitions: AdaptiveStepTransition<TStep>[];
|
|
135
235
|
};
|
|
236
|
+
/** Registry that overrides the UI for selected steps. */
|
|
136
237
|
type AdaptiveStepRegistry<TContext extends AdaptiveContextBase, TRequirement extends string, TStep extends string> = Partial<Record<TStep, (args: AdaptiveStepRendererArgs<TContext, TRequirement, TStep>) => React.ReactNode>>;
|
|
238
|
+
/** Arguments supplied to renderStep for full step-body customization. */
|
|
137
239
|
type AdaptiveStepRenderArgs<TContext extends AdaptiveContextBase = AdaptiveContextBase, TRequirement extends string = AppRequirement, TStep extends string = AdaptiveStep> = {
|
|
138
240
|
step: TStep;
|
|
139
241
|
context: TContext;
|
|
140
242
|
busy: boolean;
|
|
141
243
|
message: string | null;
|
|
142
244
|
errorMessage: string | null;
|
|
245
|
+
fieldErrors: AdaptiveFlowFieldErrors;
|
|
143
246
|
missingRequirements: TRequirement[];
|
|
144
247
|
requirements: readonly TRequirement[];
|
|
145
248
|
defaultView: React.ReactNode;
|
|
@@ -148,6 +251,7 @@ type AdaptiveStepRenderArgs<TContext extends AdaptiveContextBase = AdaptiveConte
|
|
|
148
251
|
adapter?: AdaptiveFlowAdapter<TContext>;
|
|
149
252
|
transitions: AdaptiveStepTransition<TStep>[];
|
|
150
253
|
};
|
|
254
|
+
/** Props accepted by the AdaptiveFlow component. */
|
|
151
255
|
type AdaptiveFlowProps<TContext extends AdaptiveContextBase = AdaptiveContext, TRequirement extends string = AppRequirement, TStep extends string = AdaptiveStep> = {
|
|
152
256
|
adapter?: AdaptiveFlowAdapter<TContext>;
|
|
153
257
|
requirements?: readonly TRequirement[];
|
|
@@ -172,7 +276,36 @@ type AdaptiveFlowProps<TContext extends AdaptiveContextBase = AdaptiveContext, T
|
|
|
172
276
|
unstyled?: boolean;
|
|
173
277
|
persistence?: AdaptiveFlowPersistence<TContext>;
|
|
174
278
|
validators?: AdaptiveFlowValidators<TContext>;
|
|
279
|
+
schemas?: AdaptiveFlowSchemas;
|
|
280
|
+
oauthProviders?: readonly AdaptiveFlowOAuthProvider[];
|
|
281
|
+
retryPolicy?: AdaptiveFlowRetryPolicy;
|
|
282
|
+
};
|
|
283
|
+
/** Options accepted by the headless useAdaptiveFlow hook. */
|
|
284
|
+
type UseAdaptiveFlowOptions<TContext extends AdaptiveContextBase = AdaptiveContext, TRequirement extends string = AppRequirement, TStep extends string = AdaptiveStep> = Omit<AdaptiveFlowProps<TContext, TRequirement, TStep>, "stepTitles" | "renderStep" | "stepRegistry" | "className" | "classNames" | "styles" | "unstyled">;
|
|
285
|
+
/** Result object returned by useAdaptiveFlow. */
|
|
286
|
+
type UseAdaptiveFlowResult<TContext extends AdaptiveContextBase, TRequirement extends string, TStep extends string> = {
|
|
287
|
+
context: TContext;
|
|
288
|
+
step: TStep;
|
|
289
|
+
completeStep: TStep;
|
|
290
|
+
requirements: readonly TRequirement[];
|
|
291
|
+
missingRequirements: TRequirement[];
|
|
292
|
+
transitions: AdaptiveStepTransition<TStep>[];
|
|
293
|
+
busy: boolean;
|
|
294
|
+
message: string | null;
|
|
295
|
+
errorMessage: string | null;
|
|
296
|
+
fieldErrors: AdaptiveFlowFieldErrors;
|
|
297
|
+
setContextPatch: (patch: Partial<TContext>) => void;
|
|
298
|
+
run: (job: () => Promise<void>) => Promise<void>;
|
|
299
|
+
handleEmail: (emailInput: string) => void;
|
|
300
|
+
handleOtp: (code: string) => void;
|
|
301
|
+
handlePassword: (password: string) => void;
|
|
302
|
+
handleProfile: (profile: AdaptiveContextBase["profile"]) => void;
|
|
303
|
+
handleTos: () => void;
|
|
304
|
+
handleOAuth: (provider: OAuthProvider) => void;
|
|
175
305
|
};
|
|
176
|
-
|
|
306
|
+
/** Headless flow hook that owns state, evaluation, persistence, retries, and adapter wiring. */
|
|
307
|
+
declare function useAdaptiveFlow<TContext extends AdaptiveContextBase = AdaptiveContext, TRequirement extends string = AppRequirement, TStep extends string = AdaptiveStep>({ adapter, requirements, requirementGraph, requirementGraphConfig, requirementResolvers, completeStep, initialValue, onComplete, onError, onStepTransition, persistence, validators, schemas, oauthProviders, retryPolicy, }: UseAdaptiveFlowOptions<TContext, TRequirement, TStep>): UseAdaptiveFlowResult<TContext, TRequirement, TStep>;
|
|
308
|
+
/** Default UI wrapper around useAdaptiveFlow that renders the built-in flow shell and step UI. */
|
|
309
|
+
declare function AdaptiveFlow<TContext extends AdaptiveContextBase = AdaptiveContext, TRequirement extends string = AppRequirement, TStep extends string = AdaptiveStep>({ adapter, requirements, requirementGraph, requirementGraphConfig, requirementResolvers, completeStep, stepTitles, renderStep, stepRegistry, initialValue, onComplete, onError, onStepTransition, className, classNames, styles, unstyled, persistence, validators, schemas, oauthProviders, retryPolicy, }: AdaptiveFlowProps<TContext, TRequirement, TStep>): react_jsx_runtime.JSX.Element;
|
|
177
310
|
|
|
178
|
-
export { type AdaptiveContext, type AdaptiveContextBase, AdaptiveFlow, type AdaptiveFlowAdapter, type AdaptiveFlowClassNames, type AdaptiveFlowPersistence, type AdaptiveFlowProps, type AdaptiveFlowStorageDriver, type AdaptiveFlowStyleSlot, type AdaptiveFlowStyles, type AdaptiveFlowValidationContext, type AdaptiveFlowValidators, type AdaptiveStep, type AdaptiveStepRegistry, type AdaptiveStepRenderArgs, type AdaptiveStepRendererArgs, type AdaptiveStepTransition, type AppRequirement, type DefaultAdaptiveStep, DefaultAdaptiveSteps, type DefaultAppRequirement, DefaultAppRequirements, type IdentityResolution, type MaybePromise, type OAuthProvider, type PersistedAdaptiveFlowState, type RequirementGraph, type RequirementGraphNode, type RequirementResolver, createDefaultRequirementGraph, createRequirementGraph, defaultRequirementResolvers, defaultRequirements, evaluateNextStep, getMissingRequirements, initialContext };
|
|
311
|
+
export { type AdaptiveContext, type AdaptiveContextBase, AdaptiveFlow, type AdaptiveFlowAdapter, type AdaptiveFlowClassNames, type AdaptiveFlowFieldErrors, type AdaptiveFlowOAuthProvider, type AdaptiveFlowPersistence, type AdaptiveFlowProps, type AdaptiveFlowRetryPolicy, type AdaptiveFlowSchema, type AdaptiveFlowSchemas, type AdaptiveFlowStorageDriver, type AdaptiveFlowStyleSlot, type AdaptiveFlowStyles, type AdaptiveFlowValidationContext, type AdaptiveFlowValidationIssue, type AdaptiveFlowValidationResult, type AdaptiveFlowValidators, type AdaptiveStep, type AdaptiveStepRegistry, type AdaptiveStepRenderArgs, type AdaptiveStepRendererArgs, type AdaptiveStepTransition, type AppRequirement, type DefaultAdaptiveStep, DefaultAdaptiveSteps, type DefaultAppRequirement, DefaultAppRequirements, type IdentityResolution, type MaybePromise, type OAuthProvider, type PersistedAdaptiveFlowState, type RequirementGraph, type RequirementGraphNode, type RequirementResolver, type UseAdaptiveFlowOptions, type UseAdaptiveFlowResult, createDefaultRequirementGraph, createRequirementGraph, defaultRequirementResolvers, defaultRequirements, evaluateNextStep, getMissingRequirements, initialContext, useAdaptiveFlow };
|