@player-tools/fluent 0.12.1--canary.241.6077
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 +2396 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2276 -0
- package/dist/index.mjs +2276 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
- package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
- package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
- package/src/core/base-builder/__tests__/registry.test.ts +534 -0
- package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
- package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
- package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
- package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
- package/src/core/base-builder/__tests__/switch.test.ts +815 -0
- package/src/core/base-builder/__tests__/template.test.ts +596 -0
- package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
- package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
- package/src/core/base-builder/conditional/index.ts +64 -0
- package/src/core/base-builder/context.ts +152 -0
- package/src/core/base-builder/errors.ts +69 -0
- package/src/core/base-builder/fluent-builder-base.ts +308 -0
- package/src/core/base-builder/guards.ts +137 -0
- package/src/core/base-builder/id/generator.ts +290 -0
- package/src/core/base-builder/id/registry.ts +152 -0
- package/src/core/base-builder/index.ts +72 -0
- package/src/core/base-builder/resolution/path-resolver.ts +116 -0
- package/src/core/base-builder/resolution/pipeline.ts +103 -0
- package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -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 +84 -0
- package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
- package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
- package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
- package/src/core/base-builder/resolution/steps/switches.ts +71 -0
- package/src/core/base-builder/resolution/steps/templates.ts +40 -0
- package/src/core/base-builder/resolution/value-resolver.ts +333 -0
- package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
- package/src/core/base-builder/storage/value-storage.ts +282 -0
- package/src/core/base-builder/types.ts +266 -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 +118 -0
- package/src/core/index.ts +8 -0
- package/src/core/mocks/generated/action.builder.ts +92 -0
- package/src/core/mocks/generated/choice-item.builder.ts +120 -0
- package/src/core/mocks/generated/choice.builder.ts +134 -0
- package/src/core/mocks/generated/collection.builder.ts +93 -0
- package/src/core/mocks/generated/field-collection.builder.ts +86 -0
- package/src/core/mocks/generated/index.ts +10 -0
- package/src/core/mocks/generated/info.builder.ts +64 -0
- package/src/core/mocks/generated/input.builder.ts +63 -0
- package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
- package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
- package/src/core/mocks/generated/text.builder.ts +47 -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 +81 -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 +196 -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 +15 -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/errors.d.ts +45 -0
- package/types/core/base-builder/fluent-builder-base.d.ts +147 -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 +9 -0
- package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
- package/types/core/base-builder/resolution/pipeline.d.ts +27 -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/nested-asset-wrappers.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 +62 -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 +183 -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 +6 -0
- package/types/index.d.ts +3 -0
- package/types/types.d.ts +163 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Asset } from "@player-ui/types";
|
|
2
|
+
import type { FluentBuilder, BaseBuildContext } from "../types";
|
|
3
|
+
import { isFluentBuilder, isAsset, isAssetWrapperValue } from "../guards";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves a value or function to its final value
|
|
7
|
+
*
|
|
8
|
+
* Generic helper that unwraps functions to their return values.
|
|
9
|
+
* Handles both simple functions and ConditionalValue types.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveValueOrFunction<V>(value: V | (() => V)): V {
|
|
12
|
+
if (typeof value === "function" && !isFluentBuilder(value)) {
|
|
13
|
+
// SAFETY: We've checked it's a function and not a FluentBuilder,
|
|
14
|
+
// so it must be a function returning V
|
|
15
|
+
return (value as () => V)();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return value as V;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type guard to check if a value should be wrapped in AssetWrapper format
|
|
23
|
+
*/
|
|
24
|
+
export function shouldWrapInAssetWrapper(
|
|
25
|
+
value: unknown,
|
|
26
|
+
): value is
|
|
27
|
+
| FluentBuilder<unknown, BaseBuildContext>
|
|
28
|
+
| Asset
|
|
29
|
+
| Array<FluentBuilder<unknown, BaseBuildContext> | Asset> {
|
|
30
|
+
// Don't wrap if already wrapped (has 'asset' property but not 'type')
|
|
31
|
+
if (isAssetWrapperValue(value)) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Wrap FluentBuilders
|
|
36
|
+
if (isFluentBuilder(value)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Wrap Assets (objects with 'type' property)
|
|
41
|
+
if (isAsset(value)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Wrap arrays of builders/assets
|
|
46
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
47
|
+
const firstItem = value[0];
|
|
48
|
+
return isFluentBuilder(firstItem) || isAsset(firstItem);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Wraps a value in AssetWrapper format if needed
|
|
56
|
+
* This enables if() and ifElse() to work with unwrapped asset builders
|
|
57
|
+
*/
|
|
58
|
+
export function maybeWrapAsset<V>(value: V): V | { asset: V } {
|
|
59
|
+
if (shouldWrapInAssetWrapper(value)) {
|
|
60
|
+
return { asset: value };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { determineSlotName, genId, peekId } from "./id/generator";
|
|
2
|
+
import {
|
|
3
|
+
BranchTypes,
|
|
4
|
+
type BaseBuildContext,
|
|
5
|
+
type NestedContextParams,
|
|
6
|
+
type AssetMetadata,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
function buildContextParams<C extends BaseBuildContext>(
|
|
10
|
+
parentContext: C,
|
|
11
|
+
parameterName: string,
|
|
12
|
+
index?: number,
|
|
13
|
+
): NestedContextParams<C> {
|
|
14
|
+
const params: NestedContextParams<C> = {
|
|
15
|
+
parentContext,
|
|
16
|
+
parameterName,
|
|
17
|
+
};
|
|
18
|
+
if (index !== undefined) {
|
|
19
|
+
return { ...params, index };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return params;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates a nested context for child builders with automatic ID generation.
|
|
27
|
+
*
|
|
28
|
+
* 1. Determines the appropriate slot name from the parameter name
|
|
29
|
+
* 2. Generates a unique ID using the slot name
|
|
30
|
+
* 3. Creates a new context with the generated ID
|
|
31
|
+
*
|
|
32
|
+
* Use this when you want automatic ID generation for nested builders.
|
|
33
|
+
* For manual context manipulation, use object spreading directly.
|
|
34
|
+
*/
|
|
35
|
+
export function createNestedContext<C extends BaseBuildContext>({
|
|
36
|
+
parentContext,
|
|
37
|
+
parameterName,
|
|
38
|
+
index,
|
|
39
|
+
assetMetadata,
|
|
40
|
+
}: {
|
|
41
|
+
readonly parentContext: C;
|
|
42
|
+
readonly parameterName: string;
|
|
43
|
+
readonly index?: number;
|
|
44
|
+
readonly assetMetadata?: AssetMetadata;
|
|
45
|
+
}): C {
|
|
46
|
+
if (parentContext.nestedContextGenerator) {
|
|
47
|
+
const contextParams = buildContextParams(
|
|
48
|
+
parentContext,
|
|
49
|
+
parameterName,
|
|
50
|
+
index,
|
|
51
|
+
);
|
|
52
|
+
const generated = parentContext.nestedContextGenerator(contextParams);
|
|
53
|
+
// Safe assertion: nestedContextGenerator returns the same context type
|
|
54
|
+
return generated as C;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const slotName = determineSlotName(parameterName, assetMetadata);
|
|
58
|
+
|
|
59
|
+
let generatedId: string | undefined;
|
|
60
|
+
let branch: BaseBuildContext["branch"];
|
|
61
|
+
|
|
62
|
+
if (parentContext && "parentId" in parentContext) {
|
|
63
|
+
// For array items, use peekId to avoid registering the same slot multiple times
|
|
64
|
+
// For non-array items, use genId to register the slot
|
|
65
|
+
if (index !== undefined) {
|
|
66
|
+
generatedId = peekId({
|
|
67
|
+
parentId: parentContext.parentId,
|
|
68
|
+
branch: { type: BranchTypes.SLOT, name: slotName },
|
|
69
|
+
});
|
|
70
|
+
branch = { type: BranchTypes.ARRAY_ITEM, index };
|
|
71
|
+
} else {
|
|
72
|
+
generatedId = genId({
|
|
73
|
+
parentId: parentContext.parentId,
|
|
74
|
+
branch: { type: BranchTypes.SLOT, name: slotName },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const newContext: BaseBuildContext =
|
|
80
|
+
index !== undefined
|
|
81
|
+
? {
|
|
82
|
+
...parentContext,
|
|
83
|
+
parameterName,
|
|
84
|
+
index,
|
|
85
|
+
...(generatedId ? { parentId: generatedId } : {}),
|
|
86
|
+
...(assetMetadata ? { assetMetadata } : {}),
|
|
87
|
+
...(branch ? { branch } : {}),
|
|
88
|
+
}
|
|
89
|
+
: {
|
|
90
|
+
...parentContext,
|
|
91
|
+
parameterName,
|
|
92
|
+
...(generatedId ? { parentId: generatedId } : {}),
|
|
93
|
+
...(assetMetadata ? { assetMetadata } : {}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Safe assertion: newContext extends BaseBuildContext with additional properties
|
|
97
|
+
return newContext as C;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Creates a template context for template value generation.
|
|
102
|
+
*
|
|
103
|
+
* Template contexts use a special template branch with depth tracking
|
|
104
|
+
* to support nested templates (_index_, _index1_, _index2_, etc.).
|
|
105
|
+
*/
|
|
106
|
+
export function createTemplateContext<C extends BaseBuildContext>({
|
|
107
|
+
parentContext,
|
|
108
|
+
depth = 0,
|
|
109
|
+
}: {
|
|
110
|
+
readonly parentContext: C;
|
|
111
|
+
readonly depth?: number;
|
|
112
|
+
}): C {
|
|
113
|
+
const parentId = genId(parentContext);
|
|
114
|
+
|
|
115
|
+
const templateContext: BaseBuildContext = {
|
|
116
|
+
...parentContext,
|
|
117
|
+
parentId,
|
|
118
|
+
branch: {
|
|
119
|
+
type: BranchTypes.TEMPLATE,
|
|
120
|
+
depth,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return templateContext as C;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a switch context for switch case asset generation.
|
|
129
|
+
*
|
|
130
|
+
* Switch contexts use a special switch branch with index tracking and kind
|
|
131
|
+
* to support sequential case indexing across all switches.
|
|
132
|
+
*/
|
|
133
|
+
export function createSwitchContext<C extends BaseBuildContext>({
|
|
134
|
+
parentContext,
|
|
135
|
+
index,
|
|
136
|
+
kind,
|
|
137
|
+
}: {
|
|
138
|
+
readonly parentContext: C;
|
|
139
|
+
readonly index: number;
|
|
140
|
+
readonly kind: "static" | "dynamic";
|
|
141
|
+
}): C {
|
|
142
|
+
const switchContext: BaseBuildContext = {
|
|
143
|
+
...parentContext,
|
|
144
|
+
branch: {
|
|
145
|
+
type: BranchTypes.SWITCH,
|
|
146
|
+
index,
|
|
147
|
+
kind,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return switchContext as C;
|
|
152
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for fluent builder errors.
|
|
3
|
+
* Use these codes for programmatic error handling.
|
|
4
|
+
*/
|
|
5
|
+
export const ErrorCodes = {
|
|
6
|
+
/** Context is missing when required */
|
|
7
|
+
MISSING_CONTEXT: "FLUENT_MISSING_CONTEXT",
|
|
8
|
+
/** Invalid branch type in ID generation */
|
|
9
|
+
INVALID_BRANCH: "FLUENT_INVALID_BRANCH",
|
|
10
|
+
/** Template produced no output */
|
|
11
|
+
TEMPLATE_NO_OUTPUT: "FLUENT_TEMPLATE_NO_OUTPUT",
|
|
12
|
+
/** Invalid path in value resolution */
|
|
13
|
+
INVALID_PATH: "FLUENT_INVALID_PATH",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Type for error codes
|
|
18
|
+
*/
|
|
19
|
+
export type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Custom error class for fluent builder errors.
|
|
23
|
+
* Provides structured error information with error codes.
|
|
24
|
+
*/
|
|
25
|
+
export class FluentError extends Error {
|
|
26
|
+
readonly code: ErrorCode;
|
|
27
|
+
readonly context?: Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
code: ErrorCode,
|
|
31
|
+
message: string,
|
|
32
|
+
context?: Record<string, unknown>,
|
|
33
|
+
) {
|
|
34
|
+
const contextStr = context ? ` Context: ${JSON.stringify(context)}` : "";
|
|
35
|
+
super(`[${code}] ${message}${contextStr}`);
|
|
36
|
+
this.name = "FluentError";
|
|
37
|
+
this.code = code;
|
|
38
|
+
this.context = context;
|
|
39
|
+
|
|
40
|
+
// Maintains proper stack trace in V8 environments
|
|
41
|
+
if (Error.captureStackTrace) {
|
|
42
|
+
Error.captureStackTrace(this, FluentError);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Creates a FluentError with the given code and message.
|
|
49
|
+
* This is a convenience function for creating errors.
|
|
50
|
+
*
|
|
51
|
+
* @param code - The error code from ErrorCodes
|
|
52
|
+
* @param message - Human-readable error message
|
|
53
|
+
* @param context - Optional context object with additional details
|
|
54
|
+
* @returns A FluentError instance
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* throw createFluentError(
|
|
58
|
+
* ErrorCodes.MISSING_CONTEXT,
|
|
59
|
+
* "Context is required for template resolution",
|
|
60
|
+
* { templateCount: 3 }
|
|
61
|
+
* );
|
|
62
|
+
*/
|
|
63
|
+
export function createFluentError(
|
|
64
|
+
code: ErrorCode,
|
|
65
|
+
message: string,
|
|
66
|
+
context?: Record<string, unknown>,
|
|
67
|
+
): FluentError {
|
|
68
|
+
return new FluentError(code, message, context);
|
|
69
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import type { template as easyDslTemplate } from "..";
|
|
2
|
+
import {
|
|
3
|
+
type BaseBuildContext,
|
|
4
|
+
type ConditionalValue,
|
|
5
|
+
type SwitchMetadata,
|
|
6
|
+
type FluentPartial,
|
|
7
|
+
FLUENT_BUILDER_SYMBOL,
|
|
8
|
+
StorageKeys,
|
|
9
|
+
} from "./types";
|
|
10
|
+
import { ValueStorage } from "./storage/value-storage";
|
|
11
|
+
import { AuxiliaryStorage } from "./storage/auxiliary-storage";
|
|
12
|
+
import { executeBuildPipeline } from "./resolution/pipeline";
|
|
13
|
+
import { resolveValueOrFunction, maybeWrapAsset } from "./conditional";
|
|
14
|
+
import { switch_ } from "../switch";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Base class for all generated builders
|
|
18
|
+
* Provides core functionality for the builder pattern with Player UI-specific features
|
|
19
|
+
*
|
|
20
|
+
* This class delegates to specialized components:
|
|
21
|
+
* - ValueStorage: Manages property values, builders, and mixed arrays
|
|
22
|
+
* - AuxiliaryStorage: Manages metadata (templates, switches)
|
|
23
|
+
* - Build Pipeline: Orchestrates the 8-step build process
|
|
24
|
+
* - Conditional Logic: Handles if/ifElse with auto-wrapping
|
|
25
|
+
*/
|
|
26
|
+
export abstract class FluentBuilderBase<
|
|
27
|
+
T,
|
|
28
|
+
C extends BaseBuildContext = BaseBuildContext,
|
|
29
|
+
> {
|
|
30
|
+
readonly [FLUENT_BUILDER_SYMBOL]: true = true as const;
|
|
31
|
+
|
|
32
|
+
protected valueStorage: ValueStorage<T>;
|
|
33
|
+
protected auxiliaryStorage: AuxiliaryStorage;
|
|
34
|
+
protected context?: C;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new builder instance
|
|
38
|
+
* @param initial - Optional initial values (accepts FluentPartial for TaggedTemplateValue support)
|
|
39
|
+
*/
|
|
40
|
+
constructor(initial?: FluentPartial<T, C>) {
|
|
41
|
+
this.valueStorage = new ValueStorage(initial as Partial<T>);
|
|
42
|
+
this.auxiliaryStorage = new AuxiliaryStorage();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Accessor for generated builders to access values
|
|
47
|
+
* This maintains backward compatibility with generated code
|
|
48
|
+
*/
|
|
49
|
+
protected get values(): Partial<T> {
|
|
50
|
+
return this.valueStorage.getValues() as Partial<T>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Setter for generated builders to set values in bulk
|
|
55
|
+
* Used by withAdditionalProperties() in generated builders
|
|
56
|
+
*/
|
|
57
|
+
protected set values(newValues: Partial<T>) {
|
|
58
|
+
Object.entries(newValues).forEach(([key, value]) => {
|
|
59
|
+
this.valueStorage.set(key as keyof T, value);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Sets additional properties in bulk that may not be defined in the type schema
|
|
65
|
+
* Useful for extensibility and forward compatibility
|
|
66
|
+
*/
|
|
67
|
+
public withAdditionalProperties(values: Partial<T>): this {
|
|
68
|
+
this.values = values;
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sets a property value, intelligently routing it to the appropriate storage
|
|
74
|
+
* @param key - The property key to set
|
|
75
|
+
* @param value - The value to set
|
|
76
|
+
* @returns This builder instance for chaining
|
|
77
|
+
*/
|
|
78
|
+
protected set<K extends keyof T>(key: K, value: unknown): this {
|
|
79
|
+
this.valueStorage.set(key, value);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Builds the final object with defaults and nested builder resolution
|
|
85
|
+
* Executes the complete 8-step build pipeline
|
|
86
|
+
*
|
|
87
|
+
* @param defaults - Optional default values
|
|
88
|
+
* @param context - Optional build context
|
|
89
|
+
*/
|
|
90
|
+
protected buildWithDefaults(defaults?: Partial<T>, context?: C): T {
|
|
91
|
+
const arrayProperties = this.getArrayPropertiesMetadata();
|
|
92
|
+
const assetWrapperPaths = this.getAssetWrapperPathsMetadata();
|
|
93
|
+
return executeBuildPipeline(
|
|
94
|
+
this.valueStorage,
|
|
95
|
+
this.auxiliaryStorage,
|
|
96
|
+
defaults,
|
|
97
|
+
context,
|
|
98
|
+
arrayProperties,
|
|
99
|
+
assetWrapperPaths,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets array property metadata from the builder class
|
|
105
|
+
* Generated builders include a static __arrayProperties__ set
|
|
106
|
+
*/
|
|
107
|
+
private getArrayPropertiesMetadata(): ReadonlySet<string> {
|
|
108
|
+
const constructor = this.constructor as typeof FluentBuilderBase & {
|
|
109
|
+
__arrayProperties__?: ReadonlySet<string>;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return constructor.__arrayProperties__ ?? new Set();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Gets asset wrapper paths metadata from the builder class.
|
|
117
|
+
* Generated builders include a static __assetWrapperPaths__ array of paths.
|
|
118
|
+
* Each path is an array of property names leading to an AssetWrapper.
|
|
119
|
+
*/
|
|
120
|
+
protected getAssetWrapperPathsMetadata(): ReadonlyArray<
|
|
121
|
+
ReadonlyArray<string>
|
|
122
|
+
> {
|
|
123
|
+
const constructor = this.constructor as typeof FluentBuilderBase & {
|
|
124
|
+
__assetWrapperPaths__?: ReadonlyArray<ReadonlyArray<string>>;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return constructor.__assetWrapperPaths__ ?? [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Conditionally sets a property based on a predicate
|
|
132
|
+
*
|
|
133
|
+
* Accepts unwrapped Asset builders and automatically wraps them in AssetWrapper format.
|
|
134
|
+
*
|
|
135
|
+
* @param predicate - Function to determine if the property should be set
|
|
136
|
+
* @param property - The property key
|
|
137
|
+
* @param value - The value, builder, or function returning either
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* action()
|
|
141
|
+
* .if(() => showLabel, "label", text().withValue("Submit"))
|
|
142
|
+
*/
|
|
143
|
+
public if<K extends keyof T>(
|
|
144
|
+
predicate: (builder: this) => boolean,
|
|
145
|
+
property: K,
|
|
146
|
+
value: ConditionalValue<T[K], C>,
|
|
147
|
+
): this {
|
|
148
|
+
if (predicate(this)) {
|
|
149
|
+
const resolved = resolveValueOrFunction(value);
|
|
150
|
+
const wrapped = maybeWrapAsset(resolved);
|
|
151
|
+
|
|
152
|
+
// SAFETY: Type assertion is necessary and safe here because:
|
|
153
|
+
// 1. `maybeWrapAsset` uses type guards to check if wrapping is needed
|
|
154
|
+
// 2. For AssetWrapper properties, unwrapped builders are wrapped as { asset: builder }
|
|
155
|
+
// 3. For other properties, values pass through unchanged
|
|
156
|
+
// 4. The runtime behavior ensures type safety that TypeScript can't statically verify
|
|
157
|
+
this.set(property, wrapped as T[K]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Conditionally sets a property choosing between two values
|
|
165
|
+
*
|
|
166
|
+
* Accepts unwrapped Asset builders and automatically wraps them in AssetWrapper format.
|
|
167
|
+
*
|
|
168
|
+
* @param predicate - Function to determine which value to use
|
|
169
|
+
* @param property - The property key
|
|
170
|
+
* @param trueValue - Value to use if predicate is true
|
|
171
|
+
* @param falseValue - Value to use if predicate is false
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* action()
|
|
175
|
+
* .ifElse(
|
|
176
|
+
* () => isActive,
|
|
177
|
+
* "label",
|
|
178
|
+
* text().withValue("Deactivate"),
|
|
179
|
+
* text().withValue("Activate")
|
|
180
|
+
* )
|
|
181
|
+
*/
|
|
182
|
+
public ifElse<K extends keyof T>(
|
|
183
|
+
predicate: (builder: this) => boolean,
|
|
184
|
+
property: K,
|
|
185
|
+
trueValue: ConditionalValue<T[K], C>,
|
|
186
|
+
falseValue: ConditionalValue<T[K], C>,
|
|
187
|
+
): this {
|
|
188
|
+
const valueToUse = predicate(this) ? trueValue : falseValue;
|
|
189
|
+
const resolved = resolveValueOrFunction(valueToUse);
|
|
190
|
+
const wrapped = maybeWrapAsset(resolved);
|
|
191
|
+
|
|
192
|
+
// SAFETY: Type assertion is necessary and safe here because:
|
|
193
|
+
// 1. `maybeWrapAsset` uses type guards to check if wrapping is needed
|
|
194
|
+
// 2. For AssetWrapper properties, unwrapped builders are wrapped as { asset: builder }
|
|
195
|
+
// 3. For other properties, values pass through unchanged
|
|
196
|
+
// 4. The runtime behavior ensures type safety that TypeScript can't statically verify
|
|
197
|
+
this.set(property, wrapped as T[K]);
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Checks if a property has been set
|
|
203
|
+
*/
|
|
204
|
+
public has<K extends keyof T>(key: K): boolean {
|
|
205
|
+
return this.valueStorage.has(key);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Peeks at a property value without resolving builders
|
|
210
|
+
*/
|
|
211
|
+
public peek<K extends keyof T>(key: K): T[K] | undefined {
|
|
212
|
+
return this.valueStorage.peek(key);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Gets builder for a property if one is set
|
|
217
|
+
*/
|
|
218
|
+
public peekBuilder<K extends keyof T>(
|
|
219
|
+
key: K,
|
|
220
|
+
): import("./types").FluentBuilder<T[K], C> | undefined {
|
|
221
|
+
return this.valueStorage.peekBuilder<K, C>(key);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Gets the type of value stored for a property
|
|
226
|
+
*/
|
|
227
|
+
public getValueType<K extends keyof T>(
|
|
228
|
+
key: K,
|
|
229
|
+
): "static" | "builder" | "mixed-array" | "unset" {
|
|
230
|
+
return this.valueStorage.getValueType(key);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Clones the builder, creating an independent copy with the same state.
|
|
235
|
+
*
|
|
236
|
+
* Note: This method requires that the builder class has a constructor that
|
|
237
|
+
* accepts an optional `initial` parameter (like all generated builders do).
|
|
238
|
+
* If your custom builder has required constructor parameters, you must
|
|
239
|
+
* override this method.
|
|
240
|
+
*/
|
|
241
|
+
public clone(): this {
|
|
242
|
+
// Generated builders have constructor signature: constructor(initial?: Partial<T>)
|
|
243
|
+
// We create a new instance and copy the internal state
|
|
244
|
+
const BuilderClass = this.constructor as new (initial?: unknown) => this;
|
|
245
|
+
|
|
246
|
+
// Create new instance (passing undefined is safe for generated builders)
|
|
247
|
+
let cloned: this;
|
|
248
|
+
try {
|
|
249
|
+
cloned = new BuilderClass();
|
|
250
|
+
} catch (error) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
`clone() failed: Builder class "${this.constructor.name}" requires constructor arguments. ` +
|
|
253
|
+
`Override clone() in your subclass to handle this case.`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
cloned.valueStorage = this.valueStorage.clone();
|
|
258
|
+
cloned.auxiliaryStorage = this.auxiliaryStorage.clone();
|
|
259
|
+
if (this.context) {
|
|
260
|
+
cloned.context = this.context;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return cloned;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Unsets a property, removing it from the builder
|
|
268
|
+
*/
|
|
269
|
+
public unset<K extends keyof T>(key: K): this {
|
|
270
|
+
this.valueStorage.unset(key);
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Clears all properties from the builder, resetting it to initial state
|
|
276
|
+
*/
|
|
277
|
+
public clear(): this {
|
|
278
|
+
this.valueStorage.clear();
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Adds a template to this builder
|
|
284
|
+
*/
|
|
285
|
+
public template(templateFn: ReturnType<typeof easyDslTemplate>): this {
|
|
286
|
+
this.auxiliaryStorage.push(StorageKeys.TEMPLATES, templateFn);
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Adds a switch to this builder at a specific path
|
|
292
|
+
*/
|
|
293
|
+
public switch(
|
|
294
|
+
path: ReadonlyArray<string | number>,
|
|
295
|
+
switchFnArgs: Parameters<typeof switch_>[0],
|
|
296
|
+
): this {
|
|
297
|
+
this.auxiliaryStorage.push<SwitchMetadata<C>>(StorageKeys.SWITCHES, {
|
|
298
|
+
path,
|
|
299
|
+
switchFn: switch_(switchFnArgs),
|
|
300
|
+
});
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Abstract build method to be implemented by generated builders
|
|
306
|
+
*/
|
|
307
|
+
abstract build(context?: C): T;
|
|
308
|
+
}
|