@praxisui/dynamic-form 1.0.0-beta.40 → 1.0.0-beta.41

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.
@@ -22,7 +22,7 @@ import { MatBadgeModule } from '@angular/material/badge';
22
22
  import { firstValueFrom, BehaviorSubject, Subject, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs';
23
23
  import { take, takeUntil, debounceTime, finalize, map } from 'rxjs/operators';
24
24
  import * as i1$2 from '@praxisui/core';
25
- import { PraxisIconDirective, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, deepMerge, ASYNC_CONFIG_STORAGE, migrateFormLayoutRule, ensureIds, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceQuickConnectComponent, normalizeFormConfig as normalizeFormConfig$1, mapFieldDefinitionsToMetadata, syncWithServerMetadata, PRAXIS_LOADING_CTX, normalizeControlTypeKey, resolveSpan, resolveOffset, resolveOrder, resolveHidden, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, buildSchemaId, CONNECTION_STORAGE, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, createDefaultFormConfig, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, GLOBAL_ACTION_SPEC_CATALOG, getGlobalActionUiSchema } from '@praxisui/core';
25
+ import { PraxisIconDirective, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, deepMerge, ASYNC_CONFIG_STORAGE, migrateFormLayoutRule, ensureIds, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceQuickConnectComponent, normalizeFormConfig as normalizeFormConfig$1, mapFieldDefinitionsToMetadata, syncWithServerMetadata, PRAXIS_LOADING_CTX, normalizeControlTypeKey, resolveSpan, resolveOffset, resolveOrder, resolveHidden, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, CONNECTION_STORAGE, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, createDefaultFormConfig, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, GLOBAL_ACTION_SPEC_CATALOG, getGlobalActionUiSchema } from '@praxisui/core';
26
26
  import * as i1 from '@praxisui/dynamic-fields';
27
27
  import { getControlTypeCatalog, ConfirmDialogComponent, DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
28
28
  import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
@@ -6190,6 +6190,79 @@ class PraxisDynamicForm {
6190
6190
  const id = this.componentKeyId();
6191
6191
  return id ? `form-schema-meta:${id}` : null;
6192
6192
  }
6193
+ getSchemaMetaIndexKey() {
6194
+ const key = this.getSchemaMetaKey();
6195
+ return key ? `${key}:index` : null;
6196
+ }
6197
+ getSchemaMetaKeyForSchemaId(schemaId) {
6198
+ const scopeKey = this.getSchemaMetaKey();
6199
+ const id = String(schemaId || '').trim();
6200
+ if (!scopeKey || !id)
6201
+ return null;
6202
+ return `${scopeKey}:schema:${id}`;
6203
+ }
6204
+ getSchemaMetaKeyForContext(context) {
6205
+ try {
6206
+ const schemaId = buildSchemaId(context);
6207
+ return this.getSchemaMetaKeyForSchemaId(schemaId) || this.getSchemaMetaKey();
6208
+ }
6209
+ catch {
6210
+ return this.getSchemaMetaKey();
6211
+ }
6212
+ }
6213
+ loadStoredSchemaMeta(key) {
6214
+ if (!key)
6215
+ return Promise.resolve(undefined);
6216
+ return firstValueFrom(this.asyncConfigStorage.loadConfig(key))
6217
+ .then((value) => value ?? undefined)
6218
+ .catch(() => undefined);
6219
+ }
6220
+ rememberSchemaMetaContext(schemaId) {
6221
+ const id = String(schemaId || '').trim();
6222
+ if (!id)
6223
+ return;
6224
+ const indexKey = this.getSchemaMetaIndexKey();
6225
+ if (!indexKey)
6226
+ return;
6227
+ this.asyncConfigStorage
6228
+ .loadConfig(indexKey)
6229
+ .pipe(take(1))
6230
+ .subscribe({
6231
+ next: (stored) => {
6232
+ const unique = new Set((Array.isArray(stored) ? stored : [])
6233
+ .map((item) => String(item || '').trim())
6234
+ .filter((item) => !!item));
6235
+ unique.add(id);
6236
+ this.asyncConfigStorage
6237
+ .saveConfig(indexKey, Array.from(unique))
6238
+ .pipe(take(1))
6239
+ .subscribe({ error: () => { } });
6240
+ },
6241
+ error: () => { },
6242
+ });
6243
+ }
6244
+ isSameSchemaContext(left, right) {
6245
+ if (!left || !right)
6246
+ return false;
6247
+ return (left.path === right.path &&
6248
+ left.operation === right.operation &&
6249
+ left.schemaType === right.schemaType &&
6250
+ left.includeInternalSchemas === right.includeInternalSchemas &&
6251
+ left.tenant === right.tenant &&
6252
+ left.locale === right.locale &&
6253
+ left.apiOrigin === right.apiOrigin);
6254
+ }
6255
+ isStoredSchemaMetaForContext(storedMeta, schemaId, context) {
6256
+ if (!storedMeta || typeof storedMeta !== 'object')
6257
+ return false;
6258
+ const storedSchemaId = String(storedMeta.schemaId || '').trim();
6259
+ if (storedSchemaId && storedSchemaId !== schemaId)
6260
+ return false;
6261
+ const storedContext = storedMeta.schemaContext;
6262
+ if (storedContext && !this.isSameSchemaContext(storedContext, context))
6263
+ return false;
6264
+ return true;
6265
+ }
6193
6266
  getPrefsKey() {
6194
6267
  const id = this.componentKeyId();
6195
6268
  return id ? `form-schema-prefs:${id}` : null;
@@ -6362,13 +6435,27 @@ class PraxisDynamicForm {
6362
6435
  // Verify-only using ETag without downloading schema body
6363
6436
  async verifyServerSchemaVersion() {
6364
6437
  try {
6365
- const metaKey = this.getSchemaMetaKey();
6366
- if (!this.resourcePath || !metaKey)
6438
+ if (!this.resourcePath)
6367
6439
  return;
6368
6440
  // Only verify when we already have a base configuration (sections)
6369
6441
  if ((this.config?.sections?.length ?? 0) === 0)
6370
6442
  return;
6371
6443
  const ctx = this.buildSchemaContext();
6444
+ const schemaContext = {
6445
+ path: ctx.path,
6446
+ operation: ctx.operation,
6447
+ schemaType: ctx.schemaType,
6448
+ includeInternalSchemas: ctx.includeInternalSchemas,
6449
+ tenant: ctx.tenant,
6450
+ locale: ctx.locale,
6451
+ apiOrigin: ctx.apiOrigin,
6452
+ };
6453
+ const currentSchemaId = buildSchemaId(schemaContext);
6454
+ const scopedMetaKey = this.getSchemaMetaKeyForSchemaId(currentSchemaId) ||
6455
+ this.getSchemaMetaKeyForContext(schemaContext) ||
6456
+ this.getSchemaMetaKey();
6457
+ if (!scopedMetaKey)
6458
+ return;
6372
6459
  const baseUrl = ctx.baseUrl;
6373
6460
  const u = new URL(baseUrl);
6374
6461
  u.searchParams.set('path', ctx.path);
@@ -6376,8 +6463,20 @@ class PraxisDynamicForm {
6376
6463
  u.searchParams.set('schemaType', String(ctx.schemaType || 'response'));
6377
6464
  u.searchParams.set('includeInternalSchemas', String(!!ctx.includeInternalSchemas));
6378
6465
  // Load previously persisted meta (serverHash)
6379
- const persistedMeta = await firstValueFrom(this.asyncConfigStorage.loadConfig(metaKey)).catch(() => undefined);
6380
- const previousHash = persistedMeta?.serverHash || this.lastSchemaMeta?.serverHash;
6466
+ const scopedMeta = await this.loadStoredSchemaMeta(scopedMetaKey);
6467
+ const legacyScopeKey = this.getSchemaMetaKey();
6468
+ const legacyMeta = legacyScopeKey && legacyScopeKey !== scopedMetaKey
6469
+ ? await this.loadStoredSchemaMeta(legacyScopeKey)
6470
+ : undefined;
6471
+ const persistedMeta = this.isStoredSchemaMetaForContext(scopedMeta, currentSchemaId, schemaContext)
6472
+ ? scopedMeta
6473
+ : this.isStoredSchemaMetaForContext(legacyMeta, currentSchemaId, schemaContext)
6474
+ ? legacyMeta
6475
+ : undefined;
6476
+ const lastLocalHash = this.isSameSchemaContext(this.lastSchemaMeta?.context, schemaContext)
6477
+ ? this.lastSchemaMeta?.serverHash
6478
+ : undefined;
6479
+ const previousHash = persistedMeta?.serverHash || lastLocalHash;
6381
6480
  // If we have no prior hash and no base, skip verification (handled by initial fetch)
6382
6481
  if (!previousHash && (this.config?.sections?.length ?? 0) === 0) {
6383
6482
  return;
@@ -6397,14 +6496,22 @@ class PraxisDynamicForm {
6397
6496
  const res = await fetchWithETag({ url: u.toString(), schemaHash: previousHash, tenant, locale });
6398
6497
  const nowIso = new Date().toISOString();
6399
6498
  const metaToSave = { ...(persistedMeta || {}) };
6499
+ metaToSave.schemaId = currentSchemaId;
6500
+ metaToSave.schemaContext = schemaContext;
6400
6501
  metaToSave.lastVerifiedAt = nowIso;
6502
+ if (!metaToSave.serverHash && previousHash) {
6503
+ metaToSave.serverHash = previousHash;
6504
+ }
6401
6505
  if (res.status === 304) {
6402
- this.asyncConfigStorage.saveConfig(metaKey, metaToSave).subscribe();
6506
+ this.asyncConfigStorage.saveConfig(scopedMetaKey, metaToSave).subscribe();
6507
+ this.rememberSchemaMetaContext(currentSchemaId);
6403
6508
  // Mirror meta in memory (config.metadata) without persisting config
6404
6509
  try {
6405
6510
  const mem = { ...(this.config.metadata || {}) };
6406
6511
  if (metaToSave.serverHash)
6407
6512
  mem.serverHash = metaToSave.serverHash;
6513
+ mem.schemaId = metaToSave.schemaId;
6514
+ mem.schemaContext = metaToSave.schemaContext;
6408
6515
  mem.lastVerifiedAt = metaToSave.lastVerifiedAt;
6409
6516
  this.config = { ...this.config, metadata: mem };
6410
6517
  this.cdr.markForCheck();
@@ -6420,19 +6527,22 @@ class PraxisDynamicForm {
6420
6527
  // status === 200: server hash changed (or first verification without hash). Do not apply schema here.
6421
6528
  const newHash = res.schemaHash;
6422
6529
  metaToSave.serverHash = newHash;
6423
- this.asyncConfigStorage.saveConfig(metaKey, metaToSave).subscribe();
6530
+ this.asyncConfigStorage.saveConfig(scopedMetaKey, metaToSave).subscribe();
6531
+ this.rememberSchemaMetaContext(currentSchemaId);
6424
6532
  // Mirror meta in memory (config.metadata) without persisting config
6425
6533
  try {
6426
6534
  const mem = { ...(this.config.metadata || {}) };
6427
6535
  if (metaToSave.serverHash)
6428
6536
  mem.serverHash = metaToSave.serverHash;
6537
+ mem.schemaId = metaToSave.schemaId;
6538
+ mem.schemaContext = metaToSave.schemaContext;
6429
6539
  mem.lastVerifiedAt = metaToSave.lastVerifiedAt;
6430
6540
  this.config = { ...this.config, metadata: mem };
6431
6541
  this.cdr.markForCheck();
6432
6542
  }
6433
6543
  catch { }
6434
- // Only mark outdated when there was a previous base and we are in edit mode
6435
- const hadBase = (this.config?.sections?.length ?? 0) > 0 || !!previousHash;
6544
+ // Only mark outdated when there is a confirmed prior hash for this context.
6545
+ const hadBase = !!previousHash;
6436
6546
  this.schemaOutdated = !!(this.editModeEnabled && hadBase);
6437
6547
  this.schemaStatusChange.emit({ outdated: this.schemaOutdated, serverHash: newHash, lastVerifiedAt: metaToSave.lastVerifiedAt, formId: this.formId });
6438
6548
  // Load state for new hash
@@ -9019,6 +9129,15 @@ class PraxisDynamicForm {
9019
9129
  return this.schemaCache;
9020
9130
  }
9021
9131
  const ctx = this.buildSchemaContext();
9132
+ const schemaContext = {
9133
+ path: ctx.path,
9134
+ operation: ctx.operation,
9135
+ schemaType: ctx.schemaType,
9136
+ includeInternalSchemas: ctx.includeInternalSchemas,
9137
+ tenant: ctx.tenant,
9138
+ locale: ctx.locale,
9139
+ apiOrigin: ctx.apiOrigin,
9140
+ };
9022
9141
  const loadingCtx = this.buildLoadingContext('schema', 'Carregando configuração…', true);
9023
9142
  this.beginLoading(loadingCtx);
9024
9143
  this.emitLoadingState('schema', 'loading', 'Carregando schema do formulário...');
@@ -9052,8 +9171,8 @@ class PraxisDynamicForm {
9052
9171
  ? legacyResult.pipe(take(1))
9053
9172
  : legacyResult);
9054
9173
  try {
9055
- const schemaId = buildSchemaId(ctx);
9056
- this.lastSchemaMeta = { schemaId, serverHash: '', context: ctx };
9174
+ const schemaId = buildSchemaId(schemaContext);
9175
+ this.lastSchemaMeta = { schemaId, context: schemaContext };
9057
9176
  }
9058
9177
  catch { }
9059
9178
  this.schemaCache = legacyDefs;
@@ -9089,19 +9208,27 @@ class PraxisDynamicForm {
9089
9208
  }
9090
9209
  }
9091
9210
  catch { }
9092
- const schemaId = buildSchemaId(ctx);
9093
- this.lastSchemaMeta = { schemaId, serverHash: entry.schemaHash, context: ctx };
9211
+ const schemaId = buildSchemaId(schemaContext);
9212
+ this.lastSchemaMeta = { schemaId, serverHash: entry.schemaHash, context: schemaContext };
9094
9213
  this.schemaCache = defs;
9095
9214
  this.emitLoadingState('schema', 'success', 'Schema carregado.');
9096
9215
  // Persist meta for future ETag verifications
9097
9216
  try {
9098
- const metaKey = this.getSchemaMetaKey();
9217
+ const metaKey = this.getSchemaMetaKeyForSchemaId(schemaId) ||
9218
+ this.getSchemaMetaKeyForContext(schemaContext) ||
9219
+ this.getSchemaMetaKey();
9099
9220
  if (metaKey) {
9100
9221
  const nowIso = new Date().toISOString();
9101
9222
  this.asyncConfigStorage
9102
- .saveConfig(metaKey, { serverHash: entry.schemaHash, lastVerifiedAt: nowIso })
9223
+ .saveConfig(metaKey, {
9224
+ schemaId,
9225
+ schemaContext,
9226
+ serverHash: entry.schemaHash,
9227
+ lastVerifiedAt: nowIso,
9228
+ })
9103
9229
  .pipe(take(1))
9104
9230
  .subscribe({ error: () => { } });
9231
+ this.rememberSchemaMetaContext(schemaId);
9105
9232
  }
9106
9233
  }
9107
9234
  catch { }
@@ -15522,7 +15649,31 @@ class PraxisDynamicFormConfigEditor {
15522
15649
  }
15523
15650
  this.schemaPrefs = { ...this.schemaPrefs, ...allowed };
15524
15651
  }
15525
- this.serverMeta = (await firstValueFrom(this.storage.loadConfig(`form-schema-meta:${key}`)).catch(() => undefined)) || undefined;
15652
+ const schemaScopeKey = `form-schema-meta:${key}`;
15653
+ const schemaId = String(this.editedConfig?.metadata?.schemaId ||
15654
+ this.initialConfig?.metadata?.schemaId ||
15655
+ '').trim();
15656
+ const schemaMetaCandidates = [];
15657
+ if (schemaId) {
15658
+ schemaMetaCandidates.push(`${schemaScopeKey}:schema:${schemaId}`);
15659
+ }
15660
+ if (!schemaId) {
15661
+ const storedIds = await firstValueFrom(this.storage.loadConfig(`${schemaScopeKey}:index`)).catch(() => []);
15662
+ const ids = (Array.isArray(storedIds) ? storedIds : [])
15663
+ .map((item) => String(item || '').trim())
15664
+ .filter((item) => !!item);
15665
+ ids.forEach((id) => schemaMetaCandidates.push(`${schemaScopeKey}:schema:${id}`));
15666
+ }
15667
+ schemaMetaCandidates.push(schemaScopeKey); // legacy fallback
15668
+ let resolvedMeta;
15669
+ for (const candidate of Array.from(new Set(schemaMetaCandidates))) {
15670
+ const loaded = await firstValueFrom(this.storage.loadConfig(candidate)).catch(() => undefined);
15671
+ if (loaded && typeof loaded === 'object') {
15672
+ resolvedMeta = loaded;
15673
+ break;
15674
+ }
15675
+ }
15676
+ this.serverMeta = resolvedMeta || undefined;
15526
15677
  }
15527
15678
  catch { }
15528
15679
  this.cdr.detectChanges();