@ng-forge/dynamic-forms 1.3.0-next.2 → 1.3.0-next.4

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 (92) hide show
  1. package/fesm2022/ng-forge-dynamic-forms-active-page-initialized.event-bnrwNw7Y.mjs +156 -0
  2. package/fesm2022/ng-forge-dynamic-forms-active-page-initialized.event-bnrwNw7Y.mjs.map +1 -0
  3. package/fesm2022/ng-forge-dynamic-forms-array-field.component-Dv9mPnJ3.mjs +770 -0
  4. package/fesm2022/ng-forge-dynamic-forms-array-field.component-Dv9mPnJ3.mjs.map +1 -0
  5. package/fesm2022/ng-forge-dynamic-forms-array-item-registry.service-D-AxVLdk.mjs +83 -0
  6. package/fesm2022/ng-forge-dynamic-forms-array-item-registry.service-D-AxVLdk.mjs.map +1 -0
  7. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-B6WH0Etk.mjs +1887 -0
  8. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-B6WH0Etk.mjs.map +1 -0
  9. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-VjJn-JeD.mjs +954 -0
  10. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-VjJn-JeD.mjs.map +1 -0
  11. package/fesm2022/ng-forge-dynamic-forms-container-field-processors-C_JRvaVt.mjs +117 -0
  12. package/fesm2022/ng-forge-dynamic-forms-container-field-processors-C_JRvaVt.mjs.map +1 -0
  13. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Bg7Win9n.mjs +228 -0
  14. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Bg7Win9n.mjs.map +1 -0
  15. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-R2yc-H0T.mjs +102 -0
  16. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-R2yc-H0T.mjs.map +1 -0
  17. package/fesm2022/ng-forge-dynamic-forms-debounce-CG3uP5B-.mjs +4 -0
  18. package/fesm2022/{ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs.map → ng-forge-dynamic-forms-debounce-CG3uP5B-.mjs.map} +1 -1
  19. package/fesm2022/ng-forge-dynamic-forms-default-value-D5M4MNE-.mjs +77 -0
  20. package/fesm2022/ng-forge-dynamic-forms-default-value-D5M4MNE-.mjs.map +1 -0
  21. package/fesm2022/ng-forge-dynamic-forms-dev-mode-4rbYBBo_.mjs +4 -0
  22. package/fesm2022/ng-forge-dynamic-forms-dev-mode-4rbYBBo_.mjs.map +1 -0
  23. package/fesm2022/ng-forge-dynamic-forms-df-field-outlet.directive-CQsDMV-s.mjs +644 -0
  24. package/fesm2022/ng-forge-dynamic-forms-df-field-outlet.directive-CQsDMV-s.mjs.map +1 -0
  25. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-DKgkpIpG.mjs +56 -0
  26. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-DKgkpIpG.mjs.map +1 -0
  27. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dd-gHJ_7.mjs +38 -0
  28. package/fesm2022/{ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs.map → ng-forge-dynamic-forms-form-schema.service-Dd-gHJ_7.mjs.map} +1 -1
  29. package/fesm2022/ng-forge-dynamic-forms-form-state-manager-BJKBX4FL.mjs +1545 -0
  30. package/fesm2022/ng-forge-dynamic-forms-form-state-manager-BJKBX4FL.mjs.map +1 -0
  31. package/fesm2022/ng-forge-dynamic-forms-group-field.component-BjZi0bhq.mjs +292 -0
  32. package/fesm2022/ng-forge-dynamic-forms-group-field.component-BjZi0bhq.mjs.map +1 -0
  33. package/fesm2022/ng-forge-dynamic-forms-integration.mjs +1318 -843
  34. package/fesm2022/ng-forge-dynamic-forms-integration.mjs.map +1 -1
  35. package/fesm2022/ng-forge-dynamic-forms-internal.mjs +3421 -3641
  36. package/fesm2022/ng-forge-dynamic-forms-internal.mjs.map +1 -1
  37. package/fesm2022/ng-forge-dynamic-forms-page-field.component-Bsd8YpzL.mjs +327 -0
  38. package/fesm2022/ng-forge-dynamic-forms-page-field.component-Bsd8YpzL.mjs.map +1 -0
  39. package/fesm2022/ng-forge-dynamic-forms-resolve-field-ByGa5cpo.mjs +329 -0
  40. package/fesm2022/ng-forge-dynamic-forms-resolve-field-ByGa5cpo.mjs.map +1 -0
  41. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CuFycQb-.mjs +81 -0
  42. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-CuFycQb-.mjs.map +1 -0
  43. package/fesm2022/ng-forge-dynamic-forms-schema-builder-C62afItJ.mjs +400 -0
  44. package/fesm2022/ng-forge-dynamic-forms-schema-builder-C62afItJ.mjs.map +1 -0
  45. package/fesm2022/ng-forge-dynamic-forms-schema-registry.service-axBV9FBH.mjs +45 -0
  46. package/fesm2022/ng-forge-dynamic-forms-schema-registry.service-axBV9FBH.mjs.map +1 -0
  47. package/fesm2022/ng-forge-dynamic-forms-schema.mjs +8 -14
  48. package/fesm2022/ng-forge-dynamic-forms-schema.mjs.map +1 -1
  49. package/fesm2022/ng-forge-dynamic-forms-testing.mjs +68 -73
  50. package/fesm2022/ng-forge-dynamic-forms-testing.mjs.map +1 -1
  51. package/fesm2022/ng-forge-dynamic-forms-text-field.component--MAh_h4Q.mjs +257 -0
  52. package/fesm2022/ng-forge-dynamic-forms-text-field.component--MAh_h4Q.mjs.map +1 -0
  53. package/fesm2022/ng-forge-dynamic-forms-validation-execution.token-BFyySKtp.mjs +8 -0
  54. package/fesm2022/ng-forge-dynamic-forms-validation-execution.token-BFyySKtp.mjs.map +1 -0
  55. package/fesm2022/ng-forge-dynamic-forms-with-logger-config-BAATqYY0.mjs +94 -0
  56. package/fesm2022/ng-forge-dynamic-forms-with-logger-config-BAATqYY0.mjs.map +1 -0
  57. package/fesm2022/ng-forge-dynamic-forms-wrapper-chain-BnntfNU1.mjs +115 -0
  58. package/fesm2022/ng-forge-dynamic-forms-wrapper-chain-BnntfNU1.mjs.map +1 -0
  59. package/fesm2022/ng-forge-dynamic-forms.mjs +2195 -3
  60. package/fesm2022/ng-forge-dynamic-forms.mjs.map +1 -1
  61. package/package.json +1 -1
  62. package/types/ng-forge-dynamic-forms-integration.d.ts +543 -415
  63. package/types/ng-forge-dynamic-forms-internal.d.ts +2329 -2437
  64. package/types/ng-forge-dynamic-forms-schema.d.ts +13 -16
  65. package/types/ng-forge-dynamic-forms-testing.d.ts +34 -37
  66. package/types/ng-forge-dynamic-forms.d.ts +796 -722
  67. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs +0 -750
  68. package/fesm2022/ng-forge-dynamic-forms-array-field.component-CSCKq_1K.mjs.map +0 -1
  69. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs +0 -2394
  70. package/fesm2022/ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map +0 -1
  71. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs +0 -1054
  72. package/fesm2022/ng-forge-dynamic-forms-bootstrap-web-mcp-BtjqeJOj.mjs.map +0 -1
  73. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs +0 -86
  74. package/fesm2022/ng-forge-dynamic-forms-container-field.component-Dc3ROhHL.mjs.map +0 -1
  75. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs +0 -33
  76. package/fesm2022/ng-forge-dynamic-forms-css-wrapper.component-BzgX8lAD.mjs.map +0 -1
  77. package/fesm2022/ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs +0 -17
  78. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs +0 -25
  79. package/fesm2022/ng-forge-dynamic-forms-field-errors-wrapper.component-CIKbWqYA.mjs.map +0 -1
  80. package/fesm2022/ng-forge-dynamic-forms-form-schema.service-Dx5Dlb15.mjs +0 -24
  81. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs +0 -177
  82. package/fesm2022/ng-forge-dynamic-forms-group-field.component-DAC3P9DC.mjs.map +0 -1
  83. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs +0 -5133
  84. package/fesm2022/ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs.map +0 -1
  85. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs +0 -176
  86. package/fesm2022/ng-forge-dynamic-forms-page-field.component-DPKJcwUO.mjs.map +0 -1
  87. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs +0 -19
  88. package/fesm2022/ng-forge-dynamic-forms-row-wrapper.component-DMEeKZny.mjs.map +0 -1
  89. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs +0 -570
  90. package/fesm2022/ng-forge-dynamic-forms-schema-builder-BvDTzfdM.mjs.map +0 -1
  91. package/fesm2022/ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs +0 -126
  92. package/fesm2022/ng-forge-dynamic-forms-text-field.component-BB-jutBq.mjs.map +0 -1
@@ -1,2394 +0,0 @@
1
- import { untracked, isWritableSignal, isSignal, inject, Injector, DestroyRef, computed } from '@angular/core';
2
- import { createDefaultDerivationLogConfig, shouldLog, ExpressionParser, DynamicFormError, getNestedValue, createFormFieldStateMap, isEqual, readFieldStateInfo, evaluateCondition, extractStringDependencies, extractExpressionDependencies, isDerivationLogicConfig, hasTargetProperty, getChangedKeys, resolveHttpRequest, getChangedKeysWithin, DynamicFormLogger, FunctionRegistryService, FORM_OPTIONS, EXTERNAL_DATA } from '@ng-forge/dynamic-forms/internal';
3
- import { t as traverseFieldsWithContext, q as DEV_MODE, u as DERIVATION_WARNING_TRACKER, v as FormStateManager, w as DERIVATION_LOG_CONFIG, P as PROPERTY_OVERRIDE_STORE } from './ng-forge-dynamic-forms-ng-forge-dynamic-forms-CXQe_5td.mjs';
4
- import { HttpClient } from '@angular/common/http';
5
- import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
6
- import { explicitEffect } from 'ngxtension/explicit-effect';
7
- import { D as DEFAULT_DEBOUNCE_MS } from './ng-forge-dynamic-forms-debounce-C9F4v1Ke.mjs';
8
- import { EMPTY, startWith, pairwise, map, filter, debounceTime, switchMap, Observable, from, distinctUntilChanged, merge, combineLatestWith, auditTime, exhaustMap, scheduled, queueScheduler, of, timer, catchError as catchError$1 } from 'rxjs';
9
- import { catchError } from 'rxjs/operators';
10
-
11
- class ActiveDerivationLogger {
12
- logger;
13
- config = createDefaultDerivationLogConfig();
14
- setConfig(config) {
15
- this.config = config;
16
- }
17
- cycleStart(trigger, entryCount) {
18
- if (!shouldLog(this.config, 'verbose'))
19
- return;
20
- this.logger.debug(`Derivation - Starting cycle (${trigger}) with ${entryCount} derivation(s)`);
21
- }
22
- iteration(iterationNumber) {
23
- if (!shouldLog(this.config, 'verbose'))
24
- return;
25
- this.logger.debug(`Derivation - Iteration ${iterationNumber}`);
26
- }
27
- evaluation(entry) {
28
- if (!shouldLog(this.config, 'verbose'))
29
- return;
30
- const name = entry.debugName ? `"${entry.debugName}"` : entry.fieldKey;
31
- switch (entry.result) {
32
- case 'applied':
33
- this.logger.debug(`Derivation - Applied ${name}`, {
34
- field: entry.fieldKey,
35
- previousValue: entry.previousValue,
36
- newValue: entry.newValue,
37
- ...(entry.durationMs !== undefined && { durationMs: entry.durationMs }),
38
- });
39
- break;
40
- case 'skipped':
41
- this.logger.debug(`Derivation - Skipped ${name} (${this.formatSkipReason(entry.skipReason)})`, {
42
- field: entry.fieldKey,
43
- reason: entry.skipReason,
44
- });
45
- break;
46
- case 'error':
47
- this.logger.debug(`Derivation - Error ${name}`, {
48
- field: entry.fieldKey,
49
- error: entry.error,
50
- });
51
- break;
52
- }
53
- }
54
- summary(result, trigger) {
55
- if (!shouldLog(this.config, 'summary'))
56
- return;
57
- if (result.appliedCount === 0 && result.errorCount === 0)
58
- return;
59
- this.logger.debug(`Derivation - Cycle complete (${trigger})`, {
60
- applied: result.appliedCount,
61
- skipped: result.skippedCount,
62
- errors: result.errorCount,
63
- iterations: result.iterations,
64
- });
65
- }
66
- maxIterationsReached(result, trigger) {
67
- this.logger.warn(`Derivation - Max iterations reached (${trigger}). ` +
68
- `This may indicate a loop in derivation logic. ` +
69
- `Applied: ${result.appliedCount}, Skipped: ${result.skippedCount}, Errors: ${result.errorCount}`);
70
- }
71
- formatSkipReason(reason) {
72
- switch (reason) {
73
- case 'condition-false':
74
- return 'condition not met';
75
- case 'value-unchanged':
76
- return 'value unchanged';
77
- case 'already-applied':
78
- return 'already applied this cycle';
79
- case 'user-override':
80
- return 'user override active';
81
- case 'target-not-found':
82
- return 'target not found';
83
- default:
84
- return 'unknown';
85
- }
86
- }
87
- }
88
- class NoopDerivationLogger {
89
- cycleStart() {
90
- /* no-op */
91
- }
92
- iteration() {
93
- /* no-op */
94
- }
95
- evaluation() {
96
- /* no-op */
97
- }
98
- summary() {
99
- /* no-op */
100
- }
101
- maxIterationsReached() {
102
- /* no-op */
103
- }
104
- }
105
- const NOOP_INSTANCE = new NoopDerivationLogger();
106
- function createDerivationLogger(config, logger) {
107
- const effectiveConfig = config ?? createDefaultDerivationLogConfig();
108
- if (effectiveConfig.level === 'none') {
109
- return NOOP_INSTANCE;
110
- }
111
- const service = new ActiveDerivationLogger();
112
- service.logger = logger;
113
- service.setConfig(effectiveConfig);
114
- return service;
115
- }
116
-
117
- function parseArrayPath(path) {
118
- const ARRAY_PLACEHOLDER = '.$.';
119
- const placeholderIndex = path.indexOf(ARRAY_PLACEHOLDER);
120
- if (placeholderIndex === -1) {
121
- return {
122
- arrayPath: '',
123
- relativePath: '',
124
- isArrayPath: false,
125
- };
126
- }
127
- return {
128
- arrayPath: path.substring(0, placeholderIndex),
129
- relativePath: path.substring(placeholderIndex + ARRAY_PLACEHOLDER.length),
130
- isArrayPath: true,
131
- };
132
- }
133
- function resolveArrayPath(path, index) {
134
- const info = parseArrayPath(path);
135
- if (!info.isArrayPath) {
136
- return path;
137
- }
138
- return `${info.arrayPath}.${index}.${info.relativePath}`;
139
- }
140
- function isArrayPlaceholderPath(path) {
141
- return path.includes('.$.');
142
- }
143
- function extractArrayPath(path) {
144
- return parseArrayPath(path).arrayPath;
145
- }
146
- function splitPath(path) {
147
- if (!path)
148
- return [];
149
- return path
150
- .replace(/\[(\d+)\]/g, '.$1')
151
- .split('.')
152
- .filter(Boolean);
153
- }
154
- function joinPath(segments) {
155
- return segments.join('.');
156
- }
157
- function getParentPath(path) {
158
- const segments = splitPath(path);
159
- if (segments.length <= 1)
160
- return '';
161
- return joinPath(segments.slice(0, -1));
162
- }
163
- function getLeafPath(path) {
164
- const segments = splitPath(path);
165
- return segments[segments.length - 1] ?? '';
166
- }
167
- function parseMultiArrayPath(path) {
168
- const parts = path.split('.');
169
- const segments = [];
170
- const placeholderPositions = [];
171
- for (let i = 0; i < parts.length; i++) {
172
- if (parts[i] === '$') {
173
- placeholderPositions.push(i);
174
- }
175
- else {
176
- segments.push(parts[i]);
177
- }
178
- }
179
- return {
180
- isArrayPath: placeholderPositions.length > 0,
181
- placeholderCount: placeholderPositions.length,
182
- segments,
183
- placeholderPositions,
184
- };
185
- }
186
- function resolveMultiArrayPath(path, indices) {
187
- const parts = path.split('.');
188
- let indexPointer = 0;
189
- const resolved = parts.map((part) => {
190
- if (part === '$') {
191
- if (indexPointer >= indices.length) {
192
- throw new Error(`Not enough indices provided for path '${path}'. ` +
193
- `Expected ${parts.filter((p) => p === '$').length} indices, got ${indices.length}.`);
194
- }
195
- return String(indices[indexPointer++]);
196
- }
197
- return part;
198
- });
199
- if (indexPointer < indices.length) {
200
- throw new Error(`Too many indices provided for path '${path}'. ` + `Expected ${indexPointer} indices, got ${indices.length}.`);
201
- }
202
- return resolved.join('.');
203
- }
204
- function countArrayPlaceholders(path) {
205
- return path.split('.').filter((part) => part === '$').length;
206
- }
207
-
208
- function createEmptyDerivationCollection() {
209
- return { entries: [] };
210
- }
211
- function createDerivationChainContext() {
212
- return {
213
- appliedDerivations: new Set(),
214
- iteration: 0,
215
- };
216
- }
217
- function createDerivationKey(fieldKey) {
218
- return fieldKey;
219
- }
220
- function parseDerivationKey(key) {
221
- return { fieldKey: key };
222
- }
223
-
224
- function isThenableOrObservable(value) {
225
- if (value === null || typeof value !== 'object')
226
- return false;
227
- const candidate = value;
228
- return typeof candidate.then === 'function' || typeof candidate.subscribe === 'function';
229
- }
230
- function computeValueFromEntry(entry, evalContext, options) {
231
- if (entry.value !== undefined) {
232
- return entry.value;
233
- }
234
- if (entry.expression) {
235
- return ExpressionParser.evaluate(entry.expression, evalContext);
236
- }
237
- if (entry.fn || entry.functionName) {
238
- if (entry.fn && entry.functionName) {
239
- evalContext.logger.warn(`Both "fn" and "functionName" are set on derivation for '${options.subject}'. Inline "fn" takes precedence; "functionName" is ignored.`);
240
- }
241
- const fn = entry.fn ?? (entry.functionName ? options.derivationFunctions?.[entry.functionName] : undefined);
242
- if (!fn) {
243
- const kind = options.functionKind ?? 'Derivation function';
244
- throw new DynamicFormError(`${kind} '${entry.functionName}' not found in customFnConfig.derivations`);
245
- }
246
- const result = fn(evalContext);
247
- if (isThenableOrObservable(result)) {
248
- evalContext.logger.warn(`Derivation for '${options.subject}' returned a Promise or Observable. ` +
249
- `Synchronous derivations must return a value; use 'asyncDerivations' or an HTTP derivation for asynchronous logic. Skipping.`);
250
- return undefined;
251
- }
252
- return result;
253
- }
254
- throw new DynamicFormError(`Derivation for ${options.subject} has no value source. Specify 'value', 'expression', 'functionName', or 'fn'.`);
255
- }
256
-
257
- function getParentPathInScope(fieldKey) {
258
- const lastDot = fieldKey.lastIndexOf('.');
259
- if (lastDot <= 0)
260
- return undefined;
261
- return fieldKey.slice(0, lastDot);
262
- }
263
- function resolveArrayItemScope(pathInfo, arrayItem) {
264
- const relativePath = pathInfo.isArrayPath ? pathInfo.relativePath : '';
265
- const innerParentPath = relativePath.includes('.') ? relativePath.slice(0, relativePath.lastIndexOf('.')) : '';
266
- const groupValue = innerParentPath ? getNestedValue(arrayItem, innerParentPath) : arrayItem;
267
- const fieldValue = relativePath ? getNestedValue(arrayItem, relativePath) : arrayItem;
268
- return { relativePath, groupValue, fieldValue };
269
- }
270
-
271
- const MAX_DERIVATION_ITERATIONS = 10;
272
- const ARRAY_PLACEHOLDER = '.$.';
273
- const MAX_AST_CACHE_SIZE = 1000;
274
- const DERIVATION_KEY_DELIMITER = '\x00';
275
-
276
- function resolveFieldInstance(rootForm, fieldPath) {
277
- const parts = fieldPath.split('.');
278
- let current = rootForm;
279
- for (let i = 0; i < parts.length - 1; i++) {
280
- const next = current[parts[i]];
281
- if (!next)
282
- return undefined;
283
- current = next;
284
- }
285
- const fieldAccessor = current[parts[parts.length - 1]];
286
- if (!fieldAccessor)
287
- return undefined;
288
- return untracked(fieldAccessor);
289
- }
290
- function readFieldDirty(rootForm, fieldPath) {
291
- const fieldInstance = resolveFieldInstance(rootForm, fieldPath);
292
- if (!fieldInstance)
293
- return undefined;
294
- return untracked(fieldInstance.dirty);
295
- }
296
- function resetFieldState(rootForm, fieldPath) {
297
- const fieldInstance = resolveFieldInstance(rootForm, fieldPath);
298
- if (!fieldInstance)
299
- return;
300
- fieldInstance.reset();
301
- }
302
- function applyValueToForm(targetPath, value, rootForm, logger, warningTracker) {
303
- if (!targetPath.includes('.')) {
304
- return setFieldValue(rootForm, targetPath, value, logger, warningTracker);
305
- }
306
- const parts = targetPath.split('.');
307
- let current = rootForm;
308
- for (let i = 0; i < parts.length - 1; i++) {
309
- const part = parts[i];
310
- if (part === '$') {
311
- return false;
312
- }
313
- const next = current[part];
314
- if (!next) {
315
- warnMissingField(targetPath, logger, warningTracker);
316
- return false; }
317
- current = next;
318
- }
319
- const finalPart = parts[parts.length - 1];
320
- return setFieldValue(current, finalPart, value, logger, warningTracker);
321
- }
322
- function setFieldValue(parent, fieldKey, value, logger, warningTracker) {
323
- const fieldAccessor = parent[fieldKey];
324
- if (!fieldAccessor) {
325
- warnMissingField(fieldKey, logger, warningTracker);
326
- return false;
327
- }
328
- const fieldInstance = untracked(fieldAccessor);
329
- if (!fieldInstance || typeof fieldInstance !== 'object') {
330
- warnMissingField(fieldKey, logger, warningTracker);
331
- return false;
332
- }
333
- const valueSignal = fieldInstance.value;
334
- if (isWritableSignal(valueSignal)) {
335
- valueSignal.set(value);
336
- return true;
337
- }
338
- else {
339
- warnMissingField(fieldKey, logger, warningTracker);
340
- return false;
341
- }
342
- }
343
- function warnMissingField(fieldKey, logger, warningTracker) {
344
- if (warningTracker) {
345
- if (warningTracker.warnedKeys.has(fieldKey)) {
346
- return;
347
- }
348
- warningTracker.warnedKeys.add(fieldKey);
349
- }
350
- logger?.warn(`Derivation: target field '${fieldKey}' not found in form. ` +
351
- `Ensure the field is defined in your form configuration. ` +
352
- `This warning is shown once per field.`);
353
- }
354
-
355
- function applyDerivations(collection, context, changedFields) {
356
- const chainContext = createDerivationChainContext();
357
- chainContext.changedFields = changedFields;
358
- chainContext.formFieldState = createFormFieldStateMap(context.rootForm, false);
359
- const { derivationLogger } = context;
360
- const maxIterations = context.maxIterations ?? MAX_DERIVATION_ITERATIONS;
361
- let appliedCount = 0;
362
- let skippedCount = 0;
363
- let errorCount = 0;
364
- let warnCount = 0;
365
- const entriesToProcess = changedFields ? getEntriesForChangedFields$1(collection.entries, changedFields) : collection.entries;
366
- derivationLogger.cycleStart('onChange', entriesToProcess.length);
367
- let hasChanges = true;
368
- while (hasChanges && chainContext.iteration < maxIterations) {
369
- chainContext.iteration++;
370
- hasChanges = false;
371
- derivationLogger.iteration(chainContext.iteration);
372
- for (const entry of entriesToProcess) {
373
- const result = tryApplyDerivation(entry, context, chainContext);
374
- if (result.applied) {
375
- appliedCount++;
376
- hasChanges = true;
377
- }
378
- else if (result.error) {
379
- errorCount++;
380
- }
381
- else if (result.warning) {
382
- warnCount++;
383
- skippedCount++;
384
- }
385
- else {
386
- skippedCount++;
387
- }
388
- }
389
- }
390
- const processingResult = {
391
- appliedCount,
392
- skippedCount,
393
- errorCount,
394
- warnCount,
395
- iterations: chainContext.iteration,
396
- maxIterationsReached: chainContext.iteration >= maxIterations,
397
- };
398
- if (processingResult.maxIterationsReached) {
399
- derivationLogger.maxIterationsReached(processingResult, 'onChange');
400
- }
401
- derivationLogger.summary(processingResult, 'onChange');
402
- return processingResult;
403
- }
404
- function getEntriesForChangedFields$1(entries, changedFields) {
405
- return entries.filter((entry) => {
406
- if (entry.dependsOn.length === 0 || entry.dependsOn.includes('*')) {
407
- return true;
408
- }
409
- if (entry.dependsOn.some((dep) => changedFields.has(dep))) {
410
- return true;
411
- }
412
- for (const changed of changedFields) {
413
- if (entry.fieldKey.startsWith(changed + '.'))
414
- return true;
415
- if (entry.dependsOn.some((dep) => dep.startsWith(changed + '.')))
416
- return true;
417
- }
418
- return false;
419
- });
420
- }
421
- function tryApplyDerivation(entry, context, chainContext) {
422
- const { derivationLogger } = context;
423
- if (entry.http || entry.asyncFunctionName || entry.asyncFn) {
424
- return { applied: false, fieldKey: entry.fieldKey };
425
- }
426
- if (isArrayPlaceholderPath(entry.fieldKey)) {
427
- return tryApplyArrayDerivation(entry, context, chainContext);
428
- }
429
- const derivationKey = createDerivationKey(entry.fieldKey);
430
- if (chainContext.appliedDerivations.has(derivationKey)) {
431
- derivationLogger.evaluation({
432
- debugName: entry.debugName,
433
- fieldKey: entry.fieldKey,
434
- result: 'skipped',
435
- skipReason: 'already-applied',
436
- });
437
- return { applied: false, fieldKey: entry.fieldKey };
438
- }
439
- if (shouldSkipForUserOverride(entry, entry.fieldKey, context, chainContext)) {
440
- derivationLogger.evaluation({
441
- debugName: entry.debugName,
442
- fieldKey: entry.fieldKey,
443
- result: 'skipped',
444
- skipReason: 'user-override',
445
- });
446
- return { applied: false, fieldKey: entry.fieldKey };
447
- }
448
- const formValue = untracked(() => context.formValue());
449
- const evalContext = createEvaluationContext$1(entry, formValue, context, chainContext);
450
- if (!evaluateDerivationCondition(entry.condition, evalContext)) {
451
- derivationLogger.evaluation({
452
- debugName: entry.debugName,
453
- fieldKey: entry.fieldKey,
454
- result: 'skipped',
455
- skipReason: 'condition-false',
456
- });
457
- return { applied: false, fieldKey: entry.fieldKey };
458
- }
459
- let newValue;
460
- try {
461
- newValue = computeDerivedValue(entry, evalContext, context);
462
- }
463
- catch (error) {
464
- const errorMessage = error instanceof Error ? error.message : String(error);
465
- context.logger.error(formatDerivationError(entry, 'compute', errorMessage));
466
- derivationLogger.evaluation({
467
- debugName: entry.debugName,
468
- fieldKey: entry.fieldKey,
469
- result: 'error',
470
- error: errorMessage,
471
- });
472
- return {
473
- applied: false,
474
- fieldKey: entry.fieldKey,
475
- error: errorMessage,
476
- };
477
- }
478
- const currentValue = getNestedValue(formValue, entry.fieldKey);
479
- if (isEqual(currentValue, newValue)) {
480
- derivationLogger.evaluation({
481
- debugName: entry.debugName,
482
- fieldKey: entry.fieldKey,
483
- result: 'skipped',
484
- skipReason: 'value-unchanged',
485
- });
486
- return { applied: false, fieldKey: entry.fieldKey };
487
- }
488
- try {
489
- const wasApplied = applyValueToForm(entry.fieldKey, newValue, context.rootForm, context.logger, context.warningTracker);
490
- if (wasApplied) {
491
- chainContext.appliedDerivations.add(derivationKey);
492
- derivationLogger.evaluation({
493
- debugName: entry.debugName,
494
- fieldKey: entry.fieldKey,
495
- result: 'applied',
496
- previousValue: currentValue,
497
- newValue,
498
- });
499
- return { applied: true, fieldKey: entry.fieldKey, newValue };
500
- }
501
- else {
502
- derivationLogger.evaluation({
503
- debugName: entry.debugName,
504
- fieldKey: entry.fieldKey,
505
- result: 'skipped',
506
- skipReason: 'target-not-found',
507
- });
508
- return {
509
- applied: false,
510
- fieldKey: entry.fieldKey,
511
- warning: `Field '${entry.fieldKey}' not found`,
512
- };
513
- }
514
- }
515
- catch (error) {
516
- const errorMessage = error instanceof Error ? error.message : String(error);
517
- context.logger.error(formatDerivationError(entry, 'apply', errorMessage));
518
- derivationLogger.evaluation({
519
- debugName: entry.debugName,
520
- fieldKey: entry.fieldKey,
521
- result: 'error',
522
- error: errorMessage,
523
- });
524
- return {
525
- applied: false,
526
- fieldKey: entry.fieldKey,
527
- error: errorMessage,
528
- };
529
- }
530
- }
531
- function tryApplyArrayDerivation(entry, context, chainContext) {
532
- const formValue = untracked(() => context.formValue());
533
- const pathInfo = parseArrayPath(entry.fieldKey);
534
- if (!pathInfo.isArrayPath) {
535
- return { applied: false, fieldKey: entry.fieldKey, error: 'Invalid array derivation path' };
536
- }
537
- const { arrayPath } = pathInfo;
538
- const arrayValue = getNestedValue(formValue, arrayPath);
539
- if (!Array.isArray(arrayValue)) {
540
- return { applied: false, fieldKey: entry.fieldKey };
541
- }
542
- let appliedAny = false;
543
- for (let i = 0; i < arrayValue.length; i++) {
544
- const resolvedPath = resolveArrayPath(entry.fieldKey, i);
545
- const derivationKey = createDerivationKey(resolvedPath);
546
- if (chainContext.appliedDerivations.has(derivationKey)) {
547
- continue;
548
- }
549
- if (shouldSkipForUserOverride(entry, resolvedPath, context, chainContext)) {
550
- context.derivationLogger.evaluation({
551
- debugName: entry.debugName,
552
- fieldKey: resolvedPath,
553
- result: 'skipped',
554
- skipReason: 'user-override',
555
- });
556
- continue;
557
- }
558
- const arrayItem = arrayValue[i];
559
- const evalContext = createArrayItemEvaluationContext$1(entry, arrayItem, formValue, i, arrayPath, context, chainContext);
560
- if (!evaluateDerivationCondition(entry.condition, evalContext)) {
561
- continue;
562
- }
563
- let newValue;
564
- try {
565
- newValue = computeDerivedValue(entry, evalContext, context);
566
- }
567
- catch (error) {
568
- const errorMessage = error instanceof Error ? error.message : String(error);
569
- context.logger.error(formatDerivationError(entry, 'compute', `[index ${i}] ${errorMessage}`));
570
- continue;
571
- }
572
- const currentValue = getNestedValue(formValue, resolvedPath);
573
- if (isEqual(currentValue, newValue)) {
574
- continue;
575
- }
576
- try {
577
- applyValueToForm(resolvedPath, newValue, context.rootForm, context.logger, context.warningTracker);
578
- chainContext.appliedDerivations.add(derivationKey);
579
- appliedAny = true;
580
- }
581
- catch (error) {
582
- const errorMessage = error instanceof Error ? error.message : String(error);
583
- context.logger.error(formatDerivationError(entry, 'apply', `[index ${i}] ${errorMessage}`));
584
- }
585
- }
586
- return { applied: appliedAny, fieldKey: entry.fieldKey };
587
- }
588
- function createEvaluationContext$1(entry, formValue, context, chainContext) {
589
- const fieldValue = getNestedValue(formValue, entry.fieldKey);
590
- const fieldAccessor = getFieldTreeByPath(context.rootForm, entry.fieldKey);
591
- const fieldState = fieldAccessor ? readFieldStateInfo(fieldAccessor, false) : undefined;
592
- const parentPath = getParentPathInScope(entry.fieldKey);
593
- const groupValue = parentPath ? getNestedValue(formValue, parentPath) : undefined;
594
- const scopedToGroup = parentPath !== undefined;
595
- return {
596
- fieldValue,
597
- formValue: scopedToGroup ? (groupValue ?? {}) : formValue,
598
- fieldPath: entry.fieldKey,
599
- groupValue,
600
- customFunctions: context.customFunctions,
601
- externalData: context.externalData,
602
- logger: context.logger,
603
- rootFormValue: formValue,
604
- fieldState,
605
- formFieldState: chainContext.formFieldState,
606
- };
607
- }
608
- function getFieldTreeByPath(root, path) {
609
- let current = root;
610
- for (const segment of path.split('.')) {
611
- if (current === undefined || current === null)
612
- return undefined;
613
- current = current[segment];
614
- }
615
- return current;
616
- }
617
- function createArrayItemEvaluationContext$1(entry, arrayItem, rootFormValue, itemIndex, arrayPath, context, chainContext) {
618
- const pathInfo = parseArrayPath(entry.fieldKey);
619
- let fieldState;
620
- if (pathInfo.isArrayPath && pathInfo.relativePath) {
621
- const rootFormRecord = context.rootForm;
622
- const arrayAccessor = rootFormRecord[arrayPath];
623
- if (arrayAccessor) {
624
- const arrayItems = arrayAccessor;
625
- const itemAccessor = arrayItems[String(itemIndex)];
626
- if (itemAccessor) {
627
- const itemFields = itemAccessor;
628
- const fieldAccessor = itemFields[pathInfo.relativePath];
629
- if (fieldAccessor) {
630
- fieldState = readFieldStateInfo(fieldAccessor, false);
631
- }
632
- }
633
- }
634
- }
635
- const { relativePath, groupValue, fieldValue } = resolveArrayItemScope(pathInfo, arrayItem);
636
- return {
637
- fieldValue,
638
- formValue: arrayItem,
639
- fieldPath: relativePath ? `${arrayPath}.${itemIndex}.${relativePath}` : `${arrayPath}.${itemIndex}`,
640
- groupValue,
641
- customFunctions: context.customFunctions,
642
- externalData: context.externalData,
643
- logger: context.logger,
644
- rootFormValue,
645
- arrayIndex: itemIndex,
646
- arrayPath,
647
- fieldState,
648
- formFieldState: chainContext.formFieldState,
649
- };
650
- }
651
- function evaluateDerivationCondition(condition, context) {
652
- if (typeof condition === 'boolean') {
653
- return condition;
654
- }
655
- return evaluateCondition(condition, context);
656
- }
657
- function computeDerivedValue(entry, evalContext, applicatorContext) {
658
- return computeValueFromEntry(entry, evalContext, {
659
- derivationFunctions: applicatorContext.derivationFunctions,
660
- subject: entry.fieldKey,
661
- });
662
- }
663
- const ERROR_PREFIX$1 = '[Derivation]';
664
- function formatDerivationError(entry, phase, message) {
665
- const source = entry.expression
666
- ? `expression: "${entry.expression.substring(0, 50)}${entry.expression.length > 50 ? '...' : ''}"`
667
- : entry.functionName
668
- ? `function: "${entry.functionName}"`
669
- : entry.fn
670
- ? 'inline fn'
671
- : entry.value !== undefined
672
- ? 'static value'
673
- : 'unknown source';
674
- return `${ERROR_PREFIX$1} Failed to ${phase} derivation (field: ${entry.fieldKey}, ${source}): ${message}`;
675
- }
676
- function hasDependencyChanged(entry, changedFields) {
677
- return entry.dependsOn.some((dep) => dep === '*' || changedFields.has(dep));
678
- }
679
- function shouldSkipForUserOverride(entry, resolvedFieldKey, context, chainContext) {
680
- if (!entry.stopOnUserOverride)
681
- return false;
682
- const isDirty = readFieldDirty(context.rootForm, resolvedFieldKey) ?? false;
683
- if (!isDirty)
684
- return false;
685
- if (entry.reEngageOnDependencyChange && chainContext.changedFields) {
686
- if (hasDependencyChanged(entry, chainContext.changedFields)) {
687
- resetFieldState(context.rootForm, resolvedFieldKey);
688
- return false;
689
- }
690
- }
691
- return true;
692
- }
693
- function applyDerivationsForTrigger(collection, trigger, context, changedFields) {
694
- const filteredEntries = collection.entries.filter((entry) => {
695
- if (entry.http || entry.asyncFunctionName || entry.asyncFn)
696
- return false;
697
- if (trigger === 'onChange') {
698
- return !entry.trigger || entry.trigger === 'onChange';
699
- }
700
- return entry.trigger === trigger;
701
- });
702
- const filteredCollection = {
703
- entries: filteredEntries,
704
- };
705
- return applyDerivations(filteredCollection, context, changedFields);
706
- }
707
- function getDebouncedDerivationEntries(collection) {
708
- return collection.entries.filter((entry) => entry.trigger === 'debounced');
709
- }
710
-
711
- function topologicalSort(entries) {
712
- if (entries.length <= 1) {
713
- return [...entries];
714
- }
715
- const graph = new Map();
716
- const inDegree = new Map();
717
- for (const entry of entries) {
718
- graph.set(entry, new Set());
719
- inDegree.set(entry, 0);
720
- }
721
- const derivationsByField = new Map();
722
- for (const entry of entries) {
723
- const fieldEntries = derivationsByField.get(entry.fieldKey) ?? [];
724
- fieldEntries.push(entry);
725
- derivationsByField.set(entry.fieldKey, fieldEntries);
726
- if (entry.fieldKey.includes('.$.')) {
727
- const relativePath = entry.fieldKey.split('.$.')[1];
728
- if (relativePath) {
729
- const relativeEntries = derivationsByField.get(relativePath) ?? [];
730
- relativeEntries.push(entry);
731
- derivationsByField.set(relativePath, relativeEntries);
732
- }
733
- }
734
- }
735
- const wildcardDependents = [];
736
- for (const entry of entries) {
737
- if (entry.dependsOn.includes('*')) {
738
- wildcardDependents.push(entry);
739
- }
740
- }
741
- for (const entryB of entries) {
742
- const groupPath = entryB.fieldKey.includes('.') && !entryB.fieldKey.includes('.$.')
743
- ? entryB.fieldKey.slice(0, entryB.fieldKey.lastIndexOf('.'))
744
- : undefined;
745
- for (const dep of entryB.dependsOn) {
746
- if (dep === '*')
747
- continue;
748
- const producers = [
749
- ...(derivationsByField.get(dep) ?? []),
750
- ...(groupPath && !dep.includes('.') ? (derivationsByField.get(`${groupPath}.${dep}`) ?? []) : []),
751
- ];
752
- for (const entryA of producers) {
753
- const entryAEdges = graph.get(entryA);
754
- if (entryA !== entryB && entryAEdges && !entryAEdges.has(entryB)) {
755
- entryAEdges.add(entryB);
756
- inDegree.set(entryB, (inDegree.get(entryB) ?? 0) + 1);
757
- }
758
- }
759
- }
760
- }
761
- for (const entryB of wildcardDependents) {
762
- for (const entryA of entries) {
763
- const entryAEdges = graph.get(entryA);
764
- if (entryA !== entryB && entryAEdges && !entryAEdges.has(entryB)) {
765
- entryAEdges.add(entryB);
766
- inDegree.set(entryB, (inDegree.get(entryB) ?? 0) + 1);
767
- }
768
- }
769
- }
770
- const queue = [];
771
- const sorted = [];
772
- for (const entry of entries) {
773
- if (inDegree.get(entry) === 0) {
774
- queue.push(entry);
775
- }
776
- }
777
- while (queue.length > 0) {
778
- const current = queue.shift();
779
- if (!current)
780
- break; sorted.push(current);
781
- const neighbors = graph.get(current) ?? new Set();
782
- for (const neighbor of neighbors) {
783
- const newDegree = (inDegree.get(neighbor) ?? 0) - 1;
784
- inDegree.set(neighbor, newDegree);
785
- if (newDegree === 0) {
786
- queue.push(neighbor);
787
- }
788
- }
789
- }
790
- if (sorted.length !== entries.length) {
791
- const sortedSet = new Set(sorted);
792
- for (const entry of entries) {
793
- if (!sortedSet.has(entry)) {
794
- sorted.push(entry);
795
- }
796
- }
797
- }
798
- return sorted;
799
- }
800
-
801
- function extractDependenciesFromConfig(config) {
802
- const deps = new Set();
803
- if (config.dependsOn && config.dependsOn.length > 0) {
804
- config.dependsOn.forEach((dep) => deps.add(dep));
805
- }
806
- else {
807
- if (config.expression) {
808
- extractStringDependencies(config.expression).forEach((dep) => deps.add(dep));
809
- }
810
- if (config.functionName || config.fn) {
811
- deps.add('*');
812
- }
813
- }
814
- if (config.condition && config.condition !== true) {
815
- extractExpressionDependencies(config.condition).forEach((dep) => deps.add(dep));
816
- }
817
- return Array.from(deps);
818
- }
819
-
820
- function extractInlineFn(config) {
821
- return config.fn;
822
- }
823
- function extractInlineAsyncFn(config) {
824
- return config.asyncFn;
825
- }
826
-
827
- const SELF_DEPENDENCY_TOKEN = '$self';
828
- const GROUP_DEPENDENCY_TOKEN = '$group';
829
- function buildEffectiveFieldKey(fieldKey, context) {
830
- if (context.arrayPath && context.groupPath) {
831
- return `${context.arrayPath}.$.${context.groupPath}.${fieldKey}`;
832
- }
833
- if (context.arrayPath) {
834
- return `${context.arrayPath}.$.${fieldKey}`;
835
- }
836
- if (context.groupPath) {
837
- return `${context.groupPath}.${fieldKey}`;
838
- }
839
- return fieldKey;
840
- }
841
- function buildNearestGroupPath(context) {
842
- if (context.arrayPath && context.groupPath) {
843
- return `${context.arrayPath}.$.${context.groupPath}`;
844
- }
845
- return context.arrayPath ?? context.groupPath;
846
- }
847
- function resolveTokenDependencies(deps, effectiveFieldKey, context) {
848
- return deps.map((dep) => {
849
- if (dep === SELF_DEPENDENCY_TOKEN) {
850
- return effectiveFieldKey;
851
- }
852
- if (dep.startsWith(`${SELF_DEPENDENCY_TOKEN}.`)) {
853
- return effectiveFieldKey + dep.slice(SELF_DEPENDENCY_TOKEN.length);
854
- }
855
- if (dep === GROUP_DEPENDENCY_TOKEN || dep.startsWith(`${GROUP_DEPENDENCY_TOKEN}.`)) {
856
- const groupPath = buildNearestGroupPath(context);
857
- if (!groupPath) {
858
- throw new DynamicFormError(`Derivation for '${effectiveFieldKey}' uses '${dep}' but the field has no parent group or array. ` +
859
- `'${GROUP_DEPENDENCY_TOKEN}' is only valid for fields nested inside a group or array container.`);
860
- }
861
- if (dep === GROUP_DEPENDENCY_TOKEN)
862
- return groupPath;
863
- return groupPath + dep.slice(GROUP_DEPENDENCY_TOKEN.length);
864
- }
865
- return dep;
866
- });
867
- }
868
- const CONTEXT_TRAVERSAL_OPTIONS = {
869
- onArrayChild: (_parent, field) => ({ arrayPath: field.key, groupPath: undefined }),
870
- onGroupChild: (parent, field) => {
871
- if (!field.key)
872
- return {};
873
- return { groupPath: parent.groupPath ? `${parent.groupPath}.${field.key}` : field.key };
874
- },
875
- };
876
-
877
- function collectDerivations(fields) {
878
- const entries = [];
879
- traverseFieldsWithContext(fields, {}, (field, context) => collectValueEntriesFromField(field, entries, context), CONTEXT_TRAVERSAL_OPTIONS);
880
- return { entries: topologicalSort(entries) };
881
- }
882
- function collectAllDerivations(fields) {
883
- const valueEntries = [];
884
- const propertyEntries = [];
885
- traverseFieldsWithContext(fields, {}, (field, context) => {
886
- collectValueEntriesFromField(field, valueEntries, context);
887
- collectPropertyEntriesFromField(field, propertyEntries, context);
888
- }, CONTEXT_TRAVERSAL_OPTIONS);
889
- return {
890
- value: { entries: topologicalSort(valueEntries) },
891
- property: { entries: propertyEntries },
892
- };
893
- }
894
- function collectValueEntriesFromField(field, entries, context) {
895
- const fieldKey = field.key;
896
- if (!fieldKey)
897
- return;
898
- const validationField = field;
899
- if (validationField.derivation) {
900
- entries.push(createShorthandEntry(fieldKey, validationField.derivation, context));
901
- }
902
- if (validationField.logic) {
903
- for (const logicConfig of validationField.logic) {
904
- if (!isDerivationLogicConfig(logicConfig))
905
- continue;
906
- if (hasTargetProperty(logicConfig))
907
- continue; entries.push(createValueLogicEntry(fieldKey, logicConfig, context));
908
- }
909
- }
910
- }
911
- function collectPropertyEntriesFromField(field, entries, context) {
912
- const fieldKey = field.key;
913
- if (!fieldKey)
914
- return;
915
- const validationField = field;
916
- if (!validationField.logic)
917
- return;
918
- for (const logicConfig of validationField.logic) {
919
- if (!isDerivationLogicConfig(logicConfig))
920
- continue;
921
- if (!hasTargetProperty(logicConfig))
922
- continue;
923
- entries.push(createPropertyLogicEntry(fieldKey, logicConfig, context));
924
- }
925
- }
926
- function createShorthandEntry(fieldKey, expression, context) {
927
- const effectiveFieldKey = buildEffectiveFieldKey(fieldKey, context);
928
- return {
929
- fieldKey: effectiveFieldKey,
930
- dependsOn: extractStringDependencies(expression),
931
- condition: true,
932
- expression,
933
- trigger: 'onChange',
934
- isShorthand: true,
935
- };
936
- }
937
- function createValueLogicEntry(fieldKey, config, context) {
938
- const effectiveFieldKey = buildEffectiveFieldKey(fieldKey, context);
939
- if (config.source === 'http') {
940
- if (config.dependsOn.length === 0) {
941
- throw new DynamicFormError(`HTTP derivation for '${effectiveFieldKey}' requires explicit 'dependsOn'. ` +
942
- `Wildcard dependencies would trigger HTTP requests on every form change.`);
943
- }
944
- if (config.dependsOn.includes('*')) {
945
- throw new DynamicFormError(`HTTP derivation for '${effectiveFieldKey}' cannot use wildcard ('*') in 'dependsOn'. ` +
946
- `Wildcards would trigger HTTP requests on every form change. Specify explicit field dependencies instead.`);
947
- }
948
- }
949
- if (config.source === 'asyncFunction') {
950
- if (config.dependsOn.length === 0) {
951
- throw new DynamicFormError(`Async derivation for '${effectiveFieldKey}' requires explicit 'dependsOn'. ` +
952
- `Wildcard dependencies would trigger async functions on every form change.`);
953
- }
954
- if (config.dependsOn.includes('*')) {
955
- throw new DynamicFormError(`Async derivation for '${effectiveFieldKey}' cannot use wildcard ('*') in 'dependsOn'. ` +
956
- `Wildcards would trigger async functions on every form change. Specify explicit field dependencies instead.`);
957
- }
958
- }
959
- const dependsOn = resolveTokenDependencies(extractDependenciesFromConfig(config), effectiveFieldKey, context);
960
- const condition = config.condition ?? true;
961
- const trigger = config.trigger ?? 'onChange';
962
- const debounceMs = trigger === 'debounced' ? config.debounceMs : undefined;
963
- return {
964
- fieldKey: effectiveFieldKey,
965
- dependsOn,
966
- condition,
967
- value: config.value,
968
- expression: config.expression,
969
- functionName: config.functionName,
970
- fn: extractInlineFn(config),
971
- http: config.http,
972
- responseExpression: config.responseExpression,
973
- asyncFunctionName: config.asyncFunctionName,
974
- asyncFn: extractInlineAsyncFn(config),
975
- trigger,
976
- debounceMs,
977
- isShorthand: false,
978
- originalConfig: config,
979
- debugName: config.debugName,
980
- stopOnUserOverride: config.stopOnUserOverride,
981
- reEngageOnDependencyChange: config.reEngageOnDependencyChange,
982
- };
983
- }
984
- function createPropertyLogicEntry(fieldKey, config, context) {
985
- const effectiveFieldKey = buildEffectiveFieldKey(fieldKey, context);
986
- const errorLocation = `'${effectiveFieldKey}.${config.targetProperty}'`;
987
- const untypedConfig = config;
988
- if (untypedConfig.source === 'http' || untypedConfig.source === 'asyncFunction') {
989
- const conflictingSyncSources = [];
990
- if (untypedConfig.value !== undefined)
991
- conflictingSyncSources.push('value');
992
- if (untypedConfig.expression !== undefined)
993
- conflictingSyncSources.push('expression');
994
- if (untypedConfig.functionName !== undefined)
995
- conflictingSyncSources.push('functionName');
996
- if (untypedConfig.fn !== undefined)
997
- conflictingSyncSources.push('fn');
998
- if (conflictingSyncSources.length > 0) {
999
- throw new DynamicFormError(`Property derivation for ${errorLocation} sets 'source: ${JSON.stringify(untypedConfig.source)}' alongside ` +
1000
- `sync value source(s) [${conflictingSyncSources.join(', ')}]. These are mutually exclusive — remove either ` +
1001
- `the sync fields or the async source.`);
1002
- }
1003
- }
1004
- if (untypedConfig.source === 'http' && (untypedConfig.asyncFunctionName !== undefined || untypedConfig.asyncFn !== undefined)) {
1005
- throw new DynamicFormError(`Property derivation for ${errorLocation} sets 'source: "http"' alongside an async function — these are mutually exclusive.`);
1006
- }
1007
- if (untypedConfig.source === 'asyncFunction' && (untypedConfig.http !== undefined || untypedConfig.responseExpression !== undefined)) {
1008
- throw new DynamicFormError(`Property derivation for ${errorLocation} sets 'source: "asyncFunction"' alongside HTTP fields — these are mutually exclusive.`);
1009
- }
1010
- if (untypedConfig.source === 'http') {
1011
- if (!untypedConfig.http) {
1012
- throw new DynamicFormError(`HTTP property derivation for ${errorLocation} requires an 'http' config object.`);
1013
- }
1014
- if (typeof untypedConfig.responseExpression !== 'string' || untypedConfig.responseExpression.trim() === '') {
1015
- throw new DynamicFormError(`HTTP property derivation for ${errorLocation} requires a non-empty 'responseExpression'.`);
1016
- }
1017
- if (!Array.isArray(untypedConfig.dependsOn) || untypedConfig.dependsOn.length === 0) {
1018
- throw new DynamicFormError(`HTTP property derivation for ${errorLocation} requires explicit 'dependsOn'. ` +
1019
- `Wildcard dependencies would trigger HTTP requests on every form change.`);
1020
- }
1021
- if (untypedConfig.dependsOn.includes('*')) {
1022
- throw new DynamicFormError(`HTTP property derivation for ${errorLocation} cannot use wildcard ('*') in 'dependsOn'. ` +
1023
- `Wildcards would trigger HTTP requests on every form change. Specify explicit field dependencies instead.`);
1024
- }
1025
- }
1026
- if (untypedConfig.source === 'asyncFunction') {
1027
- if (!untypedConfig.asyncFunctionName && !untypedConfig.asyncFn) {
1028
- throw new DynamicFormError(`Async property derivation for ${errorLocation} requires either 'asyncFunctionName' or 'asyncFn'.`);
1029
- }
1030
- if (!Array.isArray(untypedConfig.dependsOn) || untypedConfig.dependsOn.length === 0) {
1031
- throw new DynamicFormError(`Async property derivation for ${errorLocation} requires explicit 'dependsOn'. ` +
1032
- `Wildcard dependencies would trigger async functions on every form change.`);
1033
- }
1034
- if (untypedConfig.dependsOn.includes('*')) {
1035
- throw new DynamicFormError(`Async property derivation for ${errorLocation} cannot use wildcard ('*') in 'dependsOn'. ` +
1036
- `Wildcards would trigger async functions on every form change. Specify explicit field dependencies instead.`);
1037
- }
1038
- }
1039
- const dependsOn = resolveTokenDependencies(extractDependenciesFromConfig(config), effectiveFieldKey, context);
1040
- const condition = config.condition ?? true;
1041
- const trigger = config.trigger ?? 'onChange';
1042
- const debounceMs = trigger === 'debounced' ? config.debounceMs : undefined;
1043
- return {
1044
- fieldKey: effectiveFieldKey,
1045
- targetProperty: config.targetProperty,
1046
- dependsOn,
1047
- condition,
1048
- value: config.value,
1049
- expression: config.expression,
1050
- functionName: config.functionName,
1051
- fn: extractInlineFn(config),
1052
- http: config.http,
1053
- responseExpression: config.responseExpression,
1054
- asyncFunctionName: config.asyncFunctionName,
1055
- asyncFn: extractInlineAsyncFn(config),
1056
- trigger,
1057
- debounceMs,
1058
- debugName: config.debugName,
1059
- originalConfig: config,
1060
- };
1061
- }
1062
-
1063
- function collectPropertyDerivations(fields) {
1064
- return collectAllDerivations(fields).property;
1065
- }
1066
-
1067
- function detectCycles(collection) {
1068
- const graph = buildDependencyGraph(collection);
1069
- if (graph.size === 0) {
1070
- return { hasCycle: false };
1071
- }
1072
- const bidirectionalPairs = detectBidirectionalPairs(collection);
1073
- const result = detectCyclesWithDFS(graph, bidirectionalPairs);
1074
- if (bidirectionalPairs.size > 0) {
1075
- result.bidirectionalPairs = Array.from(bidirectionalPairs);
1076
- }
1077
- return result;
1078
- }
1079
- function detectBidirectionalPairs(collection) {
1080
- const pairs = new Set();
1081
- const dependencyMap = new Map();
1082
- for (const entry of collection.entries) {
1083
- const deps = dependencyMap.get(entry.fieldKey) ?? new Set();
1084
- for (const dep of entry.dependsOn) {
1085
- if (dep !== '*' && dep !== entry.fieldKey) {
1086
- deps.add(dep);
1087
- }
1088
- }
1089
- dependencyMap.set(entry.fieldKey, deps);
1090
- }
1091
- for (const [fieldA, depsA] of dependencyMap) {
1092
- for (const fieldB of depsA) {
1093
- const depsB = dependencyMap.get(fieldB);
1094
- if (depsB?.has(fieldA)) {
1095
- const pairKey = fieldA < fieldB ? `${fieldA}↔${fieldB}` : `${fieldB}↔${fieldA}`;
1096
- pairs.add(pairKey);
1097
- }
1098
- }
1099
- }
1100
- return pairs;
1101
- }
1102
- function isBidirectionalCycle(cyclePath, bidirectionalPairs) {
1103
- if (cyclePath.length !== 3) {
1104
- return false;
1105
- }
1106
- const [first, second, third] = cyclePath;
1107
- if (first !== third) {
1108
- return false;
1109
- }
1110
- const pairKey = first < second ? `${first}↔${second}` : `${second}↔${first}`;
1111
- return bidirectionalPairs.has(pairKey);
1112
- }
1113
- function buildDependencyGraph(collection) {
1114
- const graph = new Map();
1115
- const ensureNode = (fieldKey) => {
1116
- let node = graph.get(fieldKey);
1117
- if (!node) {
1118
- node = {
1119
- fieldKey,
1120
- dependsOn: new Set(),
1121
- dependedOnBy: new Set(),
1122
- };
1123
- graph.set(fieldKey, node);
1124
- }
1125
- return node;
1126
- };
1127
- for (const entry of collection.entries) {
1128
- const fieldNode = ensureNode(entry.fieldKey);
1129
- for (const dep of entry.dependsOn) {
1130
- if (dep !== '*' && dep !== entry.fieldKey) {
1131
- const depNode = ensureNode(dep);
1132
- fieldNode.dependsOn.add(dep);
1133
- depNode.dependedOnBy.add(entry.fieldKey);
1134
- }
1135
- }
1136
- }
1137
- return graph;
1138
- }
1139
- function detectCyclesWithDFS(graph, bidirectionalPairs) {
1140
- const visitState = new Map();
1141
- const parent = new Map();
1142
- for (const fieldKey of graph.keys()) {
1143
- visitState.set(fieldKey, 0 /* VisitState.Unvisited */);
1144
- }
1145
- for (const fieldKey of graph.keys()) {
1146
- if (visitState.get(fieldKey) === 0 /* VisitState.Unvisited */) {
1147
- const cycleResult = dfsVisit(fieldKey, graph, visitState, parent, [], bidirectionalPairs);
1148
- if (cycleResult) {
1149
- return cycleResult;
1150
- }
1151
- }
1152
- }
1153
- return { hasCycle: false };
1154
- }
1155
- function dfsVisit(fieldKey, graph, visitState, parent, path, bidirectionalPairs) {
1156
- visitState.set(fieldKey, 1 /* VisitState.InProgress */);
1157
- path.push(fieldKey);
1158
- const node = graph.get(fieldKey);
1159
- if (!node) {
1160
- visitState.set(fieldKey, 2 /* VisitState.Completed */);
1161
- path.pop();
1162
- return null;
1163
- }
1164
- for (const targetFieldKey of node.dependedOnBy) {
1165
- const targetState = visitState.get(targetFieldKey);
1166
- if (targetState === 1 /* VisitState.InProgress */) {
1167
- const cyclePath = extractCyclePath(path, targetFieldKey);
1168
- if (isBidirectionalCycle(cyclePath, bidirectionalPairs)) {
1169
- continue;
1170
- }
1171
- return {
1172
- hasCycle: true,
1173
- cyclePath,
1174
- errorMessage: formatCycleError(cyclePath),
1175
- };
1176
- }
1177
- if (targetState === 0 /* VisitState.Unvisited */) {
1178
- parent.set(targetFieldKey, fieldKey);
1179
- const result = dfsVisit(targetFieldKey, graph, visitState, parent, path, bidirectionalPairs);
1180
- if (result) {
1181
- return result;
1182
- }
1183
- }
1184
- }
1185
- visitState.set(fieldKey, 2 /* VisitState.Completed */);
1186
- path.pop();
1187
- return null;
1188
- }
1189
- function extractCyclePath(path, cycleStart) {
1190
- const cycleStartIndex = path.indexOf(cycleStart);
1191
- if (cycleStartIndex === -1) {
1192
- return [...path, cycleStart];
1193
- }
1194
- const cyclePath = path.slice(cycleStartIndex);
1195
- cyclePath.push(cycleStart); return cyclePath;
1196
- }
1197
- function formatCycleError(cyclePath) {
1198
- const pathStr = cyclePath.join(' -> ');
1199
- return (`Derivation cycle detected: ${pathStr}\n` +
1200
- `This would cause an infinite loop at runtime. ` +
1201
- `Remove one of the derivations to break the cycle.`);
1202
- }
1203
- function validateNoCycles(collection, logger) {
1204
- const result = detectCycles(collection);
1205
- if (result.hasCycle) {
1206
- throw new Error(result.errorMessage);
1207
- }
1208
- if (DEV_MODE && result.bidirectionalPairs && result.bidirectionalPairs.length > 0 && logger) {
1209
- logger.warn('Derivation: bidirectional derivation patterns detected. ' +
1210
- 'These patterns stabilize via equality checks, but may oscillate with floating-point values ' +
1211
- '(e.g., currency conversions with rounding). ' +
1212
- 'Consider adding tolerance-based comparisons for numeric values.', result.bidirectionalPairs);
1213
- }
1214
- }
1215
-
1216
- const LOG_PREFIX$3 = 'HTTP Derivation -';
1217
- const DEFAULT_HTTP_DEBOUNCE_MS$1 = 300;
1218
- function createHttpDerivationStream(entry, formValue$, context) {
1219
- if (isArrayPlaceholderPath(entry.fieldKey)) {
1220
- context.logger.warn(`${LOG_PREFIX$3} HTTP derivation for array field '${entry.fieldKey}' is not supported. ` +
1221
- `Array HTTP derivations will be supported in a future release.`);
1222
- return EMPTY;
1223
- }
1224
- if (!entry.http || !entry.responseExpression) {
1225
- return EMPTY;
1226
- }
1227
- const responseExpression = entry.responseExpression;
1228
- const debounceMs = entry.debounceMs ?? DEFAULT_HTTP_DEBOUNCE_MS$1;
1229
- return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
1230
- current: current,
1231
- changedFields: getChangedKeys(previous, current),
1232
- })),
1233
- filter(({ changedFields }) => {
1234
- if (changedFields.size === 0)
1235
- return false;
1236
- return entry.dependsOn.some((dep) => changedFields.has(dep));
1237
- }), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
1238
- return new Observable((subscriber) => {
1239
- const formAccessor = untracked(() => context.form());
1240
- if (entry.stopOnUserOverride) {
1241
- const isDirty = readFieldDirty(formAccessor, entry.fieldKey);
1242
- if (isDirty) {
1243
- if (entry.reEngageOnDependencyChange && changedFields.size > 0) {
1244
- if (entry.dependsOn.some((dep) => changedFields.has(dep))) {
1245
- resetFieldState(formAccessor, entry.fieldKey);
1246
- }
1247
- else {
1248
- const derivationLogger = untracked(() => context.derivationLogger());
1249
- derivationLogger.evaluation({
1250
- debugName: entry.debugName,
1251
- fieldKey: entry.fieldKey,
1252
- result: 'skipped',
1253
- skipReason: 'user-override',
1254
- });
1255
- subscriber.complete();
1256
- return;
1257
- }
1258
- }
1259
- else {
1260
- const derivationLogger = untracked(() => context.derivationLogger());
1261
- derivationLogger.evaluation({
1262
- debugName: entry.debugName,
1263
- fieldKey: entry.fieldKey,
1264
- result: 'skipped',
1265
- skipReason: 'user-override',
1266
- });
1267
- subscriber.complete();
1268
- return;
1269
- }
1270
- }
1271
- }
1272
- const customFunctions = context.customFunctions?.();
1273
- const externalData = context.externalData?.();
1274
- const fieldValue = getNestedValue(current, entry.fieldKey);
1275
- const fieldAccessor = formAccessor[entry.fieldKey];
1276
- const fieldState = fieldAccessor ? readFieldStateInfo(fieldAccessor, false) : undefined;
1277
- const evalContext = {
1278
- fieldValue,
1279
- formValue: current,
1280
- fieldPath: entry.fieldKey,
1281
- customFunctions,
1282
- externalData,
1283
- logger: context.logger,
1284
- fieldState,
1285
- formFieldState: createFormFieldStateMap(formAccessor, false),
1286
- };
1287
- if (entry.condition !== true) {
1288
- const conditionMet = typeof entry.condition === 'boolean' ? entry.condition : evaluateCondition(entry.condition, evalContext);
1289
- if (!conditionMet) {
1290
- const derivationLogger = untracked(() => context.derivationLogger());
1291
- derivationLogger.evaluation({
1292
- debugName: entry.debugName,
1293
- fieldKey: entry.fieldKey,
1294
- result: 'skipped',
1295
- skipReason: 'condition-false',
1296
- });
1297
- subscriber.complete();
1298
- return;
1299
- }
1300
- }
1301
- const resolvedRequest = resolveHttpRequest(entry.http, evalContext);
1302
- if (!resolvedRequest) {
1303
- const derivationLogger = untracked(() => context.derivationLogger());
1304
- derivationLogger.evaluation({
1305
- debugName: entry.debugName,
1306
- fieldKey: entry.fieldKey,
1307
- result: 'skipped',
1308
- skipReason: 'condition-false',
1309
- });
1310
- subscriber.complete();
1311
- return;
1312
- }
1313
- const method = (resolvedRequest.method ?? 'GET').toUpperCase();
1314
- const httpSub = context.httpClient
1315
- .request(method, resolvedRequest.url, {
1316
- body: resolvedRequest.body,
1317
- headers: resolvedRequest.headers,
1318
- })
1319
- .subscribe({
1320
- next: (response) => {
1321
- try {
1322
- const newValue = ExpressionParser.evaluate(responseExpression, { response });
1323
- const currentFormValue = untracked(() => context.formValue());
1324
- const currentValue = getNestedValue(currentFormValue, entry.fieldKey);
1325
- if (isEqual(currentValue, newValue)) {
1326
- const derivationLogger = untracked(() => context.derivationLogger());
1327
- derivationLogger.evaluation({
1328
- debugName: entry.debugName,
1329
- fieldKey: entry.fieldKey,
1330
- result: 'skipped',
1331
- skipReason: 'value-unchanged',
1332
- });
1333
- subscriber.complete();
1334
- return;
1335
- }
1336
- const currentForm = untracked(() => context.form());
1337
- if (entry.stopOnUserOverride && readFieldDirty(currentForm, entry.fieldKey)) {
1338
- const derivationLogger = untracked(() => context.derivationLogger());
1339
- derivationLogger.evaluation({
1340
- debugName: entry.debugName,
1341
- fieldKey: entry.fieldKey,
1342
- result: 'skipped',
1343
- skipReason: 'user-override',
1344
- });
1345
- subscriber.complete();
1346
- return;
1347
- }
1348
- applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
1349
- const derivationLogger = untracked(() => context.derivationLogger());
1350
- derivationLogger.evaluation({
1351
- debugName: entry.debugName,
1352
- fieldKey: entry.fieldKey,
1353
- result: 'applied',
1354
- previousValue: currentValue,
1355
- newValue,
1356
- });
1357
- subscriber.complete();
1358
- }
1359
- catch (error) {
1360
- const message = error instanceof Error ? error.message : String(error);
1361
- context.logger.warn(`${LOG_PREFIX$3} Failed to process response for '${entry.fieldKey}': ${message}`);
1362
- subscriber.complete();
1363
- }
1364
- },
1365
- error: (error) => {
1366
- const message = error instanceof Error ? error.message : String(error);
1367
- context.logger.warn(`${LOG_PREFIX$3} HTTP request failed for '${entry.fieldKey}': ${message}`);
1368
- subscriber.complete();
1369
- },
1370
- });
1371
- return () => {
1372
- httpSub.unsubscribe();
1373
- };
1374
- });
1375
- }), catchError((error) => {
1376
- const message = error instanceof Error ? error.message : String(error);
1377
- context.logger.warn(`${LOG_PREFIX$3} Unexpected stream error for '${entry.fieldKey}': ${message}`);
1378
- return EMPTY;
1379
- }));
1380
- }
1381
-
1382
- const LOG_PREFIX$2 = 'Async Derivation -';
1383
- const DEFAULT_ASYNC_DEBOUNCE_MS$1 = 300;
1384
- function createAsyncDerivationStream(entry, formValue$, context) {
1385
- if (isArrayPlaceholderPath(entry.fieldKey)) {
1386
- context.logger.warn(`${LOG_PREFIX$2} Async derivation for array field '${entry.fieldKey}' is not supported. ` +
1387
- `Array async derivations will be supported in a future release.`);
1388
- return EMPTY;
1389
- }
1390
- if (!entry.asyncFunctionName && !entry.asyncFn) {
1391
- context.logger.warn(`${LOG_PREFIX$2} Derivation for '${entry.fieldKey}' has neither "asyncFunctionName" nor "asyncFn". ` +
1392
- `One of the two is required for source: 'asyncFunction'.`);
1393
- return EMPTY;
1394
- }
1395
- if (entry.asyncFn && entry.asyncFunctionName) {
1396
- context.logger.warn(`${LOG_PREFIX$2} Both "asyncFn" and "asyncFunctionName" are set on derivation for '${entry.fieldKey}'. ` +
1397
- `Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.`);
1398
- }
1399
- const inlineAsyncFn = entry.asyncFn;
1400
- const asyncFunctionName = entry.asyncFunctionName;
1401
- const debounceMs = entry.debounceMs ?? DEFAULT_ASYNC_DEBOUNCE_MS$1;
1402
- return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
1403
- current: current,
1404
- changedFields: getChangedKeys(previous, current),
1405
- })),
1406
- filter(({ changedFields }) => {
1407
- if (changedFields.size === 0)
1408
- return false;
1409
- return entry.dependsOn.some((dep) => changedFields.has(dep));
1410
- }), debounceTime(debounceMs), switchMap(({ current, changedFields }) => {
1411
- return new Observable((subscriber) => {
1412
- const formAccessor = untracked(() => context.form());
1413
- if (entry.stopOnUserOverride) {
1414
- const isDirty = readFieldDirty(formAccessor, entry.fieldKey);
1415
- if (isDirty) {
1416
- if (entry.reEngageOnDependencyChange) {
1417
- resetFieldState(formAccessor, entry.fieldKey);
1418
- }
1419
- else {
1420
- const derivationLogger = untracked(() => context.derivationLogger());
1421
- derivationLogger.evaluation({
1422
- debugName: entry.debugName,
1423
- fieldKey: entry.fieldKey,
1424
- result: 'skipped',
1425
- skipReason: 'user-override',
1426
- });
1427
- subscriber.complete();
1428
- return;
1429
- }
1430
- }
1431
- }
1432
- const customFunctions = context.customFunctions?.();
1433
- const asyncDerivationFunctions = context.asyncDerivationFunctions?.();
1434
- const externalData = context.externalData?.();
1435
- const asyncFn = inlineAsyncFn ?? (asyncFunctionName ? asyncDerivationFunctions?.[asyncFunctionName] : undefined);
1436
- if (!asyncFn) {
1437
- context.logger.warn(`${LOG_PREFIX$2} Async derivation function '${asyncFunctionName}' not found for field '${entry.fieldKey}'. ` +
1438
- `Register it in customFnConfig.asyncDerivations.`);
1439
- subscriber.complete();
1440
- return;
1441
- }
1442
- const fieldValue = getNestedValue(current, entry.fieldKey);
1443
- const fieldAccessor = formAccessor[entry.fieldKey];
1444
- const fieldState = fieldAccessor ? readFieldStateInfo(fieldAccessor, false) : undefined;
1445
- const evalContext = {
1446
- fieldValue,
1447
- formValue: current,
1448
- fieldPath: entry.fieldKey,
1449
- customFunctions,
1450
- externalData,
1451
- logger: context.logger,
1452
- fieldState,
1453
- formFieldState: createFormFieldStateMap(formAccessor, false),
1454
- };
1455
- if (entry.condition !== true) {
1456
- const conditionMet = typeof entry.condition === 'boolean' ? entry.condition : evaluateCondition(entry.condition, evalContext);
1457
- if (!conditionMet) {
1458
- const derivationLogger = untracked(() => context.derivationLogger());
1459
- derivationLogger.evaluation({
1460
- debugName: entry.debugName,
1461
- fieldKey: entry.fieldKey,
1462
- result: 'skipped',
1463
- skipReason: 'condition-false',
1464
- });
1465
- subscriber.complete();
1466
- return;
1467
- }
1468
- }
1469
- const result$ = from(asyncFn(evalContext));
1470
- const asyncSub = result$.subscribe({
1471
- next: (newValue) => {
1472
- try {
1473
- const currentFormValue = untracked(() => context.formValue());
1474
- const currentValue = getNestedValue(currentFormValue, entry.fieldKey);
1475
- if (isEqual(currentValue, newValue)) {
1476
- const derivationLogger = untracked(() => context.derivationLogger());
1477
- derivationLogger.evaluation({
1478
- debugName: entry.debugName,
1479
- fieldKey: entry.fieldKey,
1480
- result: 'skipped',
1481
- skipReason: 'value-unchanged',
1482
- });
1483
- subscriber.complete();
1484
- return;
1485
- }
1486
- const currentForm = untracked(() => context.form());
1487
- if (entry.stopOnUserOverride && readFieldDirty(currentForm, entry.fieldKey)) {
1488
- const derivationLogger = untracked(() => context.derivationLogger());
1489
- derivationLogger.evaluation({
1490
- debugName: entry.debugName,
1491
- fieldKey: entry.fieldKey,
1492
- result: 'skipped',
1493
- skipReason: 'user-override',
1494
- });
1495
- subscriber.complete();
1496
- return;
1497
- }
1498
- applyValueToForm(entry.fieldKey, newValue, currentForm, context.logger, context.warningTracker);
1499
- const derivationLogger = untracked(() => context.derivationLogger());
1500
- derivationLogger.evaluation({
1501
- debugName: entry.debugName,
1502
- fieldKey: entry.fieldKey,
1503
- result: 'applied',
1504
- previousValue: currentValue,
1505
- newValue,
1506
- });
1507
- subscriber.complete();
1508
- }
1509
- catch (error) {
1510
- const message = error instanceof Error ? error.message : String(error);
1511
- context.logger.warn(`${LOG_PREFIX$2} Failed to process result for '${entry.fieldKey}': ${message}`);
1512
- subscriber.complete();
1513
- }
1514
- },
1515
- error: (error) => {
1516
- const message = error instanceof Error ? error.message : String(error);
1517
- context.logger.warn(`${LOG_PREFIX$2} Async function failed for '${entry.fieldKey}': ${message}`);
1518
- subscriber.complete();
1519
- },
1520
- complete: () => {
1521
- subscriber.complete();
1522
- },
1523
- });
1524
- return () => {
1525
- asyncSub.unsubscribe();
1526
- };
1527
- });
1528
- }), catchError((error) => {
1529
- const message = error instanceof Error ? error.message : String(error);
1530
- context.logger.warn(`${LOG_PREFIX$2} Unexpected stream error for '${entry.fieldKey}': ${message}`);
1531
- return EMPTY;
1532
- }));
1533
- }
1534
-
1535
- function resolveExternalData(externalDataSignal) {
1536
- const externalDataRecord = untracked(() => externalDataSignal?.());
1537
- if (!externalDataRecord)
1538
- return undefined;
1539
- const resolved = {};
1540
- for (const [key, value] of Object.entries(externalDataRecord)) {
1541
- if (isSignal(value)) {
1542
- resolved[key] = untracked(() => value());
1543
- }
1544
- else {
1545
- resolved[key] = value;
1546
- }
1547
- }
1548
- return resolved;
1549
- }
1550
-
1551
- function getDebouncePeriods(entries) {
1552
- const periods = new Set();
1553
- for (const entry of entries) {
1554
- if (entry.trigger === 'debounced') {
1555
- periods.add(entry.debounceMs ?? DEFAULT_DEBOUNCE_MS);
1556
- }
1557
- }
1558
- return Array.from(periods);
1559
- }
1560
- function filterEntriesByDebouncePeriod(entries, period) {
1561
- return entries.filter((entry) => entry.trigger === 'debounced' && (entry.debounceMs ?? DEFAULT_DEBOUNCE_MS) === period);
1562
- }
1563
-
1564
- function entrySetsEqual(prev, next, sig) {
1565
- if (prev.length !== next.length)
1566
- return false;
1567
- const counts = new Map();
1568
- for (const item of prev) {
1569
- const key = sig(item);
1570
- counts.set(key, (counts.get(key) ?? 0) + 1);
1571
- }
1572
- for (const item of next) {
1573
- const key = sig(item);
1574
- const remaining = counts.get(key);
1575
- if (!remaining)
1576
- return false;
1577
- if (remaining === 1)
1578
- counts.delete(key);
1579
- else
1580
- counts.set(key, remaining - 1);
1581
- }
1582
- return counts.size === 0;
1583
- }
1584
- function buildEntryStreamPipeline(config) {
1585
- return config.collection$.pipe(map((collection) => config.selectEntries(collection)), distinctUntilChanged((prev, next) => entrySetsEqual(prev, next, config.entrySignature)), switchMap((entries) => {
1586
- if (entries.length === 0)
1587
- return EMPTY;
1588
- const streams = entries.map((entry) => config.createStream(entry)).filter((stream) => stream !== null);
1589
- if (streams.length === 0)
1590
- return EMPTY;
1591
- return merge(...streams);
1592
- }));
1593
- }
1594
-
1595
- function rootSegment(path) {
1596
- const dot = path.indexOf('.');
1597
- return dot === -1 ? path : path.slice(0, dot);
1598
- }
1599
- function watchedRootKeys(collection) {
1600
- const watched = new Set();
1601
- for (const entry of collection.entries) {
1602
- const dependsOn = entry.dependsOn ?? [];
1603
- if (dependsOn.length === 0 || dependsOn.includes('*'))
1604
- continue;
1605
- for (const dep of dependsOn)
1606
- watched.add(rootSegment(dep));
1607
- if (entry.fieldKey)
1608
- watched.add(rootSegment(entry.fieldKey));
1609
- }
1610
- return watched;
1611
- }
1612
- function setupOnChangeStream(opts) {
1613
- const collection$ = toObservable(opts.collectionSignal, { injector: opts.injector });
1614
- const formValue$ = toObservable(opts.formValueSignal, { injector: opts.injector });
1615
- const extra$ = opts.additionalAwakeners?.length ? opts.additionalAwakeners.map((s) => toObservable(s, { injector: opts.injector })) : [];
1616
- collection$
1617
- .pipe(filter((collection) => collection !== null),
1618
- map((collection) => [collection, watchedRootKeys(collection)]), combineLatestWith(formValue$, ...extra$), auditTime(0), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => {
1619
- const [collection, watched] = current[0];
1620
- return {
1621
- collection,
1622
- changedFields: getChangedKeysWithin(previous?.[1] ?? null, current[1], watched),
1623
- };
1624
- }), exhaustMap(({ collection, changedFields }) => {
1625
- opts.applyOnChange(collection, changedFields);
1626
- return scheduled([null], queueScheduler);
1627
- }), takeUntilDestroyed(opts.destroyRef))
1628
- .subscribe({
1629
- error: (err) => opts.logger.error(opts.errorLabel, err),
1630
- });
1631
- }
1632
- function setupDebouncedStream(opts) {
1633
- toObservable(opts.formValueSignal, { injector: opts.injector })
1634
- .pipe(debounceTime(DEFAULT_DEBOUNCE_MS), startWith(null), pairwise(), filter((pair) => pair[1] !== null), map(([previous, current]) => ({ current, changedFields: getChangedKeys(previous, current) })), filter(({ changedFields }) => changedFields.size > 0), switchMap(({ changedFields }) => {
1635
- const collection = untracked(() => opts.collectionSignal());
1636
- if (!collection)
1637
- return of(null);
1638
- const debouncePeriods = getDebouncePeriods(collection.entries);
1639
- if (debouncePeriods.length === 0)
1640
- return of(null);
1641
- const periodStreams = debouncePeriods.map((debounceMs) => {
1642
- const additionalWait = Math.max(0, debounceMs - DEFAULT_DEBOUNCE_MS);
1643
- if (additionalWait === 0) {
1644
- return of(null).pipe(map(() => {
1645
- opts.applyDebouncedForPeriod(debounceMs, collection, changedFields);
1646
- }));
1647
- }
1648
- return timer(additionalWait).pipe(map(() => {
1649
- const fresh = untracked(() => opts.collectionSignal()) ?? collection;
1650
- opts.applyDebouncedForPeriod(debounceMs, fresh, changedFields);
1651
- }));
1652
- });
1653
- return merge(...periodStreams);
1654
- }), takeUntilDestroyed(opts.destroyRef))
1655
- .subscribe({
1656
- error: (err) => opts.logger.error(opts.errorLabel, err),
1657
- });
1658
- }
1659
- function setupEntryAsyncStream(opts) {
1660
- const collection$ = toObservable(opts.collectionSignal, { injector: opts.injector });
1661
- buildEntryStreamPipeline({
1662
- collection$,
1663
- selectEntries: opts.selectEntries,
1664
- entrySignature: opts.entrySignature,
1665
- createStream: opts.createStream,
1666
- })
1667
- .pipe(catchError$1((err) => {
1668
- opts.logger.error(opts.errorLabel, err);
1669
- return EMPTY;
1670
- }), takeUntilDestroyed(opts.destroyRef))
1671
- .subscribe();
1672
- }
1673
-
1674
- function createEmptyPropertyDerivationCollection() {
1675
- return { entries: [] };
1676
- }
1677
- function isAsyncPropertyDerivationEntry(entry) {
1678
- return Boolean(entry.http) || Boolean(entry.asyncFunctionName) || Boolean(entry.asyncFn);
1679
- }
1680
-
1681
- const ERROR_PREFIX = '[PropertyDerivation]';
1682
- function applyPropertyDerivations(collection, context, changedFields) {
1683
- let appliedCount = 0;
1684
- let skippedCount = 0;
1685
- let errorCount = 0;
1686
- const baseEntries = changedFields ? getEntriesForChangedFields(collection.entries, changedFields) : collection.entries;
1687
- const entriesToProcess = baseEntries.filter((entry) => !isAsyncPropertyDerivationEntry(entry));
1688
- for (const entry of entriesToProcess) {
1689
- try {
1690
- const applied = tryApplyPropertyDerivation(entry, context);
1691
- if (applied) {
1692
- appliedCount++;
1693
- }
1694
- else {
1695
- skippedCount++;
1696
- }
1697
- }
1698
- catch (error) {
1699
- errorCount++;
1700
- const errorMessage = error instanceof Error ? error.message : String(error);
1701
- context.logger.error(`${ERROR_PREFIX} Failed to process property derivation (field: ${entry.fieldKey}, property: ${entry.targetProperty}): ${errorMessage}`);
1702
- }
1703
- }
1704
- return { appliedCount, skippedCount, errorCount };
1705
- }
1706
- function applyPropertyDerivationsForTrigger(collection, trigger, context, changedFields) {
1707
- const filteredEntries = collection.entries.filter((entry) => {
1708
- if (trigger === 'onChange') {
1709
- return !entry.trigger || entry.trigger === 'onChange';
1710
- }
1711
- return entry.trigger === trigger;
1712
- });
1713
- const filteredCollection = { entries: filteredEntries };
1714
- return applyPropertyDerivations(filteredCollection, context, changedFields);
1715
- }
1716
- function getEntriesForChangedFields(entries, changedFields) {
1717
- return entries.filter((entry) => {
1718
- if (entry.dependsOn.includes('*'))
1719
- return true;
1720
- if (entry.dependsOn.some((dep) => changedFields.has(dep)))
1721
- return true;
1722
- for (const changed of changedFields) {
1723
- if (entry.fieldKey.startsWith(changed + '.'))
1724
- return true;
1725
- if (entry.dependsOn.some((dep) => dep.startsWith(changed + '.')))
1726
- return true;
1727
- }
1728
- return false;
1729
- });
1730
- }
1731
- function tryApplyPropertyDerivation(entry, context) {
1732
- if (isArrayPlaceholderPath(entry.fieldKey)) {
1733
- return tryApplyArrayPropertyDerivation(entry, context);
1734
- }
1735
- const formValue = untracked(() => context.formValue());
1736
- const evalContext = createEvaluationContext(entry, formValue, context);
1737
- if (!evaluatePropertyCondition(entry.condition, evalContext)) {
1738
- return false;
1739
- }
1740
- const newValue = computePropertyValue(entry, evalContext, context);
1741
- context.store.setOverride(entry.fieldKey, entry.targetProperty, newValue);
1742
- return true;
1743
- }
1744
- function tryApplyArrayPropertyDerivation(entry, context) {
1745
- const formValue = untracked(() => context.formValue());
1746
- const pathInfo = parseArrayPath(entry.fieldKey);
1747
- if (!pathInfo.isArrayPath) {
1748
- return false;
1749
- }
1750
- const { arrayPath } = pathInfo;
1751
- const arrayValue = getNestedValue(formValue, arrayPath);
1752
- if (!Array.isArray(arrayValue)) {
1753
- return false;
1754
- }
1755
- let appliedAny = false;
1756
- for (let i = 0; i < arrayValue.length; i++) {
1757
- const resolvedPath = resolveArrayPath(entry.fieldKey, i);
1758
- const arrayItem = arrayValue[i];
1759
- const evalContext = createArrayItemEvaluationContext(entry, arrayItem, formValue, i, arrayPath, context);
1760
- if (!evaluatePropertyCondition(entry.condition, evalContext)) {
1761
- continue;
1762
- }
1763
- try {
1764
- const newValue = computePropertyValue(entry, evalContext, context);
1765
- context.store.setOverride(resolvedPath, entry.targetProperty, newValue);
1766
- appliedAny = true;
1767
- }
1768
- catch (error) {
1769
- const errorMessage = error instanceof Error ? error.message : String(error);
1770
- context.logger.error(`${ERROR_PREFIX} Failed to compute property derivation (field: ${entry.fieldKey}, property: ${entry.targetProperty}, index: ${i}): ${errorMessage}`);
1771
- }
1772
- }
1773
- return appliedAny;
1774
- }
1775
- function createEvaluationContext(entry, formValue, context) {
1776
- const fieldValue = getNestedValue(formValue, entry.fieldKey);
1777
- const parentPath = getParentPathInScope(entry.fieldKey);
1778
- const groupValue = parentPath ? getNestedValue(formValue, parentPath) : undefined;
1779
- const scopedToGroup = parentPath !== undefined;
1780
- return {
1781
- fieldValue,
1782
- formValue: scopedToGroup ? (groupValue ?? {}) : formValue,
1783
- fieldPath: entry.fieldKey,
1784
- groupValue,
1785
- customFunctions: context.customFunctions,
1786
- externalData: context.externalData,
1787
- logger: context.logger,
1788
- rootFormValue: formValue,
1789
- };
1790
- }
1791
- function createArrayItemEvaluationContext(entry, arrayItem, rootFormValue, itemIndex, arrayPath, context) {
1792
- const pathInfo = parseArrayPath(entry.fieldKey);
1793
- const { relativePath, groupValue, fieldValue } = resolveArrayItemScope(pathInfo, arrayItem);
1794
- return {
1795
- fieldValue,
1796
- formValue: arrayItem,
1797
- fieldPath: relativePath ? `${arrayPath}.${itemIndex}.${relativePath}` : `${arrayPath}.${itemIndex}`,
1798
- groupValue,
1799
- customFunctions: context.customFunctions,
1800
- externalData: context.externalData,
1801
- logger: context.logger,
1802
- rootFormValue,
1803
- arrayIndex: itemIndex,
1804
- arrayPath,
1805
- };
1806
- }
1807
- function evaluatePropertyCondition(condition, context) {
1808
- if (typeof condition === 'boolean') {
1809
- return condition;
1810
- }
1811
- return evaluateCondition(condition, context);
1812
- }
1813
- function computePropertyValue(entry, evalContext, applicatorContext) {
1814
- return computeValueFromEntry(entry, evalContext, {
1815
- derivationFunctions: applicatorContext.derivationFunctions,
1816
- subject: `${entry.fieldKey}.${entry.targetProperty}`,
1817
- functionKind: 'Property derivation function',
1818
- });
1819
- }
1820
-
1821
- const LOG_PREFIX$1 = 'HTTP Property Derivation -';
1822
- const DEFAULT_HTTP_DEBOUNCE_MS = 300;
1823
- function createHttpPropertyDerivationStream(entry, formValue$, context) {
1824
- if (isArrayPlaceholderPath(entry.fieldKey)) {
1825
- context.logger.warn(`${LOG_PREFIX$1} HTTP property derivation for array field '${entry.fieldKey}' is not supported. ` +
1826
- `Array HTTP property derivations will be supported in a future release.`);
1827
- return EMPTY;
1828
- }
1829
- if (!entry.http || !entry.responseExpression) {
1830
- return EMPTY;
1831
- }
1832
- const responseExpression = entry.responseExpression;
1833
- const debounceMs = entry.debounceMs ?? DEFAULT_HTTP_DEBOUNCE_MS;
1834
- return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
1835
- current: current,
1836
- changedFields: getChangedKeys(previous, current),
1837
- })), filter(({ changedFields }) => {
1838
- if (changedFields.size === 0)
1839
- return false;
1840
- return entry.dependsOn.some((dep) => changedFields.has(dep));
1841
- }), debounceTime(debounceMs), switchMap(({ current }) => {
1842
- return new Observable((subscriber) => {
1843
- const customFunctions = context.customFunctions?.();
1844
- const externalData = context.externalData?.();
1845
- const fieldValue = getNestedValue(current, entry.fieldKey);
1846
- const evalContext = {
1847
- fieldValue,
1848
- formValue: current,
1849
- fieldPath: entry.fieldKey,
1850
- customFunctions,
1851
- externalData,
1852
- logger: context.logger,
1853
- };
1854
- if (entry.condition !== true) {
1855
- const conditionMet = typeof entry.condition === 'boolean' ? entry.condition : evaluateCondition(entry.condition, evalContext);
1856
- if (!conditionMet) {
1857
- subscriber.complete();
1858
- return;
1859
- }
1860
- }
1861
- const resolvedRequest = resolveHttpRequest(entry.http, evalContext);
1862
- if (!resolvedRequest) {
1863
- subscriber.complete();
1864
- return;
1865
- }
1866
- const method = (resolvedRequest.method ?? 'GET').toUpperCase();
1867
- const httpSub = context.httpClient
1868
- .request(method, resolvedRequest.url, {
1869
- body: resolvedRequest.body,
1870
- headers: resolvedRequest.headers,
1871
- })
1872
- .subscribe({
1873
- next: (response) => {
1874
- try {
1875
- const newValue = ExpressionParser.evaluate(responseExpression, { response });
1876
- untracked(() => context.store.setOverride(entry.fieldKey, entry.targetProperty, newValue));
1877
- subscriber.complete();
1878
- }
1879
- catch (error) {
1880
- const message = error instanceof Error ? error.message : String(error);
1881
- context.logger.warn(`${LOG_PREFIX$1} Failed to process response for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1882
- subscriber.complete();
1883
- }
1884
- },
1885
- error: (error) => {
1886
- const message = error instanceof Error ? error.message : String(error);
1887
- context.logger.warn(`${LOG_PREFIX$1} HTTP request failed for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1888
- subscriber.complete();
1889
- },
1890
- });
1891
- return () => {
1892
- httpSub.unsubscribe();
1893
- };
1894
- });
1895
- }), catchError((error) => {
1896
- const message = error instanceof Error ? error.message : String(error);
1897
- context.logger.warn(`${LOG_PREFIX$1} Unexpected stream error for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1898
- return EMPTY;
1899
- }));
1900
- }
1901
-
1902
- const LOG_PREFIX = 'Async Property Derivation -';
1903
- const DEFAULT_ASYNC_DEBOUNCE_MS = 300;
1904
- function createAsyncPropertyDerivationStream(entry, formValue$, context) {
1905
- if (isArrayPlaceholderPath(entry.fieldKey)) {
1906
- context.logger.warn(`${LOG_PREFIX} Async property derivation for array field '${entry.fieldKey}' is not supported. ` +
1907
- `Array async property derivations will be supported in a future release.`);
1908
- return EMPTY;
1909
- }
1910
- if (!entry.asyncFunctionName && !entry.asyncFn) {
1911
- context.logger.warn(`${LOG_PREFIX} Property derivation for '${entry.fieldKey}.${entry.targetProperty}' has neither "asyncFunctionName" nor "asyncFn". ` +
1912
- `One of the two is required for source: 'asyncFunction'.`);
1913
- return EMPTY;
1914
- }
1915
- if (entry.asyncFn && entry.asyncFunctionName) {
1916
- context.logger.warn(`${LOG_PREFIX} Both "asyncFn" and "asyncFunctionName" are set on property derivation for '${entry.fieldKey}.${entry.targetProperty}'. ` +
1917
- `Inline "asyncFn" takes precedence; "asyncFunctionName" is ignored.`);
1918
- }
1919
- const inlineAsyncFn = entry.asyncFn;
1920
- const asyncFunctionName = entry.asyncFunctionName;
1921
- const debounceMs = entry.debounceMs ?? DEFAULT_ASYNC_DEBOUNCE_MS;
1922
- return formValue$.pipe(startWith(null), pairwise(), map(([previous, current]) => ({
1923
- current: current,
1924
- changedFields: getChangedKeys(previous, current),
1925
- })), filter(({ changedFields }) => {
1926
- if (changedFields.size === 0)
1927
- return false;
1928
- return entry.dependsOn.some((dep) => changedFields.has(dep));
1929
- }), debounceTime(debounceMs), switchMap(({ current }) => {
1930
- return new Observable((subscriber) => {
1931
- const customFunctions = context.customFunctions?.();
1932
- const asyncDerivationFunctions = context.asyncDerivationFunctions?.();
1933
- const externalData = context.externalData?.();
1934
- const asyncFn = inlineAsyncFn ?? (asyncFunctionName ? asyncDerivationFunctions?.[asyncFunctionName] : undefined);
1935
- if (!asyncFn) {
1936
- context.logger.warn(`${LOG_PREFIX} Async derivation function '${asyncFunctionName}' not found for '${entry.fieldKey}.${entry.targetProperty}'. ` +
1937
- `Register it in customFnConfig.asyncDerivations.`);
1938
- subscriber.complete();
1939
- return;
1940
- }
1941
- const fieldValue = getNestedValue(current, entry.fieldKey);
1942
- const evalContext = {
1943
- fieldValue,
1944
- formValue: current,
1945
- fieldPath: entry.fieldKey,
1946
- customFunctions,
1947
- externalData,
1948
- logger: context.logger,
1949
- };
1950
- if (entry.condition !== true) {
1951
- const conditionMet = typeof entry.condition === 'boolean' ? entry.condition : evaluateCondition(entry.condition, evalContext);
1952
- if (!conditionMet) {
1953
- subscriber.complete();
1954
- return;
1955
- }
1956
- }
1957
- const result$ = from(asyncFn(evalContext));
1958
- const asyncSub = result$.subscribe({
1959
- next: (newValue) => {
1960
- try {
1961
- untracked(() => context.store.setOverride(entry.fieldKey, entry.targetProperty, newValue));
1962
- subscriber.complete();
1963
- }
1964
- catch (error) {
1965
- const message = error instanceof Error ? error.message : String(error);
1966
- context.logger.warn(`${LOG_PREFIX} Failed to process result for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1967
- subscriber.complete();
1968
- }
1969
- },
1970
- error: (error) => {
1971
- const message = error instanceof Error ? error.message : String(error);
1972
- context.logger.warn(`${LOG_PREFIX} Async function failed for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1973
- subscriber.complete();
1974
- },
1975
- complete: () => {
1976
- subscriber.complete();
1977
- },
1978
- });
1979
- return () => {
1980
- asyncSub.unsubscribe();
1981
- };
1982
- });
1983
- }), catchError((error) => {
1984
- const message = error instanceof Error ? error.message : String(error);
1985
- context.logger.warn(`${LOG_PREFIX} Unexpected stream error for '${entry.fieldKey}.${entry.targetProperty}': ${message}`);
1986
- return EMPTY;
1987
- }));
1988
- }
1989
-
1990
- class DerivationOrchestrator {
1991
- config;
1992
- injector = inject(Injector);
1993
- destroyRef = inject(DestroyRef);
1994
- logger = inject(DynamicFormLogger);
1995
- warningTracker = inject(DERIVATION_WARNING_TRACKER);
1996
- functionRegistry = inject(FunctionRegistryService);
1997
- httpClient = inject(HttpClient, { optional: true });
1998
- formOptions;
1999
- derivationCollection;
2000
- propertyDerivationCollection;
2001
- constructor(config) {
2002
- this.config = config;
2003
- const valueEnabled = !!(config.form && config.derivationLogger);
2004
- const propertyStore = config.propertyStore;
2005
- const propertyEnabled = !!propertyStore;
2006
- const sharedAllDerivations = valueEnabled && propertyEnabled
2007
- ? computed(() => {
2008
- const fields = config.schemaFields();
2009
- if (!fields || fields.length === 0)
2010
- return null;
2011
- return collectAllDerivations(fields);
2012
- })
2013
- : null;
2014
- this.derivationCollection = valueEnabled
2015
- ? this.setupValuePipeline(sharedAllDerivations)
2016
- : computed(() => null);
2017
- this.propertyDerivationCollection = propertyStore
2018
- ? this.setupPropertyPipeline(propertyStore, sharedAllDerivations)
2019
- : computed(() => null);
2020
- }
2021
- setupValuePipeline(sharedAllDerivations) {
2022
- this.formOptions = inject(FORM_OPTIONS);
2023
- const collection = computed(() => {
2024
- const fields = this.config.schemaFields();
2025
- if (!fields || fields.length === 0) {
2026
- return null;
2027
- }
2028
- const collected = sharedAllDerivations ? (sharedAllDerivations()?.value ?? null) : collectDerivations(fields);
2029
- if (!collected || collected.entries.length === 0) {
2030
- return null;
2031
- }
2032
- validateNoCycles(collected, this.logger);
2033
- if (DEV_MODE) {
2034
- warnAboutWildcardDependencies(this.logger, collected.entries, fields.length);
2035
- warnAboutMisconfiguredReEngagement(this.logger, collected.entries);
2036
- }
2037
- return collected;
2038
- }, /* @ts-ignore */
2039
- ...(ngDevMode ? [{ debugName: "collection" }] : /* istanbul ignore next */ []));
2040
- setupOnChangeStream({
2041
- injector: this.injector,
2042
- destroyRef: this.destroyRef,
2043
- logger: this.logger,
2044
- errorLabel: 'Derivation onChange stream error',
2045
- collectionSignal: collection,
2046
- formValueSignal: this.config.formValue,
2047
- additionalAwakeners: [this.config.form],
2048
- applyOnChange: (current, changedFields) => {
2049
- this.applyOnChangeDerivations(current, untracked(() => this.config.form()), changedFields);
2050
- },
2051
- });
2052
- setupDebouncedStream({
2053
- injector: this.injector,
2054
- destroyRef: this.destroyRef,
2055
- logger: this.logger,
2056
- errorLabel: 'Derivation debounced stream error',
2057
- collectionSignal: collection,
2058
- formValueSignal: this.config.formValue,
2059
- applyDebouncedForPeriod: (debounceMs, current, changedFields) => {
2060
- const formAccessor = untracked(() => this.config.form());
2061
- if (!formAccessor)
2062
- return;
2063
- this.applyDebouncedEntriesForPeriod(debounceMs, current, formAccessor, changedFields);
2064
- },
2065
- });
2066
- this.setupValueHttpStreams(collection);
2067
- this.setupValueAsyncFunctionStreams(collection);
2068
- return collection;
2069
- }
2070
- setupPropertyPipeline(propertyStore, sharedAllDerivations) {
2071
- const collection = computed(() => {
2072
- const fields = this.config.schemaFields();
2073
- if (!fields || fields.length === 0) {
2074
- return null;
2075
- }
2076
- const collected = sharedAllDerivations ? (sharedAllDerivations()?.property ?? null) : collectPropertyDerivations(fields);
2077
- if (!collected || collected.entries.length === 0) {
2078
- return null;
2079
- }
2080
- return collected;
2081
- }, /* @ts-ignore */
2082
- ...(ngDevMode ? [{ debugName: "collection" }] : /* istanbul ignore next */ []));
2083
- explicitEffect([collection, this.config.schemaFields], ([current, fields]) => {
2084
- propertyStore.clear();
2085
- if (!current)
2086
- return;
2087
- for (const entry of current.entries) {
2088
- propertyStore.registerField(entry.fieldKey);
2089
- }
2090
- if (DEV_MODE) {
2091
- warnAboutWildcardPropertyDependencies(this.logger, current.entries, fields?.length ?? 0);
2092
- }
2093
- });
2094
- setupOnChangeStream({
2095
- injector: this.injector,
2096
- destroyRef: this.destroyRef,
2097
- logger: this.logger,
2098
- errorLabel: 'Property derivation onChange stream error',
2099
- collectionSignal: collection,
2100
- formValueSignal: this.config.formValue,
2101
- applyOnChange: (current) => this.applyOnChangePropertyDerivations(current, propertyStore),
2102
- });
2103
- setupDebouncedStream({
2104
- injector: this.injector,
2105
- destroyRef: this.destroyRef,
2106
- logger: this.logger,
2107
- errorLabel: 'Property derivation debounced stream error',
2108
- collectionSignal: collection,
2109
- formValueSignal: this.config.formValue,
2110
- applyDebouncedForPeriod: (debounceMs, current, changedFields) => {
2111
- this.applyDebouncedPropertyEntriesForPeriod(debounceMs, current, propertyStore, changedFields);
2112
- },
2113
- });
2114
- this.setupPropertyHttpStreams(collection, propertyStore);
2115
- this.setupPropertyAsyncFunctionStreams(collection, propertyStore);
2116
- return collection;
2117
- }
2118
- applyOnChangeDerivations(collection, formAccessor, changedFields) {
2119
- const applicatorContext = {
2120
- formValue: this.config.formValue,
2121
- rootForm: formAccessor,
2122
- derivationFunctions: this.functionRegistry.getDerivationFunctions(),
2123
- customFunctions: this.functionRegistry.getCustomFunctions(),
2124
- externalData: resolveExternalData(this.config.externalData),
2125
- logger: this.logger,
2126
- warningTracker: this.warningTracker,
2127
- derivationLogger: untracked(() => this.config.derivationLogger()),
2128
- maxIterations: untracked(() => this.formOptions()?.maxDerivationIterations),
2129
- };
2130
- const result = applyDerivationsForTrigger(collection, 'onChange', applicatorContext, changedFields);
2131
- if (result.maxIterationsReached) {
2132
- this.logger.warn(`Derivation processing reached max iterations. ` +
2133
- `This may indicate a loop in derivation logic that wasn't caught at build time. ` +
2134
- `Applied: ${result.appliedCount}, Skipped: ${result.skippedCount}, Errors: ${result.errorCount}`);
2135
- }
2136
- }
2137
- applyDebouncedEntriesForPeriod(debounceMs, collection, formAccessor, changedFields) {
2138
- const debouncedEntries = collection.entries.filter((entry) => entry.trigger === 'debounced' && (entry.debounceMs ?? DEFAULT_DEBOUNCE_MS) === debounceMs);
2139
- if (debouncedEntries.length === 0) {
2140
- return;
2141
- }
2142
- const filteredCollection = {
2143
- entries: debouncedEntries,
2144
- };
2145
- const applicatorContext = {
2146
- formValue: this.config.formValue,
2147
- rootForm: formAccessor,
2148
- derivationFunctions: this.functionRegistry.getDerivationFunctions(),
2149
- customFunctions: this.functionRegistry.getCustomFunctions(),
2150
- externalData: resolveExternalData(this.config.externalData),
2151
- logger: this.logger,
2152
- warningTracker: this.warningTracker,
2153
- derivationLogger: untracked(() => this.config.derivationLogger()),
2154
- maxIterations: untracked(() => this.formOptions()?.maxDerivationIterations),
2155
- };
2156
- const result = applyDerivationsForTrigger(filteredCollection, 'debounced', applicatorContext, changedFields);
2157
- if (result.maxIterationsReached) {
2158
- this.logger.warn(`Debounced derivation processing reached max iterations (${debounceMs}ms). ` +
2159
- `Applied: ${result.appliedCount}, Skipped: ${result.skippedCount}, Errors: ${result.errorCount}`);
2160
- }
2161
- }
2162
- setupValueHttpStreams(collectionSignal) {
2163
- const formValue$ = toObservable(this.config.formValue, { injector: this.injector });
2164
- setupEntryAsyncStream({
2165
- injector: this.injector,
2166
- destroyRef: this.destroyRef,
2167
- logger: this.logger,
2168
- errorLabel: 'HTTP Derivation: outer stream error',
2169
- collectionSignal,
2170
- selectEntries: (collection) => collection?.entries.filter((entry) => entry.http) ?? [],
2171
- entrySignature: valueHttpEntrySignature,
2172
- createStream: (entry) => {
2173
- if (!this.httpClient) {
2174
- this.logger.error('HTTP Derivation: HttpClient is not available. Ensure provideHttpClient() is included in your application providers.');
2175
- return null;
2176
- }
2177
- const context = {
2178
- formValue: this.config.formValue,
2179
- form: this.config.form,
2180
- httpClient: this.httpClient,
2181
- logger: this.logger,
2182
- derivationLogger: this.config.derivationLogger,
2183
- customFunctions: () => this.functionRegistry.getCustomFunctions(),
2184
- externalData: () => resolveExternalData(this.config.externalData),
2185
- warningTracker: this.warningTracker,
2186
- };
2187
- return createHttpDerivationStream(entry, formValue$, context);
2188
- },
2189
- });
2190
- }
2191
- setupValueAsyncFunctionStreams(collectionSignal) {
2192
- const formValue$ = toObservable(this.config.formValue, { injector: this.injector });
2193
- setupEntryAsyncStream({
2194
- injector: this.injector,
2195
- destroyRef: this.destroyRef,
2196
- logger: this.logger,
2197
- errorLabel: 'Async Derivation: outer stream error',
2198
- collectionSignal,
2199
- selectEntries: (collection) => collection?.entries.filter((entry) => entry.asyncFunctionName || entry.asyncFn) ?? [],
2200
- entrySignature: valueAsyncEntrySignature,
2201
- createStream: (entry) => createAsyncDerivationStream(entry, formValue$, {
2202
- formValue: this.config.formValue,
2203
- form: this.config.form,
2204
- logger: this.logger,
2205
- derivationLogger: this.config.derivationLogger,
2206
- customFunctions: () => this.functionRegistry.getCustomFunctions(),
2207
- asyncDerivationFunctions: () => this.functionRegistry.getAsyncDerivationFunctions(),
2208
- externalData: () => resolveExternalData(this.config.externalData),
2209
- warningTracker: this.warningTracker,
2210
- }),
2211
- });
2212
- }
2213
- applyOnChangePropertyDerivations(collection, store) {
2214
- const applicatorContext = {
2215
- formValue: this.config.formValue,
2216
- store,
2217
- derivationFunctions: this.functionRegistry.getDerivationFunctions(),
2218
- customFunctions: this.functionRegistry.getCustomFunctions(),
2219
- externalData: resolveExternalData(this.config.externalData),
2220
- logger: this.logger,
2221
- warningTracker: this.warningTracker,
2222
- };
2223
- applyPropertyDerivationsForTrigger(collection, 'onChange', applicatorContext);
2224
- }
2225
- applyDebouncedPropertyEntriesForPeriod(debounceMs, collection, store, changedFields) {
2226
- const debouncedEntries = collection.entries.filter((entry) => entry.trigger === 'debounced' && (entry.debounceMs ?? DEFAULT_DEBOUNCE_MS) === debounceMs);
2227
- if (debouncedEntries.length === 0)
2228
- return;
2229
- const filteredCollection = { entries: debouncedEntries };
2230
- const applicatorContext = {
2231
- formValue: this.config.formValue,
2232
- store,
2233
- derivationFunctions: this.functionRegistry.getDerivationFunctions(),
2234
- customFunctions: this.functionRegistry.getCustomFunctions(),
2235
- externalData: resolveExternalData(this.config.externalData),
2236
- logger: this.logger,
2237
- warningTracker: this.warningTracker,
2238
- };
2239
- applyPropertyDerivationsForTrigger(filteredCollection, 'debounced', applicatorContext, changedFields);
2240
- }
2241
- setupPropertyHttpStreams(collectionSignal, store) {
2242
- const formValue$ = toObservable(this.config.formValue, { injector: this.injector });
2243
- setupEntryAsyncStream({
2244
- injector: this.injector,
2245
- destroyRef: this.destroyRef,
2246
- logger: this.logger,
2247
- errorLabel: 'HTTP Property Derivation: outer stream error',
2248
- collectionSignal,
2249
- selectEntries: (collection) => collection?.entries.filter((entry) => entry.http) ?? [],
2250
- entrySignature: propertyHttpEntrySignature,
2251
- createStream: (entry) => {
2252
- if (!this.httpClient) {
2253
- this.logger.error('HTTP Property Derivation: HttpClient is not available. Ensure provideHttpClient() is included in your application providers.');
2254
- return null;
2255
- }
2256
- return createHttpPropertyDerivationStream(entry, formValue$, {
2257
- formValue: this.config.formValue,
2258
- store,
2259
- httpClient: this.httpClient,
2260
- logger: this.logger,
2261
- customFunctions: () => this.functionRegistry.getCustomFunctions(),
2262
- externalData: () => resolveExternalData(this.config.externalData),
2263
- });
2264
- },
2265
- });
2266
- }
2267
- setupPropertyAsyncFunctionStreams(collectionSignal, store) {
2268
- const formValue$ = toObservable(this.config.formValue, { injector: this.injector });
2269
- setupEntryAsyncStream({
2270
- injector: this.injector,
2271
- destroyRef: this.destroyRef,
2272
- logger: this.logger,
2273
- errorLabel: 'Async Property Derivation: outer stream error',
2274
- collectionSignal,
2275
- selectEntries: (collection) => collection?.entries.filter((entry) => entry.asyncFunctionName || entry.asyncFn) ?? [],
2276
- entrySignature: propertyAsyncEntrySignature,
2277
- createStream: (entry) => createAsyncPropertyDerivationStream(entry, formValue$, {
2278
- formValue: this.config.formValue,
2279
- store,
2280
- logger: this.logger,
2281
- customFunctions: () => this.functionRegistry.getCustomFunctions(),
2282
- asyncDerivationFunctions: () => this.functionRegistry.getAsyncDerivationFunctions(),
2283
- externalData: () => resolveExternalData(this.config.externalData),
2284
- }),
2285
- });
2286
- }
2287
- }
2288
- function valueHttpEntrySignature(entry) {
2289
- const config = {
2290
- http: entry.http,
2291
- responseExpression: entry.responseExpression,
2292
- dependsOn: entry.dependsOn,
2293
- debounceMs: entry.debounceMs,
2294
- trigger: entry.trigger,
2295
- condition: entry.condition,
2296
- stopOnUserOverride: entry.stopOnUserOverride,
2297
- reEngageOnDependencyChange: entry.reEngageOnDependencyChange,
2298
- };
2299
- return `${entry.fieldKey}:${JSON.stringify(config)}`;
2300
- }
2301
- function valueAsyncEntrySignature(entry) {
2302
- const config = {
2303
- asyncFunctionName: entry.asyncFunctionName,
2304
- asyncFnId: entry.asyncFn ? `inline:${entry.fieldKey}` : undefined,
2305
- dependsOn: entry.dependsOn,
2306
- debounceMs: entry.debounceMs,
2307
- trigger: entry.trigger,
2308
- condition: entry.condition,
2309
- stopOnUserOverride: entry.stopOnUserOverride,
2310
- reEngageOnDependencyChange: entry.reEngageOnDependencyChange,
2311
- };
2312
- return `${entry.fieldKey}:${JSON.stringify(config)}`;
2313
- }
2314
- function propertyHttpEntrySignature(entry) {
2315
- const config = {
2316
- http: entry.http,
2317
- responseExpression: entry.responseExpression,
2318
- dependsOn: entry.dependsOn,
2319
- debounceMs: entry.debounceMs,
2320
- trigger: entry.trigger,
2321
- condition: entry.condition,
2322
- };
2323
- return `${entry.fieldKey}.${entry.targetProperty}:${JSON.stringify(config)}`;
2324
- }
2325
- function propertyAsyncEntrySignature(entry) {
2326
- const config = {
2327
- asyncFunctionName: entry.asyncFunctionName,
2328
- asyncFnId: entry.asyncFn ? `inline:${entry.fieldKey}.${entry.targetProperty}` : undefined,
2329
- dependsOn: entry.dependsOn,
2330
- debounceMs: entry.debounceMs,
2331
- trigger: entry.trigger,
2332
- condition: entry.condition,
2333
- };
2334
- return `${entry.fieldKey}.${entry.targetProperty}:${JSON.stringify(config)}`;
2335
- }
2336
- function createDerivationOrchestrator(config) {
2337
- return new DerivationOrchestrator(config);
2338
- }
2339
- function warnAboutWildcardDependencies(logger, entries, fieldCount) {
2340
- const wildcardEntries = entries.filter((entry) => entry.dependsOn.includes('*'));
2341
- if (wildcardEntries.length === 0)
2342
- return;
2343
- const implicitWildcards = wildcardEntries.filter((entry) => !entry.http &&
2344
- !entry.asyncFunctionName &&
2345
- !entry.asyncFn &&
2346
- (entry.functionName || entry.fn) &&
2347
- (!entry.originalConfig?.dependsOn || entry.originalConfig.dependsOn.length === 0));
2348
- if (implicitWildcards.length > 0) {
2349
- const derivationDescs = implicitWildcards.map((e) => `${e.fieldKey} (${e.functionName ?? '<inline fn>'})`);
2350
- logger.warn('Derivation: custom functions without explicit dependsOn detected. ' +
2351
- `These run on EVERY form change, which may impact performance (form has ${fieldCount} fields). ` +
2352
- 'Consider specifying explicit dependsOn arrays for better performance.', derivationDescs);
2353
- }
2354
- }
2355
- function warnAboutMisconfiguredReEngagement(logger, entries) {
2356
- const misconfigured = entries.filter((entry) => entry.reEngageOnDependencyChange && !entry.stopOnUserOverride);
2357
- if (misconfigured.length === 0)
2358
- return;
2359
- const fieldKeys = misconfigured.map((e) => e.debugName ?? e.fieldKey);
2360
- logger.warn('Derivation: reEngageOnDependencyChange without stopOnUserOverride detected. ' +
2361
- 'reEngageOnDependencyChange only takes effect when stopOnUserOverride is true. ' +
2362
- 'Either add stopOnUserOverride: true or remove reEngageOnDependencyChange.', fieldKeys);
2363
- }
2364
- function warnAboutWildcardPropertyDependencies(logger, entries, fieldCount) {
2365
- const implicitWildcards = entries.filter((entry) => entry.dependsOn.includes('*') &&
2366
- (entry.functionName || entry.fn) &&
2367
- (!entry.originalConfig?.dependsOn || entry.originalConfig.dependsOn.length === 0));
2368
- if (implicitWildcards.length > 0) {
2369
- const derivationDescs = implicitWildcards.map((e) => `${e.fieldKey}.${e.targetProperty} (${e.functionName ?? '<inline fn>'})`);
2370
- logger.warn('PropertyDerivation: custom functions without explicit dependsOn detected. ' +
2371
- `These run on EVERY form change, which may impact performance (form has ${fieldCount} fields). ` +
2372
- 'Consider specifying explicit dependsOn arrays for better performance.', derivationDescs);
2373
- }
2374
- }
2375
-
2376
- function bootstrapDerivationOrchestrator() {
2377
- const stateManager = inject(FormStateManager);
2378
- const logger = inject(DynamicFormLogger);
2379
- const logConfig = inject(DERIVATION_LOG_CONFIG);
2380
- const externalData = inject(EXTERNAL_DATA);
2381
- const propertyStore = inject(PROPERTY_OVERRIDE_STORE);
2382
- const config = {
2383
- schemaFields: computed(() => stateManager.formSetup()?.schemaFields),
2384
- formValue: stateManager.formValue,
2385
- form: computed(() => stateManager.form()),
2386
- derivationLogger: computed(() => createDerivationLogger(logConfig, logger)),
2387
- propertyStore,
2388
- externalData,
2389
- };
2390
- createDerivationOrchestrator(config);
2391
- }
2392
-
2393
- export { bootstrapDerivationOrchestrator };
2394
- //# sourceMappingURL=ng-forge-dynamic-forms-bootstrap-derivation-orchestrator-CTOTwzzw.mjs.map