@wizzard-packages/core 0.3.0 → 0.4.1-canary-20260904173354

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,166 @@
1
+ /**
2
+ * The expression language.
3
+ *
4
+ * A flow has to survive `JSON.stringify`, so a predicate cannot be a function.
5
+ * It is a small tagged object instead, evaluated by this module. There is no
6
+ * `eval` and no `new Function`, so a flow served by a backend is data, not code.
7
+ *
8
+ * Ten operators cover the predicates wizards actually express. Anything else —
9
+ * arithmetic, regular expressions, a remote lookup — goes through `$ref`, which
10
+ * names a function in the registry. That keeps the grammar small and makes the
11
+ * one dangerous thing explicit and greppable.
12
+ */
13
+ type Json = null | boolean | number | string | Json[] | {
14
+ [key: string]: Json;
15
+ };
16
+ type Expr = null | boolean | number | string | readonly Expr[] | {
17
+ $get: string;
18
+ } | {
19
+ $ref: string;
20
+ args?: Json;
21
+ } | {
22
+ $not: Expr;
23
+ } | {
24
+ $and: readonly Expr[];
25
+ } | {
26
+ $or: readonly Expr[];
27
+ } | {
28
+ $eq: readonly [Expr, Expr];
29
+ } | {
30
+ $ne: readonly [Expr, Expr];
31
+ } | {
32
+ $gt: readonly [Expr, Expr];
33
+ } | {
34
+ $gte: readonly [Expr, Expr];
35
+ } | {
36
+ $lt: readonly [Expr, Expr];
37
+ } | {
38
+ $lte: readonly [Expr, Expr];
39
+ } | {
40
+ $in: readonly [Expr, Expr];
41
+ } | {
42
+ $empty: Expr;
43
+ };
44
+ /** The roots a `$get` path may address. */
45
+ interface Scope {
46
+ data: Record<string, unknown>;
47
+ ctx: Record<string, unknown>;
48
+ /** Present inside a `repeat` group. */
49
+ loop?: {
50
+ index: number;
51
+ item: unknown;
52
+ };
53
+ }
54
+ type Resolver = (args: Json | undefined, scope: Scope) => unknown;
55
+ type Registry = Readonly<Record<string, Resolver>>;
56
+ /** Thrown when a synchronous evaluation meets a `$ref` it cannot resolve. */
57
+ declare class ExprError extends Error {
58
+ }
59
+ /**
60
+ * Evaluates synchronously. Throws `ExprError` on a `$ref` that the registry
61
+ * does not define, or whose resolver returns a promise.
62
+ *
63
+ * Most flows contain no `$ref` at all, so `isSync` lets the engine take this
64
+ * path and know which steps are reachable before the first paint.
65
+ */
66
+ declare function evaluate(e: Expr, scope: Scope, registry?: Registry): unknown;
67
+ /** Evaluates to a boolean. An absent expression is `true`. */
68
+ declare function test(e: Expr | undefined, scope: Scope, registry?: Registry): boolean;
69
+ /**
70
+ * True when the expression contains no `$ref`, and can therefore be evaluated
71
+ * before the first paint. Computed once per flow, not per navigation.
72
+ */
73
+ declare function isSync(e: Expr | undefined): boolean;
74
+ /**
75
+ * Evaluates with `$ref` allowed to be asynchronous.
76
+ *
77
+ * Used only where the engine is already awaiting — a guard, a load. Reachability
78
+ * takes the synchronous path when `isSync` allows it, which is the common case
79
+ * and the reason a flow renders correctly on the first frame.
80
+ */
81
+ declare function evaluateAsync(e: Expr, scope: Scope, registry?: AsyncRegistry): Promise<unknown>;
82
+ type AsyncResolver = (args: Json | undefined, scope: Scope) => unknown | Promise<unknown>;
83
+ type AsyncRegistry = Readonly<Record<string, AsyncResolver>>;
84
+ /** Evaluates to a boolean, awaiting `$ref`. An absent expression is `true`. */
85
+ declare function testAsync(e: Expr | undefined, scope: Scope, registry?: AsyncRegistry): Promise<boolean>;
86
+
87
+ /**
88
+ * The flow definition. Pure JSON — no functions, no class instances, nothing
89
+ * that `JSON.stringify` would drop. The same object can be written by hand,
90
+ * served by a backend, or generated; the engine cannot tell which.
91
+ *
92
+ * Everything that cannot be serialized is named instead: `view` names a
93
+ * component, `$ref` names a function, both looked up in a registry the host
94
+ * supplies.
95
+ */
96
+ interface FlowDefinition {
97
+ id: string;
98
+ /** Bumped by the producer when the shape changes; used to reject stale patches. */
99
+ version?: number;
100
+ /** Default linear order. A step missing from `order` is reachable only via `on.next`. */
101
+ order?: readonly string[];
102
+ /** Keyed by id, not an array, so a patch can address one step without index maths. */
103
+ steps: Readonly<Record<string, StepDef>>;
104
+ policy?: NavigationPolicy;
105
+ validate?: {
106
+ on?: 'change' | 'blur' | 'next' | 'manual';
107
+ debounceMs?: number;
108
+ };
109
+ }
110
+ type NavigationPolicy = 'sequential' | 'visited' | 'free';
111
+ type Target = string | {
112
+ to: string;
113
+ when?: Expr;
114
+ };
115
+ interface StepBase {
116
+ label?: string;
117
+ /** Reachability. A step whose `when` is false is skipped, not shown disabled. */
118
+ when?: Expr;
119
+ guards?: {
120
+ enter?: Expr;
121
+ exit?: Expr;
122
+ };
123
+ on?: {
124
+ next?: Target | readonly Target[];
125
+ back?: Target | 'auto';
126
+ };
127
+ /** Key of this step's slice of `data`. Defaults to the step id. */
128
+ slice?: string;
129
+ /** Field schema for the host to render. The engine never reads it. */
130
+ ui?: Json;
131
+ /** Loaded on enter, with an abort signal. */
132
+ load?: {
133
+ $ref: string;
134
+ args?: Json;
135
+ };
136
+ /** The body of this step arrives later, from the host. */
137
+ deferred?: boolean;
138
+ }
139
+ interface AtomStep extends StepBase {
140
+ view?: string;
141
+ validate?: {
142
+ $ref: string;
143
+ args?: Json;
144
+ };
145
+ }
146
+ /**
147
+ * A sub-flow. With `repeat` it is also the loop construct — the same machinery,
148
+ * because a loop is a sub-flow entered once per item. Iteration state lives in
149
+ * the runtime frame, never in the definition, so the flow stays static and
150
+ * diffable.
151
+ */
152
+ interface GroupStep extends StepBase {
153
+ flow: string | FlowDefinition;
154
+ repeat?: {
155
+ over: Expr;
156
+ keyBy?: string;
157
+ };
158
+ /** Parent expressions piped into the child's `ctx`. */
159
+ input?: Readonly<Record<string, Expr>>;
160
+ }
161
+ type StepDef = AtomStep | GroupStep;
162
+ declare const isGroup: (s: StepDef) => s is GroupStep;
163
+ /** Sentinel target meaning the flow is finished. */
164
+ declare const END: "@end";
165
+
166
+ export { type AtomStep as A, END as E, type FlowDefinition as F, type GroupStep as G, type Json as J, type NavigationPolicy as N, type Registry as R, type StepDef as S, type Target as T, type AsyncRegistry as a, type Scope as b, type Expr as c, type Resolver as d, ExprError as e, evaluate as f, evaluateAsync as g, type AsyncResolver as h, isSync as i, testAsync as j, isGroup as k, test as t };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * The expression language.
3
+ *
4
+ * A flow has to survive `JSON.stringify`, so a predicate cannot be a function.
5
+ * It is a small tagged object instead, evaluated by this module. There is no
6
+ * `eval` and no `new Function`, so a flow served by a backend is data, not code.
7
+ *
8
+ * Ten operators cover the predicates wizards actually express. Anything else —
9
+ * arithmetic, regular expressions, a remote lookup — goes through `$ref`, which
10
+ * names a function in the registry. That keeps the grammar small and makes the
11
+ * one dangerous thing explicit and greppable.
12
+ */
13
+ type Json = null | boolean | number | string | Json[] | {
14
+ [key: string]: Json;
15
+ };
16
+ type Expr = null | boolean | number | string | readonly Expr[] | {
17
+ $get: string;
18
+ } | {
19
+ $ref: string;
20
+ args?: Json;
21
+ } | {
22
+ $not: Expr;
23
+ } | {
24
+ $and: readonly Expr[];
25
+ } | {
26
+ $or: readonly Expr[];
27
+ } | {
28
+ $eq: readonly [Expr, Expr];
29
+ } | {
30
+ $ne: readonly [Expr, Expr];
31
+ } | {
32
+ $gt: readonly [Expr, Expr];
33
+ } | {
34
+ $gte: readonly [Expr, Expr];
35
+ } | {
36
+ $lt: readonly [Expr, Expr];
37
+ } | {
38
+ $lte: readonly [Expr, Expr];
39
+ } | {
40
+ $in: readonly [Expr, Expr];
41
+ } | {
42
+ $empty: Expr;
43
+ };
44
+ /** The roots a `$get` path may address. */
45
+ interface Scope {
46
+ data: Record<string, unknown>;
47
+ ctx: Record<string, unknown>;
48
+ /** Present inside a `repeat` group. */
49
+ loop?: {
50
+ index: number;
51
+ item: unknown;
52
+ };
53
+ }
54
+ type Resolver = (args: Json | undefined, scope: Scope) => unknown;
55
+ type Registry = Readonly<Record<string, Resolver>>;
56
+ /** Thrown when a synchronous evaluation meets a `$ref` it cannot resolve. */
57
+ declare class ExprError extends Error {
58
+ }
59
+ /**
60
+ * Evaluates synchronously. Throws `ExprError` on a `$ref` that the registry
61
+ * does not define, or whose resolver returns a promise.
62
+ *
63
+ * Most flows contain no `$ref` at all, so `isSync` lets the engine take this
64
+ * path and know which steps are reachable before the first paint.
65
+ */
66
+ declare function evaluate(e: Expr, scope: Scope, registry?: Registry): unknown;
67
+ /** Evaluates to a boolean. An absent expression is `true`. */
68
+ declare function test(e: Expr | undefined, scope: Scope, registry?: Registry): boolean;
69
+ /**
70
+ * True when the expression contains no `$ref`, and can therefore be evaluated
71
+ * before the first paint. Computed once per flow, not per navigation.
72
+ */
73
+ declare function isSync(e: Expr | undefined): boolean;
74
+ /**
75
+ * Evaluates with `$ref` allowed to be asynchronous.
76
+ *
77
+ * Used only where the engine is already awaiting — a guard, a load. Reachability
78
+ * takes the synchronous path when `isSync` allows it, which is the common case
79
+ * and the reason a flow renders correctly on the first frame.
80
+ */
81
+ declare function evaluateAsync(e: Expr, scope: Scope, registry?: AsyncRegistry): Promise<unknown>;
82
+ type AsyncResolver = (args: Json | undefined, scope: Scope) => unknown | Promise<unknown>;
83
+ type AsyncRegistry = Readonly<Record<string, AsyncResolver>>;
84
+ /** Evaluates to a boolean, awaiting `$ref`. An absent expression is `true`. */
85
+ declare function testAsync(e: Expr | undefined, scope: Scope, registry?: AsyncRegistry): Promise<boolean>;
86
+
87
+ /**
88
+ * The flow definition. Pure JSON — no functions, no class instances, nothing
89
+ * that `JSON.stringify` would drop. The same object can be written by hand,
90
+ * served by a backend, or generated; the engine cannot tell which.
91
+ *
92
+ * Everything that cannot be serialized is named instead: `view` names a
93
+ * component, `$ref` names a function, both looked up in a registry the host
94
+ * supplies.
95
+ */
96
+ interface FlowDefinition {
97
+ id: string;
98
+ /** Bumped by the producer when the shape changes; used to reject stale patches. */
99
+ version?: number;
100
+ /** Default linear order. A step missing from `order` is reachable only via `on.next`. */
101
+ order?: readonly string[];
102
+ /** Keyed by id, not an array, so a patch can address one step without index maths. */
103
+ steps: Readonly<Record<string, StepDef>>;
104
+ policy?: NavigationPolicy;
105
+ validate?: {
106
+ on?: 'change' | 'blur' | 'next' | 'manual';
107
+ debounceMs?: number;
108
+ };
109
+ }
110
+ type NavigationPolicy = 'sequential' | 'visited' | 'free';
111
+ type Target = string | {
112
+ to: string;
113
+ when?: Expr;
114
+ };
115
+ interface StepBase {
116
+ label?: string;
117
+ /** Reachability. A step whose `when` is false is skipped, not shown disabled. */
118
+ when?: Expr;
119
+ guards?: {
120
+ enter?: Expr;
121
+ exit?: Expr;
122
+ };
123
+ on?: {
124
+ next?: Target | readonly Target[];
125
+ back?: Target | 'auto';
126
+ };
127
+ /** Key of this step's slice of `data`. Defaults to the step id. */
128
+ slice?: string;
129
+ /** Field schema for the host to render. The engine never reads it. */
130
+ ui?: Json;
131
+ /** Loaded on enter, with an abort signal. */
132
+ load?: {
133
+ $ref: string;
134
+ args?: Json;
135
+ };
136
+ /** The body of this step arrives later, from the host. */
137
+ deferred?: boolean;
138
+ }
139
+ interface AtomStep extends StepBase {
140
+ view?: string;
141
+ validate?: {
142
+ $ref: string;
143
+ args?: Json;
144
+ };
145
+ }
146
+ /**
147
+ * A sub-flow. With `repeat` it is also the loop construct — the same machinery,
148
+ * because a loop is a sub-flow entered once per item. Iteration state lives in
149
+ * the runtime frame, never in the definition, so the flow stays static and
150
+ * diffable.
151
+ */
152
+ interface GroupStep extends StepBase {
153
+ flow: string | FlowDefinition;
154
+ repeat?: {
155
+ over: Expr;
156
+ keyBy?: string;
157
+ };
158
+ /** Parent expressions piped into the child's `ctx`. */
159
+ input?: Readonly<Record<string, Expr>>;
160
+ }
161
+ type StepDef = AtomStep | GroupStep;
162
+ declare const isGroup: (s: StepDef) => s is GroupStep;
163
+ /** Sentinel target meaning the flow is finished. */
164
+ declare const END: "@end";
165
+
166
+ export { type AtomStep as A, END as E, type FlowDefinition as F, type GroupStep as G, type Json as J, type NavigationPolicy as N, type Registry as R, type StepDef as S, type Target as T, type AsyncRegistry as a, type Scope as b, type Expr as c, type Resolver as d, ExprError as e, evaluate as f, evaluateAsync as g, type AsyncResolver as h, isSync as i, testAsync as j, isGroup as k, test as t };