@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.
Files changed (121) hide show
  1. package/dist/cjs/index.cjs +2257 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/index.legacy-esm.js +2143 -0
  4. package/dist/index.mjs +2143 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +36 -0
  7. package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2257 -0
  8. package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
  9. package/src/core/base-builder/__tests__/registry.test.ts +411 -0
  10. package/src/core/base-builder/__tests__/switch.test.ts +501 -0
  11. package/src/core/base-builder/__tests__/template.test.ts +449 -0
  12. package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
  13. package/src/core/base-builder/conditional/index.ts +64 -0
  14. package/src/core/base-builder/context.ts +151 -0
  15. package/src/core/base-builder/fluent-builder-base.ts +261 -0
  16. package/src/core/base-builder/guards.ts +137 -0
  17. package/src/core/base-builder/id/generator.ts +286 -0
  18. package/src/core/base-builder/id/registry.ts +152 -0
  19. package/src/core/base-builder/index.ts +60 -0
  20. package/src/core/base-builder/resolution/path-resolver.ts +108 -0
  21. package/src/core/base-builder/resolution/pipeline.ts +96 -0
  22. package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
  23. package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
  24. package/src/core/base-builder/resolution/steps/builders.ts +85 -0
  25. package/src/core/base-builder/resolution/steps/mixed-arrays.ts +117 -0
  26. package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
  27. package/src/core/base-builder/resolution/steps/switches.ts +63 -0
  28. package/src/core/base-builder/resolution/steps/templates.ts +30 -0
  29. package/src/core/base-builder/resolution/value-resolver.ts +308 -0
  30. package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
  31. package/src/core/base-builder/storage/value-storage.ts +280 -0
  32. package/src/core/base-builder/types.ts +184 -0
  33. package/src/core/base-builder/utils.ts +10 -0
  34. package/src/core/flow/__tests__/index.test.ts +292 -0
  35. package/src/core/flow/index.ts +141 -0
  36. package/src/core/index.ts +8 -0
  37. package/src/core/mocks/generated/action.builder.ts +109 -0
  38. package/src/core/mocks/generated/choice.builder.ts +161 -0
  39. package/src/core/mocks/generated/choiceItem.builder.ts +133 -0
  40. package/src/core/mocks/generated/collection.builder.ts +117 -0
  41. package/src/core/mocks/generated/index.ts +7 -0
  42. package/src/core/mocks/generated/info.builder.ts +80 -0
  43. package/src/core/mocks/generated/input.builder.ts +75 -0
  44. package/src/core/mocks/generated/text.builder.ts +63 -0
  45. package/src/core/mocks/index.ts +1 -0
  46. package/src/core/mocks/types/action.ts +92 -0
  47. package/src/core/mocks/types/choice.ts +129 -0
  48. package/src/core/mocks/types/collection.ts +140 -0
  49. package/src/core/mocks/types/info.ts +7 -0
  50. package/src/core/mocks/types/input.ts +7 -0
  51. package/src/core/mocks/types/text.ts +5 -0
  52. package/src/core/schema/__tests__/index.test.ts +127 -0
  53. package/src/core/schema/index.ts +195 -0
  54. package/src/core/schema/types.ts +7 -0
  55. package/src/core/switch/__tests__/index.test.ts +156 -0
  56. package/src/core/switch/index.ts +76 -0
  57. package/src/core/tagged-template/README.md +448 -0
  58. package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
  59. package/src/core/tagged-template/__tests__/index.test.ts +190 -0
  60. package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
  61. package/src/core/tagged-template/binding.ts +95 -0
  62. package/src/core/tagged-template/expression.ts +92 -0
  63. package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
  64. package/src/core/tagged-template/index.ts +5 -0
  65. package/src/core/tagged-template/std.ts +472 -0
  66. package/src/core/tagged-template/types.ts +123 -0
  67. package/src/core/template/__tests__/index.test.ts +380 -0
  68. package/src/core/template/index.ts +191 -0
  69. package/src/core/utils/index.ts +160 -0
  70. package/src/fp/README.md +411 -0
  71. package/src/fp/__tests__/index.test.ts +1178 -0
  72. package/src/fp/index.ts +386 -0
  73. package/src/gen/common.ts +2 -0
  74. package/src/gen/plugin.mjs +315 -0
  75. package/src/index.ts +5 -0
  76. package/src/types.ts +203 -0
  77. package/types/core/base-builder/conditional/index.d.ts +21 -0
  78. package/types/core/base-builder/context.d.ts +39 -0
  79. package/types/core/base-builder/fluent-builder-base.d.ts +132 -0
  80. package/types/core/base-builder/guards.d.ts +58 -0
  81. package/types/core/base-builder/id/generator.d.ts +69 -0
  82. package/types/core/base-builder/id/registry.d.ts +93 -0
  83. package/types/core/base-builder/index.d.ts +8 -0
  84. package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
  85. package/types/core/base-builder/resolution/pipeline.d.ts +25 -0
  86. package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
  87. package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
  88. package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
  89. package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
  90. package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
  91. package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
  92. package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
  93. package/types/core/base-builder/resolution/value-resolver.d.ts +37 -0
  94. package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
  95. package/types/core/base-builder/storage/value-storage.d.ts +82 -0
  96. package/types/core/base-builder/types.d.ts +141 -0
  97. package/types/core/base-builder/utils.d.ts +2 -0
  98. package/types/core/flow/index.d.ts +23 -0
  99. package/types/core/index.d.ts +8 -0
  100. package/types/core/mocks/index.d.ts +2 -0
  101. package/types/core/mocks/types/action.d.ts +58 -0
  102. package/types/core/mocks/types/choice.d.ts +95 -0
  103. package/types/core/mocks/types/collection.d.ts +102 -0
  104. package/types/core/mocks/types/info.d.ts +7 -0
  105. package/types/core/mocks/types/input.d.ts +7 -0
  106. package/types/core/mocks/types/text.d.ts +5 -0
  107. package/types/core/schema/index.d.ts +34 -0
  108. package/types/core/schema/types.d.ts +5 -0
  109. package/types/core/switch/index.d.ts +21 -0
  110. package/types/core/tagged-template/binding.d.ts +19 -0
  111. package/types/core/tagged-template/expression.d.ts +11 -0
  112. package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
  113. package/types/core/tagged-template/index.d.ts +6 -0
  114. package/types/core/tagged-template/std.d.ts +174 -0
  115. package/types/core/tagged-template/types.d.ts +69 -0
  116. package/types/core/template/index.d.ts +97 -0
  117. package/types/core/utils/index.d.ts +47 -0
  118. package/types/fp/index.d.ts +149 -0
  119. package/types/gen/common.d.ts +3 -0
  120. package/types/index.d.ts +3 -0
  121. package/types/types.d.ts +163 -0
@@ -0,0 +1,286 @@
1
+ import { globalIdRegistry } from "./registry";
2
+ import type { AssetMetadata, BaseBuildContext } from "../types";
3
+
4
+ export { globalIdRegistry, createIdRegistry, IDRegistry } from "./registry";
5
+
6
+ /**
7
+ * Resets the global ID registry, clearing all registered IDs.
8
+ * This is useful for testing scenarios where you need a clean slate.
9
+ */
10
+ export const resetGlobalIdSet = (): void => {
11
+ globalIdRegistry.reset();
12
+ };
13
+
14
+ /**
15
+ * Internal function that generates a base ID from context without registry operations.
16
+ * This is the core ID generation logic shared between peekId and genId.
17
+ *
18
+ * @param context - The context containing parent ID and optional branch information
19
+ * @param functionName - The name of the calling function (for error messages)
20
+ * @returns The generated base ID
21
+ */
22
+ const _generateBaseId = (
23
+ context: BaseBuildContext,
24
+ functionName: string,
25
+ ): string => {
26
+ // Validate context
27
+ if (!context) {
28
+ throw new Error(
29
+ `${functionName}: Context is undefined. Please provide a valid BaseBuildContext object.`,
30
+ );
31
+ }
32
+
33
+ const { parentId, branch } = context;
34
+
35
+ let baseId: string;
36
+
37
+ if (!branch) {
38
+ baseId = parentId || "";
39
+ } else {
40
+ switch (branch.type) {
41
+ case "custom":
42
+ baseId = parentId || "";
43
+ break;
44
+
45
+ case "slot":
46
+ if (!branch.name) {
47
+ throw new Error(
48
+ `${functionName}: Slot branch requires a 'name' property. ` +
49
+ `Context: ${JSON.stringify(context)}`,
50
+ );
51
+ }
52
+ baseId = `${parentId ? `${parentId}-` : ""}${branch.name}`;
53
+ break;
54
+
55
+ case "array-item":
56
+ if (typeof branch.index !== "number") {
57
+ throw new Error(
58
+ `${functionName}: Array-item branch requires a numeric 'index' property. ` +
59
+ `Got: ${typeof branch.index}. Context: ${JSON.stringify(context)}`,
60
+ );
61
+ }
62
+ if (branch.index < 0) {
63
+ throw new Error(
64
+ `${functionName}: Array-item index must be non-negative. ` +
65
+ `Got: ${branch.index}. Context: ${JSON.stringify(context)}`,
66
+ );
67
+ }
68
+ baseId = `${parentId}-${branch.index}`;
69
+ break;
70
+
71
+ case "template":
72
+ if (branch.depth !== undefined && branch.depth < 0) {
73
+ throw new Error(
74
+ `${functionName}: Template depth must be non-negative. ` +
75
+ `Got: ${branch.depth}. Context: ${JSON.stringify(context)}`,
76
+ );
77
+ }
78
+ baseId = `${parentId}-_index${branch.depth || ""}_`;
79
+ break;
80
+
81
+ case "switch":
82
+ if (typeof branch.index !== "number") {
83
+ throw new Error(
84
+ `${functionName}: Switch branch requires a numeric 'index' property. ` +
85
+ `Got: ${typeof branch.index}. Context: ${JSON.stringify(context)}`,
86
+ );
87
+ }
88
+ if (branch.index < 0) {
89
+ throw new Error(
90
+ `${functionName}: Switch index must be non-negative. ` +
91
+ `Got: ${branch.index}. Context: ${JSON.stringify(context)}`,
92
+ );
93
+ }
94
+ if (!branch.kind || !["static", "dynamic"].includes(branch.kind)) {
95
+ throw new Error(
96
+ `${functionName}: Switch branch requires 'kind' to be 'static' or 'dynamic'. ` +
97
+ `Got: ${branch.kind}. Context: ${JSON.stringify(context)}`,
98
+ );
99
+ }
100
+ baseId = `${parentId}-${branch.kind}Switch-${branch.index}`;
101
+ break;
102
+
103
+ default: {
104
+ const exhaustiveCheck: never = branch;
105
+ throw new Error(
106
+ `${functionName}: Unhandled branch type: ${JSON.stringify(exhaustiveCheck)}`,
107
+ );
108
+ }
109
+ }
110
+ }
111
+
112
+ return baseId;
113
+ };
114
+
115
+ /**
116
+ * Generates an ID without registering it in the global registry.
117
+ * This is useful for intermediate ID lookups where you don't want to consume the ID.
118
+ *
119
+ * @param context - The context containing parent ID and optional branch information
120
+ * @returns The generated ID (without collision detection or registration)
121
+ *
122
+ * @example
123
+ * // Use this when you need to generate a parent ID for nested context creation
124
+ * const parentId = peekId({ parentId: 'collection' }); // Doesn't register 'collection'
125
+ * const nestedCtx = { parentId, branch: { type: 'slot', name: 'label' } };
126
+ */
127
+ export const peekId = (context: BaseBuildContext): string => {
128
+ return _generateBaseId(context, "peekId");
129
+ };
130
+
131
+ /**
132
+ * Generates a unique identifier based on the parent ID and branch information.
133
+ *
134
+ * This function creates hierarchical IDs for various element types in a DSL structure,
135
+ * maintaining relationships between parent and child elements through consistent naming patterns.
136
+ * IDs are automatically checked for uniqueness and modified if collisions are detected.
137
+ *
138
+ * @param {BaseBuildContext} context - The context containing parent ID and optional branch information
139
+ * @param {string} context.parentId - The ID of the parent element
140
+ * @param {IdBranch} [context.branch] - Optional branch information to specify the type of child element
141
+ *
142
+ * @returns {string} A generated ID string representing the element's unique identifier
143
+ *
144
+ * @throws {Error} If context is invalid or incomplete
145
+ *
146
+ * @example
147
+ * // Slot branch
148
+ * genId({ parentId: 'parent', branch: { type: 'slot', name: 'header' } })
149
+ * // Returns: 'parent-header'
150
+ *
151
+ * @example
152
+ * // Array item branch
153
+ * genId({ parentId: 'list', branch: { type: 'array-item', index: 2 } })
154
+ * // Returns: 'list-2'
155
+ *
156
+ * @example
157
+ * // Template branch
158
+ * genId({ parentId: 'template', branch: { type: 'template', depth: 1 } })
159
+ * // Returns: 'template-_index1_'
160
+ *
161
+ * @example
162
+ * // Switch branch
163
+ * genId({ parentId: 'condition', branch: { type: 'switch', index: 0, kind: 'static' } })
164
+ * // Returns: 'condition-staticSwitch-0'
165
+ *
166
+ * @example
167
+ * // Custom ID case (no branch)
168
+ * genId({ parentId: 'custom-id' })
169
+ * // Returns: 'custom-id'
170
+ */
171
+ export const genId = (context: BaseBuildContext): string => {
172
+ const baseId = _generateBaseId(context, "genId");
173
+
174
+ const { parentId, branch } = context;
175
+
176
+ if (!parentId && !branch) {
177
+ console.warn(
178
+ "genId: Context appears incomplete (no parentId or branch). " +
179
+ "This may result in an empty or invalid ID. " +
180
+ "Consider using context helper functions from 'fluent/utils/context'.",
181
+ );
182
+ }
183
+
184
+ if (parentId === "") {
185
+ console.warn(
186
+ "genId: parentId is an empty string. " +
187
+ "This may indicate a missing or improperly initialized context.",
188
+ );
189
+ }
190
+
191
+ // Ensure the generated ID is unique
192
+ const uniqueId = globalIdRegistry.ensureUnique(baseId);
193
+
194
+ // Warn if collision was detected
195
+ if (process.env.NODE_ENV !== "production" && uniqueId !== baseId) {
196
+ console.warn(
197
+ `genId: ID collision detected. Original: "${baseId}", Modified to: "${uniqueId}". ` +
198
+ `Consider providing more specific IDs to avoid collisions.`,
199
+ );
200
+ }
201
+
202
+ return uniqueId;
203
+ };
204
+
205
+ export function determineSlotName(
206
+ parameterName: string,
207
+ assetMetadata?: AssetMetadata,
208
+ ): string {
209
+ if (!assetMetadata) {
210
+ return parameterName;
211
+ }
212
+
213
+ const { type, binding, value } = assetMetadata;
214
+
215
+ // Rule 1: If the asset type is `action`, append last segment of `value` if any
216
+ // Note: value can be a binding (TaggedTemplateValue) or plain string
217
+ if (type === "action" && value) {
218
+ const cleanValue = value.replace(/^\{\{|\}\}$/g, "");
219
+ const segments = cleanValue.split(".");
220
+ const lastSegment = segments[segments.length - 1];
221
+ return lastSegment ? `${type}-${lastSegment}` : type;
222
+ }
223
+
224
+ // Rule 2: If it's not `action` but has `binding`, append last fragment of binding
225
+ if (type !== "action" && binding) {
226
+ const cleanBinding = binding.replace(/^\{\{|\}\}$/g, "");
227
+ const segments = cleanBinding.split(".");
228
+ const lastSegment = segments[segments.length - 1];
229
+ if (lastSegment) {
230
+ return `${type || parameterName}-${lastSegment}`;
231
+ }
232
+ }
233
+
234
+ // Rule 3: Otherwise, just use the type
235
+ return type || parameterName;
236
+ }
237
+
238
+ export function generateAssetId<C extends BaseBuildContext>(params: {
239
+ readonly context?: C;
240
+ readonly parameterName?: string;
241
+ readonly assetMetadata?: AssetMetadata;
242
+ readonly explicitId?: string;
243
+ }): string {
244
+ const {
245
+ context,
246
+ parameterName = "asset",
247
+ assetMetadata,
248
+ explicitId,
249
+ } = params;
250
+
251
+ if (explicitId) {
252
+ return explicitId;
253
+ }
254
+
255
+ if (context && "parentId" in context) {
256
+ // Determine the slot name based on asset metadata (action value, binding, or type)
257
+ const slotName = determineSlotName(parameterName, assetMetadata);
258
+
259
+ if (context.branch) {
260
+ // When there's a branch, generate base ID then append asset type
261
+ // This creates IDs like "parent-0-questionAnswer", "parent-slot-text", etc.
262
+ const baseId = genId(context);
263
+
264
+ // Append the asset type as a suffix
265
+ // Use genId again to ensure uniqueness and proper registration
266
+ return genId({
267
+ ...context,
268
+ parentId: baseId,
269
+ branch: { type: "slot", name: slotName },
270
+ } as C);
271
+ }
272
+
273
+ if (context.parentId) {
274
+ // When there's a parentId but no branch, use slotName (determined from metadata)
275
+ // This creates IDs like "parent-text", "parent-action-next", "parent-input-firstName", etc.
276
+ // Generate and register the ID to enable collision detection
277
+ return genId({
278
+ ...context,
279
+ branch: { type: "slot", name: slotName },
280
+ } as C);
281
+ }
282
+ }
283
+
284
+ const slotName = determineSlotName(parameterName, assetMetadata);
285
+ return slotName;
286
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * ID Registry for tracking and ensuring unique IDs across asset generation.
3
+ * This registry maintains a set of used IDs and provides collision resolution
4
+ * by appending numeric suffixes when duplicates are detected.
5
+ *
6
+ * Special handling for template placeholders:
7
+ * - IDs ending with template placeholders like `_index_`, `_row_` are allowed to be duplicated
8
+ * - IDs with placeholders followed by additional segments enforce uniqueness normally
9
+ */
10
+ export class IDRegistry {
11
+ private usedIds: Set<string>;
12
+ private isEnabled: boolean;
13
+
14
+ constructor(enabled = true) {
15
+ this.usedIds = new Set<string>();
16
+ this.isEnabled = enabled;
17
+ }
18
+
19
+ /**
20
+ * Ensures the given ID is unique, modifying it if necessary.
21
+ * If the ID already exists, appends a numeric suffix (-1, -2, etc.)
22
+ * until a unique ID is found.
23
+ *
24
+ * Special handling for template placeholders:
25
+ * - IDs ending with `_index_`, `_row_`, etc. are allowed as duplicates
26
+ * - IDs with placeholders followed by segments (e.g., `_index_.field`) enforce uniqueness
27
+ *
28
+ * @param baseId - The desired ID
29
+ * @returns A unique ID (either the original or modified with suffix)
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const registry = new IDRegistry();
34
+ * registry.ensureUnique("my-id"); // "my-id"
35
+ * registry.ensureUnique("my-id"); // "my-id-1"
36
+ * registry.ensureUnique("list-_index_"); // "list-_index_" (allowed duplicate)
37
+ * registry.ensureUnique("list-_index_"); // "list-_index_" (allowed duplicate)
38
+ * ```
39
+ */
40
+ ensureUnique(baseId: string): string {
41
+ // If registry is disabled, return the ID as-is
42
+ if (!this.isEnabled) {
43
+ return baseId;
44
+ }
45
+
46
+ // Check if this ID contains template placeholders
47
+ if (this.isTemplatePlaceholderID(baseId)) {
48
+ // For template placeholder IDs, don't enforce uniqueness
49
+ // These will be replaced at runtime, so duplicates are acceptable
50
+ return baseId;
51
+ }
52
+
53
+ // If the ID hasn't been used, register and return it
54
+ if (!this.usedIds.has(baseId)) {
55
+ this.usedIds.add(baseId);
56
+ return baseId;
57
+ }
58
+
59
+ // ID collision detected - append counter until unique
60
+ let counter = 1;
61
+ let uniqueId = `${baseId}-${counter}`;
62
+
63
+ while (this.usedIds.has(uniqueId)) {
64
+ counter++;
65
+ uniqueId = `${baseId}-${counter}`;
66
+ }
67
+
68
+ this.usedIds.add(uniqueId);
69
+ return uniqueId;
70
+ }
71
+
72
+ /**
73
+ * Checks if an ID has already been used.
74
+ *
75
+ * @param id - The ID to check
76
+ * @returns true if the ID has been used, false otherwise
77
+ */
78
+ has(id: string): boolean {
79
+ return this.usedIds.has(id);
80
+ }
81
+
82
+ /**
83
+ * Clears all registered IDs from the registry.
84
+ * Useful for resetting state between test runs or separate flows.
85
+ */
86
+ reset(): void {
87
+ this.usedIds.clear();
88
+ }
89
+
90
+ /**
91
+ * Enables or disables the uniqueness checking.
92
+ * When disabled, all IDs pass through unchanged.
93
+ *
94
+ * @param enabled - Whether to enable uniqueness checking
95
+ */
96
+ setEnabled(enabled: boolean): void {
97
+ this.isEnabled = enabled;
98
+ }
99
+
100
+ /**
101
+ * Returns the number of unique IDs currently registered.
102
+ *
103
+ * @returns The count of registered IDs
104
+ */
105
+ size(): number {
106
+ return this.usedIds.size;
107
+ }
108
+
109
+ /**
110
+ * Returns a snapshot of all registered IDs.
111
+ * Useful for debugging and testing.
112
+ *
113
+ * @returns An array of all registered IDs
114
+ */
115
+ getRegisteredIds(): string[] {
116
+ return Array.from(this.usedIds);
117
+ }
118
+
119
+ /**
120
+ * Checks if an ID contains template placeholders that should be exempt from uniqueness checks.
121
+ * Template placeholders are patterns like `_index_`, `_index1_`, `_row_` that are replaced at runtime.
122
+ *
123
+ * IDs ending with just a placeholder (e.g., "parent-_index_") are allowed as duplicates.
124
+ * IDs with placeholders followed by additional segments (e.g., "parent-_index_-field") are not.
125
+ *
126
+ * @param id - The ID to check
127
+ * @returns true if the ID should be exempt from uniqueness checks
128
+ */
129
+ private isTemplatePlaceholderID(id: string): boolean {
130
+ // Pattern to match template placeholder at the end of an ID
131
+ // Matches: _index_, _index1_, _row_, _item_, etc. at the end of the string
132
+ const templatePlaceholderPattern = /_(?:index|row|item)\d*_$/;
133
+ return templatePlaceholderPattern.test(id);
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Global singleton instance of the ID registry.
139
+ * This ensures consistent ID tracking across the entire application.
140
+ */
141
+ export const globalIdRegistry: IDRegistry = new IDRegistry();
142
+
143
+ /**
144
+ * Creates a new isolated ID registry instance.
145
+ * Useful for testing or when you need separate ID tracking contexts.
146
+ *
147
+ * @param enabled - Whether the registry should be enabled by default
148
+ * @returns A new IDRegistry instance
149
+ */
150
+ export function createIdRegistry(enabled = true): IDRegistry {
151
+ return new IDRegistry(enabled);
152
+ }
@@ -0,0 +1,60 @@
1
+ export {
2
+ FLUENT_BUILDER_SYMBOL,
3
+ type NestedContextParams,
4
+ type NestedContextGenerator,
5
+ type AssetMetadata,
6
+ type BaseBuildContext,
7
+ type FluentBuilder,
8
+ type AnyAssetBuilder,
9
+ type MixedArrayMetadata,
10
+ type TemplateMetadata,
11
+ type IdBranch,
12
+ type SlotBranch,
13
+ type ArrayItemBranch,
14
+ type TemplateBranch,
15
+ type SwitchBranch,
16
+ type CustomBranch,
17
+ type ValuePath,
18
+ type SwitchMetadata,
19
+ type ConditionalValue,
20
+ } from "./types";
21
+
22
+ export {
23
+ isFluentBuilder,
24
+ isBuilderArray,
25
+ isPlainObject,
26
+ isAsset,
27
+ isAssetWrapper,
28
+ isAssetWrapperWithAsset,
29
+ needsAssetWrapper,
30
+ isAssetWrapperValue,
31
+ isSwitchResult,
32
+ isStringOrUndefined,
33
+ } from "./guards";
34
+
35
+ export {
36
+ determineSlotName,
37
+ generateAssetId,
38
+ genId,
39
+ peekId,
40
+ resetGlobalIdSet,
41
+ globalIdRegistry,
42
+ createIdRegistry,
43
+ IDRegistry,
44
+ } from "./id/generator";
45
+
46
+ export {
47
+ createNestedContext,
48
+ createTemplateContext,
49
+ createSwitchContext,
50
+ } from "./context";
51
+
52
+ export {
53
+ extractValue,
54
+ resolveValue,
55
+ resolveAndWrapAsset,
56
+ } from "./resolution/value-resolver";
57
+
58
+ export { FluentBuilderBase } from "./fluent-builder-base";
59
+
60
+ export { createInspectMethod } from "./utils";
@@ -0,0 +1,108 @@
1
+ import type { ValuePath } from "../types";
2
+ import { isAssetWrapperValue } from "../guards";
3
+
4
+ /**
5
+ * Sets a value at a nested path in an object
6
+ * Handles nested objects, arrays, and AssetWrapper structures
7
+ *
8
+ * @param obj - The target object to modify
9
+ * @param path - Array of keys/indices representing the path
10
+ * @param value - The value to set at the path
11
+ *
12
+ * @example
13
+ * setValueAtPath(obj, ["actions", 0, "label"], "Click me")
14
+ * // Sets obj.actions[0].label = "Click me"
15
+ */
16
+ export function setValueAtPath(
17
+ obj: Record<string, unknown>,
18
+ path: ValuePath,
19
+ value: unknown,
20
+ ): void {
21
+ if (path.length === 0) return;
22
+
23
+ if (path.length === 1) {
24
+ obj[path[0]] = value;
25
+ return;
26
+ }
27
+
28
+ const [currentKey, ...restPath] = path;
29
+ const nextKey = restPath[0];
30
+ const currentValue = obj[currentKey];
31
+
32
+ // Check if current value is an AssetWrapper containing an array
33
+ const isAssetWrapperWithArray =
34
+ isAssetWrapperValue(currentValue) && Array.isArray(currentValue.asset);
35
+
36
+ if (isAssetWrapperWithArray && typeof nextKey === "number") {
37
+ setValueInAssetWrapperArray(
38
+ obj,
39
+ currentKey,
40
+ currentValue as { asset: unknown[] },
41
+ nextKey,
42
+ restPath,
43
+ value,
44
+ );
45
+ } else if (Array.isArray(currentValue) && typeof nextKey === "number") {
46
+ setValueInArray(obj, currentKey, currentValue, nextKey, restPath, value);
47
+ } else {
48
+ setValueInObject(obj, currentKey, restPath, value);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Sets value in an array within an AssetWrapper
54
+ */
55
+ function setValueInAssetWrapperArray(
56
+ obj: Record<string, unknown>,
57
+ currentKey: string | number,
58
+ wrappedArray: { asset: unknown[] },
59
+ nextKey: number,
60
+ restPath: ValuePath,
61
+ value: unknown,
62
+ ): void {
63
+ const arrayResult = [...wrappedArray.asset];
64
+ if (restPath.length === 1) {
65
+ arrayResult[nextKey] = value;
66
+ } else {
67
+ const nestedObj = (arrayResult[nextKey] as Record<string, unknown>) ?? {};
68
+ setValueAtPath(nestedObj, restPath.slice(1), value);
69
+ arrayResult[nextKey] = nestedObj;
70
+ }
71
+ obj[currentKey] = { asset: arrayResult };
72
+ }
73
+
74
+ /**
75
+ * Sets value in a regular array
76
+ */
77
+ function setValueInArray(
78
+ obj: Record<string, unknown>,
79
+ currentKey: string | number,
80
+ array: unknown[],
81
+ nextKey: number,
82
+ restPath: ValuePath,
83
+ value: unknown,
84
+ ): void {
85
+ const arrayResult = [...array];
86
+ if (restPath.length === 1) {
87
+ arrayResult[nextKey] = value;
88
+ } else {
89
+ const nestedObj = (arrayResult[nextKey] as Record<string, unknown>) ?? {};
90
+ setValueAtPath(nestedObj, restPath.slice(1), value);
91
+ arrayResult[nextKey] = nestedObj;
92
+ }
93
+ obj[currentKey] = arrayResult;
94
+ }
95
+
96
+ /**
97
+ * Sets value in a nested object
98
+ */
99
+ function setValueInObject(
100
+ obj: Record<string, unknown>,
101
+ currentKey: string | number,
102
+ restPath: ValuePath,
103
+ value: unknown,
104
+ ): void {
105
+ const nestedObj = (obj[currentKey] as Record<string, unknown>) ?? {};
106
+ setValueAtPath(nestedObj, restPath, value);
107
+ obj[currentKey] = nestedObj;
108
+ }
@@ -0,0 +1,96 @@
1
+ import type { BaseBuildContext } from "../types";
2
+ import type { ValueStorage } from "../storage/value-storage";
3
+ import type { AuxiliaryStorage } from "../storage/auxiliary-storage";
4
+ import { resolveStaticValues } from "./steps/static-values";
5
+ import { generateAssetIdForBuilder } from "./steps/asset-id";
6
+ import { resolveAssetWrappers } from "./steps/asset-wrappers";
7
+ import { resolveMixedArrays } from "./steps/mixed-arrays";
8
+ import { resolveBuilders } from "./steps/builders";
9
+ import { resolveSwitches } from "./steps/switches";
10
+ import { resolveTemplates } from "./steps/templates";
11
+
12
+ /**
13
+ * Creates a nested context for child assets
14
+ * This is Step 3 of the build process
15
+ */
16
+ function createNestedParentContext<C extends BaseBuildContext>(
17
+ result: Record<string, unknown>,
18
+ context: C | undefined,
19
+ ): C | undefined {
20
+ if (!context) {
21
+ return undefined;
22
+ }
23
+
24
+ // Extract parent ID with type checking
25
+ const parentId =
26
+ "id" in result && typeof result.id === "string" ? result.id : undefined;
27
+
28
+ // Create context for nested assets
29
+ // We clear the branch to avoid double-nesting
30
+ return {
31
+ ...context,
32
+ parentId,
33
+ branch: undefined,
34
+ } as C;
35
+ }
36
+
37
+ /**
38
+ * Executes the complete build pipeline
39
+ *
40
+ * The pipeline consists of 8 steps that transform builder state into a final object:
41
+ * 1. Resolve static values (extract TaggedTemplateValue, resolve simple builders)
42
+ * 2. Generate asset ID if needed
43
+ * 3. Create nested context for child assets
44
+ * 4. Resolve AssetWrapper values
45
+ * 5. Resolve mixed arrays
46
+ * 6. Resolve builders
47
+ * 7. Resolve switches
48
+ * 8. Resolve templates
49
+ *
50
+ * @param valueStorage - Storage containing property values
51
+ * @param auxiliaryStorage - Storage containing metadata (templates, switches)
52
+ * @param defaults - Optional default values to merge into result
53
+ * @param context - Optional build context
54
+ * @param arrayProperties - Set of property names that are array types
55
+ * @returns The fully built object
56
+ */
57
+ export function executeBuildPipeline<T, C extends BaseBuildContext>(
58
+ valueStorage: ValueStorage<T>,
59
+ auxiliaryStorage: AuxiliaryStorage,
60
+ defaults: Partial<T> | undefined,
61
+ context: C | undefined,
62
+ arrayProperties: ReadonlySet<string>,
63
+ ): T {
64
+ const result: Record<string, unknown> = defaults ? { ...defaults } : {};
65
+
66
+ // Step 1: Resolve static values
67
+ resolveStaticValues(valueStorage, result, context);
68
+
69
+ // Step 2: Generate asset ID if needed
70
+ generateAssetIdForBuilder(valueStorage, result, context);
71
+
72
+ // Step 3: Create nested context for child assets
73
+ const nestedParentContext = createNestedParentContext(result, context);
74
+
75
+ // Step 4: Resolve AssetWrapper values
76
+ resolveAssetWrappers(valueStorage, result, nestedParentContext);
77
+
78
+ // Step 5: Resolve mixed arrays
79
+ resolveMixedArrays(valueStorage, result, nestedParentContext);
80
+
81
+ // Step 6: Resolve builders
82
+ resolveBuilders(valueStorage, result, nestedParentContext);
83
+
84
+ // Step 7: Resolve switches
85
+ resolveSwitches(
86
+ auxiliaryStorage,
87
+ result,
88
+ nestedParentContext,
89
+ arrayProperties,
90
+ );
91
+
92
+ // Step 8: Resolve templates
93
+ resolveTemplates(auxiliaryStorage, result, context);
94
+
95
+ return result as T;
96
+ }