@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.
Files changed (134) hide show
  1. package/dist/cjs/index.cjs +2396 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/index.legacy-esm.js +2276 -0
  4. package/dist/index.mjs +2276 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +38 -0
  7. package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
  8. package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
  9. package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
  10. package/src/core/base-builder/__tests__/registry.test.ts +534 -0
  11. package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
  12. package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
  13. package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
  14. package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
  15. package/src/core/base-builder/__tests__/switch.test.ts +815 -0
  16. package/src/core/base-builder/__tests__/template.test.ts +596 -0
  17. package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
  18. package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
  19. package/src/core/base-builder/conditional/index.ts +64 -0
  20. package/src/core/base-builder/context.ts +152 -0
  21. package/src/core/base-builder/errors.ts +69 -0
  22. package/src/core/base-builder/fluent-builder-base.ts +308 -0
  23. package/src/core/base-builder/guards.ts +137 -0
  24. package/src/core/base-builder/id/generator.ts +290 -0
  25. package/src/core/base-builder/id/registry.ts +152 -0
  26. package/src/core/base-builder/index.ts +72 -0
  27. package/src/core/base-builder/resolution/path-resolver.ts +116 -0
  28. package/src/core/base-builder/resolution/pipeline.ts +103 -0
  29. package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -0
  30. package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
  31. package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
  32. package/src/core/base-builder/resolution/steps/builders.ts +84 -0
  33. package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
  34. package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
  35. package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
  36. package/src/core/base-builder/resolution/steps/switches.ts +71 -0
  37. package/src/core/base-builder/resolution/steps/templates.ts +40 -0
  38. package/src/core/base-builder/resolution/value-resolver.ts +333 -0
  39. package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
  40. package/src/core/base-builder/storage/value-storage.ts +282 -0
  41. package/src/core/base-builder/types.ts +266 -0
  42. package/src/core/base-builder/utils.ts +10 -0
  43. package/src/core/flow/__tests__/index.test.ts +292 -0
  44. package/src/core/flow/index.ts +118 -0
  45. package/src/core/index.ts +8 -0
  46. package/src/core/mocks/generated/action.builder.ts +92 -0
  47. package/src/core/mocks/generated/choice-item.builder.ts +120 -0
  48. package/src/core/mocks/generated/choice.builder.ts +134 -0
  49. package/src/core/mocks/generated/collection.builder.ts +93 -0
  50. package/src/core/mocks/generated/field-collection.builder.ts +86 -0
  51. package/src/core/mocks/generated/index.ts +10 -0
  52. package/src/core/mocks/generated/info.builder.ts +64 -0
  53. package/src/core/mocks/generated/input.builder.ts +63 -0
  54. package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
  55. package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
  56. package/src/core/mocks/generated/text.builder.ts +47 -0
  57. package/src/core/mocks/index.ts +1 -0
  58. package/src/core/mocks/types/action.ts +92 -0
  59. package/src/core/mocks/types/choice.ts +129 -0
  60. package/src/core/mocks/types/collection.ts +140 -0
  61. package/src/core/mocks/types/info.ts +7 -0
  62. package/src/core/mocks/types/input.ts +7 -0
  63. package/src/core/mocks/types/text.ts +5 -0
  64. package/src/core/schema/__tests__/index.test.ts +127 -0
  65. package/src/core/schema/index.ts +195 -0
  66. package/src/core/schema/types.ts +7 -0
  67. package/src/core/switch/__tests__/index.test.ts +156 -0
  68. package/src/core/switch/index.ts +81 -0
  69. package/src/core/tagged-template/README.md +448 -0
  70. package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
  71. package/src/core/tagged-template/__tests__/index.test.ts +190 -0
  72. package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
  73. package/src/core/tagged-template/binding.ts +95 -0
  74. package/src/core/tagged-template/expression.ts +92 -0
  75. package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
  76. package/src/core/tagged-template/index.ts +5 -0
  77. package/src/core/tagged-template/std.ts +472 -0
  78. package/src/core/tagged-template/types.ts +123 -0
  79. package/src/core/template/__tests__/index.test.ts +380 -0
  80. package/src/core/template/index.ts +196 -0
  81. package/src/core/utils/index.ts +160 -0
  82. package/src/fp/README.md +411 -0
  83. package/src/fp/__tests__/index.test.ts +1178 -0
  84. package/src/fp/index.ts +386 -0
  85. package/src/gen/common.ts +15 -0
  86. package/src/index.ts +5 -0
  87. package/src/types.ts +203 -0
  88. package/types/core/base-builder/conditional/index.d.ts +21 -0
  89. package/types/core/base-builder/context.d.ts +39 -0
  90. package/types/core/base-builder/errors.d.ts +45 -0
  91. package/types/core/base-builder/fluent-builder-base.d.ts +147 -0
  92. package/types/core/base-builder/guards.d.ts +58 -0
  93. package/types/core/base-builder/id/generator.d.ts +69 -0
  94. package/types/core/base-builder/id/registry.d.ts +93 -0
  95. package/types/core/base-builder/index.d.ts +9 -0
  96. package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
  97. package/types/core/base-builder/resolution/pipeline.d.ts +27 -0
  98. package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
  99. package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
  100. package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
  101. package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
  102. package/types/core/base-builder/resolution/steps/nested-asset-wrappers.d.ts +14 -0
  103. package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
  104. package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
  105. package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
  106. package/types/core/base-builder/resolution/value-resolver.d.ts +62 -0
  107. package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
  108. package/types/core/base-builder/storage/value-storage.d.ts +82 -0
  109. package/types/core/base-builder/types.d.ts +183 -0
  110. package/types/core/base-builder/utils.d.ts +2 -0
  111. package/types/core/flow/index.d.ts +23 -0
  112. package/types/core/index.d.ts +8 -0
  113. package/types/core/mocks/index.d.ts +2 -0
  114. package/types/core/mocks/types/action.d.ts +58 -0
  115. package/types/core/mocks/types/choice.d.ts +95 -0
  116. package/types/core/mocks/types/collection.d.ts +102 -0
  117. package/types/core/mocks/types/info.d.ts +7 -0
  118. package/types/core/mocks/types/input.d.ts +7 -0
  119. package/types/core/mocks/types/text.d.ts +5 -0
  120. package/types/core/schema/index.d.ts +34 -0
  121. package/types/core/schema/types.d.ts +5 -0
  122. package/types/core/switch/index.d.ts +21 -0
  123. package/types/core/tagged-template/binding.d.ts +19 -0
  124. package/types/core/tagged-template/expression.d.ts +11 -0
  125. package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
  126. package/types/core/tagged-template/index.d.ts +6 -0
  127. package/types/core/tagged-template/std.d.ts +174 -0
  128. package/types/core/tagged-template/types.d.ts +69 -0
  129. package/types/core/template/index.d.ts +97 -0
  130. package/types/core/utils/index.d.ts +47 -0
  131. package/types/fp/index.d.ts +149 -0
  132. package/types/gen/common.d.ts +6 -0
  133. package/types/index.d.ts +3 -0
  134. 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
+ }