@praxisui/dynamic-form 9.0.5-rc.13 → 9.0.5-rc.15
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/README.md
CHANGED
|
@@ -35,11 +35,11 @@ When the exact request schema from `/schemas/filtered` publishes root-level `x-u
|
|
|
35
35
|
|
|
36
36
|
Triggers, source fields and request/response bindings use JSON Pointer. The runtime supports nested object bindings, accepts at most 64 total input/output bindings and rejects duplicate or hierarchically overlapping pointers. It resolves the capability only against the configured API origin and rejects protocol-relative, encoded-separator or cross-origin targets. Output bindings have one closed meaning: server-derived draft values replace their targets. Metadata cannot select `if-pristine`, `if-empty` or another incidental UI policy.
|
|
37
37
|
|
|
38
|
-
The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order
|
|
38
|
+
The runtime waits until hydration finishes and invalidates the previous generation as soon as a newer relevant value or validation status arrives, before the new debounce completes. `sourcePaths` is an intentional trigger subset of `inputs`: changing a non-trigger input invalidates the current draft, emits `input-changed-awaiting-trigger`, and blocks submit/navigation until a source trigger recalculates it. Every configured response path is resolved before target controls are written; all nested targets are populated before change observers are notified. Chained determinations receive one logical batch signal after the complete commit, rather than one execution per output control. Their dependency order uses hierarchical input/output JSON Pointer overlap, and the canonical projection rejects cycles and overlapping multi-writers. Downstream waits while upstream is pending and runs only after authoritative outputs or confirmed unchanged inputs. Null/undefined upstream outputs and failed, invalid or cancelled upstream generations settle downstream as skipped instead of invoking it with missing or stale data. If a control rejects a write, the runtime attempts to restore earlier writes and reports a terminal failure. This is a coordinated client-side batch, not a transaction or an ACID guarantee.
|
|
39
39
|
|
|
40
40
|
A determination remains pending through debounce, asynchronous validation and capability execution. Submit attempts wait with a bounded timeout and repeat the stability check after hooks that may mutate controls. Output controls are disabled transiently as server-owned draft values, but remain present in `getRawValue()`, `valueChange` and submit payloads; this state is not persisted in `FormConfig`.
|
|
41
41
|
|
|
42
|
-
Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface,
|
|
42
|
+
Hosts can observe metadata-only evidence through `reactiveDeterminationExecuted` and aggregate state through `reactiveDeterminationPendingChange`. Before navigating or closing a composed surface, inspect `hasPendingReactiveDeterminations()` and `hasUnsatisfiedReactiveDeterminations()` and await `waitForReactiveDeterminations(timeoutMs?)`; `false` means timeout or that the latest generation did not produce a current authoritative draft, and must block the transition. Cancelled pending correlations terminate as `skipped/superseded`. No event or method exposes form or response values.
|
|
43
43
|
|
|
44
44
|
Use `optionSource.dependsOn` for dependent option lists and workflow actions for persistent transitions. Recommendations, side-effect refresh and backend validation are separate capabilities. The final backend command must still authorize, recompute or validate fiscal, pricing, eligibility, compliance and approval outcomes.
|
|
45
45
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-14T00:04:43.614Z",
|
|
4
4
|
"packageName": "@praxisui/dynamic-form",
|
|
5
|
-
"packageVersion": "9.0.5-rc.
|
|
5
|
+
"packageVersion": "9.0.5-rc.15",
|
|
6
6
|
"sourceRegistry": "praxis-component-registry-ingestion",
|
|
7
7
|
"sourceRegistryVersion": "1.0.0",
|
|
8
8
|
"componentCount": 2,
|
|
@@ -212,10 +212,14 @@ class ReactiveDeterminationRuntimeService {
|
|
|
212
212
|
hasPending(form) {
|
|
213
213
|
return this.isPending(this.pendingStateByForm.get(form));
|
|
214
214
|
}
|
|
215
|
+
hasUnsatisfied(form) {
|
|
216
|
+
const state = this.pendingStateByForm.get(form);
|
|
217
|
+
return !!state && [...state.determinations.values()].some((item) => !item.satisfied);
|
|
218
|
+
}
|
|
215
219
|
waitForStable(form, timeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
|
|
216
220
|
const state = this.pendingStateByForm.get(form);
|
|
217
221
|
if (!this.isPending(state))
|
|
218
|
-
return Promise.resolve(
|
|
222
|
+
return Promise.resolve(this.isSatisfied(state));
|
|
219
223
|
return new Promise((resolve) => {
|
|
220
224
|
let settled = false;
|
|
221
225
|
const finish = (stable) => {
|
|
@@ -241,12 +245,20 @@ class ReactiveDeterminationRuntimeService {
|
|
|
241
245
|
}
|
|
242
246
|
connect(form, definitions, executor, onEvent, operationTimeoutMs = DEFAULT_OPERATION_TIMEOUT_MS) {
|
|
243
247
|
const composite = new Subscription();
|
|
248
|
+
const pendingCorrelations = new Set();
|
|
249
|
+
const publishEvent = (event) => {
|
|
250
|
+
if (event.status === 'pending')
|
|
251
|
+
pendingCorrelations.add(event.correlationId);
|
|
252
|
+
else
|
|
253
|
+
pendingCorrelations.delete(event.correlationId);
|
|
254
|
+
onEvent?.(event);
|
|
255
|
+
};
|
|
244
256
|
const connectionId = ++this.nextConnectionId;
|
|
245
257
|
const definitionIds = new Set();
|
|
246
258
|
const determinationKeyFor = (definitionIndex, definition) => `${connectionId}:${definitionIndex}:${definition.id}`;
|
|
247
259
|
definitions.forEach((definition, definitionIndex) => {
|
|
248
260
|
if (definitionIds.has(definition.id)) {
|
|
249
|
-
|
|
261
|
+
publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'duplicate-determination-id'));
|
|
250
262
|
return;
|
|
251
263
|
}
|
|
252
264
|
definitionIds.add(definition.id);
|
|
@@ -260,9 +272,8 @@ class ReactiveDeterminationRuntimeService {
|
|
|
260
272
|
const upstreamDeterminationKeys = definitions.flatMap((candidate, candidateIndex) => {
|
|
261
273
|
if (candidateIndex === definitionIndex)
|
|
262
274
|
return [];
|
|
263
|
-
const candidateOutputPaths = new Set(candidate.outputs.map((binding) => binding.fieldPath));
|
|
264
275
|
const feedsCurrent = definition.inputs.some((binding) => {
|
|
265
|
-
if (!
|
|
276
|
+
if (!candidate.outputs.some((output) => jsonPointersOverlap(output.fieldPath, binding.fieldPath)))
|
|
266
277
|
return false;
|
|
267
278
|
upstreamInputPaths.add(binding.fieldPath);
|
|
268
279
|
return true;
|
|
@@ -272,15 +283,15 @@ class ReactiveDeterminationRuntimeService {
|
|
|
272
283
|
: [];
|
|
273
284
|
});
|
|
274
285
|
if (triggerControls.length !== definition.trigger.sourcePaths.length) {
|
|
275
|
-
|
|
286
|
+
publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'source-control-missing'));
|
|
276
287
|
return;
|
|
277
288
|
}
|
|
278
289
|
if (inputControls.length !== definition.inputs.length) {
|
|
279
|
-
|
|
290
|
+
publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'input-control-missing'));
|
|
280
291
|
return;
|
|
281
292
|
}
|
|
282
293
|
if (targetControls.length !== definition.outputs.length) {
|
|
283
|
-
|
|
294
|
+
publishEvent(this.event(definition, 'skipped', createCorrelationId(), 0, 'target-control-missing'));
|
|
284
295
|
return;
|
|
285
296
|
}
|
|
286
297
|
composite.add(this.claimTargets(form, targetControls));
|
|
@@ -295,9 +306,12 @@ class ReactiveDeterminationRuntimeService {
|
|
|
295
306
|
// Publish the new authority before switchMap finalizes the old one.
|
|
296
307
|
this.beginGeneration(form, determinationKey, currentGeneration);
|
|
297
308
|
if (shouldExecute) {
|
|
298
|
-
|
|
309
|
+
publishEvent(this.event(definition, 'pending', correlationId, 0));
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
publishEvent(this.event(definition, 'skipped', correlationId, 0, 'input-changed-awaiting-trigger'));
|
|
299
313
|
}
|
|
300
|
-
return { generation: currentGeneration, correlationId, shouldExecute };
|
|
314
|
+
return { generation: currentGeneration, correlationId, shouldExecute, startedAt: Date.now() };
|
|
301
315
|
}), switchMap((signal) => {
|
|
302
316
|
const generation$ = signal.shouldExecute
|
|
303
317
|
? timer(DEFAULT_DEBOUNCE_MS).pipe(switchMap(() => this.waitForUpstreamDeterminations(form, upstreamDeterminationKeys)), switchMap((upstreamsSatisfied) => {
|
|
@@ -305,7 +319,7 @@ class ReactiveDeterminationRuntimeService {
|
|
|
305
319
|
return NEVER;
|
|
306
320
|
}
|
|
307
321
|
if (triggerControls.some((control) => control.invalid)) {
|
|
308
|
-
|
|
322
|
+
publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'invalid-source'));
|
|
309
323
|
return EMPTY;
|
|
310
324
|
}
|
|
311
325
|
if (!upstreamsSatisfied ||
|
|
@@ -315,7 +329,7 @@ class ReactiveDeterminationRuntimeService {
|
|
|
315
329
|
const value = form.get(jsonPointerSegments(binding.fieldPath))?.value;
|
|
316
330
|
return value === null || value === undefined;
|
|
317
331
|
})) {
|
|
318
|
-
|
|
332
|
+
publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'upstream-input-unavailable'));
|
|
319
333
|
return EMPTY;
|
|
320
334
|
}
|
|
321
335
|
let invocation;
|
|
@@ -323,12 +337,12 @@ class ReactiveDeterminationRuntimeService {
|
|
|
323
337
|
invocation = this.buildInvocation(form, definition);
|
|
324
338
|
}
|
|
325
339
|
catch {
|
|
326
|
-
|
|
340
|
+
publishEvent(this.event(definition, 'error', signal.correlationId, 0, 'request-binding-invalid'));
|
|
327
341
|
return EMPTY;
|
|
328
342
|
}
|
|
329
343
|
if (invocation.fingerprint === lastSuccessfulFingerprint) {
|
|
330
344
|
this.satisfyGeneration(form, determinationKey, signal.generation);
|
|
331
|
-
|
|
345
|
+
publishEvent(this.event(definition, 'skipped', signal.correlationId, 0, 'unchanged-inputs'));
|
|
332
346
|
return EMPTY;
|
|
333
347
|
}
|
|
334
348
|
const startedAt = Date.now();
|
|
@@ -338,13 +352,13 @@ class ReactiveDeterminationRuntimeService {
|
|
|
338
352
|
}
|
|
339
353
|
const commit = this.buildCommit(form, definition, response);
|
|
340
354
|
if (commit === null) {
|
|
341
|
-
|
|
355
|
+
publishEvent(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, 'response-binding-missing'));
|
|
342
356
|
return;
|
|
343
357
|
}
|
|
344
358
|
this.commit(form, commit);
|
|
345
359
|
this.satisfyGeneration(form, determinationKey, signal.generation);
|
|
346
360
|
lastSuccessfulFingerprint = invocation.fingerprint;
|
|
347
|
-
|
|
361
|
+
publishEvent(this.event(definition, 'success', signal.correlationId, Date.now() - startedAt, undefined, commit.map((entry) => entry.targetPath)));
|
|
348
362
|
}), catchError((error) => {
|
|
349
363
|
if (this.isCurrentGeneration(form, determinationKey, signal.generation)) {
|
|
350
364
|
const reason = error instanceof TimeoutError
|
|
@@ -354,13 +368,18 @@ class ReactiveDeterminationRuntimeService {
|
|
|
354
368
|
: error instanceof ReactiveDeterminationCommitError
|
|
355
369
|
? 'commit-failed'
|
|
356
370
|
: 'operation-failed';
|
|
357
|
-
|
|
371
|
+
publishEvent(this.event(definition, 'error', signal.correlationId, Date.now() - startedAt, reason));
|
|
358
372
|
}
|
|
359
373
|
return EMPTY;
|
|
360
374
|
}), ignoreElements());
|
|
361
375
|
}))
|
|
362
376
|
: EMPTY;
|
|
363
|
-
return generation$.pipe(finalize(() =>
|
|
377
|
+
return generation$.pipe(finalize(() => {
|
|
378
|
+
if (pendingCorrelations.has(signal.correlationId)) {
|
|
379
|
+
publishEvent(this.event(definition, 'skipped', signal.correlationId, Date.now() - signal.startedAt, 'superseded'));
|
|
380
|
+
}
|
|
381
|
+
this.finishGeneration(form, determinationKey, signal.generation);
|
|
382
|
+
}));
|
|
364
383
|
}))
|
|
365
384
|
.subscribe();
|
|
366
385
|
composite.add(determinationSubscription);
|
|
@@ -566,12 +585,16 @@ class ReactiveDeterminationRuntimeService {
|
|
|
566
585
|
isPending(state) {
|
|
567
586
|
return !!state && [...state.determinations.values()].some((item) => item.pending);
|
|
568
587
|
}
|
|
588
|
+
isSatisfied(state) {
|
|
589
|
+
return !state || [...state.determinations.values()].every((item) => item.satisfied);
|
|
590
|
+
}
|
|
569
591
|
notifyIfStable(state) {
|
|
570
592
|
if (this.isPending(state))
|
|
571
593
|
return;
|
|
572
594
|
const waiters = [...state.waiters];
|
|
573
595
|
state.waiters.clear();
|
|
574
|
-
|
|
596
|
+
const satisfied = this.isSatisfied(state);
|
|
597
|
+
waiters.forEach((waiter) => waiter(satisfied));
|
|
575
598
|
}
|
|
576
599
|
notifyPendingChange(state) {
|
|
577
600
|
const pending = this.isPending(state);
|
|
@@ -615,6 +638,16 @@ function jsonPointerSegments(pointer) {
|
|
|
615
638
|
}
|
|
616
639
|
return pointer.slice(1).split('/').map((segment) => segment.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
617
640
|
}
|
|
641
|
+
function jsonPointersOverlap(left, right) {
|
|
642
|
+
const leftSegments = jsonPointerSegments(left);
|
|
643
|
+
const rightSegments = jsonPointerSegments(right);
|
|
644
|
+
const shorter = Math.min(leftSegments.length, rightSegments.length);
|
|
645
|
+
for (let index = 0; index < shorter; index += 1) {
|
|
646
|
+
if (leftSegments[index] !== rightSegments[index])
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
618
651
|
function writeJsonPointer(target, pointer, value) {
|
|
619
652
|
const segments = jsonPointerSegments(pointer);
|
|
620
653
|
let current = target;
|
|
@@ -1119,6 +1152,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
|
|
|
1119
1152
|
'runtime.reactiveDetermination.error': 'Não foi possível atualizar os campos relacionados. Revise os dados ou tente novamente.',
|
|
1120
1153
|
'runtime.reactiveDetermination.submitPending': 'Aguarde a atualização dos campos relacionados antes de salvar.',
|
|
1121
1154
|
'runtime.reactiveDetermination.submitTimeout': 'Os campos relacionados não estabilizaram a tempo. Revise os dados e tente salvar novamente.',
|
|
1155
|
+
'runtime.reactiveDetermination.submitUnsatisfied': 'Os campos relacionados não foram atualizados com sucesso. Revise os dados e tente novamente.',
|
|
1122
1156
|
'common.dismiss': 'Fechar',
|
|
1123
1157
|
'config.tabs.mobileLabel': 'Seção do editor',
|
|
1124
1158
|
'config.tabs.general': 'Geral',
|
|
@@ -1608,6 +1642,7 @@ const PRAXIS_DYNAMIC_FORM_I18N_CONFIG = {
|
|
|
1608
1642
|
'runtime.reactiveDetermination.error': 'Related fields could not be updated. Review the data or try again.',
|
|
1609
1643
|
'runtime.reactiveDetermination.submitPending': 'Wait for related fields to finish updating before saving.',
|
|
1610
1644
|
'runtime.reactiveDetermination.submitTimeout': 'Related fields did not stabilize in time. Review the data and try saving again.',
|
|
1645
|
+
'runtime.reactiveDetermination.submitUnsatisfied': 'Related fields were not updated successfully. Review the data and try again.',
|
|
1611
1646
|
'common.dismiss': 'Dismiss',
|
|
1612
1647
|
'settings.title': 'Form Configuration',
|
|
1613
1648
|
'layout.defaultSectionTitle': 'Information',
|
|
@@ -11054,6 +11089,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
|
|
|
11054
11089
|
args: [{ providedIn: 'root' }]
|
|
11055
11090
|
}] });
|
|
11056
11091
|
|
|
11092
|
+
function exactSchemaOperationId(schema, rootUi) {
|
|
11093
|
+
const records = [rootUi, schema].filter((value) => !!value && typeof value === 'object' && !Array.isArray(value));
|
|
11094
|
+
for (const record of records) {
|
|
11095
|
+
for (const key of ['schemaOperationId', 'operationId', 'x-operation-id']) {
|
|
11096
|
+
const value = record[key];
|
|
11097
|
+
if (typeof value === 'string' && value.trim())
|
|
11098
|
+
return value.trim();
|
|
11099
|
+
}
|
|
11100
|
+
}
|
|
11101
|
+
return null;
|
|
11102
|
+
}
|
|
11057
11103
|
const FORM_CONFIG_LOAD_TIMEOUT_MS = 1200;
|
|
11058
11104
|
const FORM_INPUT_PREFS_LOAD_TIMEOUT_MS = 1200;
|
|
11059
11105
|
const SCHEMA_GLOBAL_CONFIG_READY_TIMEOUT_MS = 1200;
|
|
@@ -11486,6 +11532,7 @@ class PraxisDynamicForm {
|
|
|
11486
11532
|
formConfigConflictBlockThrough = new Map();
|
|
11487
11533
|
schemaRootHooks;
|
|
11488
11534
|
schemaRootReactiveDeterminations = [];
|
|
11535
|
+
schemaRootOperationId = null;
|
|
11489
11536
|
destroy$ = new Subject();
|
|
11490
11537
|
formValueChangesSubscription = null;
|
|
11491
11538
|
reactiveDeterminationSubscription = null;
|
|
@@ -13192,7 +13239,9 @@ class PraxisDynamicForm {
|
|
|
13192
13239
|
}
|
|
13193
13240
|
this.reactiveDeterminationSubscription?.unsubscribe();
|
|
13194
13241
|
const connection = new Subscription();
|
|
13195
|
-
connection.add(this.reactiveDeterminationRuntime.connect(this.form, this.schemaRootReactiveDeterminations.filter((definition) => definition.scope.formMode === this.mode
|
|
13242
|
+
connection.add(this.reactiveDeterminationRuntime.connect(this.form, this.schemaRootReactiveDeterminations.filter((definition) => definition.scope.formMode === this.mode
|
|
13243
|
+
&& (!this.schemaRootOperationId
|
|
13244
|
+
|| definition.scope.schemaOperationId === this.schemaRootOperationId)), (definition, requestBody, correlationId) => {
|
|
13196
13245
|
let headers = composeHeadersWithVersion(this.resolveApiEntry()) ?? new HttpHeaders();
|
|
13197
13246
|
headers = headers.set('X-Correlation-Id', correlationId);
|
|
13198
13247
|
return this.http
|
|
@@ -13207,7 +13256,11 @@ class PraxisDynamicForm {
|
|
|
13207
13256
|
hasPendingReactiveDeterminations() {
|
|
13208
13257
|
return !!this.form && this.reactiveDeterminationRuntime.hasPending(this.form);
|
|
13209
13258
|
}
|
|
13210
|
-
/**
|
|
13259
|
+
/** True when the latest settled generation failed or could not derive a current draft. */
|
|
13260
|
+
hasUnsatisfiedReactiveDeterminations() {
|
|
13261
|
+
return !!this.form && this.reactiveDeterminationRuntime.hasUnsatisfied(this.form);
|
|
13262
|
+
}
|
|
13263
|
+
/** Resolves true only when the latest generations settled successfully/current. */
|
|
13211
13264
|
waitForReactiveDeterminations(timeoutMs) {
|
|
13212
13265
|
if (!this.form)
|
|
13213
13266
|
return Promise.resolve(true);
|
|
@@ -15658,18 +15711,17 @@ class PraxisDynamicForm {
|
|
|
15658
15711
|
});
|
|
15659
15712
|
}
|
|
15660
15713
|
async waitForReactiveDeterminationsBeforeSubmit() {
|
|
15661
|
-
|
|
15662
|
-
|
|
15663
|
-
|
|
15664
|
-
const pendingNotice = this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitPending', undefined, 'Aguarde a atualização dos campos relacionados antes de salvar.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 5000 });
|
|
15714
|
+
const pendingNotice = this.reactiveDeterminationRuntime.hasPending(this.form)
|
|
15715
|
+
? this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitPending', undefined, 'Aguarde a atualização dos campos relacionados antes de salvar.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 5000 })
|
|
15716
|
+
: null;
|
|
15665
15717
|
const stable = await this.reactiveDeterminationRuntime.waitForStable(this.form);
|
|
15666
15718
|
try {
|
|
15667
|
-
pendingNotice
|
|
15719
|
+
pendingNotice?.dismiss();
|
|
15668
15720
|
}
|
|
15669
15721
|
catch { }
|
|
15670
15722
|
if (stable)
|
|
15671
15723
|
return true;
|
|
15672
|
-
this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.
|
|
15724
|
+
this.snackBar.open(this.i18n.t('runtime.reactiveDetermination.submitUnsatisfied', undefined, 'Os campos relacionados não foram atualizados com sucesso. Revise os dados e tente novamente.', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), this.i18n.t('common.dismiss', undefined, 'Fechar', PRAXIS_DYNAMIC_FORM_I18N_NAMESPACE), { duration: 7000 });
|
|
15673
15725
|
return false;
|
|
15674
15726
|
}
|
|
15675
15727
|
applySubmitFieldErrors(details) {
|
|
@@ -19805,6 +19857,7 @@ class PraxisDynamicForm {
|
|
|
19805
19857
|
}
|
|
19806
19858
|
const ctx = this.buildSchemaContext();
|
|
19807
19859
|
this.schemaRootReactiveDeterminations = [];
|
|
19860
|
+
this.schemaRootOperationId = null;
|
|
19808
19861
|
const schemaContext = {
|
|
19809
19862
|
path: ctx.path,
|
|
19810
19863
|
operation: ctx.operation,
|
|
@@ -19876,15 +19929,18 @@ class PraxisDynamicForm {
|
|
|
19876
19929
|
// and are never copied into the persisted/authorable FormConfig.
|
|
19877
19930
|
try {
|
|
19878
19931
|
const rootUi = entry?.schema?.['x-ui'];
|
|
19932
|
+
this.schemaRootOperationId = exactSchemaOperationId(entry?.schema, rootUi);
|
|
19879
19933
|
const hooks = rootUi && typeof rootUi === 'object' ? rootUi.hooks : undefined;
|
|
19880
19934
|
if (hooks && typeof hooks === 'object') {
|
|
19881
19935
|
this.schemaRootHooks = hooks;
|
|
19882
19936
|
}
|
|
19883
|
-
|
|
19937
|
+
const normalizedDeterminations = ctx.schemaType === 'request'
|
|
19884
19938
|
? normalizeReactiveDeterminations(rootUi && typeof rootUi === 'object'
|
|
19885
19939
|
? rootUi.reactiveDeterminations
|
|
19886
19940
|
: undefined)
|
|
19887
19941
|
: [];
|
|
19942
|
+
this.schemaRootReactiveDeterminations = normalizedDeterminations.filter((definition) => !this.schemaRootOperationId
|
|
19943
|
+
|| definition.scope.schemaOperationId === this.schemaRootOperationId);
|
|
19888
19944
|
}
|
|
19889
19945
|
catch { }
|
|
19890
19946
|
const schemaId = buildSchemaId(schemaContext);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@praxisui/dynamic-form",
|
|
3
|
-
"version": "9.0.5-rc.
|
|
3
|
+
"version": "9.0.5-rc.15",
|
|
4
4
|
"description": "Angular dynamic form engine for Praxis UI: metadata-driven forms, hooks, and services integrating @praxisui/* packages.",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@angular/common": "^21.0.0",
|
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
"@angular/forms": "^21.0.0",
|
|
10
10
|
"@angular/material": "^21.0.0",
|
|
11
11
|
"@angular/router": "^21.0.0",
|
|
12
|
-
"@praxisui/ai": "^9.0.5-rc.
|
|
13
|
-
"@praxisui/dynamic-fields": "^9.0.5-rc.
|
|
14
|
-
"@praxisui/metadata-editor": "^9.0.5-rc.
|
|
15
|
-
"@praxisui/rich-content": "^9.0.5-rc.
|
|
16
|
-
"@praxisui/settings-panel": "^9.0.5-rc.
|
|
17
|
-
"@praxisui/visual-builder": "^9.0.5-rc.
|
|
18
|
-
"@praxisui/core": "^9.0.5-rc.
|
|
12
|
+
"@praxisui/ai": "^9.0.5-rc.15",
|
|
13
|
+
"@praxisui/dynamic-fields": "^9.0.5-rc.15",
|
|
14
|
+
"@praxisui/metadata-editor": "^9.0.5-rc.15",
|
|
15
|
+
"@praxisui/rich-content": "^9.0.5-rc.15",
|
|
16
|
+
"@praxisui/settings-panel": "^9.0.5-rc.15",
|
|
17
|
+
"@praxisui/visual-builder": "^9.0.5-rc.15",
|
|
18
|
+
"@praxisui/core": "^9.0.5-rc.15",
|
|
19
19
|
"rxjs": "^7.8.0"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
@@ -86,7 +86,7 @@ Este documento e a referencia canonica da API JSON de praxis-dynamic-form.
|
|
|
86
86
|
- O estado permanece pending durante debounce, validacao assincrona e execucao. Uma tentativa de submit aguarda estabilizacao com timeout e repete a espera depois de hooks que podem alterar controles.
|
|
87
87
|
- Controles de output sao desabilitados transitoriamente como valores server-derived, sem mutar/persistir `FormConfig`; seus valores continuam em `getRawValue()`, `valueChange` e no payload de submit.
|
|
88
88
|
- `reactiveDeterminationExecuted` publica apenas IDs, paths, provenance, estado (`pending`, `success`, `skipped` ou `error`), duracao, correlation ID e motivo. Valores do formulario e da resposta nao participam da evidencia.
|
|
89
|
-
- Hosts compostos recebem estado agregado por `reactiveDeterminationPendingChange`. Antes de navegar ou fechar, devem consultar `hasPendingReactiveDeterminations()` e aguardar `waitForReactiveDeterminations(timeoutMs?)`; retorno `false`
|
|
89
|
+
- Hosts compostos recebem estado agregado por `reactiveDeterminationPendingChange`. Antes de navegar ou fechar, devem consultar `hasPendingReactiveDeterminations()` e `hasUnsatisfiedReactiveDeterminations()` e aguardar `waitForReactiveDeterminations(timeoutMs?)`; retorno `false` indica timeout ou geração mais recente sem draft autoritativo e bloqueia a transição. Correlações canceladas recebem terminal `skipped/superseded`.
|
|
90
90
|
- Cascatas de opcoes continuam em `optionSource.dependsOn`; recommendations, side-effect refresh, backend validation e workflow actions permanecem capacidades separadas.
|
|
91
91
|
- O comando backend final continua responsavel por autorizacao, recomputacao e validacao de qualquer decisao consequente.
|
|
92
92
|
|
|
@@ -370,6 +370,7 @@ declare class ReactiveDeterminationRuntimeService {
|
|
|
370
370
|
private readonly publishingCommitByForm;
|
|
371
371
|
private nextConnectionId;
|
|
372
372
|
hasPending(form: FormGroup): boolean;
|
|
373
|
+
hasUnsatisfied(form: FormGroup): boolean;
|
|
373
374
|
waitForStable(form: FormGroup, timeoutMs?: number): Promise<boolean>;
|
|
374
375
|
observePending(form: FormGroup, observer: (pending: boolean) => void): Subscription;
|
|
375
376
|
isOwnedTarget(form: FormGroup, control: AbstractControl): boolean;
|
|
@@ -389,6 +390,7 @@ declare class ReactiveDeterminationRuntimeService {
|
|
|
389
390
|
private clearDeterminationState;
|
|
390
391
|
private isCurrentGeneration;
|
|
391
392
|
private isPending;
|
|
393
|
+
private isSatisfied;
|
|
392
394
|
private notifyIfStable;
|
|
393
395
|
private notifyPendingChange;
|
|
394
396
|
static ɵfac: i0.ɵɵFactoryDeclaration<ReactiveDeterminationRuntimeService, never>;
|
|
@@ -927,6 +929,7 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
|
|
|
927
929
|
private readonly formConfigConflictBlockThrough;
|
|
928
930
|
private schemaRootHooks?;
|
|
929
931
|
private schemaRootReactiveDeterminations;
|
|
932
|
+
private schemaRootOperationId;
|
|
930
933
|
private destroy$;
|
|
931
934
|
private formValueChangesSubscription;
|
|
932
935
|
private reactiveDeterminationSubscription;
|
|
@@ -1075,7 +1078,9 @@ declare class PraxisDynamicForm implements OnInit, OnChanges, OnDestroy {
|
|
|
1075
1078
|
private scheduleReactiveDeterminationsActivation;
|
|
1076
1079
|
/** True while at least one backend Reactive Determination is unsettled. */
|
|
1077
1080
|
hasPendingReactiveDeterminations(): boolean;
|
|
1078
|
-
/**
|
|
1081
|
+
/** True when the latest settled generation failed or could not derive a current draft. */
|
|
1082
|
+
hasUnsatisfiedReactiveDeterminations(): boolean;
|
|
1083
|
+
/** Resolves true only when the latest generations settled successfully/current. */
|
|
1079
1084
|
waitForReactiveDeterminations(timeoutMs?: number): Promise<boolean>;
|
|
1080
1085
|
private setReactiveDeterminationPending;
|
|
1081
1086
|
private handleReactiveDeterminationExecutionEvent;
|