@player-tools/fluent 0.13.0--canary.221.5662
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/cjs/index.cjs +2257 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2143 -0
- package/dist/index.mjs +2143 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +36 -0
- package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2257 -0
- package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
- package/src/core/base-builder/__tests__/registry.test.ts +411 -0
- package/src/core/base-builder/__tests__/switch.test.ts +501 -0
- package/src/core/base-builder/__tests__/template.test.ts +449 -0
- package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
- package/src/core/base-builder/conditional/index.ts +64 -0
- package/src/core/base-builder/context.ts +151 -0
- package/src/core/base-builder/fluent-builder-base.ts +261 -0
- package/src/core/base-builder/guards.ts +137 -0
- package/src/core/base-builder/id/generator.ts +286 -0
- package/src/core/base-builder/id/registry.ts +152 -0
- package/src/core/base-builder/index.ts +60 -0
- package/src/core/base-builder/resolution/path-resolver.ts +108 -0
- package/src/core/base-builder/resolution/pipeline.ts +96 -0
- package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
- package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
- package/src/core/base-builder/resolution/steps/builders.ts +85 -0
- package/src/core/base-builder/resolution/steps/mixed-arrays.ts +117 -0
- package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
- package/src/core/base-builder/resolution/steps/switches.ts +63 -0
- package/src/core/base-builder/resolution/steps/templates.ts +30 -0
- package/src/core/base-builder/resolution/value-resolver.ts +308 -0
- package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
- package/src/core/base-builder/storage/value-storage.ts +280 -0
- package/src/core/base-builder/types.ts +184 -0
- package/src/core/base-builder/utils.ts +10 -0
- package/src/core/flow/__tests__/index.test.ts +292 -0
- package/src/core/flow/index.ts +141 -0
- package/src/core/index.ts +8 -0
- package/src/core/mocks/generated/action.builder.ts +109 -0
- package/src/core/mocks/generated/choice.builder.ts +161 -0
- package/src/core/mocks/generated/choiceItem.builder.ts +133 -0
- package/src/core/mocks/generated/collection.builder.ts +117 -0
- package/src/core/mocks/generated/index.ts +7 -0
- package/src/core/mocks/generated/info.builder.ts +80 -0
- package/src/core/mocks/generated/input.builder.ts +75 -0
- package/src/core/mocks/generated/text.builder.ts +63 -0
- package/src/core/mocks/index.ts +1 -0
- package/src/core/mocks/types/action.ts +92 -0
- package/src/core/mocks/types/choice.ts +129 -0
- package/src/core/mocks/types/collection.ts +140 -0
- package/src/core/mocks/types/info.ts +7 -0
- package/src/core/mocks/types/input.ts +7 -0
- package/src/core/mocks/types/text.ts +5 -0
- package/src/core/schema/__tests__/index.test.ts +127 -0
- package/src/core/schema/index.ts +195 -0
- package/src/core/schema/types.ts +7 -0
- package/src/core/switch/__tests__/index.test.ts +156 -0
- package/src/core/switch/index.ts +76 -0
- package/src/core/tagged-template/README.md +448 -0
- package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
- package/src/core/tagged-template/__tests__/index.test.ts +190 -0
- package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
- package/src/core/tagged-template/binding.ts +95 -0
- package/src/core/tagged-template/expression.ts +92 -0
- package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
- package/src/core/tagged-template/index.ts +5 -0
- package/src/core/tagged-template/std.ts +472 -0
- package/src/core/tagged-template/types.ts +123 -0
- package/src/core/template/__tests__/index.test.ts +380 -0
- package/src/core/template/index.ts +191 -0
- package/src/core/utils/index.ts +160 -0
- package/src/fp/README.md +411 -0
- package/src/fp/__tests__/index.test.ts +1178 -0
- package/src/fp/index.ts +386 -0
- package/src/gen/common.ts +2 -0
- package/src/gen/plugin.mjs +315 -0
- package/src/index.ts +5 -0
- package/src/types.ts +203 -0
- package/types/core/base-builder/conditional/index.d.ts +21 -0
- package/types/core/base-builder/context.d.ts +39 -0
- package/types/core/base-builder/fluent-builder-base.d.ts +132 -0
- package/types/core/base-builder/guards.d.ts +58 -0
- package/types/core/base-builder/id/generator.d.ts +69 -0
- package/types/core/base-builder/id/registry.d.ts +93 -0
- package/types/core/base-builder/index.d.ts +8 -0
- package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
- package/types/core/base-builder/resolution/pipeline.d.ts +25 -0
- package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
- package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
- package/types/core/base-builder/resolution/value-resolver.d.ts +37 -0
- package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
- package/types/core/base-builder/storage/value-storage.d.ts +82 -0
- package/types/core/base-builder/types.d.ts +141 -0
- package/types/core/base-builder/utils.d.ts +2 -0
- package/types/core/flow/index.d.ts +23 -0
- package/types/core/index.d.ts +8 -0
- package/types/core/mocks/index.d.ts +2 -0
- package/types/core/mocks/types/action.d.ts +58 -0
- package/types/core/mocks/types/choice.d.ts +95 -0
- package/types/core/mocks/types/collection.d.ts +102 -0
- package/types/core/mocks/types/info.d.ts +7 -0
- package/types/core/mocks/types/input.d.ts +7 -0
- package/types/core/mocks/types/text.d.ts +5 -0
- package/types/core/schema/index.d.ts +34 -0
- package/types/core/schema/types.d.ts +5 -0
- package/types/core/switch/index.d.ts +21 -0
- package/types/core/tagged-template/binding.d.ts +19 -0
- package/types/core/tagged-template/expression.d.ts +11 -0
- package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
- package/types/core/tagged-template/index.d.ts +6 -0
- package/types/core/tagged-template/std.d.ts +174 -0
- package/types/core/tagged-template/types.d.ts +69 -0
- package/types/core/template/index.d.ts +97 -0
- package/types/core/utils/index.d.ts +47 -0
- package/types/fp/index.d.ts +149 -0
- package/types/gen/common.d.ts +3 -0
- package/types/index.d.ts +3 -0
- package/types/types.d.ts +163 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Asset, AssetWrapper } from "@player-ui/types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Unique symbol to identify FluentBuilder instances
|
|
5
|
+
* Used for runtime type checking of builder objects
|
|
6
|
+
*/
|
|
7
|
+
export const FLUENT_BUILDER_SYMBOL: unique symbol =
|
|
8
|
+
Symbol.for("fluent-builder");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Core interface for all fluent builders
|
|
12
|
+
* Provides build(), peek(), and has() methods for all builder types
|
|
13
|
+
*/
|
|
14
|
+
export interface FluentBuilder<
|
|
15
|
+
T,
|
|
16
|
+
C extends BaseBuildContext = BaseBuildContext,
|
|
17
|
+
> {
|
|
18
|
+
readonly [FLUENT_BUILDER_SYMBOL]: true;
|
|
19
|
+
build(context?: C): T;
|
|
20
|
+
peek<K extends keyof T>(key: K): T[K] | undefined;
|
|
21
|
+
has<K extends keyof T>(key: K): boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Type-erased asset builder interface for generic asset handling
|
|
26
|
+
*/
|
|
27
|
+
export type AnyAssetBuilder<C extends BaseBuildContext = BaseBuildContext> = {
|
|
28
|
+
readonly [FLUENT_BUILDER_SYMBOL]: true;
|
|
29
|
+
build(context?: C): Asset;
|
|
30
|
+
peek(key: string): unknown;
|
|
31
|
+
has(key: string): boolean;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Parameters for creating nested build contexts
|
|
36
|
+
* Used by nested context generators to create child contexts
|
|
37
|
+
*/
|
|
38
|
+
export interface NestedContextParams<C extends BaseBuildContext> {
|
|
39
|
+
readonly parentContext: C;
|
|
40
|
+
readonly parameterName: string;
|
|
41
|
+
readonly index?: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Function type for custom nested context generation
|
|
46
|
+
* Allows users to customize how child contexts are created
|
|
47
|
+
*/
|
|
48
|
+
export type NestedContextGenerator<C extends BaseBuildContext> = (
|
|
49
|
+
params: NestedContextParams<C>,
|
|
50
|
+
) => C;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Metadata about an asset used for ID generation and context tracking
|
|
54
|
+
*/
|
|
55
|
+
export interface AssetMetadata {
|
|
56
|
+
readonly type?: string;
|
|
57
|
+
readonly binding?: string;
|
|
58
|
+
readonly value?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Base build context interface containing common fields for all builders
|
|
63
|
+
* Extended by specific builder implementations for custom context needs
|
|
64
|
+
*/
|
|
65
|
+
export interface BaseBuildContext {
|
|
66
|
+
readonly parentId?: string;
|
|
67
|
+
readonly parameterName?: string;
|
|
68
|
+
readonly index?: number;
|
|
69
|
+
readonly branch?: IdBranch;
|
|
70
|
+
readonly nestedContextGenerator?: NestedContextGenerator<BaseBuildContext>;
|
|
71
|
+
readonly assetMetadata?: AssetMetadata;
|
|
72
|
+
readonly [key: string]: unknown;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Slot branch for named properties (e.g., "label", "action")
|
|
77
|
+
* Creates IDs like: parent-label, parent-action
|
|
78
|
+
*/
|
|
79
|
+
export interface SlotBranch {
|
|
80
|
+
type: "slot";
|
|
81
|
+
name: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Array item branch for indexed elements
|
|
86
|
+
* Creates IDs like: parent-0, parent-1
|
|
87
|
+
*/
|
|
88
|
+
export interface ArrayItemBranch {
|
|
89
|
+
type: "array-item";
|
|
90
|
+
index: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Template branch for template placeholders
|
|
95
|
+
* Creates IDs like: parent-_index_, parent-_index1_
|
|
96
|
+
*/
|
|
97
|
+
export interface TemplateBranch {
|
|
98
|
+
type: "template";
|
|
99
|
+
depth?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Switch branch for conditional cases
|
|
104
|
+
* Creates IDs like: parent-staticSwitch-0, parent-dynamicSwitch-1
|
|
105
|
+
*/
|
|
106
|
+
export interface SwitchBranch {
|
|
107
|
+
type: "switch";
|
|
108
|
+
index: number;
|
|
109
|
+
kind: "static" | "dynamic";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Custom branch for user-defined ID patterns
|
|
114
|
+
*/
|
|
115
|
+
export interface CustomBranch {
|
|
116
|
+
type: "custom";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Union of all branch types for type-safe ID generation
|
|
121
|
+
*/
|
|
122
|
+
export type IdBranch =
|
|
123
|
+
| SlotBranch
|
|
124
|
+
| ArrayItemBranch
|
|
125
|
+
| TemplateBranch
|
|
126
|
+
| SwitchBranch
|
|
127
|
+
| CustomBranch;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Metadata for arrays containing mixed static values and builders
|
|
131
|
+
* Tracks which indices contain builders for selective resolution
|
|
132
|
+
*/
|
|
133
|
+
export interface MixedArrayMetadata {
|
|
134
|
+
readonly array: readonly unknown[];
|
|
135
|
+
readonly builderIndices: ReadonlySet<number>;
|
|
136
|
+
readonly objectIndices: ReadonlySet<number>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Metadata for template storage in FluentBuilderBase
|
|
141
|
+
*/
|
|
142
|
+
export interface TemplateMetadata {
|
|
143
|
+
readonly data: string;
|
|
144
|
+
readonly output: string;
|
|
145
|
+
readonly dynamic?: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Path type for targeting where to inject values in nested structures
|
|
150
|
+
* Example: ["actions", 0, "label"] targets actions[0].label
|
|
151
|
+
*/
|
|
152
|
+
export type ValuePath = ReadonlyArray<string | number>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Metadata for switch storage in FluentBuilderBase
|
|
156
|
+
*/
|
|
157
|
+
export interface SwitchMetadata<C extends BaseBuildContext = BaseBuildContext> {
|
|
158
|
+
readonly path: ValuePath;
|
|
159
|
+
readonly switchFn: (context: C, globalCaseIndex?: number) => unknown;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Helper type for conditional property values in if/ifElse methods
|
|
164
|
+
* Allows passing unwrapped Asset builders to AssetWrapper properties
|
|
165
|
+
* Enables: .if(() => true, "label", text().withValue("..."))
|
|
166
|
+
* Instead of: .if(() => true, "label", { asset: text().withValue("...") })
|
|
167
|
+
*/
|
|
168
|
+
export type ConditionalValue<T, C extends BaseBuildContext> = T extends
|
|
169
|
+
| AssetWrapper<infer A>
|
|
170
|
+
| undefined
|
|
171
|
+
? // For AssetWrapper properties, allow unwrapped builders/assets
|
|
172
|
+
| T
|
|
173
|
+
| FluentBuilder<T, C>
|
|
174
|
+
| FluentBuilder<A, C>
|
|
175
|
+
| A
|
|
176
|
+
| Array<FluentBuilder<A, C> | A>
|
|
177
|
+
| (() =>
|
|
178
|
+
| T
|
|
179
|
+
| FluentBuilder<T, C>
|
|
180
|
+
| FluentBuilder<A, C>
|
|
181
|
+
| A
|
|
182
|
+
| Array<FluentBuilder<A, C> | A>)
|
|
183
|
+
: // For other properties, just allow the normal types
|
|
184
|
+
T | FluentBuilder<T, C> | (() => T | FluentBuilder<T, C>);
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { flow } from "..";
|
|
3
|
+
import { Flow } from "@player-ui/types";
|
|
4
|
+
import { text } from "../../mocks";
|
|
5
|
+
|
|
6
|
+
describe("flow function", () => {
|
|
7
|
+
it("should create a flow with basic properties", () => {
|
|
8
|
+
const result = flow({
|
|
9
|
+
id: "test-flow",
|
|
10
|
+
topic: "test-flow",
|
|
11
|
+
views: [text({ value: "Hello World" })],
|
|
12
|
+
data: {
|
|
13
|
+
greeting: "Hello",
|
|
14
|
+
},
|
|
15
|
+
navigation: {
|
|
16
|
+
BEGIN: "FLOW_1",
|
|
17
|
+
FLOW_1: {
|
|
18
|
+
startState: "VIEW_1",
|
|
19
|
+
VIEW_1: {
|
|
20
|
+
state_type: "VIEW",
|
|
21
|
+
ref: "view-1",
|
|
22
|
+
transitions: {},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const expected: Flow = {
|
|
29
|
+
data: {
|
|
30
|
+
greeting: "Hello",
|
|
31
|
+
},
|
|
32
|
+
id: "test-flow",
|
|
33
|
+
topic: "test-flow",
|
|
34
|
+
navigation: {
|
|
35
|
+
BEGIN: "FLOW_1",
|
|
36
|
+
FLOW_1: {
|
|
37
|
+
VIEW_1: {
|
|
38
|
+
ref: "view-1",
|
|
39
|
+
state_type: "VIEW",
|
|
40
|
+
transitions: {},
|
|
41
|
+
},
|
|
42
|
+
startState: "VIEW_1",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
views: [
|
|
46
|
+
{
|
|
47
|
+
id: "test-flow-views-0-text",
|
|
48
|
+
type: "text",
|
|
49
|
+
value: "Hello World",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
expect(result).toEqual(expected);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should use "root" as default flow ID', () => {
|
|
58
|
+
const result = flow({
|
|
59
|
+
views: [text({ value: "Hello World" })],
|
|
60
|
+
navigation: {
|
|
61
|
+
BEGIN: "FLOW_1",
|
|
62
|
+
FLOW_1: {
|
|
63
|
+
startState: "VIEW_1",
|
|
64
|
+
VIEW_1: {
|
|
65
|
+
state_type: "VIEW",
|
|
66
|
+
ref: "view-1",
|
|
67
|
+
transitions: {},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
expect(result.id).toEqual("root");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should generate hierarchical IDs for views", () => {
|
|
77
|
+
const result = flow({
|
|
78
|
+
id: "my-flow",
|
|
79
|
+
views: [text().withValue("First View"), text().withValue("Second View")],
|
|
80
|
+
navigation: {
|
|
81
|
+
BEGIN: "FLOW_1",
|
|
82
|
+
FLOW_1: {
|
|
83
|
+
startState: "VIEW_1",
|
|
84
|
+
VIEW_1: {
|
|
85
|
+
state_type: "VIEW",
|
|
86
|
+
ref: "view-1",
|
|
87
|
+
transitions: {},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const expected: Flow["views"] = [
|
|
94
|
+
{
|
|
95
|
+
id: "my-flow-views-0-text",
|
|
96
|
+
type: "text",
|
|
97
|
+
value: "First View",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "my-flow-views-1-text",
|
|
101
|
+
type: "text",
|
|
102
|
+
value: "Second View",
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
expect(result.views).toEqual(expected);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should preserve custom IDs in views", () => {
|
|
110
|
+
const result = flow({
|
|
111
|
+
id: "custom-flow",
|
|
112
|
+
views: [
|
|
113
|
+
text().withId("custom-view-id").withValue("Custom ID View"),
|
|
114
|
+
text().withValue("Auto ID View"),
|
|
115
|
+
],
|
|
116
|
+
navigation: {
|
|
117
|
+
BEGIN: "FLOW_1",
|
|
118
|
+
FLOW_1: {
|
|
119
|
+
startState: "VIEW_1",
|
|
120
|
+
VIEW_1: {
|
|
121
|
+
state_type: "VIEW",
|
|
122
|
+
ref: "custom-view-id",
|
|
123
|
+
transitions: {},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const expected: Flow["views"] = [
|
|
130
|
+
{
|
|
131
|
+
id: "custom-view-id",
|
|
132
|
+
type: "text",
|
|
133
|
+
value: "Custom ID View",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "custom-flow-views-1-text",
|
|
137
|
+
type: "text",
|
|
138
|
+
value: "Auto ID View",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
expect(result.views).toEqual(expected);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("should handle function-based view generators", () => {
|
|
146
|
+
const result = flow({
|
|
147
|
+
id: "functional-flow",
|
|
148
|
+
views: [
|
|
149
|
+
(ctx) => ({
|
|
150
|
+
id: ctx.parentId ?? "default-id",
|
|
151
|
+
type: "text" as const,
|
|
152
|
+
value: "Function Generated View",
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
navigation: {
|
|
156
|
+
BEGIN: "FLOW_1",
|
|
157
|
+
FLOW_1: {
|
|
158
|
+
startState: "VIEW_1",
|
|
159
|
+
VIEW_1: {
|
|
160
|
+
state_type: "VIEW",
|
|
161
|
+
ref: "view-func",
|
|
162
|
+
transitions: {},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const expected: Flow["views"] = [
|
|
169
|
+
{
|
|
170
|
+
id: "functional-flow-views",
|
|
171
|
+
type: "text",
|
|
172
|
+
value: "Function Generated View",
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
expect(result.views).toEqual(expected);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("should handle a complex flow with multiple properties", () => {
|
|
180
|
+
const result = flow({
|
|
181
|
+
id: "complex-flow",
|
|
182
|
+
views: [
|
|
183
|
+
text().withId("view-1").withValue("First View"),
|
|
184
|
+
text().withId("view-2").withValue("Second View"),
|
|
185
|
+
],
|
|
186
|
+
data: {
|
|
187
|
+
user: {
|
|
188
|
+
name: "John Doe",
|
|
189
|
+
email: "john@example.com",
|
|
190
|
+
},
|
|
191
|
+
settings: {
|
|
192
|
+
theme: "dark",
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
schema: {
|
|
196
|
+
ROOT: {
|
|
197
|
+
user: {
|
|
198
|
+
type: "object",
|
|
199
|
+
},
|
|
200
|
+
settings: {
|
|
201
|
+
type: "object",
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
navigation: {
|
|
206
|
+
BEGIN: "MAIN_FLOW",
|
|
207
|
+
MAIN_FLOW: {
|
|
208
|
+
startState: "FIRST_VIEW",
|
|
209
|
+
FIRST_VIEW: {
|
|
210
|
+
state_type: "VIEW",
|
|
211
|
+
ref: "view-1",
|
|
212
|
+
transitions: {
|
|
213
|
+
NEXT: "SECOND_VIEW",
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
SECOND_VIEW: {
|
|
217
|
+
state_type: "VIEW",
|
|
218
|
+
ref: "view-2",
|
|
219
|
+
transitions: {
|
|
220
|
+
DONE: "END_STATE",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
END_STATE: {
|
|
224
|
+
state_type: "END",
|
|
225
|
+
outcome: "completed",
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const expected: Flow = {
|
|
232
|
+
data: {
|
|
233
|
+
settings: {
|
|
234
|
+
theme: "dark",
|
|
235
|
+
},
|
|
236
|
+
user: {
|
|
237
|
+
email: "john@example.com",
|
|
238
|
+
name: "John Doe",
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
id: "complex-flow",
|
|
242
|
+
navigation: {
|
|
243
|
+
BEGIN: "MAIN_FLOW",
|
|
244
|
+
MAIN_FLOW: {
|
|
245
|
+
END_STATE: {
|
|
246
|
+
outcome: "completed",
|
|
247
|
+
state_type: "END",
|
|
248
|
+
},
|
|
249
|
+
FIRST_VIEW: {
|
|
250
|
+
ref: "view-1",
|
|
251
|
+
state_type: "VIEW",
|
|
252
|
+
transitions: {
|
|
253
|
+
NEXT: "SECOND_VIEW",
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
SECOND_VIEW: {
|
|
257
|
+
ref: "view-2",
|
|
258
|
+
state_type: "VIEW",
|
|
259
|
+
transitions: {
|
|
260
|
+
DONE: "END_STATE",
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
startState: "FIRST_VIEW",
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
schema: {
|
|
267
|
+
ROOT: {
|
|
268
|
+
settings: {
|
|
269
|
+
type: "object",
|
|
270
|
+
},
|
|
271
|
+
user: {
|
|
272
|
+
type: "object",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
views: [
|
|
277
|
+
{
|
|
278
|
+
id: "view-1",
|
|
279
|
+
type: "text",
|
|
280
|
+
value: "First View",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "view-2",
|
|
284
|
+
type: "text",
|
|
285
|
+
value: "Second View",
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
expect(result).toEqual(expected);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Asset, Flow, DataModel, Navigation, Schema } from "@player-ui/types";
|
|
2
|
+
import { BaseBuildContext } from "../base-builder";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core options for creating a Player-UI Flow
|
|
6
|
+
*/
|
|
7
|
+
interface CoreFlowOptions<
|
|
8
|
+
T extends Asset = Asset,
|
|
9
|
+
C extends BaseBuildContext = BaseBuildContext,
|
|
10
|
+
> {
|
|
11
|
+
id?: string;
|
|
12
|
+
views: Array<T | { build(context?: C): T } | ((ctx: C) => T)>;
|
|
13
|
+
data?: DataModel;
|
|
14
|
+
schema?: Schema.Schema;
|
|
15
|
+
navigation: Navigation;
|
|
16
|
+
context?: C;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for creating a Player-UI Flow
|
|
21
|
+
* Allows additional properties to be passed through to the final Flow
|
|
22
|
+
*/
|
|
23
|
+
export type FlowOptions<
|
|
24
|
+
T extends Asset = Asset,
|
|
25
|
+
C extends BaseBuildContext = BaseBuildContext,
|
|
26
|
+
> = CoreFlowOptions<T, C> &
|
|
27
|
+
Omit<Flow<T>, keyof CoreFlowOptions<T, C> | "views">;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Creates a Player-UI Flow from the given options
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { flow } from './flow';
|
|
35
|
+
* import { text, autogenText } from './assets';
|
|
36
|
+
*
|
|
37
|
+
* // Basic usage (no autogen)
|
|
38
|
+
* const basicFlow = flow({
|
|
39
|
+
* id: 'example-flow',
|
|
40
|
+
* views: [text('Some Text').binding('label').id('view-1')],
|
|
41
|
+
* navigation: { ... }
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Recommended autogen usage
|
|
45
|
+
* const autogenFlow = flow({
|
|
46
|
+
* id: 'autogen-flow',
|
|
47
|
+
* autogenRunner: runner,
|
|
48
|
+
* autogenInputData: inputData, // Rich NLS data, title, subtitle, etc.
|
|
49
|
+
* views: [autogenText({ type: 'header' })],
|
|
50
|
+
* navigation: { ... }
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* // Advanced autogen usage (full control)
|
|
54
|
+
* const advancedFlow = flow({
|
|
55
|
+
* id: 'advanced-flow',
|
|
56
|
+
* autogenRunner: runner,
|
|
57
|
+
* autogenContext: customContext,
|
|
58
|
+
* views: [autogenText({ type: 'header' })],
|
|
59
|
+
* navigation: { ... }
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
function isBuilder<T>(
|
|
64
|
+
value: T | { build(context?: BaseBuildContext): T },
|
|
65
|
+
): value is { build(context?: BaseBuildContext): T } {
|
|
66
|
+
return (
|
|
67
|
+
typeof value === "object" &&
|
|
68
|
+
value !== null &&
|
|
69
|
+
"build" in value &&
|
|
70
|
+
typeof value.build === "function"
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isBuilderFunction<T>(
|
|
75
|
+
value: T | ((ctx: BaseBuildContext) => T),
|
|
76
|
+
): value is (ctx: BaseBuildContext) => T {
|
|
77
|
+
return typeof value === "function";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function flow<T extends Asset = Asset>(
|
|
81
|
+
options: FlowOptions<T>,
|
|
82
|
+
): Flow<T> {
|
|
83
|
+
const flowId = options.id || "root";
|
|
84
|
+
|
|
85
|
+
const viewsNamespace = `${flowId}-views`;
|
|
86
|
+
|
|
87
|
+
const processedViews = (() => {
|
|
88
|
+
const processViews = (): T[] => {
|
|
89
|
+
const results: T[] = [];
|
|
90
|
+
|
|
91
|
+
for (let index = 0; index < options.views.length; index++) {
|
|
92
|
+
const viewOrFn = options.views[index];
|
|
93
|
+
const ctx: BaseBuildContext = {
|
|
94
|
+
parentId: viewsNamespace,
|
|
95
|
+
branch: {
|
|
96
|
+
type: "array-item",
|
|
97
|
+
index,
|
|
98
|
+
},
|
|
99
|
+
...(options.context ?? {}),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
if (isBuilder(viewOrFn)) {
|
|
103
|
+
results.push(viewOrFn.build(ctx));
|
|
104
|
+
} else if (isBuilderFunction(viewOrFn)) {
|
|
105
|
+
results.push(viewOrFn(ctx));
|
|
106
|
+
} else {
|
|
107
|
+
results.push(viewOrFn);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return results;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// No autogen runner, process views normally
|
|
115
|
+
return processViews();
|
|
116
|
+
})();
|
|
117
|
+
|
|
118
|
+
// Extract core properties that need special handling
|
|
119
|
+
const {
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
121
|
+
views: _views,
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
123
|
+
context: _context,
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
125
|
+
autogenRunner: _autogenRunner,
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
127
|
+
autogenInputData: _autogenInputData,
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
129
|
+
autogenContext: _autogenContext,
|
|
130
|
+
...restOptions
|
|
131
|
+
} = options;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
...restOptions,
|
|
135
|
+
id: flowId,
|
|
136
|
+
views: processedViews,
|
|
137
|
+
...(options.data && { data: options.data }),
|
|
138
|
+
...(options.schema && { schema: options.schema }),
|
|
139
|
+
navigation: options.navigation,
|
|
140
|
+
};
|
|
141
|
+
}
|