@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,82 @@
1
+ /**
2
+ * Manages auxiliary data storage for builders
3
+ *
4
+ * Auxiliary data is metadata that doesn't appear in the final built object
5
+ * but is used during the build process. This includes:
6
+ * - Templates (stored under "__templates__")
7
+ * - Switches (stored under "__switches__")
8
+ * - Custom metadata
9
+ *
10
+ * This separation keeps builder state clean and makes the build process more explicit
11
+ */
12
+ export class AuxiliaryStorage {
13
+ private data: Map<string, unknown> = new Map();
14
+
15
+ /**
16
+ * Sets auxiliary data with a given key
17
+ */
18
+ set<T>(key: string, value: T): void {
19
+ this.data.set(key, value);
20
+ }
21
+
22
+ /**
23
+ * Gets auxiliary data with type assertion
24
+ * The caller is responsible for knowing the correct type
25
+ */
26
+ get<T>(key: string): T | undefined {
27
+ const value = this.data.get(key);
28
+ return value as T | undefined;
29
+ }
30
+
31
+ /**
32
+ * Pushes an item to an auxiliary data array
33
+ * Creates the array if it doesn't exist
34
+ */
35
+ push<T>(key: string, item: T): void {
36
+ const existing = this.data.get(key);
37
+ if (Array.isArray(existing)) {
38
+ existing.push(item);
39
+ } else {
40
+ this.data.set(key, [item]);
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Gets an auxiliary data array
46
+ * Returns empty array if doesn't exist or isn't an array
47
+ */
48
+ getArray<T>(key: string): T[] {
49
+ const existing = this.data.get(key);
50
+ return Array.isArray(existing) ? existing : [];
51
+ }
52
+
53
+ /**
54
+ * Checks if auxiliary data exists for a key
55
+ */
56
+ has(key: string): boolean {
57
+ return this.data.has(key);
58
+ }
59
+
60
+ /**
61
+ * Deletes auxiliary data for a key
62
+ */
63
+ delete(key: string): boolean {
64
+ return this.data.delete(key);
65
+ }
66
+
67
+ /**
68
+ * Clears all auxiliary data
69
+ */
70
+ clear(): void {
71
+ this.data.clear();
72
+ }
73
+
74
+ /**
75
+ * Clones the auxiliary storage, creating an independent copy
76
+ */
77
+ clone(): AuxiliaryStorage {
78
+ const cloned = new AuxiliaryStorage();
79
+ cloned.data = new Map(this.data);
80
+ return cloned;
81
+ }
82
+ }
@@ -0,0 +1,282 @@
1
+ import type {
2
+ BaseBuildContext,
3
+ FluentBuilder,
4
+ MixedArrayMetadata,
5
+ } from "../types";
6
+ import { isFluentBuilder } from "../guards";
7
+
8
+ /**
9
+ * Manages storage for builder property values
10
+ *
11
+ * Values are stored in three different maps based on their type:
12
+ * - values: Static values (strings, numbers, plain objects without builders)
13
+ * - builders: FluentBuilder instances and objects containing builders
14
+ * - mixedArrays: Arrays containing both static values and builders
15
+ *
16
+ * This separation allows efficient resolution during the build process
17
+ */
18
+ export class ValueStorage<T> {
19
+ private values: Partial<T> = {};
20
+ private builders: Map<
21
+ string,
22
+ FluentBuilder<unknown, BaseBuildContext> | Record<string, unknown>
23
+ > = new Map();
24
+ private mixedArrays: Map<string, MixedArrayMetadata> = new Map();
25
+
26
+ constructor(initial?: Partial<T>) {
27
+ if (initial) {
28
+ Object.entries(initial).forEach(([key, value]) => {
29
+ this.set(key as keyof T, value);
30
+ });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Sets a property value, intelligently routing it to the appropriate storage
36
+ *
37
+ * This method performs runtime type checking to determine how to store the value:
38
+ * - FluentBuilder instances → builders Map
39
+ * - Arrays with builders → mixedArrays Map
40
+ * - Objects with builders → builders Map
41
+ * - Everything else → values (static storage)
42
+ */
43
+ set<K extends keyof T>(key: K, value: unknown): void {
44
+ const keyStr = String(key);
45
+
46
+ if (isFluentBuilder(value)) {
47
+ this.builders.set(keyStr, value);
48
+ delete this.values[key];
49
+ this.mixedArrays.delete(keyStr);
50
+ } else if (Array.isArray(value)) {
51
+ this.handleArrayValue(key, keyStr, value);
52
+ } else if (
53
+ typeof value === "object" &&
54
+ value !== null &&
55
+ this.containsBuilder(value)
56
+ ) {
57
+ this.builders.set(keyStr, value as Record<string, unknown>);
58
+ delete this.values[key];
59
+ this.mixedArrays.delete(keyStr);
60
+ } else {
61
+ this.setStaticValue(key, keyStr, value);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Handles array value storage, detecting mixed arrays with builders
67
+ */
68
+ private handleArrayValue<K extends keyof T>(
69
+ key: K,
70
+ keyStr: string,
71
+ value: readonly unknown[],
72
+ ): void {
73
+ const builderIndices = new Set<number>();
74
+ const objectIndices = new Set<number>();
75
+
76
+ value.forEach((item, index) => {
77
+ if (isFluentBuilder(item)) {
78
+ builderIndices.add(index);
79
+ } else if (
80
+ typeof item === "object" &&
81
+ item !== null &&
82
+ this.containsBuilder(item)
83
+ ) {
84
+ objectIndices.add(index);
85
+ }
86
+ });
87
+
88
+ const hasBuilders = builderIndices.size > 0 || objectIndices.size > 0;
89
+
90
+ if (hasBuilders) {
91
+ this.mixedArrays.set(keyStr, {
92
+ array: value,
93
+ builderIndices,
94
+ objectIndices,
95
+ });
96
+ delete this.values[key];
97
+ } else {
98
+ this.setStaticValue(key, keyStr, value);
99
+ this.mixedArrays.delete(keyStr);
100
+ }
101
+
102
+ this.builders.delete(keyStr);
103
+ }
104
+
105
+ /**
106
+ * Sets a static value with proper type handling
107
+ */
108
+ private setStaticValue<K extends keyof T>(
109
+ key: K,
110
+ keyStr: string,
111
+ value: unknown,
112
+ ): void {
113
+ // For known types in T, TypeScript will validate at compile time
114
+ // At runtime, we trust the caller has provided a compatible type
115
+ this.values[key] = value as T[K];
116
+ this.builders.delete(keyStr);
117
+ this.mixedArrays.delete(keyStr);
118
+ }
119
+
120
+ /**
121
+ * Checks if an object contains any builders recursively
122
+ * Handles circular references gracefully
123
+ */
124
+ private containsBuilder(
125
+ obj: unknown,
126
+ visited: WeakSet<object> = new WeakSet(),
127
+ ): boolean {
128
+ if (isFluentBuilder(obj)) return true;
129
+
130
+ if (!obj || typeof obj !== "object") return false;
131
+
132
+ if (visited.has(obj)) return false;
133
+ visited.add(obj);
134
+
135
+ if (Array.isArray(obj)) {
136
+ return obj.some((item) => this.containsBuilder(item, visited));
137
+ }
138
+
139
+ const proto = Object.getPrototypeOf(obj);
140
+ if (proto === Object.prototype || proto === null) {
141
+ return Object.values(obj).some((val) =>
142
+ this.containsBuilder(val, visited),
143
+ );
144
+ }
145
+
146
+ return false;
147
+ }
148
+
149
+ /**
150
+ * Checks if a property has been set
151
+ */
152
+ has<K extends keyof T>(key: K): boolean {
153
+ const keyStr = String(key);
154
+ return (
155
+ key in this.values ||
156
+ this.builders.has(keyStr) ||
157
+ this.mixedArrays.has(keyStr)
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Peeks at a property value without resolving builders
163
+ * Returns the raw value if it's static, the array if it's mixed, or undefined if it's a builder
164
+ */
165
+ peek<K extends keyof T>(key: K): T[K] | undefined {
166
+ const keyStr = String(key);
167
+
168
+ const mixedArray = this.mixedArrays.get(keyStr);
169
+ if (mixedArray) {
170
+ return mixedArray.array as T[K];
171
+ }
172
+
173
+ if (this.builders.has(keyStr)) {
174
+ return undefined;
175
+ }
176
+
177
+ return this.values[key];
178
+ }
179
+
180
+ /**
181
+ * Gets builder for a property if one is set
182
+ */
183
+ peekBuilder<K extends keyof T, C extends BaseBuildContext>(
184
+ key: K,
185
+ ): FluentBuilder<T[K], C> | undefined {
186
+ const keyStr = String(key);
187
+ const entry = this.builders.get(keyStr);
188
+ if (!entry) return undefined;
189
+
190
+ if (isFluentBuilder<T[K], C>(entry)) {
191
+ return entry;
192
+ }
193
+
194
+ return undefined;
195
+ }
196
+
197
+ /**
198
+ * Gets the type of value stored for a property
199
+ */
200
+ getValueType<K extends keyof T>(
201
+ key: K,
202
+ ): "static" | "builder" | "mixed-array" | "unset" {
203
+ const keyStr = String(key);
204
+
205
+ if (this.mixedArrays.has(keyStr)) {
206
+ return "mixed-array";
207
+ }
208
+
209
+ if (this.builders.has(keyStr)) {
210
+ return "builder";
211
+ }
212
+
213
+ if (key in this.values) {
214
+ return "static";
215
+ }
216
+
217
+ return "unset";
218
+ }
219
+
220
+ /**
221
+ * Gets all static values for the build pipeline
222
+ */
223
+ getValues(): Readonly<Partial<T>> {
224
+ return this.values;
225
+ }
226
+
227
+ /**
228
+ * Gets all builder entries for the build pipeline
229
+ */
230
+ getBuilders(): ReadonlyMap<
231
+ string,
232
+ FluentBuilder<unknown, BaseBuildContext> | Record<string, unknown>
233
+ > {
234
+ return this.builders;
235
+ }
236
+
237
+ /**
238
+ * Gets all mixed array entries for the build pipeline
239
+ */
240
+ getMixedArrays(): ReadonlyMap<string, MixedArrayMetadata> {
241
+ return this.mixedArrays;
242
+ }
243
+
244
+ /**
245
+ * Unsets a property, removing it from storage
246
+ */
247
+ unset<K extends keyof T>(key: K): void {
248
+ const keyStr = String(key);
249
+ delete this.values[key];
250
+ this.builders.delete(keyStr);
251
+ this.mixedArrays.delete(keyStr);
252
+ }
253
+
254
+ /**
255
+ * Clears all properties from storage
256
+ */
257
+ clear(): void {
258
+ this.values = {};
259
+ this.builders.clear();
260
+ this.mixedArrays.clear();
261
+ }
262
+
263
+ /**
264
+ * Clones the storage, creating an independent copy
265
+ */
266
+ clone(): ValueStorage<T> {
267
+ const cloned = new ValueStorage<T>();
268
+ cloned.values = { ...this.values };
269
+ cloned.builders = new Map(this.builders);
270
+ cloned.mixedArrays = new Map(
271
+ Array.from(this.mixedArrays.entries()).map(([key, metadata]) => [
272
+ key,
273
+ {
274
+ array: metadata.array,
275
+ builderIndices: new Set(metadata.builderIndices),
276
+ objectIndices: new Set(metadata.objectIndices),
277
+ },
278
+ ]),
279
+ );
280
+ return cloned;
281
+ }
282
+ }
@@ -0,0 +1,266 @@
1
+ import { Asset, AssetWrapper } from "@player-ui/types";
2
+ import type { TaggedTemplateValue } from "../tagged-template/types";
3
+
4
+ /**
5
+ * Unique symbol to identify FluentBuilder instances
6
+ * Used for runtime type checking of builder objects
7
+ */
8
+ export const FLUENT_BUILDER_SYMBOL: unique symbol =
9
+ Symbol.for("fluent-builder");
10
+
11
+ /**
12
+ * Constants for branch type discriminators
13
+ * Use these instead of string literals to prevent typos
14
+ */
15
+ export const BranchTypes = {
16
+ SLOT: "slot",
17
+ ARRAY_ITEM: "array-item",
18
+ TEMPLATE: "template",
19
+ SWITCH: "switch",
20
+ CUSTOM: "custom",
21
+ } as const;
22
+
23
+ /**
24
+ * Constants for internal storage keys
25
+ * Used by AuxiliaryStorage to store templates and switches
26
+ */
27
+ export const StorageKeys = {
28
+ TEMPLATES: "__templates__",
29
+ SWITCHES: "__switches__",
30
+ } as const;
31
+
32
+ /**
33
+ * Constants for common property keys used in asset building
34
+ */
35
+ export const PropertyKeys = {
36
+ ID: "id",
37
+ TYPE: "type",
38
+ VALUE: "value",
39
+ BINDING: "binding",
40
+ } as const;
41
+
42
+ /**
43
+ * Core interface for all fluent builders
44
+ * Provides build(), peek(), and has() methods for all builder types
45
+ */
46
+ export interface FluentBuilder<
47
+ T,
48
+ C extends BaseBuildContext = BaseBuildContext,
49
+ > {
50
+ readonly [FLUENT_BUILDER_SYMBOL]: true;
51
+ build(context?: C): T;
52
+ peek<K extends keyof T>(key: K): T[K] | undefined;
53
+ has<K extends keyof T>(key: K): boolean;
54
+ }
55
+
56
+ /**
57
+ * Type-erased asset builder interface for generic asset handling
58
+ */
59
+ export type AnyAssetBuilder<C extends BaseBuildContext = BaseBuildContext> = {
60
+ readonly [FLUENT_BUILDER_SYMBOL]: true;
61
+ build(context?: C): Asset;
62
+ peek(key: string): unknown;
63
+ has(key: string): boolean;
64
+ };
65
+
66
+ /**
67
+ * Parameters for creating nested build contexts
68
+ * Used by nested context generators to create child contexts
69
+ */
70
+ export interface NestedContextParams<C extends BaseBuildContext> {
71
+ readonly parentContext: C;
72
+ readonly parameterName: string;
73
+ readonly index?: number;
74
+ }
75
+
76
+ /**
77
+ * Function type for custom nested context generation
78
+ * Allows users to customize how child contexts are created
79
+ */
80
+ export type NestedContextGenerator<C extends BaseBuildContext> = (
81
+ params: NestedContextParams<C>,
82
+ ) => C;
83
+
84
+ /**
85
+ * Metadata about an asset used for ID generation and context tracking
86
+ */
87
+ export interface AssetMetadata {
88
+ readonly type?: string;
89
+ readonly binding?: string;
90
+ readonly value?: string;
91
+ }
92
+
93
+ /**
94
+ * Base build context interface containing common fields for all builders
95
+ * Extended by specific builder implementations for custom context needs
96
+ */
97
+ export interface BaseBuildContext {
98
+ readonly parentId?: string;
99
+ readonly parameterName?: string;
100
+ readonly index?: number;
101
+ readonly branch?: IdBranch;
102
+ readonly nestedContextGenerator?: NestedContextGenerator<BaseBuildContext>;
103
+ readonly assetMetadata?: AssetMetadata;
104
+ readonly [key: string]: unknown;
105
+ }
106
+
107
+ /**
108
+ * Slot branch for named properties (e.g., "label", "action")
109
+ * Creates IDs like: parent-label, parent-action
110
+ */
111
+ export interface SlotBranch {
112
+ type: "slot";
113
+ name: string;
114
+ }
115
+
116
+ /**
117
+ * Array item branch for indexed elements
118
+ * Creates IDs like: parent-0, parent-1
119
+ */
120
+ export interface ArrayItemBranch {
121
+ type: "array-item";
122
+ index: number;
123
+ }
124
+
125
+ /**
126
+ * Template branch for template placeholders
127
+ * Creates IDs like: parent-_index_, parent-_index1_
128
+ */
129
+ export interface TemplateBranch {
130
+ type: "template";
131
+ depth?: number;
132
+ }
133
+
134
+ /**
135
+ * Switch branch for conditional cases
136
+ * Creates IDs like: parent-staticSwitch-0, parent-dynamicSwitch-1
137
+ */
138
+ export interface SwitchBranch {
139
+ type: "switch";
140
+ index: number;
141
+ kind: "static" | "dynamic";
142
+ }
143
+
144
+ /**
145
+ * Custom branch for user-defined ID patterns
146
+ */
147
+ export interface CustomBranch {
148
+ type: "custom";
149
+ }
150
+
151
+ /**
152
+ * Union of all branch types for type-safe ID generation
153
+ */
154
+ export type IdBranch =
155
+ | SlotBranch
156
+ | ArrayItemBranch
157
+ | TemplateBranch
158
+ | SwitchBranch
159
+ | CustomBranch;
160
+
161
+ /**
162
+ * Metadata for arrays containing mixed static values and builders
163
+ * Tracks which indices contain builders for selective resolution
164
+ */
165
+ export interface MixedArrayMetadata {
166
+ readonly array: readonly unknown[];
167
+ readonly builderIndices: ReadonlySet<number>;
168
+ readonly objectIndices: ReadonlySet<number>;
169
+ }
170
+
171
+ /**
172
+ * Metadata for template storage in FluentBuilderBase
173
+ */
174
+ export interface TemplateMetadata {
175
+ readonly data: string;
176
+ readonly output: string;
177
+ readonly dynamic?: boolean;
178
+ }
179
+
180
+ /**
181
+ * Path type for targeting where to inject values in nested structures
182
+ * Example: ["actions", 0, "label"] targets actions[0].label
183
+ */
184
+ export type ValuePath = ReadonlyArray<string | number>;
185
+
186
+ /**
187
+ * Metadata for switch storage in FluentBuilderBase
188
+ */
189
+ export interface SwitchMetadata<C extends BaseBuildContext = BaseBuildContext> {
190
+ readonly path: ValuePath;
191
+ readonly switchFn: (context: C, globalCaseIndex?: number) => unknown;
192
+ }
193
+
194
+ /**
195
+ * Helper type for conditional property values in if/ifElse methods
196
+ * Allows passing unwrapped Asset builders to AssetWrapper properties
197
+ * Enables: .if(() => true, "label", text().withValue("..."))
198
+ * Instead of: .if(() => true, "label", { asset: text().withValue("...") })
199
+ */
200
+ export type ConditionalValue<T, C extends BaseBuildContext> =
201
+ // Case 1: Single AssetWrapper<A> property
202
+ T extends AssetWrapper<infer A> | undefined
203
+ ?
204
+ | T
205
+ | FluentBuilder<T, C>
206
+ | FluentBuilder<A, C>
207
+ | A
208
+ | Array<FluentBuilder<A, C> | A>
209
+ | (() =>
210
+ | T
211
+ | FluentBuilder<T, C>
212
+ | FluentBuilder<A, C>
213
+ | A
214
+ | Array<FluentBuilder<A, C> | A>)
215
+ : // Case 2: Array<AssetWrapper<A>> property
216
+ T extends Array<AssetWrapper<infer A>>
217
+ ?
218
+ | T
219
+ | Array<
220
+ | AssetWrapper<A>
221
+ | FluentBuilder<AssetWrapper<A>, C>
222
+ | FluentBuilder<A, C>
223
+ | A
224
+ >
225
+ | (() =>
226
+ | T
227
+ | Array<
228
+ | AssetWrapper<A>
229
+ | FluentBuilder<AssetWrapper<A>, C>
230
+ | FluentBuilder<A, C>
231
+ | A
232
+ >)
233
+ : // Case 3: Other properties
234
+ T | FluentBuilder<T, C> | (() => T | FluentBuilder<T, C>);
235
+
236
+ /**
237
+ * Transforms property types to allow TaggedTemplateValue for scalars
238
+ * and FluentBuilder for AssetWrapper properties.
239
+ */
240
+ export type FluentPartialValue<
241
+ T,
242
+ C extends BaseBuildContext = BaseBuildContext,
243
+ > = T extends string
244
+ ? T | TaggedTemplateValue<string>
245
+ : T extends number
246
+ ? T | TaggedTemplateValue<number>
247
+ : T extends boolean
248
+ ? T | TaggedTemplateValue<boolean>
249
+ : T extends bigint
250
+ ? T | TaggedTemplateValue<bigint>
251
+ : T extends AssetWrapper<infer A>
252
+ ? T | FluentBuilder<A, C> | A
253
+ : T extends Array<AssetWrapper<infer A>>
254
+ ? Array<AssetWrapper<A> | FluentBuilder<A, C> | A>
255
+ : T extends Array<infer E>
256
+ ? Array<FluentPartialValue<E, C>>
257
+ : T extends object
258
+ ? { [K in keyof T]: FluentPartialValue<T[K], C> }
259
+ : T;
260
+
261
+ /**
262
+ * Partial type for builder constructors that allows TaggedTemplateValue for scalars.
263
+ */
264
+ export type FluentPartial<T, C extends BaseBuildContext = BaseBuildContext> = {
265
+ [K in keyof T]?: FluentPartialValue<T[K], C>;
266
+ };
@@ -0,0 +1,10 @@
1
+ export function createInspectMethod(
2
+ builderName: string,
3
+ properties: Record<string, unknown>,
4
+ ): string {
5
+ return `${builderName} { properties: ${JSON.stringify(
6
+ properties,
7
+ null,
8
+ 2,
9
+ )} }`;
10
+ }