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