@praxisui/core 10.0.0-rc.15 → 10.0.0-rc.16

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-14T16:04:24.084Z",
3
+ "generatedAt": "2026-10-14T20:20:10.826Z",
4
4
  "packageName": "@praxisui/core",
5
- "packageVersion": "10.0.0-rc.15",
5
+ "packageVersion": "10.0.0-rc.16",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 4,
@@ -5532,8 +5532,28 @@ class GenericCrudService {
5532
5532
  .post(url, entity, {
5533
5533
  headers: composeHeadersWithVersion(entry),
5534
5534
  context: options?.httpContext,
5535
+ observe: 'response',
5535
5536
  })
5536
- .pipe(catchError(this.handleError));
5537
+ .pipe(map((response) => {
5538
+ const isHttpResponse = response &&
5539
+ typeof response === 'object' &&
5540
+ 'body' in response &&
5541
+ 'headers' in response;
5542
+ if (!isHttpResponse) {
5543
+ return response;
5544
+ }
5545
+ const idField = this._lastResourceMeta.idField || 'id';
5546
+ const createdId = response.body?.data?.[idField] ??
5547
+ response.body?.data?.id;
5548
+ if (createdId !== undefined && createdId !== null) {
5549
+ const versionKey = this.resourceVersionKey(createdId, options);
5550
+ return this.captureResourceVersion(versionKey, response);
5551
+ }
5552
+ if (!response.body) {
5553
+ throw new Error('Resource response body is empty.');
5554
+ }
5555
+ return response.body;
5556
+ }), catchError(this.handleError));
5537
5557
  }
5538
5558
  /**
5539
5559
  * Atualiza um registro existente.
@@ -5558,6 +5578,26 @@ class GenericCrudService {
5558
5578
  update(id, entity, options) {
5559
5579
  return this.updateResponse(id, entity, options).pipe(map((r) => r.data));
5560
5580
  }
5581
+ getResourceVersion(id, options) {
5582
+ if (id === undefined || id === null || !this.configured) {
5583
+ return null;
5584
+ }
5585
+ const versionKey = this.resourceVersionKey(id, options);
5586
+ return this._resourceEtags.get(versionKey) ?? null;
5587
+ }
5588
+ setResourceVersion(id, etag, options) {
5589
+ if (id === undefined || id === null || !this.configured) {
5590
+ return;
5591
+ }
5592
+ const versionKey = this.resourceVersionKey(id, options);
5593
+ const formatted = this.normalizeEtagToken(etag);
5594
+ if (formatted) {
5595
+ this._resourceEtags.set(versionKey, formatted);
5596
+ }
5597
+ else {
5598
+ this._resourceEtags.delete(versionKey);
5599
+ }
5600
+ }
5561
5601
  updateResponse(id, entity, options) {
5562
5602
  if (id === undefined || id === null)
5563
5603
  throw new Error(this.errorMessages.emptyId);
@@ -5568,10 +5608,12 @@ class GenericCrudService {
5568
5608
  const url = this.getEndpointUrl('update', id, options?.parentPath, options?.endpointKey);
5569
5609
  const versionKey = this.resourceVersionKey(id, options);
5570
5610
  let headers = composeHeadersWithVersion(entry);
5571
- const etag = this._resourceEtags.get(versionKey);
5611
+ const etag = this._resourceEtags.get(versionKey) ||
5612
+ this.normalizeEtagToken(entity?.resourceVersion) ||
5613
+ null;
5572
5614
  if (etag) {
5573
5615
  headers = (headers instanceof HttpHeaders ? headers : new HttpHeaders(headers))
5574
- .set('If-Match', etag);
5616
+ .set('If-Match', this.ensureEtagFormat(etag));
5575
5617
  }
5576
5618
  return this.http
5577
5619
  .put(url, entity, {
@@ -5594,7 +5636,9 @@ class GenericCrudService {
5594
5636
  return [buildApiUrl(entry), tenant, environment, this.resourcePath, options?.parentPath ?? '', String(id)].join('|');
5595
5637
  }
5596
5638
  captureResourceVersion(key, response) {
5597
- const etag = response.headers.get('ETag');
5639
+ const etag = this.normalizeEtagToken(response.headers?.get('ETag')) ||
5640
+ this.normalizeEtagToken(response.body?.data?.resourceVersion) ||
5641
+ null;
5598
5642
  if (etag) {
5599
5643
  this._resourceEtags.set(key, etag);
5600
5644
  }
@@ -5603,6 +5647,26 @@ class GenericCrudService {
5603
5647
  }
5604
5648
  return response.body;
5605
5649
  }
5650
+ normalizeEtagToken(etag) {
5651
+ if (typeof etag !== 'string' && typeof etag !== 'number') {
5652
+ return null;
5653
+ }
5654
+ const trimmed = String(etag).trim();
5655
+ if (!trimmed) {
5656
+ return null;
5657
+ }
5658
+ const inner = trimmed.replace(/^(?:W\/)?"|"$/g, '').trim();
5659
+ if (!inner) {
5660
+ return null;
5661
+ }
5662
+ return this.ensureEtagFormat(trimmed);
5663
+ }
5664
+ ensureEtagFormat(etag) {
5665
+ if (/^(?:W\/)?".+"$/.test(etag)) {
5666
+ return etag;
5667
+ }
5668
+ return `"${etag.replace(/^"|"$/g, '')}"`;
5669
+ }
5606
5670
  /**
5607
5671
  * Remove um registro pelo ID.
5608
5672
  *
@@ -5631,6 +5695,7 @@ class GenericCrudService {
5631
5695
  this.ensureConfigured();
5632
5696
  const entry = this.resolveEndpointEntry(options?.endpointKey);
5633
5697
  const url = this.getEndpointUrl('delete', id, options?.parentPath, options?.endpointKey);
5698
+ const versionKey = this.resourceVersionKey(id, options);
5634
5699
  debugCrudService('[CRUD:Service] delete:request', {
5635
5700
  url,
5636
5701
  id,
@@ -5642,7 +5707,7 @@ class GenericCrudService {
5642
5707
  headers: composeHeadersWithVersion(entry),
5643
5708
  context: options?.httpContext,
5644
5709
  })
5645
- .pipe(catchError(this.handleError));
5710
+ .pipe(tap(() => this._resourceEtags.delete(versionKey)), catchError(this.handleError));
5646
5711
  }
5647
5712
  /**
5648
5713
  * Realiza exclusão de múltiplos registros sequencialmente.
@@ -13237,6 +13302,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
13237
13302
  type: Optional
13238
13303
  }] }] });
13239
13304
 
13305
+ const DEFAULT_FRAMEWORK_VERSION_MESSAGES = new Set([
13306
+ 'resource version is stale.',
13307
+ 'resource version is required.',
13308
+ 'the resource changed after it was read. reload it before deciding whether to retry.',
13309
+ 'the resource changed after it was read. reload and try again.',
13310
+ 'this operation requires a current resource version in if-match.',
13311
+ ]);
13240
13312
  /**
13241
13313
  * Maps HTTP or network errors to user-friendly messages.
13242
13314
  * This service centralizes error messaging so UI components don't
@@ -13258,14 +13330,19 @@ class ErrorMessageService {
13258
13330
  ? this.normalizeDetails(payload?.['errors'])
13259
13331
  : [];
13260
13332
  if (details.length) {
13333
+ const topMessage = this.textValue(payload?.['message']);
13334
+ const resolvedTopMessage = this.resolveVersionPreconditionCopy(details[0].code, topMessage ?? details[0].message, status);
13261
13335
  return {
13262
- message: this.textValue(payload?.['message']) ?? details[0].message,
13336
+ message: resolvedTopMessage,
13263
13337
  details,
13264
13338
  };
13265
13339
  }
13266
13340
  const publicMessage = this.textValue(payload?.['message']);
13267
13341
  if (publicMessage && isPublicClientError) {
13268
- return { message: publicMessage, details: [] };
13342
+ return {
13343
+ message: this.resolveVersionPreconditionCopy(undefined, publicMessage, status),
13344
+ details: [],
13345
+ };
13269
13346
  }
13270
13347
  return { message: this.fallbackMessage(status), details: [] };
13271
13348
  }
@@ -13283,6 +13360,12 @@ class ErrorMessageService {
13283
13360
  if (status !== undefined && status >= 500) {
13284
13361
  return this.tx('global.submitError.server', 'Erro interno no servidor. Tente novamente mais tarde.');
13285
13362
  }
13363
+ if (status === 412) {
13364
+ return this.staleResourceVersionMessage();
13365
+ }
13366
+ if (status === 428) {
13367
+ return this.resourceVersionRequiredMessage();
13368
+ }
13286
13369
  if (status === 400 || status === 422) {
13287
13370
  return this.tx('global.submitError.invalidData', 'Dados inválidos ou inconsistentes. Verifique os campos e tente novamente.');
13288
13371
  }
@@ -13293,17 +13376,40 @@ class ErrorMessageService {
13293
13376
  return [];
13294
13377
  return value.flatMap((item) => {
13295
13378
  const detail = this.objectValue(item);
13296
- const message = this.textValue(detail?.['message']) ?? this.textValue(detail?.['detail']);
13297
- if (!message)
13379
+ const rawMessage = this.textValue(detail?.['message']) ?? this.textValue(detail?.['detail']);
13380
+ if (!rawMessage)
13298
13381
  return [];
13382
+ const code = this.textValue(detail?.['code']);
13383
+ const message = this.resolveVersionPreconditionCopy(code, rawMessage, undefined);
13299
13384
  return [{
13300
13385
  message,
13301
- code: this.textValue(detail?.['code']),
13386
+ code,
13302
13387
  target: this.textValue(detail?.['target']),
13303
13388
  category: this.textValue(detail?.['category']),
13304
13389
  }];
13305
13390
  });
13306
13391
  }
13392
+ resolveVersionPreconditionCopy(code, candidateMessage, status) {
13393
+ const normalizedCode = String(code ?? '').trim().toUpperCase();
13394
+ const isDefaultFrameworkCopy = DEFAULT_FRAMEWORK_VERSION_MESSAGES.has(candidateMessage.trim().toLowerCase());
13395
+ if ((normalizedCode === 'STALE_RESOURCE_VERSION' || status === 412) &&
13396
+ isDefaultFrameworkCopy) {
13397
+ return this.staleResourceVersionMessage();
13398
+ }
13399
+ if ((normalizedCode === 'RESOURCE_VERSION_REQUIRED' ||
13400
+ normalizedCode === 'MISSING_IF_MATCH' ||
13401
+ status === 428) &&
13402
+ isDefaultFrameworkCopy) {
13403
+ return this.resourceVersionRequiredMessage();
13404
+ }
13405
+ return candidateMessage;
13406
+ }
13407
+ staleResourceVersionMessage() {
13408
+ return this.tx('global.submitError.staleResourceVersion', 'Este registro foi alterado após a leitura. Recarregue os dados e revise antes de tentar salvar novamente.');
13409
+ }
13410
+ resourceVersionRequiredMessage() {
13411
+ return this.tx('global.submitError.resourceVersionRequired', 'A versão atual do registro é obrigatória para confirmar esta alteração. Recarregue o registro e tente novamente.');
13412
+ }
13307
13413
  objectValue(value) {
13308
13414
  return value !== null && typeof value === 'object' && !Array.isArray(value)
13309
13415
  ? value
@@ -39980,7 +40086,7 @@ class WidgetPageDataSourceRuntimeService {
39980
40086
  /**
39981
40087
  * Constrói a URL canônica completa para o resourcePath declarado no dataSource.
39982
40088
  */
39983
- resolveUrl(resourcePath) {
40089
+ resolveUrl(resourcePath, endpointKey) {
39984
40090
  const trimmed = (resourcePath || '').trim();
39985
40091
  if (!trimmed)
39986
40092
  return '';
@@ -39989,7 +40095,10 @@ class WidgetPageDataSourceRuntimeService {
39989
40095
  }
39990
40096
  let base = '';
39991
40097
  if (this.apiUrlConfig) {
39992
- const entry = this.apiUrlConfig['default'] || Object.values(this.apiUrlConfig)[0];
40098
+ const key = (endpointKey || 'default').trim() || 'default';
40099
+ const entry = this.apiUrlConfig[key] ||
40100
+ this.apiUrlConfig['default'] ||
40101
+ Object.values(this.apiUrlConfig)[0];
39993
40102
  if (entry) {
39994
40103
  base = buildApiUrl(entry);
39995
40104
  }
@@ -40008,7 +40117,7 @@ class WidgetPageDataSourceRuntimeService {
40008
40117
  if (!this.http) {
40009
40118
  return of(dataSource.fallbackValue ?? null);
40010
40119
  }
40011
- const url = this.resolveUrl(dataSource.resourcePath);
40120
+ const url = this.resolveUrl(dataSource.resourcePath, dataSource.endpointKey);
40012
40121
  if (!url) {
40013
40122
  return of(dataSource.fallbackValue ?? null);
40014
40123
  }
@@ -40032,7 +40141,8 @@ class WidgetPageDataSourceRuntimeService {
40032
40141
  if (!this.http) {
40033
40142
  return of(dataSource.fallbackValue ?? null);
40034
40143
  }
40035
- const url = resolvedUrl ?? this.resolveUrl(dataSource.resourcePath);
40144
+ const url = resolvedUrl ??
40145
+ this.resolveUrl(dataSource.resourcePath, dataSource.endpointKey);
40036
40146
  if (!url) {
40037
40147
  return of(dataSource.fallbackValue ?? null);
40038
40148
  }
@@ -40073,6 +40183,7 @@ class WidgetPageDataSourceRuntimeService {
40073
40183
  return () => { };
40074
40184
  }
40075
40185
  const destroy$ = new Subject();
40186
+ const visibilityCallbacks = [];
40076
40187
  for (const ds of dataSources) {
40077
40188
  if (ds.autoFetch === false) {
40078
40189
  continue;
@@ -40080,12 +40191,14 @@ class WidgetPageDataSourceRuntimeService {
40080
40191
  const targetPath = ds.targetStatePath || ds.id;
40081
40192
  const intervalMs = ds.refreshIntervalMs ?? 0;
40082
40193
  let hasSucceededOnce = false;
40194
+ let skippedWhileHidden = false;
40083
40195
  onDiagnostic?.({
40084
40196
  dataSourceId: ds.id,
40085
40197
  status: 'loading',
40086
40198
  });
40087
40199
  const runSingleFetch$ = () => this.executeRawRequest(ds).pipe(map((value) => {
40088
40200
  hasSucceededOnce = true;
40201
+ skippedWhileHidden = false;
40089
40202
  const now = new Date().toISOString();
40090
40203
  onDiagnostic?.({
40091
40204
  dataSourceId: ds.id,
@@ -40120,16 +40233,43 @@ class WidgetPageDataSourceRuntimeService {
40120
40233
  }));
40121
40234
  const stream$ = intervalMs > 0
40122
40235
  ? timer(0, intervalMs).pipe(takeUntil(destroy$), switchMap(() => {
40123
- if (typeof document !== 'undefined' &&
40236
+ if (hasSucceededOnce &&
40237
+ typeof document !== 'undefined' &&
40124
40238
  document.visibilityState === 'hidden') {
40239
+ skippedWhileHidden = true;
40125
40240
  return EMPTY;
40126
40241
  }
40127
40242
  return runSingleFetch$();
40128
40243
  }))
40129
40244
  : runSingleFetch$().pipe(takeUntil(destroy$));
40130
40245
  stream$.subscribe();
40246
+ if (intervalMs > 0) {
40247
+ visibilityCallbacks.push(() => {
40248
+ if (skippedWhileHidden) {
40249
+ skippedWhileHidden = false;
40250
+ runSingleFetch$().pipe(takeUntil(destroy$)).subscribe();
40251
+ }
40252
+ });
40253
+ }
40254
+ }
40255
+ const onVisibilityChange = () => {
40256
+ if (typeof document !== 'undefined' && document.visibilityState === 'visible') {
40257
+ for (const cb of visibilityCallbacks) {
40258
+ cb();
40259
+ }
40260
+ }
40261
+ };
40262
+ if (visibilityCallbacks.length > 0 &&
40263
+ typeof document !== 'undefined' &&
40264
+ typeof document.addEventListener === 'function') {
40265
+ document.addEventListener('visibilitychange', onVisibilityChange);
40131
40266
  }
40132
40267
  return () => {
40268
+ if (visibilityCallbacks.length > 0 &&
40269
+ typeof document !== 'undefined' &&
40270
+ typeof document.removeEventListener === 'function') {
40271
+ document.removeEventListener('visibilitychange', onVisibilityChange);
40272
+ }
40133
40273
  destroy$.next();
40134
40274
  destroy$.complete();
40135
40275
  };
@@ -45716,6 +45856,7 @@ class DynamicWidgetPageComponent {
45716
45856
  pendingDataSourceUpdates = [];
45717
45857
  isCoalescingDataSourceUpdates = false;
45718
45858
  dataSourceRuntimeOverlay = new Map();
45859
+ dataSourceDiagnostics = new Map();
45719
45860
  nestedWidgetAccessor = new NestedWidgetConfigAccessor();
45720
45861
  settingsPanel = inject(SETTINGS_PANEL_BRIDGE, {
45721
45862
  optional: true,
@@ -45807,6 +45948,7 @@ class DynamicWidgetPageComponent {
45807
45948
  this.pendingDataSourceUpdates = [];
45808
45949
  this.isCoalescingDataSourceUpdates = false;
45809
45950
  this.dataSourceRuntimeOverlay.clear();
45951
+ this.dataSourceDiagnostics.clear();
45810
45952
  this.persistenceGeneration++;
45811
45953
  this.persistenceLoad?.unsubscribe();
45812
45954
  this.persistenceMutation?.unsubscribe();
@@ -45891,6 +46033,8 @@ class DynamicWidgetPageComponent {
45891
46033
  this.getDataSourcesSignature(this.pageDefinition?.dataSources) !==
45892
46034
  this.currentDataSourcesSignature) {
45893
46035
  this.dataSourceRuntimeOverlay.clear();
46036
+ this.dataSourceDiagnostics.clear();
46037
+ this.currentDataSourcesSignature = null;
45894
46038
  }
45895
46039
  this.pageRuntime = this.buildStateRuntime(this.pageDefinition?.state, this.pageDefinition?.context);
45896
46040
  this.pageState = this.pageRuntime.state;
@@ -45992,13 +46136,21 @@ class DynamicWidgetPageComponent {
45992
46136
  const state = this.stateFromCompositionSnapshot(pageWithPatchedInputs.state, cycle.snapshot.state.primaryValues);
45993
46137
  const runtimeStatePaths = Array.from(new Set((cycle.snapshot.state.changedPaths || []).filter((path) => typeof path === 'string')));
45994
46138
  const updatedPrimaryStatePaths = runtimeStatePaths.filter((path) => !this.areStateValuesEqual(this.conn.extractByPath(this.stateRuntime.normalizeState(page.state).values || {}, path), this.conn.extractByPath(state.values || {}, path)));
46139
+ for (const changedPath of updatedPrimaryStatePaths) {
46140
+ this.dataSourceRuntimeOverlay.delete(changedPath);
46141
+ }
46142
+ const nextRuntime = this.buildStateRuntime(state, pageWithPatchedInputs.context);
45995
46143
  let widgets = this.cloneWidgets(pageWithPatchedInputs.widgets || []);
45996
46144
  const directDelivery = this.applyCompositionWidgetDeliveries(widgets, cycle);
45997
46145
  widgets = directDelivery.widgets;
45998
- const projectedWidgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
46146
+ const effectiveCompositionState = {
46147
+ ...cycle.snapshot.state,
46148
+ primaryValues: this.cloneStateValues(nextRuntime.primaryValues),
46149
+ derivedValues: this.cloneStateValues(nextRuntime.derivedValues),
46150
+ };
46151
+ const projectedWidgets = this.projectPersistentCompositionStateInputs(widgets, effectiveCompositionState, cycle.snapshot.generatedAt);
45999
46152
  const stateProjectionChanged = !this.areStateValuesEqual(widgets, projectedWidgets);
46000
46153
  widgets = projectedWidgets;
46001
- const nextRuntime = this.buildStateRuntime(state, pageWithPatchedInputs.context);
46002
46154
  if (this.isTransientOnlyCompositionCycle(cycle) &&
46003
46155
  !updatedPrimaryStatePaths.length &&
46004
46156
  !directDelivery.changed &&
@@ -46826,12 +46978,16 @@ class DynamicWidgetPageComponent {
46826
46978
  return { page };
46827
46979
  }
46828
46980
  const state = this.stateFromCompositionSnapshot(page.state, cycle.snapshot.state.primaryValues);
46981
+ const runtime = this.buildStateRuntime(state, page.context);
46829
46982
  let widgets = this.cloneWidgets(page.widgets || []);
46830
46983
  widgets = this.applyCompositionWidgetDeliveries(widgets, cycle).widgets;
46831
46984
  if (this.compositionDefinition) {
46832
- widgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
46985
+ widgets = this.projectPersistentCompositionStateInputs(widgets, {
46986
+ ...cycle.snapshot.state,
46987
+ primaryValues: this.cloneStateValues(runtime.primaryValues),
46988
+ derivedValues: this.cloneStateValues(runtime.derivedValues),
46989
+ }, cycle.snapshot.generatedAt);
46833
46990
  }
46834
- const runtime = this.buildStateRuntime(state, page.context);
46835
46991
  return {
46836
46992
  page: { ...page, widgets, state },
46837
46993
  runtime,
@@ -46948,10 +47104,14 @@ class DynamicWidgetPageComponent {
46948
47104
  return;
46949
47105
  }
46950
47106
  const state = this.stateFromCompositionSnapshot(page.state, cycle.snapshot.state.primaryValues);
47107
+ const runtime = this.buildStateRuntime(state, page.context);
46951
47108
  let widgets = this.cloneWidgets(page.widgets || []);
46952
47109
  widgets = this.applyCompositionWidgetDeliveries(widgets, cycle).widgets;
46953
- widgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
46954
- const runtime = this.buildStateRuntime(state, page.context);
47110
+ widgets = this.projectPersistentCompositionStateInputs(widgets, {
47111
+ ...cycle.snapshot.state,
47112
+ primaryValues: this.cloneStateValues(runtime.primaryValues),
47113
+ derivedValues: this.cloneStateValues(runtime.derivedValues),
47114
+ }, cycle.snapshot.generatedAt);
46955
47115
  this.applyPageUpdate({ ...page, widgets, state }, true, runtime, false, true, false);
46956
47116
  }
46957
47117
  matchesRuntimeSourceRef(endpoint, sourceRef) {
@@ -48646,7 +48806,7 @@ class DynamicWidgetPageComponent {
48646
48806
  state: {
48647
48807
  primaryValues: this.cloneStateValues(runtime.primaryValues),
48648
48808
  derivedValues: this.cloneStateValues(runtime.derivedValues),
48649
- transientValues: {},
48809
+ transientValues: this.cloneStateValues(this.compositionRuntime.getSnapshot().state.transientValues),
48650
48810
  changedPaths: [],
48651
48811
  diagnostics: [],
48652
48812
  },
@@ -48730,7 +48890,7 @@ class DynamicWidgetPageComponent {
48730
48890
  state: {
48731
48891
  primaryValues: this.cloneStateValues(nextRuntime.primaryValues),
48732
48892
  derivedValues: this.cloneStateValues(nextRuntime.derivedValues),
48733
- transientValues: {},
48893
+ transientValues: this.cloneStateValues(this.compositionRuntime.getSnapshot().state.transientValues),
48734
48894
  changedPaths: updates.map((u) => u.targetStatePath),
48735
48895
  diagnostics: [],
48736
48896
  },
@@ -48747,6 +48907,17 @@ class DynamicWidgetPageComponent {
48747
48907
  this.reportStateDiagnostics(nextRuntime.diagnostics);
48748
48908
  this.changeDetector.markForCheck();
48749
48909
  }
48910
+ handleDataSourceDiagnostic(diagnostic) {
48911
+ if (this.destroyed) {
48912
+ return;
48913
+ }
48914
+ this.dataSourceDiagnostics.set(diagnostic.dataSourceId, diagnostic);
48915
+ if (diagnostic.status === 'error') {
48916
+ this.reportStateDiagnostics([
48917
+ `DataSource "${diagnostic.dataSourceId}" failed: ${diagnostic.error || 'unknown error'}`,
48918
+ ]);
48919
+ }
48920
+ }
48750
48921
  restartDataSources(page) {
48751
48922
  const dataSources = page?.dataSources;
48752
48923
  const signature = this.getDataSourcesSignature(dataSources);
@@ -48755,12 +48926,13 @@ class DynamicWidgetPageComponent {
48755
48926
  }
48756
48927
  this.currentDataSourcesSignature = signature;
48757
48928
  this.dataSourceRuntimeOverlay.clear();
48929
+ this.dataSourceDiagnostics.clear();
48758
48930
  this.dataSourceTeardown?.();
48759
48931
  this.dataSourceTeardown = undefined;
48760
48932
  if (!dataSources || dataSources.length === 0) {
48761
48933
  return;
48762
48934
  }
48763
- this.dataSourceTeardown = this.dataSourceRuntime.startDataSources(dataSources, (update) => this.handleDataSourceUpdate(update));
48935
+ this.dataSourceTeardown = this.dataSourceRuntime.startDataSources(dataSources, (update) => this.handleDataSourceUpdate(update), (diagnostic) => this.handleDataSourceDiagnostic(diagnostic));
48764
48936
  }
48765
48937
  applyLayout(layout, grouping, deviceLayoutColumnsSelected = false) {
48766
48938
  this.layout = layout;
@@ -51240,7 +51412,13 @@ class DynamicWidgetPageComponent {
51240
51412
  cloned.layout = { ...page.layout };
51241
51413
  }
51242
51414
  if (page.grouping) {
51243
- cloned.grouping = [...page.grouping];
51415
+ cloned.grouping = this.cloneStateValues(page.grouping);
51416
+ }
51417
+ if (page.dataSources) {
51418
+ cloned.dataSources = this.cloneStateValues(page.dataSources);
51419
+ }
51420
+ if (page.composition) {
51421
+ cloned.composition = this.cloneStateValues(page.composition);
51244
51422
  }
51245
51423
  if (page.canvas) {
51246
51424
  cloned.canvas = {
@@ -51251,16 +51429,20 @@ class DynamicWidgetPageComponent {
51251
51429
  };
51252
51430
  }
51253
51431
  if (page.context) {
51254
- cloned.context = { ...page.context };
51432
+ cloned.context = this.cloneStateValues(page.context);
51255
51433
  }
51256
51434
  if (page.state) {
51257
51435
  const stateObj = {
51258
- values: page.state.values ? { ...page.state.values } : {},
51436
+ values: page.state.values
51437
+ ? this.cloneStateValues(page.state.values)
51438
+ : {},
51259
51439
  };
51260
- if (page.state.schema !== undefined)
51261
- stateObj.schema = page.state.schema;
51262
- if (page.state.derived !== undefined)
51263
- stateObj.derived = page.state.derived;
51440
+ if (page.state.schema !== undefined) {
51441
+ stateObj.schema = this.cloneStateValues(page.state.schema);
51442
+ }
51443
+ if (page.state.derived !== undefined) {
51444
+ stateObj.derived = this.cloneStateValues(page.state.derived);
51445
+ }
51264
51446
  cloned.state = stateObj;
51265
51447
  }
51266
51448
  return cloned;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "10.0.0-rc.15",
3
+ "version": "10.0.0-rc.16",
4
4
  "description": "Core library for Praxis UI Workspace: types, tokens, services and utilities shared across @praxisui/* packages.",
5
5
  "peerDependencies": {
6
6
  "@angular/cdk": "^21.0.0",
@@ -6363,9 +6363,13 @@ declare class GenericCrudService<T, ID extends string | number = string | number
6363
6363
  * @returns Observable com a entidade atualizada.
6364
6364
  */
6365
6365
  update(id: ID, entity: T, options?: CrudOperationOptions): Observable<T>;
6366
+ getResourceVersion(id: ID, options?: CrudOperationOptions): string | null;
6367
+ setResourceVersion(id: ID, etag: string | null | undefined, options?: CrudOperationOptions): void;
6366
6368
  updateResponse(id: ID, entity: T, options?: CrudOperationOptions): Observable<RestApiResponse<T>>;
6367
6369
  private resourceVersionKey;
6368
6370
  private captureResourceVersion;
6371
+ private normalizeEtagToken;
6372
+ private ensureEtagFormat;
6369
6373
  /**
6370
6374
  * Remove um registro pelo ID.
6371
6375
  *
@@ -10840,6 +10844,9 @@ declare class ErrorMessageService {
10840
10844
  getSubmitErrorMessage(error: unknown): string;
10841
10845
  private fallbackMessage;
10842
10846
  private normalizeDetails;
10847
+ private resolveVersionPreconditionCopy;
10848
+ private staleResourceVersionMessage;
10849
+ private resourceVersionRequiredMessage;
10843
10850
  private objectValue;
10844
10851
  private textValue;
10845
10852
  private numberValue;
@@ -16232,6 +16239,8 @@ interface WidgetPageDataSourceDefinition {
16232
16239
  kind?: WidgetPageDataSourceKind;
16233
16240
  /** Caminho do recurso canônico no backend Praxis (ex: 'human-resources/funcionarios/stats/group-by'). */
16234
16241
  resourcePath: string;
16242
+ /** Chave opcional do endpoint configurado em API_URL (padrão: 'default'). */
16243
+ endpointKey?: string;
16235
16244
  /** Método HTTP utilizado (padrão: 'POST' para endpoints /stats, 'GET' para recursos padrão). */
16236
16245
  method?: WidgetPageDataSourceMethod;
16237
16246
  /** Parâmetros de consulta (query params para GET ou corpo da requisição para POST de analytics). */
@@ -18246,7 +18255,7 @@ declare class WidgetPageDataSourceRuntimeService {
18246
18255
  /**
18247
18256
  * Constrói a URL canônica completa para o resourcePath declarado no dataSource.
18248
18257
  */
18249
- resolveUrl(resourcePath: string): string;
18258
+ resolveUrl(resourcePath: string, endpointKey?: string): string;
18250
18259
  /**
18251
18260
  * Executa uma única consulta para um DataSource e retorna o resultado transformado.
18252
18261
  */
@@ -18776,6 +18785,7 @@ declare class DynamicWidgetPageComponent implements OnChanges, OnDestroy {
18776
18785
  private pendingDataSourceUpdates;
18777
18786
  private isCoalescingDataSourceUpdates;
18778
18787
  private readonly dataSourceRuntimeOverlay;
18788
+ private readonly dataSourceDiagnostics;
18779
18789
  private readonly nestedWidgetAccessor;
18780
18790
  private readonly settingsPanel;
18781
18791
  private readonly defaultShellEditor;
@@ -19007,6 +19017,7 @@ declare class DynamicWidgetPageComponent implements OnChanges, OnDestroy {
19007
19017
  private applyPageUpdate;
19008
19018
  private handleDataSourceUpdate;
19009
19019
  private flushPendingDataSourceUpdates;
19020
+ private handleDataSourceDiagnostic;
19010
19021
  private restartDataSources;
19011
19022
  private applyLayout;
19012
19023
  private applyCanvasLayout;