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

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
@@ -93,6 +93,15 @@ backend B7 acceptance is complete.
93
93
 
94
94
  `GovernedColorPaletteService` is the canonical Angular client for color-palette projections published by Praxis Config. Published palettes carry a stable `familyKey`, an explicit `variant` identity and token-level `displayName`/`aliases`; incomplete legacy projections are rejected before caching. The service supports list/read/capabilities/preview, exact `list(familyKey)` queries, tenant/environment-isolated caching, conditional ETag revalidation and deterministic `version`/`etag` pins. Consumers may use names and aliases for presentation and discovery only: `paletteKey` and `tokenId` remain the persisted identities, and local metadata must not become a second source of governance. `GovernedColorTokenSelection` carries the exact palette/version/ETag/token observation beside the existing CSS-string field value; applications persist it only through an explicit domain field or mapping.
95
95
 
96
+ ## Declarative Page DataSources (`page.dataSources`)
97
+
98
+ `WidgetPageDefinition.dataSources` and `WidgetPageDataSourceRuntimeService` allow dynamic pages and dashboards to fetch backend KPIs, group-by aggregations, and time-series metrics without imperative host `HttpClient` orchestration:
99
+
100
+ - **Endpoint resolution (`endpointKey` + `resourcePath`)**: resolves URLs through the `API_URL` token using `endpointKey` (falling back to `'default'`) so multi-service enterprise hosts route each DataSource to its target microservice.
101
+ - **Transform presets & JsonLogic**: supports `{ kind: 'path', path }`, `{ kind: 'group-by-map', mappings, totalField }` (initializing declared keys with `0` for sparse buckets), `{ kind: 'timeseries-last', mappings }`, or arbitrary JsonLogic expressions.
102
+ - **Transient state overlay (`targetStatePath`)**: merges resolved payloads into a non-persisted runtime overlay over `page.state.values[targetStatePath]` and coalesces concurrent emissions via microtask batching before re-evaluating `page.state.derived` and `composition.links`. Live runtime payloads never dirty `authoredPageDefinition` or trigger `autoPersist` writes.
103
+ - **Resilience & visibility-aware polling**: `errorPolicy` (`'fallback' | 'silent' | 'propagate'`) applies `fallbackValue` only before the first successful response so transient network errors during polling (`refreshIntervalMs`) never overwrite live data with static fallbacks. Subsequent poll ticks pause while `document.visibilityState === 'hidden'` and resume immediately on `visibilitychange`.
104
+
96
105
  ## LLM Recommendation Guide
97
106
 
98
107
  - `BUILTIN_WIDGET_SHELL_PRESETS` is the canonical catalog for reusable shell
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": "1.0.0",
3
- "generatedAt": "2026-10-14T20:20:10.826Z",
3
+ "generatedAt": "2026-10-14T22:05:37.957Z",
4
4
  "packageName": "@praxisui/core",
5
- "packageVersion": "10.0.0-rc.16",
5
+ "packageVersion": "10.0.0-rc.17",
6
6
  "sourceRegistry": "praxis-component-registry-ingestion",
7
7
  "sourceRegistryVersion": "1.0.0",
8
8
  "componentCount": 4,
@@ -40174,17 +40174,51 @@ class WidgetPageDataSourceRuntimeService {
40174
40174
  }
40175
40175
  return request$.pipe(map((res) => this.transformPayload(res, dataSource)));
40176
40176
  }
40177
+ /**
40178
+ * Normaliza `page.dataSources` aceitando tanto o formato canônico em Array quanto Object Map indexado por id.
40179
+ */
40180
+ normalizeDataSources(dataSources) {
40181
+ if (!dataSources || typeof dataSources !== 'object') {
40182
+ return [];
40183
+ }
40184
+ if (Array.isArray(dataSources)) {
40185
+ return dataSources.filter((item) => !!item &&
40186
+ typeof item === 'object' &&
40187
+ !Array.isArray(item) &&
40188
+ typeof item.id === 'string' &&
40189
+ item.id.trim().length > 0 &&
40190
+ typeof item.resourcePath === 'string' &&
40191
+ item.resourcePath.trim().length > 0);
40192
+ }
40193
+ return Object.entries(dataSources)
40194
+ .filter(([key, item]) => key.trim().length > 0 &&
40195
+ !!item &&
40196
+ typeof item === 'object' &&
40197
+ !Array.isArray(item) &&
40198
+ typeof item.resourcePath === 'string' &&
40199
+ item.resourcePath.trim().length > 0)
40200
+ .map(([key, item]) => {
40201
+ const candidateId = item.id;
40202
+ return {
40203
+ ...item,
40204
+ id: typeof candidateId === 'string' && candidateId.trim().length > 0
40205
+ ? candidateId.trim()
40206
+ : key.trim(),
40207
+ };
40208
+ });
40209
+ }
40177
40210
  /**
40178
40211
  * Inicia a orquestração reativa de todos os DataSources declarados em uma página dinâmica.
40179
40212
  * Retorna uma função de teardown que cancela todas as requisições em voo e timers de polling.
40180
40213
  */
40181
40214
  startDataSources(dataSources, onUpdate, onDiagnostic) {
40182
- if (!dataSources || !dataSources.length) {
40215
+ const normalizedDataSources = this.normalizeDataSources(dataSources);
40216
+ if (!normalizedDataSources.length) {
40183
40217
  return () => { };
40184
40218
  }
40185
40219
  const destroy$ = new Subject();
40186
40220
  const visibilityCallbacks = [];
40187
- for (const ds of dataSources) {
40221
+ for (const ds of normalizedDataSources) {
40188
40222
  if (ds.autoFetch === false) {
40189
40223
  continue;
40190
40224
  }
@@ -46034,6 +46068,7 @@ class DynamicWidgetPageComponent {
46034
46068
  this.currentDataSourcesSignature) {
46035
46069
  this.dataSourceRuntimeOverlay.clear();
46036
46070
  this.dataSourceDiagnostics.clear();
46071
+ this.pendingDataSourceUpdates = [];
46037
46072
  this.currentDataSourcesSignature = null;
46038
46073
  }
46039
46074
  this.pageRuntime = this.buildStateRuntime(this.pageDefinition?.state, this.pageDefinition?.context);
@@ -46366,9 +46401,8 @@ class DynamicWidgetPageComponent {
46366
46401
  };
46367
46402
  }
46368
46403
  getDataSourcesSignature(dataSources) {
46369
- return dataSources && dataSources.length > 0
46370
- ? JSON.stringify(dataSources)
46371
- : null;
46404
+ const normalized = this.dataSourceRuntime.normalizeDataSources(dataSources);
46405
+ return normalized.length > 0 ? JSON.stringify(normalized) : null;
46372
46406
  }
46373
46407
  bootstrapCompositionAdapter(page) {
46374
46408
  if (!page) {
@@ -48783,15 +48817,22 @@ class DynamicWidgetPageComponent {
48783
48817
  themePreset: themePreset || undefined,
48784
48818
  }, persist);
48785
48819
  }
48786
- applyAuthoringPageUpdate(next, persist = true, runtime = this.buildStateRuntime(next.state, next.context), syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48820
+ applyAuthoringPageUpdate(next, persist = true, runtime, syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48787
48821
  this.applyPageUpdate(next, persist, runtime, syncCompositionAdapter, skipStateWidgetProjection, emitUnchangedCanonicalPage);
48788
48822
  if (this.authoredPageDefinition) {
48789
48823
  this.pageAuthoringChange.emit(this.clonePageDefinition(this.authoredPageDefinition));
48790
48824
  }
48791
48825
  }
48792
- applyPageUpdate(next, persist = true, runtime = this.buildStateRuntime(next.state, next.context), syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48826
+ applyPageUpdate(next, persist = true, runtime, syncCompositionAdapter = true, skipStateWidgetProjection = false, emitUnchangedCanonicalPage = true) {
48793
48827
  this.assertNoLegacyConnections(next);
48794
- const normalized = { ...next, state: runtime.state };
48828
+ if (this.getDataSourcesSignature(next.dataSources) !==
48829
+ this.currentDataSourcesSignature) {
48830
+ this.dataSourceRuntimeOverlay.clear();
48831
+ this.dataSourceDiagnostics.clear();
48832
+ this.pendingDataSourceUpdates = [];
48833
+ }
48834
+ const effectiveRuntime = runtime ?? this.buildStateRuntime(next.state, next.context);
48835
+ const normalized = { ...next, state: effectiveRuntime.state };
48795
48836
  const normalizedWidgets = this.applyEditShellActions(normalized.widgets || []);
48796
48837
  const bootstrapSnapshot = syncCompositionAdapter
48797
48838
  ? this.bootstrapCompositionAdapter(normalized)
@@ -48799,13 +48840,13 @@ class DynamicWidgetPageComponent {
48799
48840
  const stateBoundWidgets = skipStateWidgetProjection
48800
48841
  ? normalizedWidgets
48801
48842
  : bootstrapSnapshot
48802
- ? this.applyBootstrapCompositionHydration(normalizedWidgets, bootstrapSnapshot, runtime)
48843
+ ? this.applyBootstrapCompositionHydration(normalizedWidgets, bootstrapSnapshot, effectiveRuntime)
48803
48844
  : this.compositionDefinition
48804
48845
  ? this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
48805
48846
  widgets: normalizedWidgets,
48806
48847
  state: {
48807
- primaryValues: this.cloneStateValues(runtime.primaryValues),
48808
- derivedValues: this.cloneStateValues(runtime.derivedValues),
48848
+ primaryValues: this.cloneStateValues(effectiveRuntime.primaryValues),
48849
+ derivedValues: this.cloneStateValues(effectiveRuntime.derivedValues),
48809
48850
  transientValues: this.cloneStateValues(this.compositionRuntime.getSnapshot().state.transientValues),
48810
48851
  changedPaths: [],
48811
48852
  diagnostics: [],
@@ -48818,9 +48859,9 @@ class DynamicWidgetPageComponent {
48818
48859
  });
48819
48860
  this.pageDefinition = hydrated;
48820
48861
  this.page = hydrated;
48821
- this.pageState = runtime.state;
48822
- this.pageRuntime = runtime;
48823
- this.applyResponsivePresentation(hydrated, hydrated?.widgets || stateBoundWidgets, runtime);
48862
+ this.pageState = effectiveRuntime.state;
48863
+ this.pageRuntime = effectiveRuntime;
48864
+ this.applyResponsivePresentation(hydrated, hydrated?.widgets || stateBoundWidgets, effectiveRuntime);
48824
48865
  this.restartDataSources(hydrated);
48825
48866
  if (this.widgetEventBus) {
48826
48867
  this.wiringSubscription?.unsubscribe();
@@ -48836,7 +48877,7 @@ class DynamicWidgetPageComponent {
48836
48877
  if (authoredPageChanged || emitUnchangedCanonicalPage) {
48837
48878
  this.pageChange.emit(canonicalPage);
48838
48879
  }
48839
- this.reportStateDiagnostics(runtime.diagnostics);
48880
+ this.reportStateDiagnostics(effectiveRuntime.diagnostics);
48840
48881
  if (authoredPageChanged &&
48841
48882
  persist &&
48842
48883
  this.autoPersist &&
@@ -48927,12 +48968,14 @@ class DynamicWidgetPageComponent {
48927
48968
  this.currentDataSourcesSignature = signature;
48928
48969
  this.dataSourceRuntimeOverlay.clear();
48929
48970
  this.dataSourceDiagnostics.clear();
48971
+ this.pendingDataSourceUpdates = [];
48930
48972
  this.dataSourceTeardown?.();
48931
48973
  this.dataSourceTeardown = undefined;
48932
- if (!dataSources || dataSources.length === 0) {
48974
+ const normalizedDataSources = this.dataSourceRuntime.normalizeDataSources(dataSources);
48975
+ if (normalizedDataSources.length === 0) {
48933
48976
  return;
48934
48977
  }
48935
- this.dataSourceTeardown = this.dataSourceRuntime.startDataSources(dataSources, (update) => this.handleDataSourceUpdate(update), (diagnostic) => this.handleDataSourceDiagnostic(diagnostic));
48978
+ this.dataSourceTeardown = this.dataSourceRuntime.startDataSources(normalizedDataSources, (update) => this.handleDataSourceUpdate(update), (diagnostic) => this.handleDataSourceDiagnostic(diagnostic));
48936
48979
  }
48937
48980
  applyLayout(layout, grouping, deviceLayoutColumnsSelected = false) {
48938
48981
  this.layout = layout;
@@ -49274,7 +49317,11 @@ class DynamicWidgetPageComponent {
49274
49317
  }
49275
49318
  return this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
49276
49319
  widgets,
49277
- state: snapshot.state,
49320
+ state: {
49321
+ ...snapshot.state,
49322
+ primaryValues: this.cloneStateValues(this.pageRuntime.primaryValues),
49323
+ derivedValues: this.cloneStateValues(this.pageRuntime.derivedValues),
49324
+ },
49278
49325
  now: snapshot.generatedAt,
49279
49326
  }).widgets;
49280
49327
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxisui/core",
3
- "version": "10.0.0-rc.16",
3
+ "version": "10.0.0-rc.17",
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",
@@ -18261,11 +18261,19 @@ declare class WidgetPageDataSourceRuntimeService {
18261
18261
  */
18262
18262
  fetchDataSource(dataSource: WidgetPageDataSourceDefinition): Observable<unknown>;
18263
18263
  private executeRawRequest;
18264
+ /**
18265
+ * Normaliza `page.dataSources` aceitando tanto o formato canônico em Array quanto Object Map indexado por id.
18266
+ */
18267
+ normalizeDataSources(dataSources: WidgetPageDataSourceDefinition[] | Record<string, Omit<WidgetPageDataSourceDefinition, 'id'> & {
18268
+ id?: string;
18269
+ }> | undefined | null): WidgetPageDataSourceDefinition[];
18264
18270
  /**
18265
18271
  * Inicia a orquestração reativa de todos os DataSources declarados em uma página dinâmica.
18266
18272
  * Retorna uma função de teardown que cancela todas as requisições em voo e timers de polling.
18267
18273
  */
18268
- startDataSources(dataSources: WidgetPageDataSourceDefinition[] | undefined, onUpdate: (event: WidgetPageDataSourceUpdateEvent) => void, onDiagnostic?: (diagnostic: WidgetPageDataSourceDiagnostic) => void): () => void;
18274
+ startDataSources(dataSources: WidgetPageDataSourceDefinition[] | Record<string, Omit<WidgetPageDataSourceDefinition, 'id'> & {
18275
+ id?: string;
18276
+ }> | undefined, onUpdate: (event: WidgetPageDataSourceUpdateEvent) => void, onDiagnostic?: (diagnostic: WidgetPageDataSourceDiagnostic) => void): () => void;
18269
18277
  /**
18270
18278
  * Transforma a resposta bruta do backend aplicando envelope canônico ou JsonLogic.
18271
18279
  */