@praxisui/core 9.0.0-beta.72 → 9.0.0-beta.74

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.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { Component, InjectionToken, Injectable, inject, Inject, Optional, makeEnvironmentProviders, APP_INITIALIZER, signal, computed, DestroyRef, ENVIRONMENT_INITIALIZER, ErrorHandler, EventEmitter, Output, Input, ChangeDetectionStrategy, Directive, SecurityContext, ViewContainerRef, SimpleChange, ContentChild, HostBinding, HostListener, ViewChildren, ViewChild, Injector, input, output, effect } from '@angular/core';
3
3
  import * as i1 from '@angular/common/http';
4
4
  import { HttpHeaders, HttpClient, HttpParams, HttpResponse, HttpContextToken, HTTP_INTERCEPTORS, withInterceptors } from '@angular/common/http';
5
- import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, map as map$1, switchMap as switchMap$1 } from 'rxjs';
5
+ import { of, defer, throwError, from, EMPTY, BehaviorSubject, firstValueFrom, Subject, map as map$1, switchMap as switchMap$1, catchError as catchError$1 } from 'rxjs';
6
6
  import { switchMap, take, map, catchError, concatMap, tap, shareReplay, takeUntil, toArray, finalize } from 'rxjs/operators';
7
7
  import * as i1$2 from '@angular/common';
8
8
  import { Location, DOCUMENT, CommonModule } from '@angular/common';
@@ -297,10 +297,10 @@ function buildSchemaId(params) {
297
297
  const schemaType = (params.schemaType || 'response').toLowerCase();
298
298
  const internal = params.includeInternalSchemas ? 'true' : 'false';
299
299
  const parts = [path, operation, schemaType, `internal:${internal}`];
300
- if (params.tenant)
301
- parts.push(`tenant:${params.tenant}`);
302
- if (params.locale)
303
- parts.push(`locale:${params.locale}`);
300
+ if (params.idField)
301
+ parts.push(`idField:${params.idField}`);
302
+ if (params.readOnly !== undefined)
303
+ parts.push(`readOnly:${params.readOnly}`);
304
304
  if (params.apiOrigin)
305
305
  parts.push(`origin:${params.apiOrigin}`);
306
306
  return parts.join('|');
@@ -1722,6 +1722,17 @@ const OPTION_SOURCE_SEARCH_MODES = new Set([
1722
1722
  'contains',
1723
1723
  'exact',
1724
1724
  ]);
1725
+ const OPTION_SOURCE_SELECTED_RELOAD_POLICIES = new Set([
1726
+ 'required',
1727
+ 'supported',
1728
+ 'unsupported-with-waiver',
1729
+ 'not-applicable',
1730
+ ]);
1731
+ const OPTION_SOURCE_INVALID_SORT_POLICIES = new Set([
1732
+ 'reject',
1733
+ 'ignore',
1734
+ 'unsupported',
1735
+ ]);
1725
1736
  const LOOKUP_OPEN_DETAIL_MODES = new Set([
1726
1737
  'samePage',
1727
1738
  'newTab',
@@ -2481,6 +2492,18 @@ class SchemaNormalizerService {
2481
2492
  if (value.includeIds !== undefined) {
2482
2493
  optionSource.includeIds = this.parseBoolean(value.includeIds);
2483
2494
  }
2495
+ if (value.selectedReloadPolicy !== undefined) {
2496
+ const selectedReloadPolicy = this.parseOptionSourceSelectedReloadPolicy(value.selectedReloadPolicy);
2497
+ if (selectedReloadPolicy) {
2498
+ optionSource.selectedReloadPolicy = selectedReloadPolicy;
2499
+ }
2500
+ }
2501
+ if (value.invalidSortPolicy !== undefined) {
2502
+ const invalidSortPolicy = this.parseOptionSourceInvalidSortPolicy(value.invalidSortPolicy);
2503
+ if (invalidSortPolicy) {
2504
+ optionSource.invalidSortPolicy = invalidSortPolicy;
2505
+ }
2506
+ }
2484
2507
  return optionSource;
2485
2508
  }
2486
2509
  parseOptionSourceType(value) {
@@ -2491,6 +2514,14 @@ class SchemaNormalizerService {
2491
2514
  const normalized = String(value ?? '').trim();
2492
2515
  return OPTION_SOURCE_SEARCH_MODES.has(normalized) ? normalized : undefined;
2493
2516
  }
2517
+ parseOptionSourceSelectedReloadPolicy(value) {
2518
+ const normalized = String(value ?? '').trim();
2519
+ return OPTION_SOURCE_SELECTED_RELOAD_POLICIES.has(normalized) ? normalized : undefined;
2520
+ }
2521
+ parseOptionSourceInvalidSortPolicy(value) {
2522
+ const normalized = String(value ?? '').trim();
2523
+ return OPTION_SOURCE_INVALID_SORT_POLICIES.has(normalized) ? normalized : undefined;
2524
+ }
2494
2525
  parseLookupOpenDetailMode(value) {
2495
2526
  const normalized = String(value ?? '').trim();
2496
2527
  return LOOKUP_OPEN_DETAIL_MODES.has(normalized) ? normalized : undefined;
@@ -3418,12 +3449,12 @@ class ApiConfigStorage {
3418
3449
  shouldLogSaveError(key, err) {
3419
3450
  if (err?.status === 404)
3420
3451
  return false;
3421
- return this.shouldPropagateSaveError(key);
3452
+ return this.shouldPropagateSaveError(key, err);
3422
3453
  }
3423
3454
  shouldLogClearError(key, err) {
3424
3455
  if (err?.status === 404)
3425
3456
  return false;
3426
- return this.shouldPropagateClearError(key);
3457
+ return this.shouldPropagateClearError(key, err);
3427
3458
  }
3428
3459
  loadConfig(key) {
3429
3460
  const existingProbe = ApiConfigStorage.loadAvailabilityProbes.get(this.baseUrl);
@@ -3437,7 +3468,7 @@ class ApiConfigStorage {
3437
3468
  const etag = cached?.etag;
3438
3469
  const { type, id } = this.resolveKey(key);
3439
3470
  const url = `${this.baseUrl}`;
3440
- const params = this.buildParams(type, id);
3471
+ const params = this.buildParams(type, id, cached?.scope);
3441
3472
  const headers = this.buildHeaders(etag ? { 'If-None-Match': this.formatEtag(etag) } : {});
3442
3473
  let releaseProbe = null;
3443
3474
  if (establishAvailability) {
@@ -3460,7 +3491,11 @@ class ApiConfigStorage {
3460
3491
  const nextEtag = this.stripQuotes(resp.headers.get('ETag'));
3461
3492
  const body = resp.body;
3462
3493
  if (nextEtag) {
3463
- this.cache.set(key, { etag: nextEtag, payload: body?.payload });
3494
+ this.cache.set(key, {
3495
+ etag: nextEtag,
3496
+ payload: body?.payload,
3497
+ scope: this.resolveResponseScope(body?.scope),
3498
+ });
3464
3499
  }
3465
3500
  releaseProbe?.();
3466
3501
  return body?.payload ?? null;
@@ -3488,7 +3523,11 @@ class ApiConfigStorage {
3488
3523
  const nextEtag = this.stripQuotes(resp.headers.get('ETag'));
3489
3524
  const body = resp.body;
3490
3525
  if (nextEtag) {
3491
- this.cache.set(key, { etag: nextEtag, payload: body?.payload });
3526
+ this.cache.set(key, {
3527
+ etag: nextEtag,
3528
+ payload: body?.payload,
3529
+ scope: this.resolveResponseScope(body?.scope),
3530
+ });
3492
3531
  }
3493
3532
  return body?.payload ?? null;
3494
3533
  }), catchError((err) => {
@@ -3510,36 +3549,47 @@ class ApiConfigStorage {
3510
3549
  saveConfig(key, config) {
3511
3550
  const { type, id } = this.resolveKey(key);
3512
3551
  const url = `${this.baseUrl}`;
3513
- const params = this.buildParams(type, id);
3514
3552
  const cached = this.cache.get(key);
3553
+ const params = this.buildParams(type, id, cached?.scope);
3515
3554
  const headers = this.buildHeaders(cached?.etag ? { 'If-Match': this.formatEtag(cached.etag) } : undefined);
3516
3555
  return this.http
3517
3556
  .put(url, { payload: config }, { observe: 'response', headers, params })
3518
3557
  .pipe(map((resp) => {
3519
3558
  const nextEtag = this.stripQuotes(resp.headers.get('ETag'));
3520
3559
  const payload = resp.body?.payload ?? config;
3521
- this.cache.set(key, { etag: nextEtag, payload });
3560
+ this.cache.set(key, {
3561
+ etag: nextEtag,
3562
+ payload,
3563
+ scope: this.resolveResponseScope(resp.body?.scope) ?? cached?.scope,
3564
+ });
3522
3565
  }), catchError((err) => {
3523
3566
  if (this.shouldLogSaveError(key, err)) {
3524
3567
  console.warn('[ApiConfigStorage] save error', err);
3525
3568
  }
3526
- return this.shouldPropagateSaveError(key)
3569
+ return this.shouldPropagateSaveError(key, err)
3527
3570
  ? throwError(() => err)
3528
3571
  // Soft-fail path: complete without next so runtime can treat as
3529
3572
  // "not acknowledged" instead of a hard error.
3530
3573
  : EMPTY;
3531
3574
  }));
3532
3575
  }
3533
- shouldPropagateSaveError(key) {
3576
+ shouldPropagateSaveError(key, err) {
3577
+ if (this.isConcurrencyConflict(err))
3578
+ return true;
3534
3579
  if (this.opts?.errorPolicy === 'fail')
3535
3580
  return true;
3536
3581
  return this.isCriticalPersistenceKey(key);
3537
3582
  }
3538
- shouldPropagateClearError(key) {
3583
+ shouldPropagateClearError(key, err) {
3584
+ if (this.isConcurrencyConflict(err))
3585
+ return true;
3539
3586
  if (this.opts?.errorPolicy === 'fail')
3540
3587
  return true;
3541
3588
  return this.isCriticalPersistenceKey(key);
3542
3589
  }
3590
+ isConcurrencyConflict(err) {
3591
+ return err?.status === 409 || err?.status === 412;
3592
+ }
3543
3593
  shouldPropagateLoadError(key, _err) {
3544
3594
  if (this.opts?.errorPolicy !== 'fail')
3545
3595
  return false;
@@ -3552,8 +3602,8 @@ class ApiConfigStorage {
3552
3602
  clearConfig(key) {
3553
3603
  const { type, id } = this.resolveKey(key);
3554
3604
  const url = `${this.baseUrl}`;
3555
- const params = this.buildParams(type, id);
3556
3605
  const cached = this.cache.get(key);
3606
+ const params = this.buildParams(type, id, cached?.scope);
3557
3607
  const headers = this.buildHeaders(cached?.etag ? { 'If-Match': this.formatEtag(cached.etag) } : undefined);
3558
3608
  return this.http
3559
3609
  .delete(url, { observe: 'response', headers, params })
@@ -3563,7 +3613,7 @@ class ApiConfigStorage {
3563
3613
  if (this.shouldLogClearError(key, err)) {
3564
3614
  console.warn('[ApiConfigStorage] clear error', err);
3565
3615
  }
3566
- return this.shouldPropagateClearError(key)
3616
+ return this.shouldPropagateClearError(key, err)
3567
3617
  ? throwError(() => err)
3568
3618
  // Soft-fail path: complete without next so runtime can treat as
3569
3619
  // "clear not acknowledged" instead of a hard error.
@@ -3588,18 +3638,23 @@ class ApiConfigStorage {
3588
3638
  }
3589
3639
  return new HttpHeaders(merged);
3590
3640
  }
3591
- buildParams(componentType, componentId) {
3641
+ buildParams(componentType, componentId, resolvedScope) {
3592
3642
  const params = {
3593
3643
  componentType,
3594
3644
  componentId,
3595
3645
  };
3596
- if (this.scope) {
3597
- params['scope'] = this.scope;
3646
+ const effectiveScope = this.scope || resolvedScope;
3647
+ if (effectiveScope) {
3648
+ params['scope'] = effectiveScope;
3598
3649
  }
3599
3650
  return new HttpParams({
3600
3651
  fromObject: params,
3601
3652
  });
3602
3653
  }
3654
+ resolveResponseScope(value) {
3655
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
3656
+ return normalized === 'user' || normalized === 'tenant' ? normalized : undefined;
3657
+ }
3603
3658
  resolveKey(key) {
3604
3659
  const type = this.componentTypeResolver(key);
3605
3660
  return { type, id: key };
@@ -4232,6 +4287,12 @@ class GenericCrudService {
4232
4287
  .set('path', path)
4233
4288
  .set('operation', operation)
4234
4289
  .set('schemaType', schemaType);
4290
+ if (options?.idField) {
4291
+ httpParams = httpParams.set('idField', options.idField);
4292
+ }
4293
+ if (options?.readOnly !== undefined) {
4294
+ httpParams = httpParams.set('readOnly', String(options.readOnly));
4295
+ }
4235
4296
  // Resolve schemas/filtered honoring relative base (dev proxy) when applicable
4236
4297
  let filteredUrl = '/schemas/filtered';
4237
4298
  let apiOrigin = '';
@@ -4243,7 +4304,17 @@ class GenericCrudService {
4243
4304
  }
4244
4305
  catch { }
4245
4306
  // Build cache key including the API origin (not the app origin)
4246
- const schemaId = buildSchemaId({ path, operation, schemaType, includeInternalSchemas: false, tenant, locale, apiOrigin });
4307
+ const schemaId = buildSchemaId({
4308
+ path,
4309
+ operation,
4310
+ schemaType,
4311
+ includeInternalSchemas: false,
4312
+ idField: options?.idField,
4313
+ readOnly: options?.readOnly,
4314
+ tenant,
4315
+ locale,
4316
+ apiOrigin,
4317
+ });
4247
4318
  const headersBase = composeHeadersWithVersion(entry);
4248
4319
  const shouldUseDirectSchemaEndpoint = GenericCrudService.unavailableFilteredSchemaUrls.has(filteredUrl);
4249
4320
  if (shouldUseDirectSchemaEndpoint) {
@@ -4258,7 +4329,13 @@ class GenericCrudService {
4258
4329
  headers = headers.set('Accept-Language', locale);
4259
4330
  if (tenant)
4260
4331
  headers = headers.set('X-Tenant', tenant);
4261
- debugCrudService('[CRUD:Service] getSchema (filtered fallback):request', { filteredUrl, path, hasCached: !!cached });
4332
+ debugCrudService('[CRUD:Service] getSchema (filtered fallback):request', {
4333
+ filteredUrl,
4334
+ path,
4335
+ idField: options?.idField,
4336
+ readOnly: options?.readOnly,
4337
+ hasCached: !!cached,
4338
+ });
4262
4339
  return this.http.get(filteredUrl, {
4263
4340
  params: httpParams,
4264
4341
  headers,
@@ -4602,6 +4679,12 @@ class GenericCrudService {
4602
4679
  if (params.includeInternalSchemas !== undefined) {
4603
4680
  httpParams = httpParams.set('includeInternalSchemas', String(params.includeInternalSchemas));
4604
4681
  }
4682
+ if (params.idField) {
4683
+ httpParams = httpParams.set('idField', params.idField);
4684
+ }
4685
+ if (params.readOnly !== undefined) {
4686
+ httpParams = httpParams.set('readOnly', String(params.readOnly));
4687
+ }
4605
4688
  const entry = this.resolveEndpointEntry(params.endpointKey);
4606
4689
  let url = '/schemas/filtered';
4607
4690
  let origin = '';
@@ -4617,7 +4700,7 @@ class GenericCrudService {
4617
4700
  const includeInternal = !!params.includeInternalSchemas;
4618
4701
  const tenant = this.globalConfig.getTenant();
4619
4702
  const locale = (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : undefined;
4620
- const schemaId = buildSchemaId({ path, operation, schemaType: schemaTypeParam, includeInternalSchemas: includeInternal, tenant, locale, apiOrigin: origin });
4703
+ const schemaId = buildSchemaId({ path, operation, schemaType: schemaTypeParam, includeInternalSchemas: includeInternal, idField: params.idField, readOnly: params.readOnly, tenant, locale, apiOrigin: origin });
4621
4704
  // Prepare headers with If-None-Match when cached
4622
4705
  const headersBase = composeHeadersWithVersion(entry);
4623
4706
  return from(this.ensureSchemaCacheReady()).pipe(concatMap(() => from(this._schemaCache.get(schemaId)).pipe(concatMap((cached) => {
@@ -4632,7 +4715,7 @@ class GenericCrudService {
4632
4715
  headers = headers.set('Accept-Language', locale);
4633
4716
  if (tenant)
4634
4717
  headers = headers.set('X-Tenant', tenant);
4635
- debugCrudService('[CRUD:Service] getFilteredSchema:request', { url, params: { path, operation, schemaType: schemaTypeParam, includeInternalSchemas: includeInternal }, hasCached: !!cached });
4718
+ debugCrudService('[CRUD:Service] getFilteredSchema:request', { url, params: { path, operation, schemaType: schemaTypeParam, includeInternalSchemas: includeInternal, idField: params.idField, readOnly: params.readOnly }, hasCached: !!cached });
4636
4719
  return this.http.get(url, {
4637
4720
  params: httpParams,
4638
4721
  headers,
@@ -5222,9 +5305,7 @@ class GenericCrudService {
5222
5305
  const includeInternal = !!options?.includeInternalSchemas;
5223
5306
  const cacheEnabled = options?.cache !== false;
5224
5307
  const endpointKey = options?.endpointKey ?? this.currentEndpointKey;
5225
- const tenant = this.globalConfig.getTenant() || '-';
5226
- const locale = (typeof navigator !== 'undefined' && navigator.language) ? navigator.language : '-';
5227
- const cacheKey = `${endpointKey}|${path}|post|request|${includeInternal ? '1' : '0'}|tenant:${tenant}|locale:${locale}`;
5308
+ const cacheKey = `${endpointKey}|${path}|post|request|${includeInternal ? '1' : '0'}`;
5228
5309
  debugCrudService('[CRUD:Service] getFilterSchema:derived', {
5229
5310
  path, includeInternal, cacheEnabled, cacheKey,
5230
5311
  });
@@ -8774,10 +8855,16 @@ class PraxisJsonLogicService {
8774
8855
  }
8775
8856
  evaluate(expression, data, options) {
8776
8857
  const context = this.createContext(data, options);
8777
- return this.evaluateValue(expression, context);
8858
+ assertJsonLogicLimits(expression, context.limits ?? DEFAULT_JSON_LOGIC_LIMITS);
8859
+ const result = this.evaluateValue(expression, context);
8860
+ if (result !== undefined) {
8861
+ assertJsonLogicLimits(result, context.limits ?? DEFAULT_JSON_LOGIC_LIMITS, 'result');
8862
+ }
8863
+ return result;
8778
8864
  }
8779
8865
  evaluateResult(expression, data, options) {
8780
- const value = this.evaluate(expression, data, options);
8866
+ const evaluated = this.evaluate(expression, data, options);
8867
+ const value = evaluated === undefined ? null : evaluated;
8781
8868
  return {
8782
8869
  value,
8783
8870
  truthy: this.isTruthy(value),
@@ -8793,6 +8880,17 @@ class PraxisJsonLogicService {
8793
8880
  validateResult(expression, options) {
8794
8881
  const issues = [];
8795
8882
  const context = this.createContext({}, options);
8883
+ try {
8884
+ assertJsonLogicLimits(expression, context.limits ?? DEFAULT_JSON_LOGIC_LIMITS);
8885
+ }
8886
+ catch (error) {
8887
+ issues.push({
8888
+ code: 'RULE_LIMIT_EXCEEDED',
8889
+ message: error instanceof Error ? error.message : 'JSON Logic expression exceeds configured limits.',
8890
+ path: '$',
8891
+ });
8892
+ return { valid: false, issues };
8893
+ }
8796
8894
  if (options?.requireExpressionObject && !this.isTopLevelExpressionObject(expression)) {
8797
8895
  issues.push({
8798
8896
  code: 'RULE_SHAPE_INVALID',
@@ -8856,6 +8954,7 @@ class PraxisJsonLogicService {
8856
8954
  allowImplicitRoot: options?.allowImplicitRoot,
8857
8955
  nowUtc: options?.nowUtc,
8858
8956
  userTimeZone: options?.userTimeZone,
8957
+ limits: { ...DEFAULT_JSON_LOGIC_LIMITS, ...options?.limits },
8859
8958
  };
8860
8959
  }
8861
8960
  evaluateValue(value, context) {
@@ -9010,13 +9109,13 @@ class PraxisJsonLogicService {
9010
9109
  const [left, right] = this.evaluateArgs(operator, rawArgs, context, 2, 2);
9011
9110
  switch (operator) {
9012
9111
  case '==':
9013
- return left == right;
9112
+ return canonicalLooseEquals(left, right);
9014
9113
  case '===':
9015
- return left === right;
9114
+ return canonicalStrictEquals(left, right);
9016
9115
  case '!=':
9017
- return left != right;
9116
+ return !canonicalLooseEquals(left, right);
9018
9117
  case '!==':
9019
- return left !== right;
9118
+ return !canonicalStrictEquals(left, right);
9020
9119
  case '>':
9021
9120
  this.assertComparable(operator, left, right);
9022
9121
  return left > right;
@@ -9037,7 +9136,7 @@ class PraxisJsonLogicService {
9037
9136
  return haystack.includes(needle);
9038
9137
  }
9039
9138
  if (Array.isArray(haystack)) {
9040
- return haystack.some((item) => item === needle);
9139
+ return haystack.some((item) => canonicalStrictEquals(item, needle));
9041
9140
  }
9042
9141
  throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', '`in` requires a string haystack or an array haystack.');
9043
9142
  }
@@ -9055,10 +9154,16 @@ class PraxisJsonLogicService {
9055
9154
  return values.reduce((product, item) => product * item, 1);
9056
9155
  case '/': {
9057
9156
  this.requireArgs('/', values, 2);
9157
+ if (values.slice(1).some((item) => item === 0)) {
9158
+ throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', 'Operator / cannot divide by zero.');
9159
+ }
9058
9160
  return values.slice(1).reduce((acc, item) => acc / item, values[0]);
9059
9161
  }
9060
9162
  case '%': {
9061
9163
  this.requireArgs('%', values, 2, 2);
9164
+ if (values[1] === 0) {
9165
+ throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', 'Operator % cannot divide by zero.');
9166
+ }
9062
9167
  return values[0] % values[1];
9063
9168
  }
9064
9169
  case 'min':
@@ -9069,7 +9174,12 @@ class PraxisJsonLogicService {
9069
9174
  }
9070
9175
  evaluateCat(rawArgs, context) {
9071
9176
  const args = this.evaluateArgs('cat', rawArgs, context, 1);
9072
- return args.map((value) => value == null ? '' : String(value)).join('');
9177
+ const parts = args.map((value) => value == null ? '' : String(value));
9178
+ const length = parts.reduce((total, part) => total + part.length, 0);
9179
+ if (length > (context.limits ?? DEFAULT_JSON_LOGIC_LIMITS).maxStringLength) {
9180
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic intermediate string exceeds the length limit.');
9181
+ }
9182
+ return parts.join('');
9073
9183
  }
9074
9184
  evaluateSubstr(rawArgs, context) {
9075
9185
  const args = this.evaluateArgs('substr', rawArgs, context, 2, 3);
@@ -9088,7 +9198,12 @@ class PraxisJsonLogicService {
9088
9198
  evaluateMerge(rawArgs, context) {
9089
9199
  const args = this.toArgumentArray(rawArgs).map((arg) => this.evaluateValue(arg, context));
9090
9200
  const result = [];
9201
+ const maxItems = (context.limits ?? DEFAULT_JSON_LOGIC_LIMITS).maxArrayItems;
9091
9202
  for (const value of args) {
9203
+ const added = Array.isArray(value) ? value.length : 1;
9204
+ if (result.length + added > maxItems) {
9205
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic intermediate array exceeds the item limit.');
9206
+ }
9092
9207
  if (Array.isArray(value)) {
9093
9208
  result.push(...value);
9094
9209
  }
@@ -9102,12 +9217,14 @@ class PraxisJsonLogicService {
9102
9217
  const [sourceExpression, mapExpression] = this.requireHigherOrderArgs('map', rawArgs, 2, 2);
9103
9218
  const source = this.evaluateValue(sourceExpression, context);
9104
9219
  this.assertArrayOperand('map', source);
9220
+ this.assertIntermediateArrayLimit(source, context);
9105
9221
  return source.map((item) => this.evaluateValue(mapExpression, this.createChildContext(context, item)));
9106
9222
  }
9107
9223
  evaluateFilter(rawArgs, context) {
9108
9224
  const [sourceExpression, predicateExpression] = this.requireHigherOrderArgs('filter', rawArgs, 2, 2);
9109
9225
  const source = this.evaluateValue(sourceExpression, context);
9110
9226
  this.assertArrayOperand('filter', source);
9227
+ this.assertIntermediateArrayLimit(source, context);
9111
9228
  return source.filter((item) => this.isTruthy(this.evaluateValue(predicateExpression, this.createChildContext(context, item))));
9112
9229
  }
9113
9230
  evaluateReduce(rawArgs, context) {
@@ -9155,6 +9272,11 @@ class PraxisJsonLogicService {
9155
9272
  throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', `Operator ${operator} requires an array as the first argument.`);
9156
9273
  }
9157
9274
  }
9275
+ assertIntermediateArrayLimit(value, context) {
9276
+ if (value.length > (context.limits ?? DEFAULT_JSON_LOGIC_LIMITS).maxArrayItems) {
9277
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic intermediate array exceeds the item limit.');
9278
+ }
9279
+ }
9158
9280
  createChildContext(context, data) {
9159
9281
  return {
9160
9282
  ...context,
@@ -9274,6 +9396,19 @@ class PraxisJsonLogicService {
9274
9396
  if (arity) {
9275
9397
  this.validateArity(operator, args.length, arity.min, arity.max, path, issues);
9276
9398
  }
9399
+ if (operator === 'matches' && typeof args[1] === 'string') {
9400
+ try {
9401
+ compileSafeRegex(args[1]);
9402
+ }
9403
+ catch (error) {
9404
+ issues.push({
9405
+ code: error instanceof PraxisJsonLogicError ? error.code : 'RULE_REGEX_INVALID',
9406
+ message: error instanceof Error ? error.message : 'Regular expression is invalid.',
9407
+ path,
9408
+ operator,
9409
+ });
9410
+ }
9411
+ }
9277
9412
  args.forEach((arg, index) => this.validateValue(arg, context, `${path}.${operator}[${index}]`, issues));
9278
9413
  }
9279
9414
  validateVar(rawArgs, context, path, issues) {
@@ -9309,11 +9444,23 @@ class PraxisJsonLogicService {
9309
9444
  if (!rawPath.length) {
9310
9445
  return;
9311
9446
  }
9447
+ let firstToken;
9448
+ try {
9449
+ firstToken = splitPath(rawPath)[0];
9450
+ }
9451
+ catch (error) {
9452
+ issues.push({
9453
+ code: error instanceof PraxisJsonLogicError ? error.code : 'RULE_PATH_INVALID',
9454
+ message: error instanceof Error ? error.message : `Invalid Praxis JSON Logic path: "${rawPath}".`,
9455
+ path,
9456
+ operator: 'var',
9457
+ });
9458
+ return;
9459
+ }
9312
9460
  const availableRoots = context.availableRoots ?? [];
9313
9461
  if (!availableRoots.length) {
9314
9462
  return;
9315
9463
  }
9316
- const firstToken = splitPath(rawPath)[0];
9317
9464
  if (!firstToken) {
9318
9465
  return;
9319
9466
  }
@@ -9358,61 +9505,12 @@ class PraxisJsonLogicService {
9358
9505
  }
9359
9506
  }
9360
9507
  getOperatorArity(operator) {
9361
- switch (operator) {
9362
- case 'and':
9363
- case 'or':
9364
- return { min: 1 };
9365
- case '!':
9366
- case '!!':
9367
- return { min: 1, max: 1 };
9368
- case 'if':
9369
- return { min: 2 };
9370
- case 'in':
9371
- case '==':
9372
- case '===':
9373
- case '!=':
9374
- case '!==':
9375
- case '>':
9376
- case '>=':
9377
- case '<':
9378
- case '<=':
9379
- return { min: 2, max: 2 };
9380
- case 'cat':
9381
- return { min: 1 };
9382
- case 'substr':
9383
- return { min: 2, max: 3 };
9384
- case 'merge':
9385
- return { min: 1 };
9386
- case 'map':
9387
- case 'filter':
9388
- case 'all':
9389
- case 'some':
9390
- case 'none':
9391
- return { min: 2, max: 2 };
9392
- case 'reduce':
9393
- return { min: 2, max: 3 };
9394
- case '+':
9395
- case '*':
9396
- case 'min':
9397
- case 'max':
9398
- return { min: 1 };
9399
- case '-':
9400
- return { min: 1 };
9401
- case '/':
9402
- return { min: 2 };
9403
- case '%':
9404
- return { min: 2, max: 2 };
9405
- default: {
9406
- const custom = this.customOperators.get(operator);
9407
- if (!custom) {
9408
- return undefined;
9409
- }
9410
- return {
9411
- min: custom.minArgs ?? 0,
9412
- max: custom.maxArgs,
9413
- };
9414
- }
9508
+ const native = BUILTIN_JSON_LOGIC_OPERATOR_DESCRIPTORS.find((descriptor) => descriptor.operator === operator);
9509
+ if (native) {
9510
+ return { min: native.minArgs ?? 0, max: native.maxArgs };
9415
9511
  }
9512
+ const custom = this.customOperators.get(operator);
9513
+ return custom ? { min: custom.minArgs ?? 0, max: custom.maxArgs } : undefined;
9416
9514
  }
9417
9515
  isSupportedOperator(operator) {
9418
9516
  return BUILTIN_JSON_LOGIC_OPERATORS.includes(operator)
@@ -9450,7 +9548,7 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9450
9548
  return container.includes(candidate);
9451
9549
  }
9452
9550
  if (Array.isArray(container)) {
9453
- return container.some((item) => item === candidate);
9551
+ return container.some((item) => canonicalStrictEquals(item, candidate));
9454
9552
  }
9455
9553
  throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', '`contains` requires a string or array as the first argument.');
9456
9554
  },
@@ -9507,9 +9605,11 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9507
9605
  throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', '`matches` requires string arguments.');
9508
9606
  }
9509
9607
  try {
9510
- return new RegExp(pattern).test(value);
9608
+ return compileSafeRegex(pattern).test(value);
9511
9609
  }
9512
- catch {
9610
+ catch (error) {
9611
+ if (error instanceof PraxisJsonLogicError)
9612
+ throw error;
9513
9613
  throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', '`matches` received an invalid regular expression.');
9514
9614
  }
9515
9615
  },
@@ -9642,7 +9742,7 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9642
9742
  maxArgs: 0,
9643
9743
  returnType: 'number',
9644
9744
  purity: 'contextual',
9645
- evaluate: () => Date.now(),
9745
+ evaluate: (_args, context) => requiredNow(context).getTime(),
9646
9746
  },
9647
9747
  {
9648
9748
  operator: 'date',
@@ -9653,10 +9753,7 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9653
9753
  purity: 'pure',
9654
9754
  evaluate: (args, _context, helpers) => {
9655
9755
  const [value] = helpers.requireArgs('date', args, 1, 1);
9656
- const parsed = parseTemporalValue(value);
9657
- if (!parsed) {
9658
- return null;
9659
- }
9756
+ const parsed = requireTemporalValue(value);
9660
9757
  if (parsed.kind === 'date-only') {
9661
9758
  return Date.parse(`${parsed.ymd}T00:00:00.000Z`);
9662
9759
  }
@@ -9672,11 +9769,8 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9672
9769
  purity: 'contextual',
9673
9770
  evaluate: (args, context, helpers) => {
9674
9771
  const [value] = helpers.requireArgs('yearsSince', args, 1, 1);
9675
- const parsed = parseTemporalValue(value);
9676
- if (!parsed) {
9677
- return null;
9678
- }
9679
- const now = context.nowUtc ? new Date(context.nowUtc) : new Date();
9772
+ const parsed = requireTemporalValue(value);
9773
+ const now = requiredNow(context);
9680
9774
  return Math.floor((now.getTime() - temporalValueToInstant(parsed).getTime()) / 31557600000);
9681
9775
  },
9682
9776
  },
@@ -9689,11 +9783,8 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9689
9783
  purity: 'contextual',
9690
9784
  evaluate: (args, context, helpers) => {
9691
9785
  const [value] = helpers.requireArgs('monthsSince', args, 1, 1);
9692
- const parsed = parseTemporalValue(value);
9693
- if (!parsed) {
9694
- return null;
9695
- }
9696
- const now = context.nowUtc ? new Date(context.nowUtc) : new Date();
9786
+ const parsed = requireTemporalValue(value);
9787
+ const now = requiredNow(context);
9697
9788
  return Math.floor((now.getTime() - temporalValueToInstant(parsed).getTime()) / 2629800000);
9698
9789
  },
9699
9790
  },
@@ -9706,11 +9797,8 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9706
9797
  purity: 'contextual',
9707
9798
  evaluate: (args, context, helpers) => {
9708
9799
  const [value] = helpers.requireArgs('daysSince', args, 1, 1);
9709
- const parsed = parseTemporalValue(value);
9710
- if (!parsed) {
9711
- return null;
9712
- }
9713
- const now = context.nowUtc ? new Date(context.nowUtc) : new Date();
9800
+ const parsed = requireTemporalValue(value);
9801
+ const now = requiredNow(context);
9714
9802
  return Math.floor((now.getTime() - temporalValueToInstant(parsed).getTime()) / 86400000);
9715
9803
  },
9716
9804
  },
@@ -9784,10 +9872,7 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9784
9872
  purity: 'contextual',
9785
9873
  evaluate: (args, context, helpers) => {
9786
9874
  const [candidate] = helpers.requireArgs('isToday', args, 1, 1);
9787
- const parsed = parseTemporalValue(candidate);
9788
- if (!parsed) {
9789
- return false;
9790
- }
9875
+ const parsed = requireTemporalValue(candidate);
9791
9876
  return temporalValueToYmd(parsed, context) === currentYmd(context);
9792
9877
  },
9793
9878
  },
@@ -9800,11 +9885,11 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9800
9885
  purity: 'contextual',
9801
9886
  evaluate: (args, context, helpers) => {
9802
9887
  const [candidate, rawAmount, rawUnit] = helpers.requireArgs('inLast', args, 3, 3);
9803
- const parsed = parseTemporalValue(candidate);
9888
+ const parsed = requireTemporalValue(candidate);
9804
9889
  const amount = Math.abs(Number(rawAmount));
9805
9890
  const unit = String(rawUnit || '').toLowerCase();
9806
- if (!parsed || !Number.isFinite(amount) || !unit) {
9807
- return false;
9891
+ if (!Number.isFinite(amount) || !/^(days?|weeks?|months?)$/.test(unit)) {
9892
+ throw temporalError('inLast requires a finite amount and one of days, weeks or months');
9808
9893
  }
9809
9894
  const today = currentYmd(context);
9810
9895
  const floor = addYmdByUnit(today, -amount, unit);
@@ -9821,10 +9906,9 @@ const DEFAULT_JSON_LOGIC_OPERATORS = [
9821
9906
  purity: 'contextual',
9822
9907
  evaluate: (args, context, helpers) => {
9823
9908
  const [candidate, rawDays] = helpers.requireArgs('weekdayIn', args, 2, 2);
9824
- const parsed = parseTemporalValue(candidate);
9825
- if (!parsed || !Array.isArray(rawDays)) {
9826
- return false;
9827
- }
9909
+ const parsed = requireTemporalValue(candidate);
9910
+ if (!Array.isArray(rawDays))
9911
+ throw new PraxisJsonLogicError('RULE_ARGUMENT_TYPE_INVALID', '`weekdayIn` requires an array of weekdays.');
9828
9912
  const normalized = rawDays
9829
9913
  .map((item) => Number(item))
9830
9914
  .filter(Number.isFinite)
@@ -9848,39 +9932,6 @@ const ALL_RULE_CONTEXT_ROOTS = [
9848
9932
  'state',
9849
9933
  'context',
9850
9934
  ];
9851
- const BUILTIN_JSON_LOGIC_OPERATORS = [
9852
- 'var',
9853
- '==',
9854
- '===',
9855
- '!=',
9856
- '!==',
9857
- '>',
9858
- '>=',
9859
- '<',
9860
- '<=',
9861
- '!',
9862
- '!!',
9863
- 'and',
9864
- 'or',
9865
- 'if',
9866
- 'in',
9867
- 'cat',
9868
- 'substr',
9869
- 'merge',
9870
- 'map',
9871
- 'filter',
9872
- 'reduce',
9873
- 'all',
9874
- 'some',
9875
- 'none',
9876
- '+',
9877
- '-',
9878
- '*',
9879
- '/',
9880
- '%',
9881
- 'min',
9882
- 'max',
9883
- ];
9884
9935
  const BUILTIN_JSON_LOGIC_OPERATOR_DESCRIPTORS = [
9885
9936
  { operator: 'var', source: 'native', minArgs: 1, maxArgs: 2, returnType: 'unknown', purity: 'pure' },
9886
9937
  { operator: '==', source: 'native', minArgs: 2, maxArgs: 2, returnType: 'boolean', purity: 'pure' },
@@ -9914,6 +9965,8 @@ const BUILTIN_JSON_LOGIC_OPERATOR_DESCRIPTORS = [
9914
9965
  { operator: 'min', source: 'native', minArgs: 1, returnType: 'number', purity: 'pure' },
9915
9966
  { operator: 'max', source: 'native', minArgs: 1, returnType: 'number', purity: 'pure' },
9916
9967
  ];
9968
+ const BUILTIN_JSON_LOGIC_OPERATORS = BUILTIN_JSON_LOGIC_OPERATOR_DESCRIPTORS
9969
+ .map((descriptor) => descriptor.operator);
9917
9970
  function isComparisonOperator(operator) {
9918
9971
  return ['==', '===', '!=', '!==', '>', '>=', '<', '<='].includes(operator);
9919
9972
  }
@@ -9935,6 +9988,8 @@ function parseTemporalValue(value) {
9935
9988
  if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
9936
9989
  return { kind: 'date-only', ymd: raw };
9937
9990
  }
9991
+ if (!/(Z|[+-]\d{2}:\d{2})$/.test(raw))
9992
+ return null;
9938
9993
  const date = new Date(raw);
9939
9994
  return Number.isNaN(date.getTime()) ? null : { kind: 'instant', date };
9940
9995
  }
@@ -9951,13 +10006,12 @@ function temporalValueToInstant(value) {
9951
10006
  return new Date(`${value.ymd}T00:00:00.000Z`);
9952
10007
  }
9953
10008
  function currentYmd(context) {
9954
- const now = context.nowUtc ? new Date(context.nowUtc) : new Date();
10009
+ const now = requiredNow(context);
9955
10010
  return formatDateYmd(now, context.userTimeZone);
9956
10011
  }
9957
10012
  function formatDateYmd(date, timeZone) {
9958
- if (!timeZone) {
9959
- return date.toISOString().slice(0, 10);
9960
- }
10013
+ if (!timeZone)
10014
+ throw temporalError('userTimeZone is required for contextual operators');
9961
10015
  const formatter = new Intl.DateTimeFormat('en-CA', {
9962
10016
  timeZone,
9963
10017
  year: 'numeric',
@@ -9969,7 +10023,7 @@ function formatDateYmd(date, timeZone) {
9969
10023
  function addYmdByUnit(ymd, amount, unit) {
9970
10024
  const [year, month, day] = ymd.split('-').map((item) => Number(item));
9971
10025
  const base = new Date(Date.UTC(year, month - 1, day));
9972
- const normalized = String(unit || 'days').toLowerCase();
10026
+ const normalized = unit.toLowerCase();
9973
10027
  if (normalized.startsWith('week')) {
9974
10028
  base.setUTCDate(base.getUTCDate() + amount * 7);
9975
10029
  return base.toISOString().slice(0, 10);
@@ -9979,8 +10033,56 @@ function addYmdByUnit(ymd, amount, unit) {
9979
10033
  .toISOString()
9980
10034
  .slice(0, 10);
9981
10035
  }
9982
- base.setUTCDate(base.getUTCDate() + amount);
9983
- return base.toISOString().slice(0, 10);
10036
+ if (normalized.startsWith('day')) {
10037
+ base.setUTCDate(base.getUTCDate() + amount);
10038
+ return base.toISOString().slice(0, 10);
10039
+ }
10040
+ throw temporalError(`Unsupported temporal unit: ${unit}`);
10041
+ }
10042
+ const DEFAULT_JSON_LOGIC_LIMITS = {
10043
+ maxDepth: 64,
10044
+ maxNodes: 10_000,
10045
+ maxExpressionBytes: 256_000,
10046
+ maxArrayItems: 10_000,
10047
+ maxStringLength: 64_000,
10048
+ maxOperations: 50_000,
10049
+ maxRegexLength: 512,
10050
+ maxRegexComplexity: 64,
10051
+ };
10052
+ function assertJsonLogicLimits(value, limits, subject = 'expression') {
10053
+ const serialized = JSON.stringify(value);
10054
+ if (new TextEncoder().encode(serialized).length > limits.maxExpressionBytes) {
10055
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', `JSON Logic ${subject} exceeds the byte limit.`);
10056
+ }
10057
+ let nodes = 0;
10058
+ let operations = 0;
10059
+ const visit = (candidate, depth) => {
10060
+ nodes += 1;
10061
+ if (nodes > limits.maxNodes || depth > limits.maxDepth) {
10062
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', `JSON Logic ${subject} exceeds structural limits.`);
10063
+ }
10064
+ if (typeof candidate === 'string' && candidate.length > limits.maxStringLength) {
10065
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic string exceeds the length limit.');
10066
+ }
10067
+ if (Array.isArray(candidate)) {
10068
+ if (candidate.length > limits.maxArrayItems) {
10069
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic array exceeds the item limit.');
10070
+ }
10071
+ candidate.forEach((item) => visit(item, depth + 1));
10072
+ return;
10073
+ }
10074
+ if (candidate !== null && typeof candidate === 'object') {
10075
+ const entries = Object.entries(candidate);
10076
+ if (entries.length === 1) {
10077
+ operations += 1;
10078
+ if (operations > limits.maxOperations) {
10079
+ throw new PraxisJsonLogicError('RULE_LIMIT_EXCEEDED', 'JSON Logic expression exceeds the operation limit.');
10080
+ }
10081
+ }
10082
+ entries.forEach(([, child]) => visit(child, depth + 1));
10083
+ }
10084
+ };
10085
+ visit(value, 0);
9984
10086
  }
9985
10087
  function ymdToWeekday(ymd) {
9986
10088
  const [year, month, day] = ymd.split('-').map((item) => Number(item));
@@ -9989,12 +10091,67 @@ function ymdToWeekday(ymd) {
9989
10091
  return weekday === 0 ? 7 : weekday;
9990
10092
  }
9991
10093
  function splitPath(path) {
10094
+ if (!path || path === '$' || path.includes('..') || path.startsWith('.') || path.endsWith('.') || /\*|\?|\[\-|\[[^\d\"]|\[[^\]]*$/.test(path)) {
10095
+ throw new PraxisJsonLogicError('RULE_PATH_INVALID', `Invalid Praxis JSON Logic path: "${path}".`);
10096
+ }
9992
10097
  const normalized = path.startsWith('$.') ? path.slice(2) : path;
9993
- return normalized
10098
+ const rewritten = normalized
9994
10099
  .replace(/\["([^"]+)"\]/g, '.$1')
9995
- .replace(/\[(\d+)\]/g, '.$1')
9996
- .split('.')
9997
- .filter(Boolean);
10100
+ .replace(/\[(\d+)\]/g, '.$1');
10101
+ if (rewritten.includes('[') || rewritten.includes(']'))
10102
+ throw new PraxisJsonLogicError('RULE_PATH_INVALID', `Invalid Praxis JSON Logic path: "${path}".`);
10103
+ return rewritten.split('.').filter(Boolean);
10104
+ }
10105
+ function canonicalStrictEquals(left, right) {
10106
+ if (typeof left === 'number' && typeof right === 'number')
10107
+ return Number.isFinite(left) && Number.isFinite(right) && left === right;
10108
+ if (Array.isArray(left) && Array.isArray(right))
10109
+ return left.length === right.length && left.every((value, index) => canonicalStrictEquals(value, right[index]));
10110
+ if (left && right && typeof left === 'object' && typeof right === 'object') {
10111
+ const a = Object.keys(left);
10112
+ const b = Object.keys(right);
10113
+ return a.length === b.length && a.every((key) => Object.prototype.hasOwnProperty.call(right, key) && canonicalStrictEquals(left[key], right[key]));
10114
+ }
10115
+ return left === right;
10116
+ }
10117
+ function canonicalLooseEquals(left, right) {
10118
+ if (left == null && right == null)
10119
+ return true;
10120
+ if (typeof left === 'number' && typeof right === 'string' && right.trim() !== '')
10121
+ return Number.isFinite(Number(right)) && left === Number(right);
10122
+ if (typeof right === 'number' && typeof left === 'string' && left.trim() !== '')
10123
+ return Number.isFinite(Number(left)) && right === Number(left);
10124
+ if (typeof left === 'boolean' && typeof right === 'number')
10125
+ return Number(left) === right;
10126
+ if (typeof right === 'boolean' && typeof left === 'number')
10127
+ return Number(right) === left;
10128
+ return canonicalStrictEquals(left, right);
10129
+ }
10130
+ function compileSafeRegex(pattern) {
10131
+ const complexity = (pattern.match(/[?{]/g) ?? []).length;
10132
+ const withoutBounds = pattern.replace(/\{\d+(?:,\d+)?\}/g, '');
10133
+ const oversizedBound = Array.from(pattern.matchAll(/\{(\d+)(?:,(\d+))?\}/g))
10134
+ .some((match) => Number(match[2] ?? match[1]) > 256);
10135
+ if (pattern.length > 512 || complexity > 64 || /[()*+|]/.test(pattern) || /\\[1-9]/.test(pattern) || /[{}]/.test(withoutBounds) || oversizedBound) {
10136
+ throw new PraxisJsonLogicError('RULE_REGEX_INVALID', 'Regular expression uses an unsupported or excessive construct.');
10137
+ }
10138
+ try {
10139
+ return new RegExp(pattern);
10140
+ }
10141
+ catch {
10142
+ throw new PraxisJsonLogicError('RULE_REGEX_INVALID', 'Regular expression is syntactically invalid.');
10143
+ }
10144
+ }
10145
+ function temporalError(message) { return new PraxisJsonLogicError('RULE_TEMPORAL_INPUT_INVALID', `${message}.`); }
10146
+ function requireTemporalValue(value) { const parsed = parseTemporalValue(value); if (!parsed)
10147
+ throw temporalError('Temporal input is invalid'); return parsed; }
10148
+ function requiredNow(context) {
10149
+ if (!context.nowUtc)
10150
+ throw temporalError('nowUtc is required for contextual operators');
10151
+ const now = new Date(context.nowUtc);
10152
+ if (Number.isNaN(now.getTime()))
10153
+ throw temporalError('nowUtc must be an ISO-8601 instant');
10154
+ return now;
9998
10155
  }
9999
10156
  function readPath(target, path) {
10000
10157
  let current = target;
@@ -13198,6 +13355,12 @@ class ResourceDiscoveryService {
13198
13355
  getCapabilities(source, options) {
13199
13356
  return this.followLink(source, 'capabilities', options);
13200
13357
  }
13358
+ getCapabilitiesByUrl(href, options) {
13359
+ return this.fetchJson(href, options);
13360
+ }
13361
+ getSchemaCatalog(query = {}, options) {
13362
+ return this.http.get(this.resolveHref('/schemas/catalog', options), { params: this.toSchemaCatalogParams(query) });
13363
+ }
13201
13364
  fetchJson(href, options) {
13202
13365
  return this.http.get(this.resolveHref(href, options));
13203
13366
  }
@@ -13234,6 +13397,16 @@ class ResourceDiscoveryService {
13234
13397
  }
13235
13398
  return href;
13236
13399
  }
13400
+ toSchemaCatalogParams(query) {
13401
+ let params = new HttpParams();
13402
+ for (const key of ['group', 'path', 'operation']) {
13403
+ const value = String(query[key] ?? '').trim();
13404
+ if (value) {
13405
+ params = params.set(key, value);
13406
+ }
13407
+ }
13408
+ return params;
13409
+ }
13237
13410
  extractLinks(source) {
13238
13411
  if (!source) {
13239
13412
  return undefined;
@@ -13371,6 +13544,484 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
13371
13544
  args: [{ providedIn: 'any' }]
13372
13545
  }] });
13373
13546
 
13547
+ const SURFACE_OPEN_PRESETS = [
13548
+ {
13549
+ id: 'praxis-dynamic-form',
13550
+ label: 'Formulário',
13551
+ description: 'Abre um formulário dinâmico com resourcePath, formId e resourceId.',
13552
+ payload: {
13553
+ presentation: 'drawer',
13554
+ widget: {
13555
+ id: 'praxis-dynamic-form',
13556
+ bindingOrder: [
13557
+ 'formId',
13558
+ 'componentInstanceId',
13559
+ 'resourcePath',
13560
+ 'resourceId',
13561
+ 'schemaUrl',
13562
+ 'readUrl',
13563
+ 'mode',
13564
+ 'presentationModeGlobal',
13565
+ ],
13566
+ inputs: {
13567
+ resourcePath: '',
13568
+ formId: '',
13569
+ mode: 'view',
13570
+ },
13571
+ },
13572
+ bindings: [],
13573
+ },
13574
+ },
13575
+ {
13576
+ id: 'praxis-table',
13577
+ label: 'Tabela',
13578
+ description: 'Abre uma tabela filtrável usando inputs declarativos e queryContext.',
13579
+ payload: {
13580
+ presentation: 'drawer',
13581
+ widget: {
13582
+ id: 'praxis-table',
13583
+ bindingOrder: ['resourcePath', 'tableId', 'queryContext'],
13584
+ inputs: {
13585
+ resourcePath: '',
13586
+ tableId: '',
13587
+ queryContext: {
13588
+ filters: {},
13589
+ },
13590
+ },
13591
+ },
13592
+ bindings: [],
13593
+ },
13594
+ },
13595
+ {
13596
+ id: 'praxis-list',
13597
+ label: 'Lista',
13598
+ description: 'Abre uma lista com config/listId e bindings configuráveis.',
13599
+ payload: {
13600
+ presentation: 'drawer',
13601
+ widget: {
13602
+ id: 'praxis-list',
13603
+ bindingOrder: ['listId', 'config'],
13604
+ inputs: {
13605
+ listId: '',
13606
+ config: {},
13607
+ },
13608
+ },
13609
+ bindings: [],
13610
+ },
13611
+ },
13612
+ {
13613
+ id: 'praxis-crud',
13614
+ label: 'CRUD',
13615
+ description: 'Abre a surface de CRUD com metadata/crudId e bindings configuráveis.',
13616
+ payload: {
13617
+ presentation: 'drawer',
13618
+ widget: {
13619
+ id: 'praxis-crud',
13620
+ bindingOrder: ['metadata', 'crudId', 'context'],
13621
+ inputs: {
13622
+ metadata: {
13623
+ component: 'praxis-crud',
13624
+ table: {
13625
+ columns: [],
13626
+ },
13627
+ data: [],
13628
+ },
13629
+ crudId: '',
13630
+ context: {},
13631
+ },
13632
+ },
13633
+ bindings: [],
13634
+ },
13635
+ },
13636
+ ];
13637
+
13638
+ class ResourceSurfaceOpenAdapterService {
13639
+ discovery = inject(ResourceDiscoveryService);
13640
+ toPayload(surface, options) {
13641
+ const resourcePath = this.normalizeResourcePath(options.resourcePath);
13642
+ if (!resourcePath) {
13643
+ throw new Error('ResourceSurfaceOpenAdapterService requires resourcePath.');
13644
+ }
13645
+ const discoveryOptions = options.endpointKey || options.apiUrlEntry
13646
+ ? {
13647
+ ...(options.endpointKey ? { endpointKey: options.endpointKey } : {}),
13648
+ ...(options.apiUrlEntry ? { apiUrlEntry: options.apiUrlEntry } : {}),
13649
+ }
13650
+ : undefined;
13651
+ const resolvedApiEntry = this.discovery.resolveApiEntry(discoveryOptions);
13652
+ const resolvedSchemaUrl = this.discovery.resolveHref(surface.schemaUrl, discoveryOptions);
13653
+ const resolvedSubmitUrl = this.isWritableFormSurface(surface.kind)
13654
+ ? this.discovery.resolveHref(surface.path, discoveryOptions)
13655
+ : null;
13656
+ const resolvedReadUrl = this.resolveItemHydrationReadUrl(surface, resourcePath, discoveryOptions);
13657
+ const basePayload = this.buildBasePayload(surface);
13658
+ const payload = {
13659
+ ...basePayload,
13660
+ presentation: options.presentation ?? basePayload.presentation,
13661
+ title: options.title ?? surface.title ?? basePayload.title,
13662
+ subtitle: options.subtitle ?? surface.description ?? basePayload.subtitle,
13663
+ icon: options.icon ?? basePayload.icon,
13664
+ context: {
13665
+ resource: {
13666
+ resourceKey: surface.resourceKey,
13667
+ resourcePath,
13668
+ resourceId: options.resourceId ?? null,
13669
+ group: options.group ?? null,
13670
+ },
13671
+ surface: {
13672
+ id: surface.id,
13673
+ resourceKey: surface.resourceKey,
13674
+ kind: surface.kind,
13675
+ scope: surface.scope,
13676
+ title: surface.title,
13677
+ description: surface.description ?? null,
13678
+ intent: surface.intent ?? null,
13679
+ operationId: surface.operationId,
13680
+ path: surface.path,
13681
+ method: surface.method,
13682
+ schemaId: surface.schemaId,
13683
+ schemaUrl: surface.schemaUrl,
13684
+ responseCardinality: surface.responseCardinality ?? null,
13685
+ availability: surface.availability,
13686
+ order: surface.order,
13687
+ tags: surface.tags,
13688
+ relatedResource: surface.relatedResource ?? null,
13689
+ },
13690
+ },
13691
+ };
13692
+ if (basePayload.widget.id === 'praxis-table') {
13693
+ payload.widget.inputs = {
13694
+ ...(payload.widget.inputs || {}),
13695
+ resourcePath,
13696
+ tableId: this.buildStableInstanceId(surface),
13697
+ queryContext: options.queryContext ?? payload.widget.inputs?.['queryContext'] ?? { filters: {} },
13698
+ };
13699
+ return payload;
13700
+ }
13701
+ const mode = this.resolveFormMode(surface.kind, surface.scope);
13702
+ payload.widget.inputs = {
13703
+ ...(payload.widget.inputs || {}),
13704
+ resourcePath,
13705
+ formId: this.buildStableInstanceId(surface),
13706
+ mode,
13707
+ schemaUrl: resolvedSchemaUrl,
13708
+ readUrl: resolvedReadUrl,
13709
+ apiEndpointKey: options.endpointKey ?? null,
13710
+ apiUrlEntry: options.apiUrlEntry ?? resolvedApiEntry,
13711
+ };
13712
+ if (surface.scope === 'ITEM' && resolvedReadUrl) {
13713
+ payload.widget.inputs['customEndpoints'] = {
13714
+ ...(payload.widget.inputs['customEndpoints'] || {}),
13715
+ getById: resolvedReadUrl,
13716
+ };
13717
+ payload.widget.bindingOrder = this.withInputBefore(payload.widget.bindingOrder, 'customEndpoints', 'resourceId');
13718
+ }
13719
+ if (this.isWritableFormSurface(surface.kind)) {
13720
+ payload.widget.inputs['submitMethod'] = surface.method.toLowerCase();
13721
+ payload.widget.inputs['submitUrl'] = resolvedSubmitUrl;
13722
+ }
13723
+ if (this.isReadableItemSurface(surface)) {
13724
+ payload.widget.inputs['presentationModeGlobal'] = true;
13725
+ }
13726
+ if (surface.scope === 'ITEM') {
13727
+ if (options.resourceId != null) {
13728
+ payload.widget.inputs['resourceId'] = options.resourceId;
13729
+ }
13730
+ else if (options.idBindingPath) {
13731
+ payload.bindings = [
13732
+ ...(payload.bindings || []),
13733
+ this.buildIdBinding(options.idBindingPath),
13734
+ ];
13735
+ }
13736
+ else {
13737
+ throw new Error(`ResourceSurfaceOpenAdapterService requires resourceId or idBindingPath for item surface "${surface.id}".`);
13738
+ }
13739
+ }
13740
+ return payload;
13741
+ }
13742
+ buildBasePayload(surface) {
13743
+ const presetId = surface.scope === 'COLLECTION' && this.isCollectionView(surface.kind)
13744
+ ? 'praxis-table'
13745
+ : 'praxis-dynamic-form';
13746
+ const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === presetId);
13747
+ if (!preset) {
13748
+ throw new Error(`Missing canonical surface preset "${presetId}".`);
13749
+ }
13750
+ return this.clone(preset.payload);
13751
+ }
13752
+ buildStableInstanceId(surface) {
13753
+ return `${surface.resourceKey}.${surface.id}`.replace(/[^a-zA-Z0-9._-]+/g, '-');
13754
+ }
13755
+ withInputBefore(bindingOrder, input, beforeInput) {
13756
+ const next = (bindingOrder || []).filter((item) => item !== input);
13757
+ const targetIndex = next.indexOf(beforeInput);
13758
+ if (targetIndex >= 0) {
13759
+ next.splice(targetIndex, 0, input);
13760
+ return next;
13761
+ }
13762
+ return [...next, input];
13763
+ }
13764
+ normalizeResourcePath(resourcePath) {
13765
+ return String(resourcePath || '').trim().replace(/^\/+/, '').replace(/\/+$/, '');
13766
+ }
13767
+ resolveFormMode(kind, scope) {
13768
+ if (kind === 'VIEW' || kind === 'READ_PROJECTION') {
13769
+ return 'view';
13770
+ }
13771
+ if (scope === 'ITEM') {
13772
+ return 'edit';
13773
+ }
13774
+ return 'create';
13775
+ }
13776
+ isCollectionView(kind) {
13777
+ return kind === 'VIEW' || kind === 'READ_PROJECTION';
13778
+ }
13779
+ isWritableFormSurface(kind) {
13780
+ return kind === 'FORM' || kind === 'PARTIAL_FORM';
13781
+ }
13782
+ isReadableFormSurface(kind) {
13783
+ return kind === 'VIEW' || kind === 'READ_PROJECTION';
13784
+ }
13785
+ isReadableItemSurface(surface) {
13786
+ return (surface.scope === 'ITEM' &&
13787
+ surface.method.toUpperCase() === 'GET' &&
13788
+ (surface.kind === 'VIEW' || surface.kind === 'READ_PROJECTION'));
13789
+ }
13790
+ isItemFormHydrationSurface(surface) {
13791
+ return surface.scope === 'ITEM'
13792
+ && (this.isReadableFormSurface(surface.kind) || this.isWritableFormSurface(surface.kind));
13793
+ }
13794
+ resolveItemHydrationReadUrl(surface, resourcePath, discoveryOptions) {
13795
+ if (!this.isItemFormHydrationSurface(surface)) {
13796
+ return null;
13797
+ }
13798
+ if (this.isReadableItemSurface(surface)) {
13799
+ return this.discovery.resolveHref(surface.path, discoveryOptions);
13800
+ }
13801
+ return this.discovery.resolveHref(this.buildCanonicalItemHref(resourcePath), discoveryOptions);
13802
+ }
13803
+ buildCanonicalItemHref(resourcePath) {
13804
+ const normalizedResourcePath = this.normalizeResourcePath(resourcePath);
13805
+ const itemHref = `${normalizedResourcePath}/{id}`;
13806
+ return normalizedResourcePath.startsWith('api/') ? `/${itemHref}` : itemHref;
13807
+ }
13808
+ buildIdBinding(idBindingPath) {
13809
+ return {
13810
+ from: idBindingPath,
13811
+ to: 'widget.inputs.resourceId',
13812
+ mode: 'path',
13813
+ };
13814
+ }
13815
+ clone(value) {
13816
+ return value == null ? value : JSON.parse(JSON.stringify(value));
13817
+ }
13818
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
13819
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, providedIn: 'any' });
13820
+ }
13821
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, decorators: [{
13822
+ type: Injectable,
13823
+ args: [{ providedIn: 'any' }]
13824
+ }] });
13825
+
13826
+ class ResourceRecordOpenError extends Error {
13827
+ code;
13828
+ detail;
13829
+ constructor(code, message, detail) {
13830
+ super(message);
13831
+ this.code = code;
13832
+ this.detail = detail;
13833
+ this.name = 'ResourceRecordOpenError';
13834
+ }
13835
+ }
13836
+ /**
13837
+ * Resolves a governed record-open reference into a contextual surface payload.
13838
+ *
13839
+ * The service deliberately re-queries discovery for the current principal. It
13840
+ * never treats the authoring-time catalog, the row label or a local URL template
13841
+ * as authorization to open the target surface.
13842
+ */
13843
+ class ResourceRecordOpenService {
13844
+ discovery = inject(ResourceDiscoveryService);
13845
+ surfaceAdapter = inject(ResourceSurfaceOpenAdapterService);
13846
+ resolve(reference, record, options) {
13847
+ return defer(() => {
13848
+ const canonicalReference = this.requireReference(reference);
13849
+ const resourceId = this.requireResourceId(record, canonicalReference.sourceIdentityField);
13850
+ return this.discovery
13851
+ .fetchJson(this.surfaceCatalogHref(canonicalReference.target.resourceKey), options)
13852
+ .pipe(map$1((catalog) => this.requireCatalog(catalog, canonicalReference.target.resourceKey, 'RESOURCE_CATALOG_INVALID')), switchMap$1((resourceCatalog) => {
13853
+ this.requireCollectionTargetSurface(resourceCatalog, canonicalReference);
13854
+ return this.discovery
13855
+ .fetchJson(this.itemHref(resourceCatalog.resourcePath, resourceId), options)
13856
+ .pipe(switchMap$1((itemResponse) => this.discovery.getSurfaces(itemResponse, options)), map$1((contextualCatalog) => this.materializeContextualSurface(contextualCatalog, resourceCatalog, canonicalReference, resourceId, options)));
13857
+ }));
13858
+ }).pipe(catchError$1((error) => throwError(() => this.normalizeFailure(error))));
13859
+ }
13860
+ materializeContextualSurface(candidate, resourceCatalog, reference, resourceId, options) {
13861
+ const contextualCatalog = this.requireCatalog(candidate, reference.target.resourceKey, 'CONTEXTUAL_CATALOG_INVALID');
13862
+ if (contextualCatalog.resourcePath !== resourceCatalog.resourcePath) {
13863
+ throw new ResourceRecordOpenError('CONTEXTUAL_CATALOG_INVALID', 'Contextual surface discovery returned a different resource path.');
13864
+ }
13865
+ if (contextualCatalog.resourceId != null &&
13866
+ String(contextualCatalog.resourceId) !== String(resourceId)) {
13867
+ throw new ResourceRecordOpenError('CONTEXTUAL_CATALOG_INVALID', 'Contextual surface discovery returned a different resource identity.');
13868
+ }
13869
+ const surface = this.requireSurface(contextualCatalog, reference.target.surfaceId, reference.target.resourceKey, 'CONTEXTUAL_SURFACE_UNAVAILABLE');
13870
+ if (!surface.availability.allowed) {
13871
+ throw new ResourceRecordOpenError('CONTEXTUAL_SURFACE_UNAVAILABLE', `Surface "${reference.target.surfaceId}" is unavailable for the current record context.`, { reason: surface.availability.reason ?? null });
13872
+ }
13873
+ const payload = this.surfaceAdapter.toPayload(surface, {
13874
+ resourcePath: contextualCatalog.resourcePath,
13875
+ resourceId,
13876
+ endpointKey: options?.endpointKey,
13877
+ apiUrlEntry: options?.apiUrlEntry,
13878
+ group: contextualCatalog.group ?? resourceCatalog.group ?? null,
13879
+ presentation: options?.presentation,
13880
+ title: options?.title,
13881
+ subtitle: options?.subtitle,
13882
+ icon: options?.icon,
13883
+ queryContext: options?.queryContext,
13884
+ });
13885
+ return {
13886
+ resourceId,
13887
+ resourcePath: contextualCatalog.resourcePath,
13888
+ surface,
13889
+ payload,
13890
+ };
13891
+ }
13892
+ requireCollectionTargetSurface(catalog, reference) {
13893
+ const surface = this.requireSurface(catalog, reference.target.surfaceId, reference.target.resourceKey, 'TARGET_SURFACE_UNAVAILABLE');
13894
+ const availabilityReason = String(surface.availability.reason ?? '').trim();
13895
+ if (!surface.availability.allowed &&
13896
+ availabilityReason !== 'resource-context-required') {
13897
+ throw new ResourceRecordOpenError('TARGET_SURFACE_UNAVAILABLE', `Surface "${reference.target.surfaceId}" is unavailable for the current principal.`, { reason: availabilityReason || null });
13898
+ }
13899
+ }
13900
+ requireSurface(catalog, surfaceId, resourceKey, failureCode) {
13901
+ const candidate = catalog.surfaces.find((surface) => this.text(surface['id']) ===
13902
+ surfaceId);
13903
+ if (!candidate) {
13904
+ throw new ResourceRecordOpenError(failureCode, `Surface "${surfaceId}" is not declared for resource "${resourceKey}".`);
13905
+ }
13906
+ const surface = candidate;
13907
+ const availability = this.record(surface['availability']);
13908
+ const kind = this.text(surface['kind']);
13909
+ const supportedKinds = [
13910
+ 'FORM',
13911
+ 'PARTIAL_FORM',
13912
+ 'VIEW',
13913
+ 'READ_PROJECTION',
13914
+ ];
13915
+ if (this.text(surface['resourceKey']) !== resourceKey ||
13916
+ this.text(surface['scope']) !== 'ITEM' ||
13917
+ !supportedKinds.includes(kind) ||
13918
+ !this.text(surface['operationId']) ||
13919
+ !this.text(surface['path']) ||
13920
+ !this.text(surface['method']) ||
13921
+ !this.text(surface['schemaUrl']) ||
13922
+ typeof availability?.['allowed'] !== 'boolean') {
13923
+ throw new ResourceRecordOpenError(failureCode, `Surface "${surfaceId}" does not satisfy the canonical item-surface contract.`);
13924
+ }
13925
+ return candidate;
13926
+ }
13927
+ requireCatalog(candidate, resourceKey, failureCode) {
13928
+ const catalog = this.record(candidate);
13929
+ const resourcePath = this.safeResourcePath(this.text(catalog?.['resourcePath']));
13930
+ if (!catalog ||
13931
+ this.text(catalog['resourceKey']) !== resourceKey ||
13932
+ !resourcePath ||
13933
+ !Array.isArray(catalog['surfaces'])) {
13934
+ throw new ResourceRecordOpenError(failureCode, `Surface catalog for resource "${resourceKey}" is invalid.`);
13935
+ }
13936
+ const resourceId = catalog['resourceId'];
13937
+ if (resourceId != null &&
13938
+ typeof resourceId !== 'string' &&
13939
+ typeof resourceId !== 'number') {
13940
+ throw new ResourceRecordOpenError(failureCode, `Surface catalog for resource "${resourceKey}" has an invalid resource identity.`);
13941
+ }
13942
+ return {
13943
+ resourceKey,
13944
+ resourcePath,
13945
+ group: this.nullableText(catalog['group']),
13946
+ resourceId: resourceId ?? null,
13947
+ surfaces: catalog['surfaces'],
13948
+ };
13949
+ }
13950
+ requireReference(candidate) {
13951
+ const reference = this.record(candidate);
13952
+ const target = this.record(reference?.['target']);
13953
+ const sourceIdentityField = this.text(reference?.['sourceIdentityField']);
13954
+ const resourceKey = this.text(target?.['resourceKey']);
13955
+ const surfaceId = this.text(target?.['surfaceId']);
13956
+ if (!sourceIdentityField ||
13957
+ sourceIdentityField.length > 200 ||
13958
+ !/^[A-Za-z0-9][A-Za-z0-9._-]{0,199}$/.test(resourceKey) ||
13959
+ !surfaceId ||
13960
+ surfaceId.length > 200) {
13961
+ throw new ResourceRecordOpenError('INVALID_REFERENCE', 'Record-open reference must declare a source field and canonical target identity.');
13962
+ }
13963
+ return {
13964
+ sourceIdentityField,
13965
+ target: { resourceKey, surfaceId },
13966
+ };
13967
+ }
13968
+ requireResourceId(candidate, sourceIdentityField) {
13969
+ const record = this.record(candidate);
13970
+ if (!record ||
13971
+ !Object.prototype.hasOwnProperty.call(record, sourceIdentityField)) {
13972
+ throw new ResourceRecordOpenError('SOURCE_IDENTITY_MISSING', `Record does not publish source identity field "${sourceIdentityField}".`);
13973
+ }
13974
+ const value = record[sourceIdentityField];
13975
+ if (typeof value === 'string' && value.trim()) {
13976
+ return value.trim();
13977
+ }
13978
+ if (typeof value === 'number' && Number.isFinite(value)) {
13979
+ return value;
13980
+ }
13981
+ throw new ResourceRecordOpenError('SOURCE_IDENTITY_INVALID', `Source identity field "${sourceIdentityField}" must contain a string or finite number.`);
13982
+ }
13983
+ surfaceCatalogHref(resourceKey) {
13984
+ return `/schemas/surfaces?resource=${encodeURIComponent(resourceKey)}`;
13985
+ }
13986
+ itemHref(resourcePath, resourceId) {
13987
+ return `${resourcePath}/${encodeURIComponent(String(resourceId))}`;
13988
+ }
13989
+ safeResourcePath(candidate) {
13990
+ const normalized = candidate.replace(/\/+$/u, '');
13991
+ if (!normalized.startsWith('/api/') ||
13992
+ normalized.includes('..') ||
13993
+ normalized.includes('?') ||
13994
+ normalized.includes('#') ||
13995
+ normalized.includes('://')) {
13996
+ return null;
13997
+ }
13998
+ return normalized;
13999
+ }
14000
+ normalizeFailure(error) {
14001
+ if (error instanceof ResourceRecordOpenError) {
14002
+ return error;
14003
+ }
14004
+ return new ResourceRecordOpenError('DISCOVERY_FAILED', 'Canonical record-open discovery failed.', error);
14005
+ }
14006
+ record(value) {
14007
+ return value != null && typeof value === 'object' && !Array.isArray(value)
14008
+ ? value
14009
+ : null;
14010
+ }
14011
+ text(value) {
14012
+ return typeof value === 'string' ? value.trim() : '';
14013
+ }
14014
+ nullableText(value) {
14015
+ return this.text(value) || null;
14016
+ }
14017
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceRecordOpenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
14018
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceRecordOpenService, providedIn: 'any' });
14019
+ }
14020
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceRecordOpenService, decorators: [{
14021
+ type: Injectable,
14022
+ args: [{ providedIn: 'any' }]
14023
+ }] });
14024
+
13374
14025
  const DEFAULT_SERVICE_KEY = 'praxis-service';
13375
14026
  const DEFAULT_RELEASE_LIMIT = 50;
13376
14027
  const DEFAULT_ITEM_LIMIT = 5;
@@ -13945,97 +14596,6 @@ function registerPraxisRuntimeComponentObservation(provider, options = {}) {
13945
14596
  return registry.register(provider, { ...options, destroyRef });
13946
14597
  }
13947
14598
 
13948
- const SURFACE_OPEN_PRESETS = [
13949
- {
13950
- id: 'praxis-dynamic-form',
13951
- label: 'Formulário',
13952
- description: 'Abre um formulário dinâmico com resourcePath, formId e resourceId.',
13953
- payload: {
13954
- presentation: 'drawer',
13955
- widget: {
13956
- id: 'praxis-dynamic-form',
13957
- bindingOrder: [
13958
- 'formId',
13959
- 'componentInstanceId',
13960
- 'resourcePath',
13961
- 'resourceId',
13962
- 'schemaUrl',
13963
- 'readUrl',
13964
- 'mode',
13965
- 'presentationModeGlobal',
13966
- ],
13967
- inputs: {
13968
- resourcePath: '',
13969
- formId: '',
13970
- mode: 'view',
13971
- },
13972
- },
13973
- bindings: [],
13974
- },
13975
- },
13976
- {
13977
- id: 'praxis-table',
13978
- label: 'Tabela',
13979
- description: 'Abre uma tabela filtrável usando inputs declarativos e queryContext.',
13980
- payload: {
13981
- presentation: 'drawer',
13982
- widget: {
13983
- id: 'praxis-table',
13984
- bindingOrder: ['resourcePath', 'tableId', 'queryContext'],
13985
- inputs: {
13986
- resourcePath: '',
13987
- tableId: '',
13988
- queryContext: {
13989
- filters: {},
13990
- },
13991
- },
13992
- },
13993
- bindings: [],
13994
- },
13995
- },
13996
- {
13997
- id: 'praxis-list',
13998
- label: 'Lista',
13999
- description: 'Abre uma lista com config/listId e bindings configuráveis.',
14000
- payload: {
14001
- presentation: 'drawer',
14002
- widget: {
14003
- id: 'praxis-list',
14004
- bindingOrder: ['listId', 'config'],
14005
- inputs: {
14006
- listId: '',
14007
- config: {},
14008
- },
14009
- },
14010
- bindings: [],
14011
- },
14012
- },
14013
- {
14014
- id: 'praxis-crud',
14015
- label: 'CRUD',
14016
- description: 'Abre a surface de CRUD com metadata/crudId e bindings configuráveis.',
14017
- payload: {
14018
- presentation: 'drawer',
14019
- widget: {
14020
- id: 'praxis-crud',
14021
- bindingOrder: ['metadata', 'crudId', 'context'],
14022
- inputs: {
14023
- metadata: {
14024
- component: 'praxis-crud',
14025
- table: {
14026
- columns: [],
14027
- },
14028
- data: [],
14029
- },
14030
- crudId: '',
14031
- context: {},
14032
- },
14033
- },
14034
- bindings: [],
14035
- },
14036
- },
14037
- ];
14038
-
14039
14599
  class ResourceActionOpenAdapterService {
14040
14600
  discovery = inject(ResourceDiscoveryService);
14041
14601
  toPayload(action, options) {
@@ -14143,194 +14703,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
14143
14703
  args: [{ providedIn: 'any' }]
14144
14704
  }] });
14145
14705
 
14146
- class ResourceSurfaceOpenAdapterService {
14147
- discovery = inject(ResourceDiscoveryService);
14148
- toPayload(surface, options) {
14149
- const resourcePath = this.normalizeResourcePath(options.resourcePath);
14150
- if (!resourcePath) {
14151
- throw new Error('ResourceSurfaceOpenAdapterService requires resourcePath.');
14152
- }
14153
- const discoveryOptions = options.endpointKey || options.apiUrlEntry
14154
- ? {
14155
- ...(options.endpointKey ? { endpointKey: options.endpointKey } : {}),
14156
- ...(options.apiUrlEntry ? { apiUrlEntry: options.apiUrlEntry } : {}),
14157
- }
14158
- : undefined;
14159
- const resolvedApiEntry = this.discovery.resolveApiEntry(discoveryOptions);
14160
- const resolvedSchemaUrl = this.discovery.resolveHref(surface.schemaUrl, discoveryOptions);
14161
- const resolvedSubmitUrl = this.isWritableFormSurface(surface.kind)
14162
- ? this.discovery.resolveHref(surface.path, discoveryOptions)
14163
- : null;
14164
- const resolvedReadUrl = this.resolveItemHydrationReadUrl(surface, resourcePath, discoveryOptions);
14165
- const basePayload = this.buildBasePayload(surface);
14166
- const payload = {
14167
- ...basePayload,
14168
- presentation: options.presentation ?? basePayload.presentation,
14169
- title: options.title ?? surface.title ?? basePayload.title,
14170
- subtitle: options.subtitle ?? surface.description ?? basePayload.subtitle,
14171
- icon: options.icon ?? basePayload.icon,
14172
- context: {
14173
- resource: {
14174
- resourceKey: surface.resourceKey,
14175
- resourcePath,
14176
- resourceId: options.resourceId ?? null,
14177
- group: options.group ?? null,
14178
- },
14179
- surface: {
14180
- id: surface.id,
14181
- resourceKey: surface.resourceKey,
14182
- kind: surface.kind,
14183
- scope: surface.scope,
14184
- title: surface.title,
14185
- description: surface.description ?? null,
14186
- intent: surface.intent ?? null,
14187
- operationId: surface.operationId,
14188
- path: surface.path,
14189
- method: surface.method,
14190
- schemaId: surface.schemaId,
14191
- schemaUrl: surface.schemaUrl,
14192
- responseCardinality: surface.responseCardinality ?? null,
14193
- availability: surface.availability,
14194
- order: surface.order,
14195
- tags: surface.tags,
14196
- relatedResource: surface.relatedResource ?? null,
14197
- },
14198
- },
14199
- };
14200
- if (basePayload.widget.id === 'praxis-table') {
14201
- payload.widget.inputs = {
14202
- ...(payload.widget.inputs || {}),
14203
- resourcePath,
14204
- tableId: this.buildStableInstanceId(surface),
14205
- queryContext: options.queryContext ?? payload.widget.inputs?.['queryContext'] ?? { filters: {} },
14206
- };
14207
- return payload;
14208
- }
14209
- const mode = this.resolveFormMode(surface.kind, surface.scope);
14210
- payload.widget.inputs = {
14211
- ...(payload.widget.inputs || {}),
14212
- resourcePath,
14213
- formId: this.buildStableInstanceId(surface),
14214
- mode,
14215
- schemaUrl: resolvedSchemaUrl,
14216
- readUrl: resolvedReadUrl,
14217
- apiEndpointKey: options.endpointKey ?? null,
14218
- apiUrlEntry: options.apiUrlEntry ?? resolvedApiEntry,
14219
- };
14220
- if (surface.scope === 'ITEM' && resolvedReadUrl) {
14221
- payload.widget.inputs['customEndpoints'] = {
14222
- ...(payload.widget.inputs['customEndpoints'] || {}),
14223
- getById: resolvedReadUrl,
14224
- };
14225
- payload.widget.bindingOrder = this.withInputBefore(payload.widget.bindingOrder, 'customEndpoints', 'resourceId');
14226
- }
14227
- if (this.isWritableFormSurface(surface.kind)) {
14228
- payload.widget.inputs['submitMethod'] = surface.method.toLowerCase();
14229
- payload.widget.inputs['submitUrl'] = resolvedSubmitUrl;
14230
- }
14231
- if (this.isReadableItemSurface(surface)) {
14232
- payload.widget.inputs['presentationModeGlobal'] = true;
14233
- }
14234
- if (surface.scope === 'ITEM') {
14235
- if (options.resourceId != null) {
14236
- payload.widget.inputs['resourceId'] = options.resourceId;
14237
- }
14238
- else if (options.idBindingPath) {
14239
- payload.bindings = [
14240
- ...(payload.bindings || []),
14241
- this.buildIdBinding(options.idBindingPath),
14242
- ];
14243
- }
14244
- else {
14245
- throw new Error(`ResourceSurfaceOpenAdapterService requires resourceId or idBindingPath for item surface "${surface.id}".`);
14246
- }
14247
- }
14248
- return payload;
14249
- }
14250
- buildBasePayload(surface) {
14251
- const presetId = surface.scope === 'COLLECTION' && this.isCollectionView(surface.kind)
14252
- ? 'praxis-table'
14253
- : 'praxis-dynamic-form';
14254
- const preset = SURFACE_OPEN_PRESETS.find((candidate) => candidate.id === presetId);
14255
- if (!preset) {
14256
- throw new Error(`Missing canonical surface preset "${presetId}".`);
14257
- }
14258
- return this.clone(preset.payload);
14259
- }
14260
- buildStableInstanceId(surface) {
14261
- return `${surface.resourceKey}.${surface.id}`.replace(/[^a-zA-Z0-9._-]+/g, '-');
14262
- }
14263
- withInputBefore(bindingOrder, input, beforeInput) {
14264
- const next = (bindingOrder || []).filter((item) => item !== input);
14265
- const targetIndex = next.indexOf(beforeInput);
14266
- if (targetIndex >= 0) {
14267
- next.splice(targetIndex, 0, input);
14268
- return next;
14269
- }
14270
- return [...next, input];
14271
- }
14272
- normalizeResourcePath(resourcePath) {
14273
- return String(resourcePath || '').trim().replace(/^\/+/, '').replace(/\/+$/, '');
14274
- }
14275
- resolveFormMode(kind, scope) {
14276
- if (kind === 'VIEW' || kind === 'READ_PROJECTION') {
14277
- return 'view';
14278
- }
14279
- if (scope === 'ITEM') {
14280
- return 'edit';
14281
- }
14282
- return 'create';
14283
- }
14284
- isCollectionView(kind) {
14285
- return kind === 'VIEW' || kind === 'READ_PROJECTION';
14286
- }
14287
- isWritableFormSurface(kind) {
14288
- return kind === 'FORM' || kind === 'PARTIAL_FORM';
14289
- }
14290
- isReadableFormSurface(kind) {
14291
- return kind === 'VIEW' || kind === 'READ_PROJECTION';
14292
- }
14293
- isReadableItemSurface(surface) {
14294
- return (surface.scope === 'ITEM' &&
14295
- surface.method.toUpperCase() === 'GET' &&
14296
- (surface.kind === 'VIEW' || surface.kind === 'READ_PROJECTION'));
14297
- }
14298
- isItemFormHydrationSurface(surface) {
14299
- return surface.scope === 'ITEM'
14300
- && (this.isReadableFormSurface(surface.kind) || this.isWritableFormSurface(surface.kind));
14301
- }
14302
- resolveItemHydrationReadUrl(surface, resourcePath, discoveryOptions) {
14303
- if (!this.isItemFormHydrationSurface(surface)) {
14304
- return null;
14305
- }
14306
- if (this.isReadableItemSurface(surface)) {
14307
- return this.discovery.resolveHref(surface.path, discoveryOptions);
14308
- }
14309
- return this.discovery.resolveHref(this.buildCanonicalItemHref(resourcePath), discoveryOptions);
14310
- }
14311
- buildCanonicalItemHref(resourcePath) {
14312
- const normalizedResourcePath = this.normalizeResourcePath(resourcePath);
14313
- const itemHref = `${normalizedResourcePath}/{id}`;
14314
- return normalizedResourcePath.startsWith('api/') ? `/${itemHref}` : itemHref;
14315
- }
14316
- buildIdBinding(idBindingPath) {
14317
- return {
14318
- from: idBindingPath,
14319
- to: 'widget.inputs.resourceId',
14320
- mode: 'path',
14321
- };
14322
- }
14323
- clone(value) {
14324
- return value == null ? value : JSON.parse(JSON.stringify(value));
14325
- }
14326
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
14327
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, providedIn: 'any' });
14328
- }
14329
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImport: i0, type: ResourceSurfaceOpenAdapterService, decorators: [{
14330
- type: Injectable,
14331
- args: [{ providedIn: 'any' }]
14332
- }] });
14333
-
14334
14706
  class SurfaceOutletRegistryService {
14335
14707
  registrations = new Map();
14336
14708
  register(registration) {
@@ -15843,7 +16215,7 @@ const DEFAULT_FAMILIES = [
15843
16215
  'kpi',
15844
16216
  'summary-list',
15845
16217
  ];
15846
- const ANALYTIC_TABLE_OPERATIONS = new Set(['group-by', 'timeseries', 'distribution']);
16218
+ const ANALYTIC_TABLE_OPERATIONS = new Set(['group-by', 'timeseries', 'distribution', 'comparison']);
15847
16219
  class AnalyticsPresentationResolver {
15848
16220
  resolve(analytics, options) {
15849
16221
  const projections = analytics?.projections?.filter(Boolean) ?? [];
@@ -16061,6 +16433,12 @@ class SchemaMetadataClient {
16061
16433
  u.searchParams.set('operation', (params.operation || 'get').toLowerCase());
16062
16434
  u.searchParams.set('schemaType', (params.schemaType || 'response').toLowerCase());
16063
16435
  u.searchParams.set('includeInternalSchemas', String(!!params.includeInternalSchemas));
16436
+ if (params.idField) {
16437
+ u.searchParams.set('idField', params.idField);
16438
+ }
16439
+ if (params.readOnly !== undefined) {
16440
+ u.searchParams.set('readOnly', String(params.readOnly));
16441
+ }
16064
16442
  const key = schemaId;
16065
16443
  if (this.inFlight.has(key))
16066
16444
  return this.inFlight.get(key);
@@ -16169,6 +16547,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.14", ngImpo
16169
16547
  args: [API_URL]
16170
16548
  }] }] });
16171
16549
 
16550
+ const COMPARISON_METRIC_OPERATIONS = new Set([
16551
+ 'COUNT',
16552
+ 'DISTINCT_COUNT',
16553
+ 'SUM',
16554
+ ]);
16172
16555
  class AnalyticsStatsRequestBuilderService {
16173
16556
  buildExecutionPlan(projection) {
16174
16557
  const dimension = projection.bindings.primaryDimension;
@@ -16232,6 +16615,26 @@ class AnalyticsStatsRequestBuilderService {
16232
16615
  limit,
16233
16616
  orderBy,
16234
16617
  };
16618
+ case 'comparison': {
16619
+ const period = projection.bindings.comparisonPeriod;
16620
+ if (!period?.field || !period.timezone || !period.preset || !period.mode) {
16621
+ throw new Error(`AnalyticsStatsRequestBuilderService requires bindings.comparisonPeriod for comparison projection "${projection.id}".`);
16622
+ }
16623
+ this.assertComparisonMetrics(metrics, projection.id);
16624
+ return {
16625
+ filter: {},
16626
+ field,
16627
+ periodField: period.field,
16628
+ metrics,
16629
+ period: {
16630
+ preset: period.preset,
16631
+ timezone: period.timezone,
16632
+ mode: period.mode,
16633
+ },
16634
+ limit,
16635
+ orderBy,
16636
+ };
16637
+ }
16235
16638
  default:
16236
16639
  throw new Error(`Unsupported analytics stats operation "${projection.source.operation}".`);
16237
16640
  }
@@ -16244,6 +16647,22 @@ class AnalyticsStatsRequestBuilderService {
16244
16647
  alias: field,
16245
16648
  };
16246
16649
  }
16650
+ assertComparisonMetrics(metrics, projectionId) {
16651
+ const aliases = new Set();
16652
+ for (const metric of metrics) {
16653
+ if (!COMPARISON_METRIC_OPERATIONS.has(metric.operation)) {
16654
+ throw new Error(`Analytics comparison metric operation "${metric.operation}" is not supported for projection "${projectionId}".`);
16655
+ }
16656
+ const alias = metric.alias?.trim();
16657
+ if (!alias) {
16658
+ throw new Error(`Analytics comparison metrics require a non-empty alias for projection "${projectionId}".`);
16659
+ }
16660
+ if (aliases.has(alias)) {
16661
+ throw new Error(`Analytics comparison metric aliases must be unique for projection "${projectionId}": "${alias}".`);
16662
+ }
16663
+ aliases.add(alias);
16664
+ }
16665
+ }
16247
16666
  mapAggregationToBackend(aggregation) {
16248
16667
  switch ((aggregation ?? '').toLowerCase()) {
16249
16668
  case 'avg':
@@ -22212,7 +22631,7 @@ const ValidationPattern = {
22212
22631
 
22213
22632
  function materializeFormLayoutFromMetadata(fields, options = {}) {
22214
22633
  const policy = normalizeLayoutPolicy(options.policy);
22215
- const visibleFields = normalizeFields(fields, options.includeHidden === true);
22634
+ const visibleFields = normalizeFields(resolveRelationPresentationFields(fields, policy), options.includeHidden === true);
22216
22635
  const layoutFields = fieldsForLayoutPolicy(visibleFields, policy);
22217
22636
  const groupedFields = groupFields(layoutFields);
22218
22637
  const sections = groupedFields.map(([groupKey, groupFields], sectionIndex) => createSection(groupKey, groupFields, sectionIndex, policy, options));
@@ -22231,9 +22650,65 @@ function materializeFormLayoutFromMetadata(fields, options = {}) {
22231
22650
  },
22232
22651
  });
22233
22652
  }
22653
+ /**
22654
+ * Detail responses commonly carry both the persisted relationship identifier
22655
+ * (`cargoId`) and a read-only projection (`cargoNome`). A compact detail must
22656
+ * present the projection to people while preserving the identifier for command
22657
+ * schemas and every non-presentation use of the same metadata.
22658
+ *
22659
+ * This intentionally requires the established pair: a select/lookup field
22660
+ * ending in `Id` and its hidden `<base>Nome` projection. It does not infer a
22661
+ * relationship from labels or fabricate a display value when the resource did
22662
+ * not publish one.
22663
+ */
22664
+ function resolveRelationPresentationFields(fields, policy) {
22665
+ if (policy.preset !== 'compactPresentation' || policy.intent === 'command') {
22666
+ return fields;
22667
+ }
22668
+ const byName = new Map(fields
22669
+ .filter((field) => !!field?.name)
22670
+ .map((field) => [field.name, field]));
22671
+ const relationIdsToHide = new Set();
22672
+ const projections = new Map();
22673
+ for (const field of fields) {
22674
+ const relationName = relationPresentationName(field);
22675
+ if (!relationName)
22676
+ continue;
22677
+ const projection = byName.get(relationName);
22678
+ if (!projection || !isFormHidden(projection))
22679
+ continue;
22680
+ relationIdsToHide.add(field.name);
22681
+ projections.set(relationName, {
22682
+ ...projection,
22683
+ label: field.label ?? projection.label,
22684
+ group: field.group ?? projection.group,
22685
+ order: field.order ?? projection.order,
22686
+ width: field.width ?? projection.width,
22687
+ icon: field.icon ?? projection.icon,
22688
+ helpText: field.helpText ?? projection.helpText,
22689
+ formHidden: false,
22690
+ hidden: false,
22691
+ visible: true,
22692
+ });
22693
+ }
22694
+ if (!relationIdsToHide.size)
22695
+ return fields;
22696
+ return fields
22697
+ .filter((field) => !relationIdsToHide.has(field.name))
22698
+ .map((field) => projections.get(field.name) ?? field);
22699
+ }
22700
+ function relationPresentationName(field) {
22701
+ const name = String(field?.name || '').trim();
22702
+ if (!name.endsWith('Id') || !field.optionSource) {
22703
+ return null;
22704
+ }
22705
+ return `${name.slice(0, -2)}Nome`;
22706
+ }
22234
22707
  function normalizeLayoutPolicy(policy) {
22235
- const intent = policy?.intent ?? (policy?.preset === 'groupedCommand' ? 'command' : 'detail');
22236
- const preset = policy?.preset ?? (intent === 'command' ? 'groupedCommand' : 'compactPresentation');
22708
+ const intent = policy?.intent ??
22709
+ (policy?.preset === 'groupedCommand' ? 'command' : 'detail');
22710
+ const preset = policy?.preset ??
22711
+ (intent === 'command' ? 'groupedCommand' : 'compactPresentation');
22237
22712
  return {
22238
22713
  source: policy?.source ?? 'authored',
22239
22714
  preset,
@@ -22254,7 +22729,9 @@ function normalizeFields(fields, includeHidden) {
22254
22729
  }
22255
22730
  function isFormHidden(field) {
22256
22731
  const anyField = field;
22257
- return anyField.formHidden === true || anyField.hidden === true || anyField.visible === false;
22732
+ return (anyField.formHidden === true ||
22733
+ anyField.hidden === true ||
22734
+ anyField.visible === false);
22258
22735
  }
22259
22736
  function groupFields(fields) {
22260
22737
  const groups = new Map();
@@ -22316,7 +22793,13 @@ function createPresentationRows(fields, sectionId, policy) {
22316
22793
  const column = {
22317
22794
  id: `${sectionId}-col-${field.name}`,
22318
22795
  fields: [field.name],
22319
- items: [{ id: `${sectionId}-item-${field.name}`, kind: 'field', fieldName: field.name }],
22796
+ items: [
22797
+ {
22798
+ id: `${sectionId}-item-${field.name}`,
22799
+ kind: 'field',
22800
+ fieldName: field.name,
22801
+ },
22802
+ ],
22320
22803
  span,
22321
22804
  };
22322
22805
  if (packingSpan >= 12) {
@@ -22352,7 +22835,13 @@ function createCommandRows(fields, sectionId) {
22352
22835
  const column = {
22353
22836
  id: `${sectionId}-col-${field.name}`,
22354
22837
  fields: [field.name],
22355
- items: [{ id: `${sectionId}-item-${field.name}`, kind: 'field', fieldName: field.name }],
22838
+ items: [
22839
+ {
22840
+ id: `${sectionId}-item-${field.name}`,
22841
+ kind: 'field',
22842
+ fieldName: field.name,
22843
+ },
22844
+ ],
22356
22845
  span,
22357
22846
  };
22358
22847
  if (mdSpan >= 12) {
@@ -22404,7 +22893,9 @@ function resolveCommandSpan(field) {
22404
22893
  return { xs: 12, sm: 12, md: 6, lg: 6, xl: 6 };
22405
22894
  if (width === 'sm')
22406
22895
  return { xs: 12, sm: 6, md: 4, lg: 4, xl: 4 };
22407
- if (width === 'xs' || controlType.includes('checkbox') || controlType.includes('toggle')) {
22896
+ if (width === 'xs' ||
22897
+ controlType.includes('checkbox') ||
22898
+ controlType.includes('toggle')) {
22408
22899
  return { xs: 12, sm: 6, md: 3, lg: 3, xl: 3 };
22409
22900
  }
22410
22901
  return { xs: 12, sm: 12, md: 6, lg: 6, xl: 6 };
@@ -22422,7 +22913,9 @@ function resolvePresentationRowPackingSpan(span, policy) {
22422
22913
  }
22423
22914
  function hasExplicitWidth(field) {
22424
22915
  const rawWidth = field.width;
22425
- return rawWidth !== undefined && rawWidth !== null && String(rawWidth).trim() !== '';
22916
+ return (rawWidth !== undefined &&
22917
+ rawWidth !== null &&
22918
+ String(rawWidth).trim() !== '');
22426
22919
  }
22427
22920
  function shouldUseSchemaWidth(field, policy, summarySpan) {
22428
22921
  if (!hasExplicitWidth(field)) {
@@ -22560,7 +23053,8 @@ function resolveSummaryPriority(field, summary) {
22560
23053
  const explicitFieldPriority = numberOrNull(summary.fieldPriority?.[field.name] ?? summary.fieldPriority?.[fieldName]);
22561
23054
  if (explicitFieldPriority !== null)
22562
23055
  return explicitFieldPriority;
22563
- const explicitGroupPriority = numberOrNull(summary.groupPriority?.[String(field.group || '')] ?? summary.groupPriority?.[groupName]);
23056
+ const explicitGroupPriority = numberOrNull(summary.groupPriority?.[String(field.group || '')] ??
23057
+ summary.groupPriority?.[groupName]);
22564
23058
  if (explicitGroupPriority !== null)
22565
23059
  return explicitGroupPriority;
22566
23060
  const fieldPriority = numberOrNull(field.summaryPriority ??
@@ -22568,7 +23062,9 @@ function resolveSummaryPriority(field, summary) {
22568
23062
  field.presentationPriority);
22569
23063
  if (fieldPriority !== null)
22570
23064
  return fieldPriority;
22571
- return typeof field.order === 'number' ? field.order : Number.MAX_SAFE_INTEGER;
23065
+ return typeof field.order === 'number'
23066
+ ? field.order
23067
+ : Number.MAX_SAFE_INTEGER;
22572
23068
  }
22573
23069
  function resolveFieldSummaryRoles(field) {
22574
23070
  const raw = [
@@ -22605,7 +23101,9 @@ function numberOrNull(value) {
22605
23101
  if (typeof value === 'number' && Number.isFinite(value)) {
22606
23102
  return value;
22607
23103
  }
22608
- if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
23104
+ if (typeof value === 'string' &&
23105
+ value.trim() !== '' &&
23106
+ Number.isFinite(Number(value))) {
22609
23107
  return Number(value);
22610
23108
  }
22611
23109
  return null;
@@ -22635,7 +23133,9 @@ function resolvePresentationRole(groupName, fields, map) {
22635
23133
  return 'markers';
22636
23134
  if (normalized.includes('regra') || normalized.includes('rule'))
22637
23135
  return 'rules';
22638
- if (fields.some((field) => String(field.controlType || '').toLowerCase().includes('checkbox'))) {
23136
+ if (fields.some((field) => String(field.controlType || '')
23137
+ .toLowerCase()
23138
+ .includes('checkbox'))) {
22639
23139
  return 'markers';
22640
23140
  }
22641
23141
  return 'default';
@@ -28184,7 +28684,10 @@ class DynamicWidgetLoaderDirective {
28184
28684
  this.tryRender();
28185
28685
  }
28186
28686
  ngOnChanges(changes) {
28187
- if (changes['widget'] || changes['context'] || changes['autoWireOutputs']) {
28687
+ if (changes['widget']
28688
+ || changes['ownerWidgetKey']
28689
+ || changes['context']
28690
+ || changes['autoWireOutputs']) {
28188
28691
  this.tryRender();
28189
28692
  }
28190
28693
  }
@@ -28234,7 +28737,7 @@ class DynamicWidgetLoaderDirective {
28234
28737
  if (!def || !def.id)
28235
28738
  return;
28236
28739
  const meta = this.registry.get(def.id);
28237
- const inputs = this.withInferredIdentityInputs(def.id, def.inputs || {}, def.childWidgetKey);
28740
+ const inputs = this.withInferredIdentityInputs(def.id, def.inputs || {}, def.childWidgetKey, this.ownerWidgetKey, meta || undefined);
28238
28741
  this.normalizeMaterializedRuntimeInputs(def.id, inputs, meta || undefined);
28239
28742
  const outputs = def.outputs || {};
28240
28743
  try {
@@ -28380,7 +28883,8 @@ class DynamicWidgetLoaderDirective {
28380
28883
  try {
28381
28884
  const hadPreviousValue = Object.prototype.hasOwnProperty.call(this.boundInputValues, key);
28382
28885
  const previousValue = this.boundInputValues[key];
28383
- if (hadPreviousValue && Object.is(previousValue, value)) {
28886
+ if (hadPreviousValue
28887
+ && this.areInputValuesEquivalent(previousValue, value)) {
28384
28888
  return;
28385
28889
  }
28386
28890
  if (this.isSignalInput(compRef.instance, key)) {
@@ -28471,7 +28975,7 @@ class DynamicWidgetLoaderDirective {
28471
28975
  }
28472
28976
  return value;
28473
28977
  }
28474
- withInferredIdentityInputs(id, inputs, childWidgetKey) {
28978
+ withInferredIdentityInputs(id, inputs, childWidgetKey, ownerWidgetKey, meta) {
28475
28979
  const identityInputByComponent = {
28476
28980
  'praxis-table': 'tableId',
28477
28981
  'praxis-crud': 'crudId',
@@ -28483,16 +28987,96 @@ class DynamicWidgetLoaderDirective {
28483
28987
  'praxis-files-upload': 'filesUploadId',
28484
28988
  };
28485
28989
  const identityInput = identityInputByComponent[id];
28486
- const fallbackId = String(childWidgetKey || '').trim();
28487
- if (!identityInput || !fallbackId) {
28990
+ const normalizedChildWidgetKey = String(childWidgetKey || '').trim();
28991
+ const normalizedOwnerWidgetKey = String(ownerWidgetKey || '').trim();
28992
+ const fallbackId = normalizedChildWidgetKey || normalizedOwnerWidgetKey;
28993
+ const declaredInputs = new Set((meta?.inputs || []).map((input) => input.name));
28994
+ if (!identityInput
28995
+ || !fallbackId
28996
+ || !declaredInputs.has(identityInput)) {
28488
28997
  return { ...inputs };
28489
28998
  }
28490
28999
  return {
28491
29000
  ...inputs,
28492
29001
  [identityInput]: inputs[identityInput] || fallbackId,
28493
- componentInstanceId: inputs['componentInstanceId'] || fallbackId,
29002
+ ...(declaredInputs.has('componentInstanceId')
29003
+ ? { componentInstanceId: inputs['componentInstanceId'] || fallbackId }
29004
+ : {}),
28494
29005
  };
28495
29006
  }
29007
+ areInputValuesEquivalent(previousValue, value) {
29008
+ if (Object.is(previousValue, value)) {
29009
+ return true;
29010
+ }
29011
+ return this.areJsonLikeInputsEqual(previousValue, value, new Set(), new Set());
29012
+ }
29013
+ areJsonLikeInputsEqual(previousValue, value, previousAncestors, valueAncestors) {
29014
+ const previousType = typeof previousValue;
29015
+ const valueType = typeof value;
29016
+ if (previousValue == null
29017
+ || value == null
29018
+ || previousType !== 'object'
29019
+ || valueType !== 'object') {
29020
+ return Object.is(previousValue, value)
29021
+ && previousType !== 'function'
29022
+ && previousType !== 'symbol'
29023
+ && previousType !== 'bigint'
29024
+ && valueType !== 'function'
29025
+ && valueType !== 'symbol'
29026
+ && valueType !== 'bigint';
29027
+ }
29028
+ try {
29029
+ if (Object.getOwnPropertySymbols(previousValue).length > 0
29030
+ || Object.getOwnPropertySymbols(value).length > 0) {
29031
+ return false;
29032
+ }
29033
+ const previousIsArray = Array.isArray(previousValue);
29034
+ const valueIsArray = Array.isArray(value);
29035
+ if (previousIsArray !== valueIsArray) {
29036
+ return false;
29037
+ }
29038
+ if (previousIsArray) {
29039
+ if (previousValue.length !== value.length) {
29040
+ return false;
29041
+ }
29042
+ }
29043
+ else {
29044
+ const previousPrototype = Object.getPrototypeOf(previousValue);
29045
+ const valuePrototype = Object.getPrototypeOf(value);
29046
+ if ((previousPrototype !== Object.prototype && previousPrototype !== null)
29047
+ || (valuePrototype !== Object.prototype && valuePrototype !== null)) {
29048
+ return false;
29049
+ }
29050
+ }
29051
+ if (previousAncestors.has(previousValue)
29052
+ || valueAncestors.has(value)) {
29053
+ return false;
29054
+ }
29055
+ const previousKeys = Object.keys(previousValue);
29056
+ const keys = Object.keys(value);
29057
+ if (previousKeys.length !== keys.length) {
29058
+ return false;
29059
+ }
29060
+ previousAncestors.add(previousValue);
29061
+ valueAncestors.add(value);
29062
+ try {
29063
+ for (const key of previousKeys) {
29064
+ if (!Object.prototype.hasOwnProperty.call(value, key)
29065
+ || !this.areJsonLikeInputsEqual(previousValue[key], value[key], previousAncestors, valueAncestors)) {
29066
+ return false;
29067
+ }
29068
+ }
29069
+ return true;
29070
+ }
29071
+ finally {
29072
+ previousAncestors.delete(previousValue);
29073
+ valueAncestors.delete(value);
29074
+ }
29075
+ }
29076
+ catch {
29077
+ return false;
29078
+ }
29079
+ }
28496
29080
  normalizeMaterializedRuntimeInputs(id, inputs, meta) {
28497
29081
  const allowedInputs = new Set((meta?.inputs || []).map((input) => input.name));
28498
29082
  const accepts = (key) => !allowedInputs.size || allowedInputs.has(key);
@@ -30720,6 +31304,16 @@ class CompositionValidatorService {
30720
31304
  this.nestedPortCatalog = nestedPortCatalog;
30721
31305
  this.jsonLogic = jsonLogic;
30722
31306
  }
31307
+ validateLinks(links, context = {}) {
31308
+ const linkDiagnostics = links.flatMap((link) => this.validateLink(link, {
31309
+ ...context,
31310
+ links,
31311
+ }));
31312
+ return [
31313
+ ...linkDiagnostics,
31314
+ ...this.validateFeedbackCycles(links),
31315
+ ];
31316
+ }
30723
31317
  validateLink(link, context = {}) {
30724
31318
  const diagnostics = [];
30725
31319
  this.validateEndpointDirections(link, diagnostics);
@@ -30998,6 +31592,100 @@ class CompositionValidatorService {
30998
31592
  transformKinds: link.transform.steps.map((step) => step.kind),
30999
31593
  }));
31000
31594
  }
31595
+ validateFeedbackCycles(links) {
31596
+ const edges = links.map((link) => ({
31597
+ link,
31598
+ from: this.feedbackNodeKey(link.from),
31599
+ to: this.feedbackNodeKey(link.to),
31600
+ }));
31601
+ const adjacency = new Map();
31602
+ for (const edge of edges) {
31603
+ const current = adjacency.get(edge.from) ?? [];
31604
+ current.push(edge);
31605
+ adjacency.set(edge.from, current);
31606
+ }
31607
+ const diagnostics = [];
31608
+ const seenCycles = new Set();
31609
+ for (const edge of edges) {
31610
+ const path = this.findFeedbackPath(edge.to, edge.from, adjacency, [edge.link.id]);
31611
+ if (!path) {
31612
+ continue;
31613
+ }
31614
+ const cycleLinkIds = this.stableUniqueLinkIds(path);
31615
+ const cycleKey = cycleLinkIds.join('|');
31616
+ if (seenCycles.has(cycleKey)) {
31617
+ continue;
31618
+ }
31619
+ seenCycles.add(cycleKey);
31620
+ const cycleLinks = cycleLinkIds
31621
+ .map((id) => links.find((link) => link.id === id))
31622
+ .filter((link) => !!link);
31623
+ const guarded = cycleLinks.every((link) => this.isIntentionalGuardedFeedback(link));
31624
+ diagnostics.push(this.createFeedbackCycleDiagnostic(cycleLinks[0] ?? edge.link, cycleLinkIds, guarded));
31625
+ }
31626
+ return diagnostics;
31627
+ }
31628
+ findFeedbackPath(current, target, adjacency, path, visited = new Set()) {
31629
+ if (current === target) {
31630
+ return path;
31631
+ }
31632
+ if (visited.has(current)) {
31633
+ return null;
31634
+ }
31635
+ visited.add(current);
31636
+ for (const edge of adjacency.get(current) ?? []) {
31637
+ const next = this.findFeedbackPath(edge.to, target, adjacency, [...path, edge.link.id], new Set(visited));
31638
+ if (next) {
31639
+ return next;
31640
+ }
31641
+ }
31642
+ return null;
31643
+ }
31644
+ stableUniqueLinkIds(linkIds) {
31645
+ return Array.from(new Set(linkIds)).sort((left, right) => left.localeCompare(right));
31646
+ }
31647
+ feedbackNodeKey(endpoint) {
31648
+ if (endpoint.kind === 'state') {
31649
+ return `state:${endpoint.ref.layer ?? 'values'}:${endpoint.ref.path}`;
31650
+ }
31651
+ if (endpoint.kind === 'global-action') {
31652
+ return `global-action:${endpoint.ref.actionId}`;
31653
+ }
31654
+ const nested = endpoint.ref.nestedPath?.length
31655
+ ? endpoint.ref.nestedPath.map((segment) => [
31656
+ segment.kind,
31657
+ segment.id,
31658
+ segment.key,
31659
+ segment.index,
31660
+ segment.componentType,
31661
+ ].filter((part) => part !== undefined && part !== '').join(':')).join('/')
31662
+ : '';
31663
+ return `component:${endpoint.ref.widget}:${nested}`;
31664
+ }
31665
+ isIntentionalGuardedFeedback(link) {
31666
+ const tags = link.metadata?.tags ?? [];
31667
+ const declaresIntent = tags.includes('intentional-feedback');
31668
+ const hasGuard = !!link.condition
31669
+ || !!link.policy?.distinct
31670
+ || !!link.policy?.distinctBy
31671
+ || typeof link.policy?.debounceMs === 'number';
31672
+ return declaresIntent && hasGuard;
31673
+ }
31674
+ createFeedbackCycleDiagnostic(link, cycleLinkIds, guarded) {
31675
+ const code = guarded
31676
+ ? 'SEMANTIC_FEEDBACK_CYCLE_GUARDED'
31677
+ : 'SEMANTIC_FEEDBACK_CYCLE_UNGUARDED';
31678
+ const message = guarded
31679
+ ? `O ciclo de feedback entre links ${cycleLinkIds.join(', ')} foi declarado como intencional e guardado.`
31680
+ : `A composicao contem ciclo de feedback nao guardado entre links ${cycleLinkIds.join(', ')}.`;
31681
+ return this.createDiagnostic(link, code, guarded ? 'warning' : 'error', message, {
31682
+ kind: 'link',
31683
+ linkId: cycleLinkIds[0],
31684
+ }, {
31685
+ cycleLinkIds,
31686
+ acceptancePath: 'Use metadata.tags com intentional-feedback e ao menos um guard canonico por link: condition, distinct, distinctBy ou debounceMs.',
31687
+ }, !guarded);
31688
+ }
31001
31689
  endpointSemanticKind(endpoint) {
31002
31690
  return endpoint.ref.semanticKind;
31003
31691
  }
@@ -32239,6 +32927,8 @@ class CompositionRuntimeEngine {
32239
32927
  const matchedLinkIds = matchedLinks.map((link) => link.id);
32240
32928
  let workingState = cloneStateSnapshot(this.store.getSnapshot().state);
32241
32929
  const runtimeDiagnostics = this.createNestedWidgetEventBridgeDiagnostics(event, matchedLinks, occurredAt);
32930
+ const blockedFeedbackDiagnostics = this.createFeedbackCycleBlockedDiagnostics(matchedLinks, occurredAt);
32931
+ runtimeDiagnostics.push(...blockedFeedbackDiagnostics.values());
32242
32932
  const linkResults = new Map();
32243
32933
  const trace = [];
32244
32934
  const emittedDiagnosticIds = new Set();
@@ -32257,6 +32947,7 @@ class CompositionRuntimeEngine {
32257
32947
  for (const link of matchedLinks) {
32258
32948
  const previousLink = this.store.getSnapshot().links.find((item) => item.linkId === link.id);
32259
32949
  const stateBefore = cloneStateSnapshot(workingState);
32950
+ const blockedFeedbackDiagnostic = blockedFeedbackDiagnostics.get(link.id);
32260
32951
  trace.push(this.traceService.createEntry(event.eventId, sequence++, {
32261
32952
  timestamp: occurredAt,
32262
32953
  phase: 'source-resolved',
@@ -32275,6 +32966,29 @@ class CompositionRuntimeEngine {
32275
32966
  ...endpointTraceDetails('target', link.to),
32276
32967
  },
32277
32968
  }));
32969
+ if (blockedFeedbackDiagnostic) {
32970
+ trace.push(this.traceService.createEntry(event.eventId, sequence++, {
32971
+ timestamp: occurredAt,
32972
+ phase: 'delivery-skipped',
32973
+ linkId: link.id,
32974
+ subject: {
32975
+ kind: 'link',
32976
+ linkId: link.id,
32977
+ label: link.metadata?.label,
32978
+ },
32979
+ inputSnapshot: stateBefore,
32980
+ outputSnapshot: workingState,
32981
+ details: {
32982
+ status: 'skipped',
32983
+ skippedReason: 'feedback-cycle-blocked',
32984
+ ...endpointTraceDetails('source', link.from),
32985
+ ...endpointTraceDetails('target', link.to),
32986
+ },
32987
+ diagnostics: [blockedFeedbackDiagnostic.code],
32988
+ }));
32989
+ sequence = this.appendDiagnosticTraceEntries(trace, emittedDiagnosticIds, event.eventId, sequence, [blockedFeedbackDiagnostic], link.id);
32990
+ continue;
32991
+ }
32278
32992
  if (link.transform) {
32279
32993
  trace.push(this.traceService.createEntry(event.eventId, sequence++, {
32280
32994
  timestamp: occurredAt,
@@ -32377,11 +33091,19 @@ class CompositionRuntimeEngine {
32377
33091
  stateSnapshot: materializedState,
32378
33092
  linkPatches: matchedLinks.map((link) => ({
32379
33093
  linkId: link.id,
32380
- status: linkResults.get(link.id)?.status,
32381
- lastEventAt: linkResults.has(link.id) ? occurredAt : undefined,
32382
- lastDispatchTraceId: linkResults.has(link.id) ? event.eventId : undefined,
33094
+ status: blockedFeedbackDiagnostics.has(link.id)
33095
+ ? 'skipped'
33096
+ : linkResults.get(link.id)?.status,
33097
+ lastEventAt: linkResults.has(link.id) || blockedFeedbackDiagnostics.has(link.id)
33098
+ ? occurredAt
33099
+ : undefined,
33100
+ lastDispatchTraceId: linkResults.has(link.id) || blockedFeedbackDiagnostics.has(link.id)
33101
+ ? event.eventId
33102
+ : undefined,
32383
33103
  lastDeliveredValuePreview: linkResults.get(link.id)?.value,
32384
- diagnostics: linkResults.get(link.id)?.diagnostics,
33104
+ diagnostics: blockedFeedbackDiagnostics.has(link.id)
33105
+ ? [blockedFeedbackDiagnostics.get(link.id)]
33106
+ : linkResults.get(link.id)?.diagnostics,
32385
33107
  })),
32386
33108
  diagnostics,
32387
33109
  traceTail: trace,
@@ -32589,10 +33311,10 @@ class CompositionRuntimeEngine {
32589
33311
  .filter((widget) => !!widget),
32590
33312
  };
32591
33313
  const links = definition.links;
32592
- return links.flatMap((link) => this.compositionValidator.validateLink(link, {
33314
+ return this.compositionValidator.validateLinks(links, {
32593
33315
  page,
32594
33316
  links,
32595
- }));
33317
+ });
32596
33318
  }
32597
33319
  createDerivedDiagnostic(definition, message, index) {
32598
33320
  const nodeKey = this.extractDerivedNodeKey(message);
@@ -32706,6 +33428,42 @@ class CompositionRuntimeEngine {
32706
33428
  createdAt: occurredAt,
32707
33429
  }));
32708
33430
  }
33431
+ createFeedbackCycleBlockedDiagnostics(matchedLinks, occurredAt) {
33432
+ const blocked = new Map();
33433
+ const semanticDiagnostics = this.store.getSnapshot().diagnostics.filter((diagnostic) => diagnostic.code === 'SEMANTIC_FEEDBACK_CYCLE_UNGUARDED');
33434
+ for (const diagnostic of semanticDiagnostics) {
33435
+ const cycleLinkIds = Array.isArray(diagnostic.details?.['cycleLinkIds'])
33436
+ ? diagnostic.details?.['cycleLinkIds'].filter((id) => typeof id === 'string')
33437
+ : [];
33438
+ for (const link of matchedLinks) {
33439
+ if (!cycleLinkIds.includes(link.id) || blocked.has(link.id)) {
33440
+ continue;
33441
+ }
33442
+ blocked.set(link.id, {
33443
+ id: `${link.id}:RUNTIME_FEEDBACK_CYCLE_BLOCKED`,
33444
+ code: 'RUNTIME_FEEDBACK_CYCLE_BLOCKED',
33445
+ severity: 'error',
33446
+ phase: 'runtime-dispatch',
33447
+ message: `O link ${link.id} nao foi executado porque pertence a um ciclo de feedback nao guardado.`,
33448
+ summary: `Link ${link.id} bloqueado por ciclo de feedback.`,
33449
+ subject: {
33450
+ kind: 'link',
33451
+ linkId: link.id,
33452
+ label: link.metadata?.label,
33453
+ },
33454
+ details: {
33455
+ cycleLinkIds,
33456
+ semanticDiagnosticId: diagnostic.id,
33457
+ },
33458
+ source: 'runtime-engine',
33459
+ blocking: true,
33460
+ transient: true,
33461
+ createdAt: occurredAt,
33462
+ });
33463
+ }
33464
+ }
33465
+ return blocked;
33466
+ }
32709
33467
  }
32710
33468
  function isComponentPortEndpoint(endpoint) {
32711
33469
  return endpoint.kind === 'component-port';
@@ -33674,6 +34432,8 @@ class DynamicWidgetPageComponent {
33674
34432
  effectiveValues: {},
33675
34433
  diagnostics: [],
33676
34434
  };
34435
+ configEditorContextRequests = new Map();
34436
+ nextConfigEditorContextRequestId = 0;
33677
34437
  layout;
33678
34438
  canvas;
33679
34439
  grouping = [];
@@ -33819,14 +34579,17 @@ class DynamicWidgetPageComponent {
33819
34579
  let widgets = this.cloneWidgets(pageWithPatchedInputs.widgets || []);
33820
34580
  const directDelivery = this.applyCompositionWidgetDeliveries(widgets, cycle);
33821
34581
  widgets = directDelivery.widgets;
33822
- const projectedWidgets = this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
33823
- widgets,
33824
- state: cycle.snapshot.state,
33825
- now: cycle.snapshot.generatedAt,
33826
- }).widgets;
34582
+ const projectedWidgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
33827
34583
  const stateProjectionChanged = !this.areStateValuesEqual(widgets, projectedWidgets);
33828
34584
  widgets = projectedWidgets;
33829
34585
  const nextRuntime = this.buildStateRuntime(state, pageWithPatchedInputs.context);
34586
+ if (this.isTransientOnlyCompositionCycle(cycle)
34587
+ && !updatedPrimaryStatePaths.length
34588
+ && !directDelivery.changed
34589
+ && !widgetInputPatchResult.changed) {
34590
+ this.applyResponsivePresentation(pageWithPatchedInputs, widgets, nextRuntime);
34591
+ return;
34592
+ }
33830
34593
  if (!updatedPrimaryStatePaths.length &&
33831
34594
  !directDelivery.changed &&
33832
34595
  !stateProjectionChanged &&
@@ -33835,6 +34598,17 @@ class DynamicWidgetPageComponent {
33835
34598
  }
33836
34599
  this.applyPageUpdate({ ...pageWithPatchedInputs, widgets, state }, true, nextRuntime, false);
33837
34600
  }
34601
+ isTransientOnlyCompositionCycle(cycle) {
34602
+ if (!this.compositionDefinition || !cycle.matchedLinkIds.length) {
34603
+ return false;
34604
+ }
34605
+ const linksById = new Map(this.compositionDefinition.links.map((link) => [link.id, link]));
34606
+ return cycle.matchedLinkIds.every((linkId) => {
34607
+ const link = linksById.get(linkId);
34608
+ return link?.to.kind === 'state'
34609
+ && (link.to.ref.layer ?? 'values') === 'transient';
34610
+ });
34611
+ }
33838
34612
  applyWidgetInputPatchToPage(page, widgetKey, evt) {
33839
34613
  const payload = evt?.payload;
33840
34614
  let inputPatch = this.extractWidgetInputPatch(payload);
@@ -34483,11 +35257,7 @@ class DynamicWidgetPageComponent {
34483
35257
  let widgets = this.cloneWidgets(page.widgets || []);
34484
35258
  widgets = this.applyCompositionWidgetDeliveries(widgets, cycle).widgets;
34485
35259
  if (this.compositionDefinition) {
34486
- widgets = this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
34487
- widgets,
34488
- state: cycle.snapshot.state,
34489
- now: cycle.snapshot.generatedAt,
34490
- }).widgets;
35260
+ widgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
34491
35261
  }
34492
35262
  const runtime = this.buildStateRuntime(state, page.context);
34493
35263
  return {
@@ -34604,11 +35374,7 @@ class DynamicWidgetPageComponent {
34604
35374
  const state = this.stateFromCompositionSnapshot(page.state, cycle.snapshot.state.primaryValues);
34605
35375
  let widgets = this.cloneWidgets(page.widgets || []);
34606
35376
  widgets = this.applyCompositionWidgetDeliveries(widgets, cycle).widgets;
34607
- widgets = this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
34608
- widgets,
34609
- state: cycle.snapshot.state,
34610
- now: cycle.snapshot.generatedAt,
34611
- }).widgets;
35377
+ widgets = this.projectPersistentCompositionStateInputs(widgets, cycle.snapshot.state, cycle.snapshot.generatedAt);
34612
35378
  const runtime = this.buildStateRuntime(state, page.context);
34613
35379
  this.applyPageUpdate({ ...page, widgets, state }, true, runtime, false, true);
34614
35380
  }
@@ -35308,20 +36074,28 @@ class DynamicWidgetPageComponent {
35308
36074
  ref.applied$.subscribe((result) => this.applyWidgetShell(key, result, false));
35309
36075
  ref.saved$.subscribe((result) => this.applyWidgetShell(key, result, true));
35310
36076
  }
35311
- openWidgetComponentSettings(key) {
35312
- if (this.dispatchWidgetComponentSettingsToRuntime(key)) {
36077
+ async openWidgetComponentSettings(key) {
36078
+ if (!this.settingsPanel) {
36079
+ this.dispatchWidgetComponentSettingsToRuntime(key);
35313
36080
  return;
35314
36081
  }
35315
- if (!this.settingsPanel)
35316
- return;
35317
36082
  const page = this.materializeRuntimeInputsForWidget(this.ensurePageDefinition(), key);
35318
36083
  const widget = page.widgets.find((w) => w.key === key);
35319
36084
  if (!widget)
35320
36085
  return;
35321
36086
  const metadata = this.componentMetadata?.get(widget.definition?.id || '');
35322
36087
  const editor = metadata?.configEditor?.component;
35323
- if (!editor)
36088
+ if (!editor) {
36089
+ this.dispatchWidgetComponentSettingsToRuntime(key);
36090
+ return;
36091
+ }
36092
+ const persistedInputs = this.cloneStateValues(widget.definition.inputs || {});
36093
+ const requestId = ++this.nextConfigEditorContextRequestId;
36094
+ this.configEditorContextRequests.set(key, requestId);
36095
+ const contextResult = await this.resolveWidgetConfigEditorContext(metadata, page, widget, key, persistedInputs);
36096
+ if (this.configEditorContextRequests.get(key) !== requestId) {
35324
36097
  return;
36098
+ }
35325
36099
  const ref = this.settingsPanel.open({
35326
36100
  id: `dynamic-page-component:${key}`,
35327
36101
  title: metadata.configEditor?.title ||
@@ -35330,7 +36104,9 @@ class DynamicWidgetPageComponent {
35330
36104
  content: {
35331
36105
  component: editor,
35332
36106
  inputs: {
35333
- inputs: this.cloneStateValues(widget.definition.inputs || {}),
36107
+ inputs: persistedInputs,
36108
+ context: contextResult.context ?? {},
36109
+ contextDiagnostics: contextResult.diagnostics ?? [],
35334
36110
  widgetKey: key,
35335
36111
  widgetType: widget.definition?.id,
35336
36112
  },
@@ -35339,6 +36115,53 @@ class DynamicWidgetPageComponent {
35339
36115
  ref.applied$.subscribe((result) => this.applyWidgetComponentInputs(key, result, false));
35340
36116
  ref.saved$.subscribe((result) => this.applyWidgetComponentInputs(key, result, true));
35341
36117
  }
36118
+ async resolveWidgetConfigEditorContext(metadata, page, widget, key, persistedInputs) {
36119
+ const resolver = metadata.configEditor?.contextResolver;
36120
+ if (!resolver) {
36121
+ return {};
36122
+ }
36123
+ try {
36124
+ const resolved = await resolver({
36125
+ componentId: metadata.id,
36126
+ widgetKey: key,
36127
+ widgetType: widget.definition?.id,
36128
+ persistedInputs: this.cloneStateValues(persistedInputs),
36129
+ page: this.cloneStateValues(page),
36130
+ widget: this.cloneStateValues(widget),
36131
+ metadata,
36132
+ });
36133
+ if (this.isConfigEditorContextResult(resolved)) {
36134
+ return {
36135
+ context: this.cloneStateValues(resolved.context ?? {}),
36136
+ diagnostics: Array.isArray(resolved.diagnostics)
36137
+ ? resolved.diagnostics.map((diagnostic) => ({ ...diagnostic }))
36138
+ : [],
36139
+ };
36140
+ }
36141
+ return {
36142
+ context: this.cloneStateValues(resolved ?? {}),
36143
+ diagnostics: [],
36144
+ };
36145
+ }
36146
+ catch (error) {
36147
+ return {
36148
+ context: {},
36149
+ diagnostics: [
36150
+ {
36151
+ code: 'config-editor-context-resolution-failed',
36152
+ severity: 'error',
36153
+ message: error instanceof Error ? error.message : String(error),
36154
+ },
36155
+ ],
36156
+ };
36157
+ }
36158
+ }
36159
+ isConfigEditorContextResult(value) {
36160
+ return !!value &&
36161
+ typeof value === 'object' &&
36162
+ !Array.isArray(value) &&
36163
+ ('context' in value || 'diagnostics' in value);
36164
+ }
35342
36165
  materializeRuntimeInputsForWidget(page, key) {
35343
36166
  const normalizedKey = String(key || '').trim();
35344
36167
  if (!normalizedKey) {
@@ -35887,7 +36710,8 @@ class DynamicWidgetPageComponent {
35887
36710
  };
35888
36711
  }
35889
36712
  applyResponsivePresentation(pageDefinition, widgets, runtime) {
35890
- const effective = this.resolveEffectivePresentation(pageDefinition, widgets);
36713
+ const runtimeWidgets = this.projectRuntimeCompositionStateInputs(widgets);
36714
+ const effective = this.resolveEffectivePresentation(pageDefinition, runtimeWidgets);
35891
36715
  if (effective.canvas) {
35892
36716
  this.applyCanvasLayout(effective.canvas, effective.layout, effective.grouping);
35893
36717
  }
@@ -35904,6 +36728,36 @@ class DynamicWidgetPageComponent {
35904
36728
  this.renderedGroups.set(effective.groups);
35905
36729
  this.widgets.set(this.resolveShellTemplates(effective.widgets, runtime));
35906
36730
  }
36731
+ projectPersistentCompositionStateInputs(widgets, state, now) {
36732
+ if (!this.compositionDefinition) {
36733
+ return this.cloneWidgets(widgets);
36734
+ }
36735
+ return this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
36736
+ widgets,
36737
+ state: {
36738
+ primaryValues: this.cloneStateValues(state.primaryValues),
36739
+ derivedValues: this.cloneStateValues(state.derivedValues),
36740
+ transientValues: {},
36741
+ changedPaths: state.changedPaths ? [...state.changedPaths] : [],
36742
+ diagnostics: [...state.diagnostics],
36743
+ },
36744
+ now,
36745
+ }).widgets;
36746
+ }
36747
+ projectRuntimeCompositionStateInputs(widgets) {
36748
+ if (!this.compositionDefinition) {
36749
+ return widgets;
36750
+ }
36751
+ const snapshot = this.compositionRuntime.getSnapshot();
36752
+ if (!Object.keys(snapshot.state.transientValues || {}).length) {
36753
+ return widgets;
36754
+ }
36755
+ return this.compositionRuntime.projectStateWidgetInputs(this.compositionDefinition, {
36756
+ widgets,
36757
+ state: snapshot.state,
36758
+ now: snapshot.generatedAt,
36759
+ }).widgets;
36760
+ }
35907
36761
  resolveEffectivePresentation(pageDefinition, widgets) {
35908
36762
  const variant = this.resolveDeviceVariant(pageDefinition?.deviceLayouts);
35909
36763
  const layout = this.mergeLayout(pageDefinition?.layout, variant?.layout);
@@ -40113,4 +40967,4 @@ function provideHookWhitelist(allowed) {
40113
40967
  * Generated bundle index. Do not edit.
40114
40968
  */
40115
40969
 
40116
- export { API_CONFIG_STORAGE_OPTIONS, API_URL, ASYNC_CONFIG_STORAGE, AllowedFileTypes, AnalyticsPresentationResolver, AnalyticsSchemaContractService, AnalyticsStatsRequestBuilderService, ApiConfigStorage, ApiEndpoint, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, BUILTIN_SHELL_PRESETS, COMPONENT_METADATA_REGISTRY_I18N_CONFIG, COMPONENT_METADATA_REGISTRY_I18N_NAMESPACE, CONFIG_STORAGE, CONNECTION_STORAGE, ComponentKeyService, ComponentMetadataRegistry, CompositionRuntimeFacade, ConsoleLoggerSink, CrudOperationResolutionService, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, DEFAULT_JSON_LOGIC_OPERATORS, DEFAULT_TABLE_CONFIG, DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK, DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION, DYNAMIC_PAGE_AI_CAPABILITIES, DYNAMIC_PAGE_COMPONENT_CONTEXT_PACK, DYNAMIC_PAGE_CONFIG_EDITOR, DYNAMIC_PAGE_SHELL_EDITOR, DefaultLoadingRenderer, DeferredAsyncConfigStorage, DomainCatalogService, DomainKnowledgeService, DomainRuleService, DynamicFormService, DynamicWidgetLoaderDirective, DynamicWidgetPageComponent, EDITORIAL_ALLOWED_CONTENT_FORMATS, EDITORIAL_COMPLIANCE_PRESETS, EDITORIAL_EXTERNAL_LINK_REL, EDITORIAL_FORM_TEMPLATE_CATALOG, EDITORIAL_HTML_ENABLED, EDITORIAL_MARKDOWN_IMAGES_ENABLED, EDITORIAL_SOLUTION_CATALOG, EDITORIAL_SOLUTION_PRESETS, EDITORIAL_THEME_PRESETS, EDITORIAL_WIDGET_CONVENTION_INPUTS, EDITORIAL_WIDGET_TAG, EMPLOYEE_ONBOARDING_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_EDITORIAL_TEMPLATE, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_TEMPLATE, EVENT_REGISTRATION_EDITORIAL_SOLUTION, EVENT_REGISTRATION_EDITORIAL_TEMPLATE, EmptyStateCardComponent, EnterpriseRuntimeContextService, ErrorMessageService, FIELD_METADATA_CAPABILITIES, FIELD_SELECTOR_REGISTRY_BASE, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, FIELD_SELECTOR_REGISTRY_OVERRIDES, FORM_HOOKS, FORM_HOOKS_PRESETS, FORM_HOOKS_WHITELIST, FORM_HOOK_RESOLVERS, FieldControlType, FieldDataType, FieldSelectorRegistry, FormHooksRegistry, GLOBAL_ACTION_CATALOG, GLOBAL_ACTION_HANDLERS, GLOBAL_ACTION_UI_SCHEMAS, GLOBAL_ANALYTICS_SERVICE, GLOBAL_API_CLIENT, GLOBAL_CONFIG, GLOBAL_DIALOG_SERVICE, GLOBAL_ROUTE_GUARD_RESOLVER, GLOBAL_SURFACE_SERVICE, GLOBAL_TOAST_SERVICE, GenericCrudService, GlobalActionService, GlobalConfigService, INLINE_FILTER_ALIAS_TOKENS, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_SET, INLINE_FILTER_CONTROL_TYPE_VALUES, INLINE_FILTER_TOKEN_TO_BASE_CONTROL_TYPE, INLINE_FILTER_TOKEN_TO_CONTROL_TYPE, IconPickerService, IconPosition, IconSize, LOGGER_LEVEL_BY_ENV, LOGGER_LEVEL_PRIORITY, LoadingOrchestrator, LocalConnectionStorage, LocalStorageAsyncAdapter, LocalStorageCacheAdapter, LocalStorageConfigService, LoggerService, LoggerThrottleTracker, LoggerWarnOnceTracker, MemoryCacheAdapter, NestedPortCatalogService, NestedWidgetConfigAccessor, NumericFormat, OVERLAY_DECIDER_DEBUG, OVERLAY_DECISION_MATRIX, ObservabilityDashboardService, OverlayDeciderService, PRAXIS_COLLECTION_EXPORT_HTTP_OPTIONS, PRAXIS_COLLECTION_EXPORT_PROVIDER, PRAXIS_CORPORATE_SENSITIVE_KEYS, PRAXIS_DEFAULT_EXPORT_SECURITY_POLICY, PRAXIS_DEFAULT_OBSERVABILITY_ALERT_RULES, PRAXIS_DYNAMIC_PAGE_COMPONENT_METADATA, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_OPTIONS, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_READY, PRAXIS_EXPORT_FORMULA_PREFIXES, PRAXIS_EXPORT_SECURITY_POLICY, PRAXIS_FOOTER_LINKS_METADATA, PRAXIS_GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_OPTIONS, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_READY, PRAXIS_GLOBAL_CONFIG_TENANT_RESOLVER, PRAXIS_HERO_BANNER_METADATA, PRAXIS_I18N_CONFIG, PRAXIS_I18N_TRANSLATOR, PRAXIS_JSON_LOGIC_OPERATORS, PRAXIS_LAYER_SCALE_DEFAULTS, PRAXIS_LAYER_SCALE_VARS, PRAXIS_LEGAL_NOTICE_METADATA, PRAXIS_LOADING_CTX, PRAXIS_LOADING_RENDERER, PRAXIS_LOGGER_CONFIG, PRAXIS_LOGGER_SINKS, PRAXIS_OBSERVABILITY_DASHBOARD_OPTIONS, PRAXIS_QUERY_FILTER_EXPRESSION_SCHEMA_VERSION, PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA, PRAXIS_RICH_TEXT_BLOCK_METADATA, PRAXIS_TABLE_DETAIL_INLINE_NODE_RESOLVERS, PRAXIS_TABLE_DETAIL_INLINE_RENDERERS, PRAXIS_TELEMETRY_TRANSPORT, PRAXIS_THEME_SURFACE_DEFAULTS, PRAXIS_THEME_SURFACE_VARS, PRAXIS_USER_CONTEXT_SUMMARY_METADATA, PRIVACY_CONSENT_EDITORIAL_SOLUTION, PRIVACY_CONSENT_EDITORIAL_TEMPLATE, PraxisCollectionExportService, PraxisCore, PraxisFooterLinksComponent, PraxisGlobalErrorHandler, PraxisHeroBannerComponent, PraxisHttpCollectionExportProvider, PraxisI18nService, PraxisIconDirective, PraxisIconPickerComponent, PraxisJsonLogicError, PraxisJsonLogicService, PraxisLayerScaleStyleService, PraxisLegalNoticeComponent, PraxisLoadingInterceptor, PraxisRelatedResourceOutletComponent, PraxisResourceIdentityComponent, PraxisRichTextBlockComponent, PraxisRuntimeComponentObservationRegistryService, PraxisSurfaceHostComponent, PraxisUserContextSummaryComponent, RESOURCE_DISCOVERY_I18N_CONFIG, RESOURCE_DISCOVERY_I18N_NAMESPACE, RULE_PROPERTY_SCHEMA, RelatedResourceSurfaceResolverService, RemoteConfigStorage, ResourceActionOpenAdapterService, ResourceDiscoveryService, ResourceQuickConnectComponent, ResourceSurfaceOpenAdapterService, SCHEMA_VIEWER_CONTEXT, SETTINGS_PANEL_BRIDGE, SETTINGS_PANEL_DATA, STEPPER_CONFIG_EDITOR, SURFACE_DRAWER_BRIDGE, SURFACE_OPEN_I18N_CONFIG, SURFACE_OPEN_I18N_NAMESPACE, SURFACE_OPEN_PRESETS, SchemaMetadataClient, SchemaNormalizerService, SchemaViewerComponent, SurfaceBindingRuntimeService, SurfaceOpenActionEditorComponent, SurfaceOpenMaterializerService, SurfaceOutletRegistryService, TABLE_CONFIG_EDITOR, TableConfigService, TelemetryLoggerSink, TelemetryService, ValidationPattern, WidgetPageStateRuntimeService, WidgetShellComponent, applyLocalCustomizations$2 as applyLocalCustomizations, applyLocalCustomizations$1 as applyLocalFormCustomizations, assertPraxisCollectionExportArtifact, assertPraxisRuntimeComponentObservationSerializable, buildAngularValidators, buildApiUrl, buildBaseColumnFromDef, buildBaseFormField, buildFormConfigFromEditorialTemplate, buildHeaders, buildPageKey, buildPraxisEffectDistinctKey, buildPraxisLayerScaleCss, buildPraxisThemeSurfaceCss, buildSchemaId, buildSchemaIdStorageKeySegment, buildValidatorsFromValidatorOptions, cancelIfCpfInvalidHook, clampRange, classifyEntityLookupResult, clonePraxisRuntimeComponentObservation, cloneTableConfig, cnpjAlphaValidator, collapseWhitespace, composeHeadersWithVersion, conditionalAsyncValidator, convertFormLayoutToConfig, createCorporateLoggerConfig, createCorporateObservabilityOptions, createCpfCnpjValidator, createDefaultFormConfig, createDefaultTableConfig, createEmptyFormConfig, createEmptyRichContentDocument, createFieldLayoutItem, createPersistedPage, customAsyncValidatorFn, customValidatorFn, debounceAsyncValidator, deepMerge, domainKnowledgeTimelineToRichContentDocument, domainRuleTimelineToRichContentDocument, ensureIds, ensureNoConflictsHookFactory, ensurePageIds, escapePraxisExportCell, evaluateFieldAccess, extractNormalizedError, fetchWithETag, fileTypeValidator, fillUndefined, generateId, getDefaultFormHints, getEditorialCompliancePresetById, getEditorialFormTemplateById, getEditorialFormTemplateCatalog, getEditorialSolutionById, getEditorialSolutionCatalog, getEditorialSolutionPresetById, getEditorialThemePresetById, getEssentialConfig, getFieldMetadataCapabilities, getFormColumnFieldNames, getFormLayoutFieldNames, getGlobalActionCatalog, getGlobalActionPayloadActualType, getGlobalActionPayloadTypeIssue, getGlobalActionUiSchema, getMissingGlobalActionPayloadKeys, getReferencedFieldMetadata, getRequiredGlobalActionPayloadKeys, getTextTransformer, hasMeaningfulGlobalActionPayloadValue, hasPraxisCollectionExportArtifact, interpolatePraxisTranslation, isAllowedEditorialContentFormat, isAllowedEditorialHref, isCssTextTransform, isEditorialComponentMeta, isEntityLookupMultiplePayloadMode, isEntityLookupPayloadMode, isEntityLookupPayloadModeCompatible, isEntityLookupResultSelectable, isEntityLookupSinglePayloadMode, isFormLayoutItem, isGlobalActionRef, isInlineFilterControlType, isLookupDialogSize, isLookupFilterFieldType, isLookupFilterOperator, isPraxisPresentationVisualizationTableSafe, isPraxisRuntimeGlobalActionEffect, isProgrammaticDateRangePreset, isRangeValidForFilter, isRequiredGlobalActionParamPayloadMissing, isRequiredGlobalActionPayloadMissing, isStaticDateRangePreset, isTableConfigV2, isValidFormConfig, isValidTableConfig, legacyCnpjValidator, legacyCpfValidator, logOnErrorHook, mapFieldDefinitionToMetadata, mapFieldDefinitionsToMetadata, matchFieldValidator, materializeFormLayoutFromMetadata, materializeResourceIdentity, maxFileSizeValidator, mergeFieldMetadata, mergePraxisI18nConfigs, mergeTableConfigs, migrateFormLayoutRule, migrateLegacyCompositionLink, migrateLegacyCompositionLinks, minWordsValidator, normalizeControlTypeKey, normalizeControlTypeToken, normalizeEditorialLink, normalizeEnd, normalizeFieldAccessMetadata, normalizeFieldConstraints, normalizeFieldPresentation, normalizeFormConfig, normalizeFormLayoutItems, normalizeFormMetadata, normalizeGlobalActionRef$1 as normalizeGlobalActionRef, normalizeLayoutPolicy, normalizeLookupFilterRequest, normalizePath, normalizePraxisDataQueryContext, normalizePraxisEffectPolicy, normalizePraxisPresentationVisualization, normalizePraxisQueryFilterExpression, normalizePraxisQueryFilterNode, normalizeResourceAvailabilityReasonCode, normalizeResourceIdentityContract, normalizeStart, normalizeUnknownError, normalizeWidgetEventPath, notifySuccessHook, parseJsonResponseOrEmpty, praxisLoadingInterceptorFn, prefillFromContextHook, provideDefaultFormHooks, provideFieldSelectorRegistryBase, provideFieldSelectorRegistryOverride, provideFieldSelectorRegistryRuntime, provideFormHookPresets, provideFormHooks, provideGlobalActionCatalog, provideGlobalActionHandler, provideGlobalConfig, provideGlobalConfigReady, provideGlobalConfigSeed, provideGlobalConfigTenant, provideHookResolvers, provideHookWhitelist, provideOverlayDecisionMatrix, providePraxisAnalyticsGlobalActions, providePraxisCollectionExportProvider, providePraxisDynamicPageMetadata, providePraxisEnterpriseRuntimeContext, providePraxisFooterLinksMetadata, providePraxisGlobalActionCatalog, providePraxisGlobalActions, providePraxisGlobalConfigBootstrap, providePraxisHeroBannerMetadata, providePraxisHttpCollectionExportProvider, providePraxisHttpLoading, providePraxisI18n, providePraxisI18nConfig, providePraxisI18nTranslator, providePraxisIconDefaults, providePraxisJsonLogicOperator, providePraxisJsonLogicOperatorOverride, providePraxisLegalNoticeMetadata, providePraxisLoadingDefaults, providePraxisLogging, providePraxisRelatedResourceOutletMetadata, providePraxisRichTextBlockMetadata, providePraxisToastGlobalActions, providePraxisUserContextSummaryMetadata, provideRemoteGlobalConfig, readPraxisExportValue, reconcileFilterConfig, reconcileFormConfig, reconcileTableConfig, registerPraxisRuntimeComponentObservation, removeDiacritics, renderPraxisPresentationVisualizationHtml, reportTelemetryHookFactory, requiredCheckedValidator, requiredPresenceValidator, resolveBuiltinPresets, resolveColumnTypeFromFieldDefinition, resolveControlTypeAlias, resolveDateRangeShortcutPreset, resolveDateRangeShortcutPresets, resolveDefaultValuePresentationFormat, resolveEntityLookupPayloadMode, resolveFieldPresentation, resolveHidden, resolveInlineFilterControlType, resolveInlineFilterControlTypeToBaseControlType, resolveLoggerConfig, resolveObservabilityOptions, resolveOffset, resolveOrder, resolvePraxisCollectionExportItems, resolvePraxisExportFields, resolvePraxisExportScope, resolvePraxisFilterCriteria, resolveResourceAvailabilityReasonKey, resolveSpan, resolveTextMaskFormat, resolveTextMaskFormatFromFieldDefinition, resolveValuePresentation, resolveValuePresentationLocale, serializeEntityLookupValueForPayload, serializeOptionSourceFilterRequest, serializePraxisCollectionToCsv, serializePraxisCollectionToExcel, serializePraxisCollectionToJson, slugify, staticDateRangePresetToPreset, stripMasksHook, supportsImplicitValuePresentation, syncWithServerMetadata, toCamel, toCapitalize, toKebab, toPascal, toSentenceCase, toSnake, toTitleCase, translateResourceAvailabilityReason, translateResourceDiscoveryText, translateUnavailableWorkflowMessage, trim, uniqueAsyncValidator, urlValidator, validateGlobalActionRef, validateGlobalActionRefs, withFormConfigSections, withMessage, withPraxisHttpLoading };
40970
+ export { API_CONFIG_STORAGE_OPTIONS, API_URL, ASYNC_CONFIG_STORAGE, AllowedFileTypes, AnalyticsPresentationResolver, AnalyticsSchemaContractService, AnalyticsStatsRequestBuilderService, ApiConfigStorage, ApiEndpoint, BUILTIN_PAGE_LAYOUT_PRESETS, BUILTIN_PAGE_THEME_PRESETS, BUILTIN_SHELL_PRESETS, COMPONENT_METADATA_REGISTRY_I18N_CONFIG, COMPONENT_METADATA_REGISTRY_I18N_NAMESPACE, CONFIG_STORAGE, CONNECTION_STORAGE, ComponentKeyService, ComponentMetadataRegistry, CompositionRuntimeFacade, ConsoleLoggerSink, CrudOperationResolutionService, DEFAULT_FIELD_SELECTOR_CONTROL_TYPE_MAP, DEFAULT_JSON_LOGIC_OPERATORS, DEFAULT_TABLE_CONFIG, DOMAIN_CATALOG_COMPONENT_CONTEXT_PACK, DOMAIN_CATALOG_CONTEXT_HINT_SCHEMA_VERSION, DYNAMIC_PAGE_AI_CAPABILITIES, DYNAMIC_PAGE_COMPONENT_CONTEXT_PACK, DYNAMIC_PAGE_CONFIG_EDITOR, DYNAMIC_PAGE_SHELL_EDITOR, DefaultLoadingRenderer, DeferredAsyncConfigStorage, DomainCatalogService, DomainKnowledgeService, DomainRuleService, DynamicFormService, DynamicWidgetLoaderDirective, DynamicWidgetPageComponent, EDITORIAL_ALLOWED_CONTENT_FORMATS, EDITORIAL_COMPLIANCE_PRESETS, EDITORIAL_EXTERNAL_LINK_REL, EDITORIAL_FORM_TEMPLATE_CATALOG, EDITORIAL_HTML_ENABLED, EDITORIAL_MARKDOWN_IMAGES_ENABLED, EDITORIAL_SOLUTION_CATALOG, EDITORIAL_SOLUTION_PRESETS, EDITORIAL_THEME_PRESETS, EDITORIAL_WIDGET_CONVENTION_INPUTS, EDITORIAL_WIDGET_TAG, EMPLOYEE_ONBOARDING_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_EDITORIAL_TEMPLATE, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_SOLUTION, EMPLOYEE_ONBOARDING_GUIDED_EDITORIAL_TEMPLATE, EVENT_REGISTRATION_EDITORIAL_SOLUTION, EVENT_REGISTRATION_EDITORIAL_TEMPLATE, EmptyStateCardComponent, EnterpriseRuntimeContextService, ErrorMessageService, FIELD_METADATA_CAPABILITIES, FIELD_SELECTOR_REGISTRY_BASE, FIELD_SELECTOR_REGISTRY_DISABLE_DEFAULTS, FIELD_SELECTOR_REGISTRY_OVERRIDES, FORM_HOOKS, FORM_HOOKS_PRESETS, FORM_HOOKS_WHITELIST, FORM_HOOK_RESOLVERS, FieldControlType, FieldDataType, FieldSelectorRegistry, FormHooksRegistry, GLOBAL_ACTION_CATALOG, GLOBAL_ACTION_HANDLERS, GLOBAL_ACTION_UI_SCHEMAS, GLOBAL_ANALYTICS_SERVICE, GLOBAL_API_CLIENT, GLOBAL_CONFIG, GLOBAL_DIALOG_SERVICE, GLOBAL_ROUTE_GUARD_RESOLVER, GLOBAL_SURFACE_SERVICE, GLOBAL_TOAST_SERVICE, GenericCrudService, GlobalActionService, GlobalConfigService, INLINE_FILTER_ALIAS_TOKENS, INLINE_FILTER_CONTROL_TYPES, INLINE_FILTER_CONTROL_TYPE_SET, INLINE_FILTER_CONTROL_TYPE_VALUES, INLINE_FILTER_TOKEN_TO_BASE_CONTROL_TYPE, INLINE_FILTER_TOKEN_TO_CONTROL_TYPE, IconPickerService, IconPosition, IconSize, LOGGER_LEVEL_BY_ENV, LOGGER_LEVEL_PRIORITY, LoadingOrchestrator, LocalConnectionStorage, LocalStorageAsyncAdapter, LocalStorageCacheAdapter, LocalStorageConfigService, LoggerService, LoggerThrottleTracker, LoggerWarnOnceTracker, MemoryCacheAdapter, NestedPortCatalogService, NestedWidgetConfigAccessor, NumericFormat, OVERLAY_DECIDER_DEBUG, OVERLAY_DECISION_MATRIX, ObservabilityDashboardService, OverlayDeciderService, PRAXIS_COLLECTION_EXPORT_HTTP_OPTIONS, PRAXIS_COLLECTION_EXPORT_PROVIDER, PRAXIS_CORPORATE_SENSITIVE_KEYS, PRAXIS_DEFAULT_EXPORT_SECURITY_POLICY, PRAXIS_DEFAULT_OBSERVABILITY_ALERT_RULES, PRAXIS_DYNAMIC_PAGE_COMPONENT_METADATA, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_OPTIONS, PRAXIS_ENTERPRISE_RUNTIME_CONTEXT_READY, PRAXIS_EXPORT_FORMULA_PREFIXES, PRAXIS_EXPORT_SECURITY_POLICY, PRAXIS_FOOTER_LINKS_METADATA, PRAXIS_GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_OPTIONS, PRAXIS_GLOBAL_CONFIG_BOOTSTRAP_READY, PRAXIS_GLOBAL_CONFIG_TENANT_RESOLVER, PRAXIS_HERO_BANNER_METADATA, PRAXIS_I18N_CONFIG, PRAXIS_I18N_TRANSLATOR, PRAXIS_JSON_LOGIC_OPERATORS, PRAXIS_LAYER_SCALE_DEFAULTS, PRAXIS_LAYER_SCALE_VARS, PRAXIS_LEGAL_NOTICE_METADATA, PRAXIS_LOADING_CTX, PRAXIS_LOADING_RENDERER, PRAXIS_LOGGER_CONFIG, PRAXIS_LOGGER_SINKS, PRAXIS_OBSERVABILITY_DASHBOARD_OPTIONS, PRAXIS_QUERY_FILTER_EXPRESSION_SCHEMA_VERSION, PRAXIS_RELATED_RESOURCE_OUTLET_COMPONENT_METADATA, PRAXIS_RICH_TEXT_BLOCK_METADATA, PRAXIS_TABLE_DETAIL_INLINE_NODE_RESOLVERS, PRAXIS_TABLE_DETAIL_INLINE_RENDERERS, PRAXIS_TELEMETRY_TRANSPORT, PRAXIS_THEME_SURFACE_DEFAULTS, PRAXIS_THEME_SURFACE_VARS, PRAXIS_USER_CONTEXT_SUMMARY_METADATA, PRIVACY_CONSENT_EDITORIAL_SOLUTION, PRIVACY_CONSENT_EDITORIAL_TEMPLATE, PraxisCollectionExportService, PraxisCore, PraxisFooterLinksComponent, PraxisGlobalErrorHandler, PraxisHeroBannerComponent, PraxisHttpCollectionExportProvider, PraxisI18nService, PraxisIconDirective, PraxisIconPickerComponent, PraxisJsonLogicError, PraxisJsonLogicService, PraxisLayerScaleStyleService, PraxisLegalNoticeComponent, PraxisLoadingInterceptor, PraxisRelatedResourceOutletComponent, PraxisResourceIdentityComponent, PraxisRichTextBlockComponent, PraxisRuntimeComponentObservationRegistryService, PraxisSurfaceHostComponent, PraxisUserContextSummaryComponent, RESOURCE_DISCOVERY_I18N_CONFIG, RESOURCE_DISCOVERY_I18N_NAMESPACE, RULE_PROPERTY_SCHEMA, RelatedResourceSurfaceResolverService, RemoteConfigStorage, ResourceActionOpenAdapterService, ResourceDiscoveryService, ResourceQuickConnectComponent, ResourceRecordOpenError, ResourceRecordOpenService, ResourceSurfaceOpenAdapterService, SCHEMA_VIEWER_CONTEXT, SETTINGS_PANEL_BRIDGE, SETTINGS_PANEL_DATA, STEPPER_CONFIG_EDITOR, SURFACE_DRAWER_BRIDGE, SURFACE_OPEN_I18N_CONFIG, SURFACE_OPEN_I18N_NAMESPACE, SURFACE_OPEN_PRESETS, SchemaMetadataClient, SchemaNormalizerService, SchemaViewerComponent, SurfaceBindingRuntimeService, SurfaceOpenActionEditorComponent, SurfaceOpenMaterializerService, SurfaceOutletRegistryService, TABLE_CONFIG_EDITOR, TableConfigService, TelemetryLoggerSink, TelemetryService, ValidationPattern, WidgetPageStateRuntimeService, WidgetShellComponent, applyLocalCustomizations$2 as applyLocalCustomizations, applyLocalCustomizations$1 as applyLocalFormCustomizations, assertPraxisCollectionExportArtifact, assertPraxisRuntimeComponentObservationSerializable, buildAngularValidators, buildApiUrl, buildBaseColumnFromDef, buildBaseFormField, buildFormConfigFromEditorialTemplate, buildHeaders, buildPageKey, buildPraxisEffectDistinctKey, buildPraxisLayerScaleCss, buildPraxisThemeSurfaceCss, buildSchemaId, buildSchemaIdStorageKeySegment, buildValidatorsFromValidatorOptions, cancelIfCpfInvalidHook, clampRange, classifyEntityLookupResult, clonePraxisRuntimeComponentObservation, cloneTableConfig, cnpjAlphaValidator, collapseWhitespace, composeHeadersWithVersion, conditionalAsyncValidator, convertFormLayoutToConfig, createCorporateLoggerConfig, createCorporateObservabilityOptions, createCpfCnpjValidator, createDefaultFormConfig, createDefaultTableConfig, createEmptyFormConfig, createEmptyRichContentDocument, createFieldLayoutItem, createPersistedPage, customAsyncValidatorFn, customValidatorFn, debounceAsyncValidator, deepMerge, domainKnowledgeTimelineToRichContentDocument, domainRuleTimelineToRichContentDocument, ensureIds, ensureNoConflictsHookFactory, ensurePageIds, escapePraxisExportCell, evaluateFieldAccess, extractNormalizedError, fetchWithETag, fileTypeValidator, fillUndefined, generateId, getDefaultFormHints, getEditorialCompliancePresetById, getEditorialFormTemplateById, getEditorialFormTemplateCatalog, getEditorialSolutionById, getEditorialSolutionCatalog, getEditorialSolutionPresetById, getEditorialThemePresetById, getEssentialConfig, getFieldMetadataCapabilities, getFormColumnFieldNames, getFormLayoutFieldNames, getGlobalActionCatalog, getGlobalActionPayloadActualType, getGlobalActionPayloadTypeIssue, getGlobalActionUiSchema, getMissingGlobalActionPayloadKeys, getReferencedFieldMetadata, getRequiredGlobalActionPayloadKeys, getTextTransformer, hasMeaningfulGlobalActionPayloadValue, hasPraxisCollectionExportArtifact, interpolatePraxisTranslation, isAllowedEditorialContentFormat, isAllowedEditorialHref, isCssTextTransform, isEditorialComponentMeta, isEntityLookupMultiplePayloadMode, isEntityLookupPayloadMode, isEntityLookupPayloadModeCompatible, isEntityLookupResultSelectable, isEntityLookupSinglePayloadMode, isFormLayoutItem, isGlobalActionRef, isInlineFilterControlType, isLookupDialogSize, isLookupFilterFieldType, isLookupFilterOperator, isPraxisPresentationVisualizationTableSafe, isPraxisRuntimeGlobalActionEffect, isProgrammaticDateRangePreset, isRangeValidForFilter, isRequiredGlobalActionParamPayloadMissing, isRequiredGlobalActionPayloadMissing, isStaticDateRangePreset, isTableConfigV2, isValidFormConfig, isValidTableConfig, legacyCnpjValidator, legacyCpfValidator, logOnErrorHook, mapFieldDefinitionToMetadata, mapFieldDefinitionsToMetadata, matchFieldValidator, materializeFormLayoutFromMetadata, materializeResourceIdentity, maxFileSizeValidator, mergeFieldMetadata, mergePraxisI18nConfigs, mergeTableConfigs, migrateFormLayoutRule, migrateLegacyCompositionLink, migrateLegacyCompositionLinks, minWordsValidator, normalizeControlTypeKey, normalizeControlTypeToken, normalizeEditorialLink, normalizeEnd, normalizeFieldAccessMetadata, normalizeFieldConstraints, normalizeFieldPresentation, normalizeFormConfig, normalizeFormLayoutItems, normalizeFormMetadata, normalizeGlobalActionRef$1 as normalizeGlobalActionRef, normalizeLayoutPolicy, normalizeLookupFilterRequest, normalizePath, normalizePraxisDataQueryContext, normalizePraxisEffectPolicy, normalizePraxisPresentationVisualization, normalizePraxisQueryFilterExpression, normalizePraxisQueryFilterNode, normalizeResourceAvailabilityReasonCode, normalizeResourceIdentityContract, normalizeStart, normalizeUnknownError, normalizeWidgetEventPath, notifySuccessHook, parseJsonResponseOrEmpty, praxisLoadingInterceptorFn, prefillFromContextHook, provideDefaultFormHooks, provideFieldSelectorRegistryBase, provideFieldSelectorRegistryOverride, provideFieldSelectorRegistryRuntime, provideFormHookPresets, provideFormHooks, provideGlobalActionCatalog, provideGlobalActionHandler, provideGlobalConfig, provideGlobalConfigReady, provideGlobalConfigSeed, provideGlobalConfigTenant, provideHookResolvers, provideHookWhitelist, provideOverlayDecisionMatrix, providePraxisAnalyticsGlobalActions, providePraxisCollectionExportProvider, providePraxisDynamicPageMetadata, providePraxisEnterpriseRuntimeContext, providePraxisFooterLinksMetadata, providePraxisGlobalActionCatalog, providePraxisGlobalActions, providePraxisGlobalConfigBootstrap, providePraxisHeroBannerMetadata, providePraxisHttpCollectionExportProvider, providePraxisHttpLoading, providePraxisI18n, providePraxisI18nConfig, providePraxisI18nTranslator, providePraxisIconDefaults, providePraxisJsonLogicOperator, providePraxisJsonLogicOperatorOverride, providePraxisLegalNoticeMetadata, providePraxisLoadingDefaults, providePraxisLogging, providePraxisRelatedResourceOutletMetadata, providePraxisRichTextBlockMetadata, providePraxisToastGlobalActions, providePraxisUserContextSummaryMetadata, provideRemoteGlobalConfig, readPraxisExportValue, reconcileFilterConfig, reconcileFormConfig, reconcileTableConfig, registerPraxisRuntimeComponentObservation, removeDiacritics, renderPraxisPresentationVisualizationHtml, reportTelemetryHookFactory, requiredCheckedValidator, requiredPresenceValidator, resolveBuiltinPresets, resolveColumnTypeFromFieldDefinition, resolveControlTypeAlias, resolveDateRangeShortcutPreset, resolveDateRangeShortcutPresets, resolveDefaultValuePresentationFormat, resolveEntityLookupPayloadMode, resolveFieldPresentation, resolveHidden, resolveInlineFilterControlType, resolveInlineFilterControlTypeToBaseControlType, resolveLoggerConfig, resolveObservabilityOptions, resolveOffset, resolveOrder, resolvePraxisCollectionExportItems, resolvePraxisExportFields, resolvePraxisExportScope, resolvePraxisFilterCriteria, resolveResourceAvailabilityReasonKey, resolveSpan, resolveTextMaskFormat, resolveTextMaskFormatFromFieldDefinition, resolveValuePresentation, resolveValuePresentationLocale, serializeEntityLookupValueForPayload, serializeOptionSourceFilterRequest, serializePraxisCollectionToCsv, serializePraxisCollectionToExcel, serializePraxisCollectionToJson, slugify, staticDateRangePresetToPreset, stripMasksHook, supportsImplicitValuePresentation, syncWithServerMetadata, toCamel, toCapitalize, toKebab, toPascal, toSentenceCase, toSnake, toTitleCase, translateResourceAvailabilityReason, translateResourceDiscoveryText, translateUnavailableWorkflowMessage, trim, uniqueAsyncValidator, urlValidator, validateGlobalActionRef, validateGlobalActionRefs, withFormConfigSections, withMessage, withPraxisHttpLoading };