ngx-dynamic-entity 0.0.1

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.
Files changed (47) hide show
  1. package/esm2022/lib/field-types/checkbox-field.component.mjs +73 -0
  2. package/esm2022/lib/field-types/date-field.component.mjs +82 -0
  3. package/esm2022/lib/field-types/dropdown-field.component.mjs +83 -0
  4. package/esm2022/lib/field-types/entity-ref-field.component.mjs +117 -0
  5. package/esm2022/lib/field-types/multi-select-field.component.mjs +91 -0
  6. package/esm2022/lib/field-types/number-field.component.mjs +73 -0
  7. package/esm2022/lib/field-types/text-field.component.mjs +74 -0
  8. package/esm2022/lib/field-types/textarea-field.component.mjs +73 -0
  9. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +86 -0
  10. package/esm2022/lib/form/dynamic-form.component.mjs +167 -0
  11. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +23 -0
  12. package/esm2022/lib/services/config.service.mjs +58 -0
  13. package/esm2022/lib/services/entity-ref-registry.service.mjs +31 -0
  14. package/esm2022/lib/services/field-registry.service.mjs +50 -0
  15. package/esm2022/lib/services/hook-registry.service.mjs +26 -0
  16. package/esm2022/lib/services/rbac.service.mjs +52 -0
  17. package/esm2022/lib/services/validator-registry.service.mjs +49 -0
  18. package/esm2022/lib/services/version.service.mjs +40 -0
  19. package/esm2022/lib/table/dynamic-table.component.mjs +160 -0
  20. package/esm2022/lib/tokens/injection-tokens.mjs +16 -0
  21. package/esm2022/ngx-dynamic-entity.mjs +5 -0
  22. package/esm2022/public-api.mjs +26 -0
  23. package/fesm2022/ngx-dynamic-entity.mjs +1352 -0
  24. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -0
  25. package/index.d.ts +5 -0
  26. package/lib/field-types/checkbox-field.component.d.ts +12 -0
  27. package/lib/field-types/date-field.component.d.ts +14 -0
  28. package/lib/field-types/dropdown-field.component.d.ts +13 -0
  29. package/lib/field-types/entity-ref-field.component.d.ts +26 -0
  30. package/lib/field-types/multi-select-field.component.d.ts +13 -0
  31. package/lib/field-types/number-field.component.d.ts +12 -0
  32. package/lib/field-types/text-field.component.d.ts +13 -0
  33. package/lib/field-types/textarea-field.component.d.ts +12 -0
  34. package/lib/form/dynamic-field/dynamic-field.component.d.ts +30 -0
  35. package/lib/form/dynamic-form.component.d.ts +52 -0
  36. package/lib/providers/provide-ngx-dynamic-entity.d.ts +29 -0
  37. package/lib/services/config.service.d.ts +25 -0
  38. package/lib/services/entity-ref-registry.service.d.ts +20 -0
  39. package/lib/services/field-registry.service.d.ts +22 -0
  40. package/lib/services/hook-registry.service.d.ts +12 -0
  41. package/lib/services/rbac.service.d.ts +32 -0
  42. package/lib/services/validator-registry.service.d.ts +19 -0
  43. package/lib/services/version.service.d.ts +26 -0
  44. package/lib/table/dynamic-table.component.d.ts +56 -0
  45. package/lib/tokens/injection-tokens.d.ts +16 -0
  46. package/package.json +37 -0
  47. package/public-api.d.ts +21 -0
@@ -0,0 +1,50 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { FIELD_TYPE_REGISTRY } from '../tokens/injection-tokens';
3
+ // Built-in field components — pre-registered in this service
4
+ import { TextFieldComponent } from '../field-types/text-field.component';
5
+ import { TextareaFieldComponent } from '../field-types/textarea-field.component';
6
+ import { NumberFieldComponent } from '../field-types/number-field.component';
7
+ import { CheckboxFieldComponent } from '../field-types/checkbox-field.component';
8
+ import { DateFieldComponent } from '../field-types/date-field.component';
9
+ import { DropdownFieldComponent } from '../field-types/dropdown-field.component';
10
+ import { MultiSelectFieldComponent } from '../field-types/multi-select-field.component';
11
+ import { EntityRefFieldComponent } from '../field-types/entity-ref-field.component';
12
+ import * as i0 from "@angular/core";
13
+ /**
14
+ * FieldRegistryService — resolves field type strings to Angular component classes.
15
+ * Open/Closed: open for extension via consumer fieldTypes config, closed for modification.
16
+ * DynamicFieldComponent uses this to mount the right component via createComponent().
17
+ */
18
+ export class FieldRegistryService {
19
+ consumerRegistry = inject(FIELD_TYPE_REGISTRY, { optional: true }) ?? new Map();
20
+ /** Built-in field type → component map. Never remove an entry. */
21
+ builtInRegistry = new Map([
22
+ ['text', TextFieldComponent],
23
+ ['textarea', TextareaFieldComponent],
24
+ ['number', NumberFieldComponent],
25
+ ['checkbox', CheckboxFieldComponent],
26
+ ['date', DateFieldComponent],
27
+ ['dropdown', DropdownFieldComponent],
28
+ ['multiSelect', MultiSelectFieldComponent],
29
+ ['entity-ref', EntityRefFieldComponent],
30
+ ]);
31
+ /**
32
+ * Resolve a field type string to a component class.
33
+ * Consumer registry takes precedence over built-ins (open for extension).
34
+ * Returns null if type is unknown — caller must handle gracefully.
35
+ */
36
+ resolve(fieldType) {
37
+ return this.consumerRegistry.get(fieldType) ?? this.builtInRegistry.get(fieldType) ?? null;
38
+ }
39
+ /** Check if a field type has a registered component */
40
+ has(fieldType) {
41
+ return this.consumerRegistry.has(fieldType) || this.builtInRegistry.has(fieldType);
42
+ }
43
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
44
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, providedIn: 'root' });
45
+ }
46
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: FieldRegistryService, decorators: [{
47
+ type: Injectable,
48
+ args: [{ providedIn: 'root' }]
49
+ }] });
50
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmllbGQtcmVnaXN0cnkuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvc2VydmljZXMvZmllbGQtcmVnaXN0cnkuc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsVUFBVSxFQUFRLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN6RCxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSw0QkFBNEIsQ0FBQztBQUVqRSw2REFBNkQ7QUFDN0QsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDekUsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sdUNBQXVDLENBQUM7QUFDN0UsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFDekUsT0FBTyxFQUFFLHNCQUFzQixFQUFFLE1BQU0seUNBQXlDLENBQUM7QUFDakYsT0FBTyxFQUFFLHlCQUF5QixFQUFFLE1BQU0sNkNBQTZDLENBQUM7QUFDeEYsT0FBTyxFQUFFLHVCQUF1QixFQUFFLE1BQU0sMkNBQTJDLENBQUM7O0FBRXBGOzs7O0dBSUc7QUFFSCxNQUFNLE9BQU8sb0JBQW9CO0lBQ2QsZ0JBQWdCLEdBQUcsTUFBTSxDQUFDLG1CQUFtQixFQUFFLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxDQUFDLElBQUksSUFBSSxHQUFHLEVBQUUsQ0FBQztJQUVqRyxrRUFBa0U7SUFDakQsZUFBZSxHQUFHLElBQUksR0FBRyxDQUFvQjtRQUM1RCxDQUFDLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQztRQUM1QixDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLFFBQVEsRUFBRSxvQkFBb0IsQ0FBQztRQUNoQyxDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLE1BQU0sRUFBRSxrQkFBa0IsQ0FBQztRQUM1QixDQUFDLFVBQVUsRUFBRSxzQkFBc0IsQ0FBQztRQUNwQyxDQUFDLGFBQWEsRUFBRSx5QkFBeUIsQ0FBQztRQUMxQyxDQUFDLFlBQVksRUFBRSx1QkFBdUIsQ0FBQztLQUN4QyxDQUFDLENBQUM7SUFFSDs7OztPQUlHO0lBQ0gsT0FBTyxDQUFDLFNBQWlCO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxJQUFJLENBQUMsZUFBZSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDN0YsQ0FBQztJQUVELHVEQUF1RDtJQUN2RCxHQUFHLENBQUMsU0FBaUI7UUFDbkIsT0FBTyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxJQUFJLElBQUksQ0FBQyxlQUFlLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7d0dBM0JVLG9CQUFvQjs0R0FBcEIsb0JBQW9CLGNBRFAsTUFBTTs7NEZBQ25CLG9CQUFvQjtrQkFEaEMsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBUeXBlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEZJRUxEX1RZUEVfUkVHSVNUUlkgfSBmcm9tICcuLi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8vIEJ1aWx0LWluIGZpZWxkIGNvbXBvbmVudHMg4oCUIHByZS1yZWdpc3RlcmVkIGluIHRoaXMgc2VydmljZVxuaW1wb3J0IHsgVGV4dEZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvdGV4dC1maWVsZC5jb21wb25lbnQnO1xuaW1wb3J0IHsgVGV4dGFyZWFGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL3RleHRhcmVhLWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBOdW1iZXJGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL251bWJlci1maWVsZC5jb21wb25lbnQnO1xuaW1wb3J0IHsgQ2hlY2tib3hGaWVsZENvbXBvbmVudCB9IGZyb20gJy4uL2ZpZWxkLXR5cGVzL2NoZWNrYm94LWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBEYXRlRmllbGRDb21wb25lbnQgfSBmcm9tICcuLi9maWVsZC10eXBlcy9kYXRlLWZpZWxkLmNvbXBvbmVudCc7XG5pbXBvcnQgeyBEcm9wZG93bkZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvZHJvcGRvd24tZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IE11bHRpU2VsZWN0RmllbGRDb21wb25lbnQgfSBmcm9tICcuLi9maWVsZC10eXBlcy9tdWx0aS1zZWxlY3QtZmllbGQuY29tcG9uZW50JztcbmltcG9ydCB7IEVudGl0eVJlZkZpZWxkQ29tcG9uZW50IH0gZnJvbSAnLi4vZmllbGQtdHlwZXMvZW50aXR5LXJlZi1maWVsZC5jb21wb25lbnQnO1xuXG4vKipcbiAqIEZpZWxkUmVnaXN0cnlTZXJ2aWNlIOKAlCByZXNvbHZlcyBmaWVsZCB0eXBlIHN0cmluZ3MgdG8gQW5ndWxhciBjb21wb25lbnQgY2xhc3Nlcy5cbiAqIE9wZW4vQ2xvc2VkOiBvcGVuIGZvciBleHRlbnNpb24gdmlhIGNvbnN1bWVyIGZpZWxkVHlwZXMgY29uZmlnLCBjbG9zZWQgZm9yIG1vZGlmaWNhdGlvbi5cbiAqIER5bmFtaWNGaWVsZENvbXBvbmVudCB1c2VzIHRoaXMgdG8gbW91bnQgdGhlIHJpZ2h0IGNvbXBvbmVudCB2aWEgY3JlYXRlQ29tcG9uZW50KCkuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgRmllbGRSZWdpc3RyeVNlcnZpY2Uge1xuICBwcml2YXRlIHJlYWRvbmx5IGNvbnN1bWVyUmVnaXN0cnkgPSBpbmplY3QoRklFTERfVFlQRV9SRUdJU1RSWSwgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBuZXcgTWFwKCk7XG5cbiAgLyoqIEJ1aWx0LWluIGZpZWxkIHR5cGUg4oaSIGNvbXBvbmVudCBtYXAuIE5ldmVyIHJlbW92ZSBhbiBlbnRyeS4gKi9cbiAgcHJpdmF0ZSByZWFkb25seSBidWlsdEluUmVnaXN0cnkgPSBuZXcgTWFwPHN0cmluZywgVHlwZTxhbnk+PihbXG4gICAgWyd0ZXh0JywgVGV4dEZpZWxkQ29tcG9uZW50XSxcbiAgICBbJ3RleHRhcmVhJywgVGV4dGFyZWFGaWVsZENvbXBvbmVudF0sXG4gICAgWydudW1iZXInLCBOdW1iZXJGaWVsZENvbXBvbmVudF0sXG4gICAgWydjaGVja2JveCcsIENoZWNrYm94RmllbGRDb21wb25lbnRdLFxuICAgIFsnZGF0ZScsIERhdGVGaWVsZENvbXBvbmVudF0sXG4gICAgWydkcm9wZG93bicsIERyb3Bkb3duRmllbGRDb21wb25lbnRdLFxuICAgIFsnbXVsdGlTZWxlY3QnLCBNdWx0aVNlbGVjdEZpZWxkQ29tcG9uZW50XSxcbiAgICBbJ2VudGl0eS1yZWYnLCBFbnRpdHlSZWZGaWVsZENvbXBvbmVudF0sXG4gIF0pO1xuXG4gIC8qKlxuICAgKiBSZXNvbHZlIGEgZmllbGQgdHlwZSBzdHJpbmcgdG8gYSBjb21wb25lbnQgY2xhc3MuXG4gICAqIENvbnN1bWVyIHJlZ2lzdHJ5IHRha2VzIHByZWNlZGVuY2Ugb3ZlciBidWlsdC1pbnMgKG9wZW4gZm9yIGV4dGVuc2lvbikuXG4gICAqIFJldHVybnMgbnVsbCBpZiB0eXBlIGlzIHVua25vd24g4oCUIGNhbGxlciBtdXN0IGhhbmRsZSBncmFjZWZ1bGx5LlxuICAgKi9cbiAgcmVzb2x2ZShmaWVsZFR5cGU6IHN0cmluZyk6IFR5cGU8YW55PiB8IG51bGwge1xuICAgIHJldHVybiB0aGlzLmNvbnN1bWVyUmVnaXN0cnkuZ2V0KGZpZWxkVHlwZSkgPz8gdGhpcy5idWlsdEluUmVnaXN0cnkuZ2V0KGZpZWxkVHlwZSkgPz8gbnVsbDtcbiAgfVxuXG4gIC8qKiBDaGVjayBpZiBhIGZpZWxkIHR5cGUgaGFzIGEgcmVnaXN0ZXJlZCBjb21wb25lbnQgKi9cbiAgaGFzKGZpZWxkVHlwZTogc3RyaW5nKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuY29uc3VtZXJSZWdpc3RyeS5oYXMoZmllbGRUeXBlKSB8fCB0aGlzLmJ1aWx0SW5SZWdpc3RyeS5oYXMoZmllbGRUeXBlKTtcbiAgfVxufVxuIl19
@@ -0,0 +1,26 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { HOOK_REGISTRY } from '../tokens/injection-tokens';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * HookRegistryService — stores and runs Angular-side pre/post hooks.
6
+ * Consumers register hooks via provideNgxDynamicEntity({ hooks: { 'clients:beforeSave': fn } }).
7
+ */
8
+ export class HookRegistryService {
9
+ registry = inject(HOOK_REGISTRY, { optional: true }) ?? new Map();
10
+ has(key) {
11
+ return this.registry.has(key);
12
+ }
13
+ async run(key, data, context = {}) {
14
+ const hook = this.registry.get(key);
15
+ if (!hook)
16
+ return data;
17
+ return hook(data, context);
18
+ }
19
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
20
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, providedIn: 'root' });
21
+ }
22
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: HookRegistryService, decorators: [{
23
+ type: Injectable,
24
+ args: [{ providedIn: 'root' }]
25
+ }] });
26
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaG9vay1yZWdpc3RyeS5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy9ob29rLXJlZ2lzdHJ5LnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDbkQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLDRCQUE0QixDQUFDOztBQUUzRDs7O0dBR0c7QUFFSCxNQUFNLE9BQU8sbUJBQW1CO0lBQ2IsUUFBUSxHQUFHLE1BQU0sQ0FBQyxhQUFhLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLENBQUMsSUFBSSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBRW5GLEdBQUcsQ0FBQyxHQUFXO1FBQ2IsT0FBTyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBRUQsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFXLEVBQUUsSUFBUyxFQUFFLFVBQWUsRUFBRTtRQUNqRCxNQUFNLElBQUksR0FBRyxJQUFJLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNwQyxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ3ZCLE9BQU8sSUFBSSxDQUFDLElBQUksRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM3QixDQUFDO3dHQVhVLG1CQUFtQjs0R0FBbkIsbUJBQW1CLGNBRE4sTUFBTTs7NEZBQ25CLG1CQUFtQjtrQkFEL0IsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEhPT0tfUkVHSVNUUlkgfSBmcm9tICcuLi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8qKlxuICogSG9va1JlZ2lzdHJ5U2VydmljZSDigJQgc3RvcmVzIGFuZCBydW5zIEFuZ3VsYXItc2lkZSBwcmUvcG9zdCBob29rcy5cbiAqIENvbnN1bWVycyByZWdpc3RlciBob29rcyB2aWEgcHJvdmlkZU5neER5bmFtaWNFbnRpdHkoeyBob29rczogeyAnY2xpZW50czpiZWZvcmVTYXZlJzogZm4gfSB9KS5cbiAqL1xuQEluamVjdGFibGUoeyBwcm92aWRlZEluOiAncm9vdCcgfSlcbmV4cG9ydCBjbGFzcyBIb29rUmVnaXN0cnlTZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSByZWdpc3RyeSA9IGluamVjdChIT09LX1JFR0lTVFJZLCB7IG9wdGlvbmFsOiB0cnVlIH0pID8/IG5ldyBNYXAoKTtcblxuICBoYXMoa2V5OiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5yZWdpc3RyeS5oYXMoa2V5KTtcbiAgfVxuXG4gIGFzeW5jIHJ1bihrZXk6IHN0cmluZywgZGF0YTogYW55LCBjb250ZXh0OiBhbnkgPSB7fSk6IFByb21pc2U8YW55PiB7XG4gICAgY29uc3QgaG9vayA9IHRoaXMucmVnaXN0cnkuZ2V0KGtleSk7XG4gICAgaWYgKCFob29rKSByZXR1cm4gZGF0YTtcbiAgICByZXR1cm4gaG9vayhkYXRhLCBjb250ZXh0KTtcbiAgfVxufVxuIl19
@@ -0,0 +1,52 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { MASKED_ROLES } from '../tokens/injection-tokens';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * RbacService — ALL permission checks and masking logic live here.
6
+ * CRITICAL SYNC: resolveEffectiveMask() must be identical to rbac.utils.js in dynamic-entity-server.
7
+ * Changing one without the other is a critical production bug (ADR-003).
8
+ */
9
+ export class RbacService {
10
+ maskedRoles = inject(MASKED_ROLES, { optional: true }) ?? [];
11
+ /**
12
+ * Check if any of the user's roles satisfy the required roles.
13
+ * hasPermission(roles, undefined) → true (no restriction).
14
+ */
15
+ hasPermission(userRoles, requiredRoles) {
16
+ if (!requiredRoles?.length)
17
+ return true;
18
+ return userRoles.some(r => requiredRoles.includes(r));
19
+ }
20
+ /**
21
+ * ADR-003: 3-level OR resolution.
22
+ * MUST match rbac.utils.js resolveEffectiveMask() exactly.
23
+ */
24
+ resolveEffectiveMask(formMask, tabMask, fieldMask) {
25
+ return !!(formMask || tabMask || fieldMask);
26
+ }
27
+ /** Check if the user's roles include any masked role */
28
+ isUserMaskedRole(userRoles) {
29
+ return this.maskedRoles.some(r => userRoles.includes(r));
30
+ }
31
+ /** Determine if a specific field should be masked for this user */
32
+ shouldMaskField(field, tab, config, userRoles) {
33
+ if (!this.isUserMaskedRole(userRoles))
34
+ return false;
35
+ return this.resolveEffectiveMask(config.maskData, tab?.maskData, field.maskData);
36
+ }
37
+ /** Get view/edit/delete permissions for a config */
38
+ getPermissions(config, userRoles) {
39
+ return {
40
+ canView: this.hasPermission(userRoles, config.permissions?.view),
41
+ canEdit: this.hasPermission(userRoles, config.permissions?.edit),
42
+ canDelete: this.hasPermission(userRoles, config.permissions?.delete),
43
+ };
44
+ }
45
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
46
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, providedIn: 'root' });
47
+ }
48
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: RbacService, decorators: [{
49
+ type: Injectable,
50
+ args: [{ providedIn: 'root' }]
51
+ }] });
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmJhYy5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy9yYmFjLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFbkQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLDRCQUE0QixDQUFDOztBQUUxRDs7OztHQUlHO0FBRUgsTUFBTSxPQUFPLFdBQVc7SUFDTCxXQUFXLEdBQUcsTUFBTSxDQUFDLFlBQVksRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUU5RTs7O09BR0c7SUFDSCxhQUFhLENBQUMsU0FBbUIsRUFBRSxhQUF3QjtRQUN6RCxJQUFJLENBQUMsYUFBYSxFQUFFLE1BQU07WUFBRSxPQUFPLElBQUksQ0FBQztRQUN4QyxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxhQUFhLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVEOzs7T0FHRztJQUNILG9CQUFvQixDQUFDLFFBQWtCLEVBQUUsT0FBaUIsRUFBRSxTQUFtQjtRQUM3RSxPQUFPLENBQUMsQ0FBQyxDQUFDLFFBQVEsSUFBSSxPQUFPLElBQUksU0FBUyxDQUFDLENBQUM7SUFDOUMsQ0FBQztJQUVELHdEQUF3RDtJQUN4RCxnQkFBZ0IsQ0FBQyxTQUFtQjtRQUNsQyxPQUFPLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzNELENBQUM7SUFFRCxtRUFBbUU7SUFDbkUsZUFBZSxDQUFDLEtBQWtCLEVBQUUsR0FBMEIsRUFBRSxNQUFvQixFQUFFLFNBQW1CO1FBQ3ZHLElBQUksQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDcEQsT0FBTyxJQUFJLENBQUMsb0JBQW9CLENBQUMsTUFBTSxDQUFDLFFBQVEsRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUNuRixDQUFDO0lBRUQsb0RBQW9EO0lBQ3BELGNBQWMsQ0FBQyxNQUFvQixFQUFFLFNBQW1CO1FBQ3RELE9BQU87WUFDTCxPQUFPLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxTQUFTLEVBQUUsTUFBTSxDQUFDLFdBQVcsRUFBRSxJQUFJLENBQUM7WUFDaEUsT0FBTyxFQUFFLElBQUksQ0FBQyxhQUFhLENBQUMsU0FBUyxFQUFFLE1BQU0sQ0FBQyxXQUFXLEVBQUUsSUFBSSxDQUFDO1lBQ2hFLFNBQVMsRUFBRSxJQUFJLENBQUMsYUFBYSxDQUFDLFNBQVMsRUFBRSxNQUFNLENBQUMsV0FBVyxFQUFFLE1BQU0sQ0FBQztTQUNyRSxDQUFDO0lBQ0osQ0FBQzt3R0F0Q1UsV0FBVzs0R0FBWCxXQUFXLGNBREUsTUFBTTs7NEZBQ25CLFdBQVc7a0JBRHZCLFVBQVU7bUJBQUMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgdHlwZSB7IEVudGl0eUNvbmZpZywgRmllbGRDb25maWcsIFRhYkNvbmZpZyB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcbmltcG9ydCB7IE1BU0tFRF9ST0xFUyB9IGZyb20gJy4uL3Rva2Vucy9pbmplY3Rpb24tdG9rZW5zJztcblxuLyoqXG4gKiBSYmFjU2VydmljZSDigJQgQUxMIHBlcm1pc3Npb24gY2hlY2tzIGFuZCBtYXNraW5nIGxvZ2ljIGxpdmUgaGVyZS5cbiAqIENSSVRJQ0FMIFNZTkM6IHJlc29sdmVFZmZlY3RpdmVNYXNrKCkgbXVzdCBiZSBpZGVudGljYWwgdG8gcmJhYy51dGlscy5qcyBpbiBkeW5hbWljLWVudGl0eS1zZXJ2ZXIuXG4gKiBDaGFuZ2luZyBvbmUgd2l0aG91dCB0aGUgb3RoZXIgaXMgYSBjcml0aWNhbCBwcm9kdWN0aW9uIGJ1ZyAoQURSLTAwMykuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgUmJhY1NlcnZpY2Uge1xuICBwcml2YXRlIHJlYWRvbmx5IG1hc2tlZFJvbGVzID0gaW5qZWN0KE1BU0tFRF9ST0xFUywgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBbXTtcblxuICAvKipcbiAgICogQ2hlY2sgaWYgYW55IG9mIHRoZSB1c2VyJ3Mgcm9sZXMgc2F0aXNmeSB0aGUgcmVxdWlyZWQgcm9sZXMuXG4gICAqIGhhc1Blcm1pc3Npb24ocm9sZXMsIHVuZGVmaW5lZCkg4oaSIHRydWUgKG5vIHJlc3RyaWN0aW9uKS5cbiAgICovXG4gIGhhc1Blcm1pc3Npb24odXNlclJvbGVzOiBzdHJpbmdbXSwgcmVxdWlyZWRSb2xlcz86IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gICAgaWYgKCFyZXF1aXJlZFJvbGVzPy5sZW5ndGgpIHJldHVybiB0cnVlO1xuICAgIHJldHVybiB1c2VyUm9sZXMuc29tZShyID0+IHJlcXVpcmVkUm9sZXMuaW5jbHVkZXMocikpO1xuICB9XG5cbiAgLyoqXG4gICAqIEFEUi0wMDM6IDMtbGV2ZWwgT1IgcmVzb2x1dGlvbi5cbiAgICogTVVTVCBtYXRjaCByYmFjLnV0aWxzLmpzIHJlc29sdmVFZmZlY3RpdmVNYXNrKCkgZXhhY3RseS5cbiAgICovXG4gIHJlc29sdmVFZmZlY3RpdmVNYXNrKGZvcm1NYXNrPzogYm9vbGVhbiwgdGFiTWFzaz86IGJvb2xlYW4sIGZpZWxkTWFzaz86IGJvb2xlYW4pOiBib29sZWFuIHtcbiAgICByZXR1cm4gISEoZm9ybU1hc2sgfHwgdGFiTWFzayB8fCBmaWVsZE1hc2spO1xuICB9XG5cbiAgLyoqIENoZWNrIGlmIHRoZSB1c2VyJ3Mgcm9sZXMgaW5jbHVkZSBhbnkgbWFza2VkIHJvbGUgKi9cbiAgaXNVc2VyTWFza2VkUm9sZSh1c2VyUm9sZXM6IHN0cmluZ1tdKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMubWFza2VkUm9sZXMuc29tZShyID0+IHVzZXJSb2xlcy5pbmNsdWRlcyhyKSk7XG4gIH1cblxuICAvKiogRGV0ZXJtaW5lIGlmIGEgc3BlY2lmaWMgZmllbGQgc2hvdWxkIGJlIG1hc2tlZCBmb3IgdGhpcyB1c2VyICovXG4gIHNob3VsZE1hc2tGaWVsZChmaWVsZDogRmllbGRDb25maWcsIHRhYjogVGFiQ29uZmlnIHwgdW5kZWZpbmVkLCBjb25maWc6IEVudGl0eUNvbmZpZywgdXNlclJvbGVzOiBzdHJpbmdbXSk6IGJvb2xlYW4ge1xuICAgIGlmICghdGhpcy5pc1VzZXJNYXNrZWRSb2xlKHVzZXJSb2xlcykpIHJldHVybiBmYWxzZTtcbiAgICByZXR1cm4gdGhpcy5yZXNvbHZlRWZmZWN0aXZlTWFzayhjb25maWcubWFza0RhdGEsIHRhYj8ubWFza0RhdGEsIGZpZWxkLm1hc2tEYXRhKTtcbiAgfVxuXG4gIC8qKiBHZXQgdmlldy9lZGl0L2RlbGV0ZSBwZXJtaXNzaW9ucyBmb3IgYSBjb25maWcgKi9cbiAgZ2V0UGVybWlzc2lvbnMoY29uZmlnOiBFbnRpdHlDb25maWcsIHVzZXJSb2xlczogc3RyaW5nW10pIHtcbiAgICByZXR1cm4ge1xuICAgICAgY2FuVmlldzogdGhpcy5oYXNQZXJtaXNzaW9uKHVzZXJSb2xlcywgY29uZmlnLnBlcm1pc3Npb25zPy52aWV3KSxcbiAgICAgIGNhbkVkaXQ6IHRoaXMuaGFzUGVybWlzc2lvbih1c2VyUm9sZXMsIGNvbmZpZy5wZXJtaXNzaW9ucz8uZWRpdCksXG4gICAgICBjYW5EZWxldGU6IHRoaXMuaGFzUGVybWlzc2lvbih1c2VyUm9sZXMsIGNvbmZpZy5wZXJtaXNzaW9ucz8uZGVsZXRlKSxcbiAgICB9O1xuICB9XG59XG4iXX0=
@@ -0,0 +1,49 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { Validators } from '@angular/forms';
3
+ import { VALIDATOR_REGISTRY } from '../tokens/injection-tokens';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * ValidatorRegistryService — resolves validator key strings to Angular ValidatorFns.
7
+ * Built-in validators: 'required', 'email', 'min:N', 'max:N', 'minLength:N', 'maxLength:N'.
8
+ * Consumers can register custom validators via provideNgxDynamicEntity({ validators: {...} }).
9
+ */
10
+ export class ValidatorRegistryService {
11
+ consumerRegistry = inject(VALIDATOR_REGISTRY, { optional: true }) ?? new Map();
12
+ /**
13
+ * Resolve a validator key to a ValidatorFn.
14
+ * Supports parameterized built-ins like 'min:0', 'maxLength:255'.
15
+ */
16
+ resolve(validatorKey) {
17
+ // Consumer validators take precedence
18
+ if (this.consumerRegistry.has(validatorKey)) {
19
+ return this.consumerRegistry.get(validatorKey);
20
+ }
21
+ // Built-in validators
22
+ if (validatorKey === 'required')
23
+ return Validators.required;
24
+ if (validatorKey === 'email')
25
+ return Validators.email;
26
+ const [name, param] = validatorKey.split(':');
27
+ const value = parseFloat(param);
28
+ if (name === 'min' && !isNaN(value))
29
+ return Validators.min(value);
30
+ if (name === 'max' && !isNaN(value))
31
+ return Validators.max(value);
32
+ if (name === 'minLength' && !isNaN(value))
33
+ return Validators.minLength(value);
34
+ if (name === 'maxLength' && !isNaN(value))
35
+ return Validators.maxLength(value);
36
+ return null;
37
+ }
38
+ /** Resolve multiple validator keys into a ValidatorFn[] */
39
+ resolveAll(validatorKeys = []) {
40
+ return validatorKeys.map(k => this.resolve(k)).filter((v) => v !== null);
41
+ }
42
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
43
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, providedIn: 'root' });
44
+ }
45
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ValidatorRegistryService, decorators: [{
46
+ type: Injectable,
47
+ args: [{ providedIn: 'root' }]
48
+ }] });
49
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdG9yLXJlZ2lzdHJ5LnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ25ELE9BQU8sRUFBZ0MsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDMUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7O0FBRWhFOzs7O0dBSUc7QUFFSCxNQUFNLE9BQU8sd0JBQXdCO0lBQ2xCLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLElBQUksR0FBRyxFQUFFLENBQUM7SUFFaEc7OztPQUdHO0lBQ0gsT0FBTyxDQUFDLFlBQW9CO1FBQzFCLHNDQUFzQztRQUN0QyxJQUFJLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUM1QyxPQUFPLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLENBQUM7UUFDakQsQ0FBQztRQUVELHNCQUFzQjtRQUN0QixJQUFJLFlBQVksS0FBSyxVQUFVO1lBQUUsT0FBTyxVQUFVLENBQUMsUUFBUSxDQUFDO1FBQzVELElBQUksWUFBWSxLQUFLLE9BQU87WUFBRSxPQUFPLFVBQVUsQ0FBQyxLQUFLLENBQUM7UUFFdEQsTUFBTSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsR0FBRyxZQUFZLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzlDLE1BQU0sS0FBSyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUVoQyxJQUFJLElBQUksS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQUUsT0FBTyxVQUFVLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ2xFLElBQUksSUFBSSxLQUFLLEtBQUssSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLFVBQVUsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDbEUsSUFBSSxJQUFJLEtBQUssV0FBVyxJQUFJLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQztZQUFFLE9BQU8sVUFBVSxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUM5RSxJQUFJLElBQUksS0FBSyxXQUFXLElBQUksQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDO1lBQUUsT0FBTyxVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBRTlFLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELDJEQUEyRDtJQUMzRCxVQUFVLENBQUMsZ0JBQTBCLEVBQUU7UUFDckMsT0FBTyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBb0IsRUFBRSxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQztJQUM3RixDQUFDO3dHQS9CVSx3QkFBd0I7NEdBQXhCLHdCQUF3QixjQURYLE1BQU07OzRGQUNuQix3QkFBd0I7a0JBRHBDLFVBQVU7bUJBQUMsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wsIFZhbGlkYXRvckZuLCBWYWxpZGF0b3JzIH0gZnJvbSAnQGFuZ3VsYXIvZm9ybXMnO1xuaW1wb3J0IHsgVkFMSURBVE9SX1JFR0lTVFJZIH0gZnJvbSAnLi4vdG9rZW5zL2luamVjdGlvbi10b2tlbnMnO1xuXG4vKipcbiAqIFZhbGlkYXRvclJlZ2lzdHJ5U2VydmljZSDigJQgcmVzb2x2ZXMgdmFsaWRhdG9yIGtleSBzdHJpbmdzIHRvIEFuZ3VsYXIgVmFsaWRhdG9yRm5zLlxuICogQnVpbHQtaW4gdmFsaWRhdG9yczogJ3JlcXVpcmVkJywgJ2VtYWlsJywgJ21pbjpOJywgJ21heDpOJywgJ21pbkxlbmd0aDpOJywgJ21heExlbmd0aDpOJy5cbiAqIENvbnN1bWVycyBjYW4gcmVnaXN0ZXIgY3VzdG9tIHZhbGlkYXRvcnMgdmlhIHByb3ZpZGVOZ3hEeW5hbWljRW50aXR5KHsgdmFsaWRhdG9yczogey4uLn0gfSkuXG4gKi9cbkBJbmplY3RhYmxlKHsgcHJvdmlkZWRJbjogJ3Jvb3QnIH0pXG5leHBvcnQgY2xhc3MgVmFsaWRhdG9yUmVnaXN0cnlTZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBjb25zdW1lclJlZ2lzdHJ5ID0gaW5qZWN0KFZBTElEQVRPUl9SRUdJU1RSWSwgeyBvcHRpb25hbDogdHJ1ZSB9KSA/PyBuZXcgTWFwKCk7XG5cbiAgLyoqXG4gICAqIFJlc29sdmUgYSB2YWxpZGF0b3Iga2V5IHRvIGEgVmFsaWRhdG9yRm4uXG4gICAqIFN1cHBvcnRzIHBhcmFtZXRlcml6ZWQgYnVpbHQtaW5zIGxpa2UgJ21pbjowJywgJ21heExlbmd0aDoyNTUnLlxuICAgKi9cbiAgcmVzb2x2ZSh2YWxpZGF0b3JLZXk6IHN0cmluZyk6IFZhbGlkYXRvckZuIHwgbnVsbCB7XG4gICAgLy8gQ29uc3VtZXIgdmFsaWRhdG9ycyB0YWtlIHByZWNlZGVuY2VcbiAgICBpZiAodGhpcy5jb25zdW1lclJlZ2lzdHJ5Lmhhcyh2YWxpZGF0b3JLZXkpKSB7XG4gICAgICByZXR1cm4gdGhpcy5jb25zdW1lclJlZ2lzdHJ5LmdldCh2YWxpZGF0b3JLZXkpO1xuICAgIH1cblxuICAgIC8vIEJ1aWx0LWluIHZhbGlkYXRvcnNcbiAgICBpZiAodmFsaWRhdG9yS2V5ID09PSAncmVxdWlyZWQnKSByZXR1cm4gVmFsaWRhdG9ycy5yZXF1aXJlZDtcbiAgICBpZiAodmFsaWRhdG9yS2V5ID09PSAnZW1haWwnKSByZXR1cm4gVmFsaWRhdG9ycy5lbWFpbDtcblxuICAgIGNvbnN0IFtuYW1lLCBwYXJhbV0gPSB2YWxpZGF0b3JLZXkuc3BsaXQoJzonKTtcbiAgICBjb25zdCB2YWx1ZSA9IHBhcnNlRmxvYXQocGFyYW0pO1xuXG4gICAgaWYgKG5hbWUgPT09ICdtaW4nICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1pbih2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtYXgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1heCh2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtaW5MZW5ndGgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1pbkxlbmd0aCh2YWx1ZSk7XG4gICAgaWYgKG5hbWUgPT09ICdtYXhMZW5ndGgnICYmICFpc05hTih2YWx1ZSkpIHJldHVybiBWYWxpZGF0b3JzLm1heExlbmd0aCh2YWx1ZSk7XG5cbiAgICByZXR1cm4gbnVsbDtcbiAgfVxuXG4gIC8qKiBSZXNvbHZlIG11bHRpcGxlIHZhbGlkYXRvciBrZXlzIGludG8gYSBWYWxpZGF0b3JGbltdICovXG4gIHJlc29sdmVBbGwodmFsaWRhdG9yS2V5czogc3RyaW5nW10gPSBbXSk6IFZhbGlkYXRvckZuW10ge1xuICAgIHJldHVybiB2YWxpZGF0b3JLZXlzLm1hcChrID0+IHRoaXMucmVzb2x2ZShrKSkuZmlsdGVyKCh2KTogdiBpcyBWYWxpZGF0b3JGbiA9PiB2ICE9PSBudWxsKTtcbiAgfVxufVxuIl19
@@ -0,0 +1,40 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { MIGRATION_STRATEGY } from '../tokens/injection-tokens';
3
+ import * as i0 from "@angular/core";
4
+ /**
5
+ * VersionService — checks config version staleness and migration strategy.
6
+ * ADR-005: Frontend strategy is 'strict' | 'graceful' only. Never 'auto'.
7
+ */
8
+ export class VersionService {
9
+ strategy = inject(MIGRATION_STRATEGY, { optional: true }) ?? 'graceful';
10
+ /**
11
+ * Check if a record is stale compared to the current config version.
12
+ * Returns true if the record needs migration.
13
+ */
14
+ needsMigration(record, config) {
15
+ if (!record._configVersion)
16
+ return false;
17
+ return record._configVersion < config.version || !!record._needsMigration;
18
+ }
19
+ /**
20
+ * Get the effective migration strategy for frontend use.
21
+ * Only 'strict' or 'graceful' — never 'auto' (ADR-005).
22
+ */
23
+ getStrategy() {
24
+ return this.strategy === 'strict' ? 'strict' : 'graceful';
25
+ }
26
+ /**
27
+ * Determine if form submission should be blocked for a stale record.
28
+ * 'strict' → block; 'graceful' → allow with warning.
29
+ */
30
+ shouldBlockSubmit(record, config) {
31
+ return this.getStrategy() === 'strict' && this.needsMigration(record, config);
32
+ }
33
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
34
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, providedIn: 'root' });
35
+ }
36
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: VersionService, decorators: [{
37
+ type: Injectable,
38
+ args: [{ providedIn: 'root' }]
39
+ }] });
40
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmVyc2lvbi5zZXJ2aWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9zZXJ2aWNlcy92ZXJzaW9uLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFFbkQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7O0FBRWhFOzs7R0FHRztBQUVILE1BQU0sT0FBTyxjQUFjO0lBQ1IsUUFBUSxHQUFHLE1BQU0sQ0FBQyxrQkFBa0IsRUFBRSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxJQUFJLFVBQVUsQ0FBQztJQUV6Rjs7O09BR0c7SUFDSCxjQUFjLENBQUMsTUFBZ0MsRUFBRSxNQUFvQjtRQUNuRSxJQUFJLENBQUMsTUFBTSxDQUFDLGNBQWM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUN6QyxPQUFPLE1BQU0sQ0FBQyxjQUFjLEdBQUcsTUFBTSxDQUFDLE9BQU8sSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLGVBQWUsQ0FBQztJQUM1RSxDQUFDO0lBRUQ7OztPQUdHO0lBQ0gsV0FBVztRQUNULE9BQU8sSUFBSSxDQUFDLFFBQVEsS0FBSyxRQUFRLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsVUFBVSxDQUFDO0lBQzVELENBQUM7SUFFRDs7O09BR0c7SUFDSCxpQkFBaUIsQ0FBQyxNQUFnQyxFQUFFLE1BQW9CO1FBQ3RFLE9BQU8sSUFBSSxDQUFDLFdBQVcsRUFBRSxLQUFLLFFBQVEsSUFBSSxJQUFJLENBQUMsY0FBYyxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsQ0FBQztJQUNoRixDQUFDO3dHQTFCVSxjQUFjOzRHQUFkLGNBQWMsY0FERCxNQUFNOzs0RkFDbkIsY0FBYztrQkFEMUIsVUFBVTttQkFBQyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBJbmplY3RhYmxlLCBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB0eXBlIHsgVmVyc2lvbmVkUmVjb3JkLCBFbnRpdHlDb25maWcsIE1pZ3JhdGlvblN0cmF0ZWd5IH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHsgTUlHUkFUSU9OX1NUUkFURUdZIH0gZnJvbSAnLi4vdG9rZW5zL2luamVjdGlvbi10b2tlbnMnO1xuXG4vKipcbiAqIFZlcnNpb25TZXJ2aWNlIOKAlCBjaGVja3MgY29uZmlnIHZlcnNpb24gc3RhbGVuZXNzIGFuZCBtaWdyYXRpb24gc3RyYXRlZ3kuXG4gKiBBRFItMDA1OiBGcm9udGVuZCBzdHJhdGVneSBpcyAnc3RyaWN0JyB8ICdncmFjZWZ1bCcgb25seS4gTmV2ZXIgJ2F1dG8nLlxuICovXG5ASW5qZWN0YWJsZSh7IHByb3ZpZGVkSW46ICdyb290JyB9KVxuZXhwb3J0IGNsYXNzIFZlcnNpb25TZXJ2aWNlIHtcbiAgcHJpdmF0ZSByZWFkb25seSBzdHJhdGVneSA9IGluamVjdChNSUdSQVRJT05fU1RSQVRFR1ksIHsgb3B0aW9uYWw6IHRydWUgfSkgPz8gJ2dyYWNlZnVsJztcblxuICAvKipcbiAgICogQ2hlY2sgaWYgYSByZWNvcmQgaXMgc3RhbGUgY29tcGFyZWQgdG8gdGhlIGN1cnJlbnQgY29uZmlnIHZlcnNpb24uXG4gICAqIFJldHVybnMgdHJ1ZSBpZiB0aGUgcmVjb3JkIG5lZWRzIG1pZ3JhdGlvbi5cbiAgICovXG4gIG5lZWRzTWlncmF0aW9uKHJlY29yZDogUGFydGlhbDxWZXJzaW9uZWRSZWNvcmQ+LCBjb25maWc6IEVudGl0eUNvbmZpZyk6IGJvb2xlYW4ge1xuICAgIGlmICghcmVjb3JkLl9jb25maWdWZXJzaW9uKSByZXR1cm4gZmFsc2U7XG4gICAgcmV0dXJuIHJlY29yZC5fY29uZmlnVmVyc2lvbiA8IGNvbmZpZy52ZXJzaW9uIHx8ICEhcmVjb3JkLl9uZWVkc01pZ3JhdGlvbjtcbiAgfVxuXG4gIC8qKlxuICAgKiBHZXQgdGhlIGVmZmVjdGl2ZSBtaWdyYXRpb24gc3RyYXRlZ3kgZm9yIGZyb250ZW5kIHVzZS5cbiAgICogT25seSAnc3RyaWN0JyBvciAnZ3JhY2VmdWwnIOKAlCBuZXZlciAnYXV0bycgKEFEUi0wMDUpLlxuICAgKi9cbiAgZ2V0U3RyYXRlZ3koKTogJ3N0cmljdCcgfCAnZ3JhY2VmdWwnIHtcbiAgICByZXR1cm4gdGhpcy5zdHJhdGVneSA9PT0gJ3N0cmljdCcgPyAnc3RyaWN0JyA6ICdncmFjZWZ1bCc7XG4gIH1cblxuICAvKipcbiAgICogRGV0ZXJtaW5lIGlmIGZvcm0gc3VibWlzc2lvbiBzaG91bGQgYmUgYmxvY2tlZCBmb3IgYSBzdGFsZSByZWNvcmQuXG4gICAqICdzdHJpY3QnIOKGkiBibG9jazsgJ2dyYWNlZnVsJyDihpIgYWxsb3cgd2l0aCB3YXJuaW5nLlxuICAgKi9cbiAgc2hvdWxkQmxvY2tTdWJtaXQocmVjb3JkOiBQYXJ0aWFsPFZlcnNpb25lZFJlY29yZD4sIGNvbmZpZzogRW50aXR5Q29uZmlnKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuIHRoaXMuZ2V0U3RyYXRlZ3koKSA9PT0gJ3N0cmljdCcgJiYgdGhpcy5uZWVkc01pZ3JhdGlvbihyZWNvcmQsIGNvbmZpZyk7XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,160 @@
1
+ import { Component, Input, Output, EventEmitter, signal, computed, inject } from '@angular/core';
2
+ import { RbacService } from '../services/rbac.service';
3
+ import { VersionService } from '../services/version.service';
4
+ import * as i0 from "@angular/core";
5
+ /**
6
+ * DynamicTableComponent — renders entity records as a searchable, sortable, paginated table.
7
+ * Columns determined by FieldConfig.tableColumn === true.
8
+ * Cell masking applied via RbacService (defense-in-depth).
9
+ * Pagination, sort, and search emit events — parent controls data fetching (smart/dumb pattern).
10
+ */
11
+ export class DynamicTableComponent {
12
+ // ─── Inputs ───────────────────────────────────────────────────────────────
13
+ config;
14
+ data = [];
15
+ userRoles = [];
16
+ language = 'en';
17
+ loading = false;
18
+ error = null;
19
+ totalRecords = 0;
20
+ pageSize = 20;
21
+ currentPage = 1;
22
+ // ─── Outputs ──────────────────────────────────────────────────────────────
23
+ rowClick = new EventEmitter();
24
+ rowEdit = new EventEmitter();
25
+ rowDelete = new EventEmitter();
26
+ pageChange = new EventEmitter();
27
+ pageSizeChange = new EventEmitter();
28
+ sortChange = new EventEmitter();
29
+ searchChange = new EventEmitter();
30
+ // ─── Services ─────────────────────────────────────────────────────────────
31
+ rbacService = inject(RbacService);
32
+ versionService = inject(VersionService);
33
+ // ─── Signals ──────────────────────────────────────────────────────────────
34
+ sortField = signal('');
35
+ sortDir = signal('desc');
36
+ searchTerm = signal('');
37
+ // ─── Computed ─────────────────────────────────────────────────────────────
38
+ tableColumns = computed(() => (this.config?.fields || []).filter(f => f.tableColumn === true));
39
+ get permissions() {
40
+ return this.rbacService.getPermissions(this.config, this.userRoles);
41
+ }
42
+ get totalPages() {
43
+ return Math.ceil(this.totalRecords / this.pageSize) || 0;
44
+ }
45
+ ngOnChanges(_changes) {
46
+ // Pre-resolve masking per column once (ONEHERMES hardening: pre-resolved at extract time)
47
+ this._resolveColumnMasks();
48
+ }
49
+ columnMaskCache = new Map();
50
+ _resolveColumnMasks() {
51
+ if (!this.config)
52
+ return;
53
+ this.columnMaskCache.clear();
54
+ for (const field of this.tableColumns()) {
55
+ const tab = (this.config.tabs || []).find(t => t.id === field.tab);
56
+ const masked = this.rbacService.shouldMaskField(field, tab, this.config, this.userRoles);
57
+ this.columnMaskCache.set(field.id, masked);
58
+ }
59
+ }
60
+ // ─── Helpers ──────────────────────────────────────────────────────────────
61
+ isCellMasked(fieldId) {
62
+ return this.columnMaskCache.get(fieldId) ?? false;
63
+ }
64
+ getCellValue(record, fieldId) {
65
+ if (this.isCellMasked(fieldId))
66
+ return 'XXXXXXXXX';
67
+ const value = record[fieldId];
68
+ if (value === null || value === undefined)
69
+ return '—';
70
+ if (typeof value === 'boolean')
71
+ return value ? 'Yes' : 'No';
72
+ if (value instanceof Date || (typeof value === 'string' && value.match(/^\d{4}-\d{2}-\d{2}/))) {
73
+ try {
74
+ return new Date(value).toLocaleDateString();
75
+ }
76
+ catch {
77
+ return String(value);
78
+ }
79
+ }
80
+ if (Array.isArray(value))
81
+ return value.join(', ');
82
+ return String(value);
83
+ }
84
+ needsMigration(record) {
85
+ return this.versionService.needsMigration(record, this.config);
86
+ }
87
+ getColumnLabel(fieldId) {
88
+ const field = (this.config?.fields || []).find(f => f.id === fieldId);
89
+ return field?.label?.[this.language] || field?.label?.['en'] || fieldId;
90
+ }
91
+ // ─── Sort / Search / Pagination ───────────────────────────────────────────
92
+ onSort(fieldId) {
93
+ if (this.sortField() === fieldId) {
94
+ const next = this.sortDir() === 'asc' ? 'desc' : 'asc';
95
+ this.sortDir.set(next);
96
+ }
97
+ else {
98
+ this.sortField.set(fieldId);
99
+ this.sortDir.set('asc');
100
+ }
101
+ this.sortChange.emit({ field: this.sortField(), dir: this.sortDir() });
102
+ }
103
+ onSearch(event) {
104
+ const value = event.target.value;
105
+ this.searchTerm.set(value);
106
+ this.searchChange.emit(value);
107
+ }
108
+ onPageChange(page) {
109
+ if (page < 1 || page > this.totalPages)
110
+ return;
111
+ this.pageChange.emit(page);
112
+ }
113
+ onEdit(event, record) {
114
+ event.stopPropagation();
115
+ this.rowEdit.emit(record);
116
+ }
117
+ onDelete(event, record) {
118
+ event.stopPropagation();
119
+ this.rowDelete.emit(record);
120
+ }
121
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicTableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
122
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: DynamicTableComponent, isStandalone: true, selector: "ngx-dynamic-table", inputs: { config: "config", data: "data", userRoles: "userRoles", language: "language", loading: "loading", error: "error", totalRecords: "totalRecords", pageSize: "pageSize", currentPage: "currentPage" }, outputs: { rowClick: "rowClick", rowEdit: "rowEdit", rowDelete: "rowDelete", pageChange: "pageChange", pageSizeChange: "pageSizeChange", sortChange: "sortChange", searchChange: "searchChange" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"ngx-table\">\n\n <!-- Search bar -->\n <div class=\"ngx-table__toolbar\">\n <input\n class=\"ngx-table__search\"\n type=\"search\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearch($event)\"\n aria-label=\"Search records\"\n />\n </div>\n\n <!-- Error -->\n @if (error) {\n <div class=\"ngx-table__error\" role=\"alert\">{{ error }}</div>\n }\n\n <!-- Table -->\n <table class=\"ngx-table__table\">\n <thead>\n <tr>\n @for (col of tableColumns(); track col.id) {\n <th\n class=\"ngx-table__th\"\n [style.width]=\"col.columnWidth || 'auto'\"\n (click)=\"onSort(col.id)\"\n [attr.aria-sort]=\"sortField() === col.id ? sortDir() : 'none'\"\n role=\"columnheader\"\n >\n {{ getColumnLabel(col.id) }}\n @if (sortField() === col.id) {\n <span class=\"ngx-table__sort-icon\">{{ sortDir() === 'asc' ? '\u2191' : '\u2193' }}</span>\n }\n </th>\n }\n <!-- Actions column -->\n @if (permissions.canEdit || permissions.canDelete) {\n <th class=\"ngx-table__th ngx-table__th--actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody>\n @if (loading) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__loading\">Loading\u2026</td>\n </tr>\n } @else if (!data || !data.length) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__empty\">No records found</td>\n </tr>\n } @else {\n @for (record of data; track record['_id']) {\n <tr class=\"ngx-table__row\" (click)=\"rowClick.emit(record)\">\n @for (col of tableColumns(); track col.id) {\n <td\n class=\"ngx-table__td\"\n [class.ngx-table__td--masked]=\"isCellMasked(col.id)\"\n >\n {{ getCellValue(record, col.id) }}\n <!-- Migration badge \u2014 shows when record is stale -->\n @if (tableColumns().length > 0 && col.id === tableColumns()[0].id && needsMigration(record)) {\n <span class=\"ngx-table__migration-badge\" title=\"Needs migration\">\u26A0</span>\n }\n </td>\n }\n <!-- Action buttons \u2014 gated by RBAC permissions -->\n @if (permissions.canEdit || permissions.canDelete) {\n <td class=\"ngx-table__td ngx-table__actions\">\n @if (permissions.canEdit) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--edit\"\n (click)=\"onEdit($event, record)\"\n title=\"Edit\"\n >\u270F</button>\n }\n @if (permissions.canDelete) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--delete\"\n (click)=\"onDelete($event, record)\"\n title=\"Delete\"\n >\uD83D\uDDD1</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <div class=\"ngx-table__pagination\">\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"onPageChange(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2190</button>\n\n <span class=\"ngx-table__page-info\">\n Page {{ currentPage }} of {{ totalPages }} ({{ totalRecords }} records)\n </span>\n\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"onPageChange(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u2192</button>\n </div>\n }\n\n</div>\n" });
123
+ }
124
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: DynamicTableComponent, decorators: [{
125
+ type: Component,
126
+ args: [{ selector: 'ngx-dynamic-table', standalone: true, imports: [], template: "<div class=\"ngx-table\">\n\n <!-- Search bar -->\n <div class=\"ngx-table__toolbar\">\n <input\n class=\"ngx-table__search\"\n type=\"search\"\n placeholder=\"Search...\"\n [value]=\"searchTerm()\"\n (input)=\"onSearch($event)\"\n aria-label=\"Search records\"\n />\n </div>\n\n <!-- Error -->\n @if (error) {\n <div class=\"ngx-table__error\" role=\"alert\">{{ error }}</div>\n }\n\n <!-- Table -->\n <table class=\"ngx-table__table\">\n <thead>\n <tr>\n @for (col of tableColumns(); track col.id) {\n <th\n class=\"ngx-table__th\"\n [style.width]=\"col.columnWidth || 'auto'\"\n (click)=\"onSort(col.id)\"\n [attr.aria-sort]=\"sortField() === col.id ? sortDir() : 'none'\"\n role=\"columnheader\"\n >\n {{ getColumnLabel(col.id) }}\n @if (sortField() === col.id) {\n <span class=\"ngx-table__sort-icon\">{{ sortDir() === 'asc' ? '\u2191' : '\u2193' }}</span>\n }\n </th>\n }\n <!-- Actions column -->\n @if (permissions.canEdit || permissions.canDelete) {\n <th class=\"ngx-table__th ngx-table__th--actions\">Actions</th>\n }\n </tr>\n </thead>\n <tbody>\n @if (loading) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__loading\">Loading\u2026</td>\n </tr>\n } @else if (!data || !data.length) {\n <tr>\n <td [attr.colspan]=\"tableColumns().length + 1\" class=\"ngx-table__empty\">No records found</td>\n </tr>\n } @else {\n @for (record of data; track record['_id']) {\n <tr class=\"ngx-table__row\" (click)=\"rowClick.emit(record)\">\n @for (col of tableColumns(); track col.id) {\n <td\n class=\"ngx-table__td\"\n [class.ngx-table__td--masked]=\"isCellMasked(col.id)\"\n >\n {{ getCellValue(record, col.id) }}\n <!-- Migration badge \u2014 shows when record is stale -->\n @if (tableColumns().length > 0 && col.id === tableColumns()[0].id && needsMigration(record)) {\n <span class=\"ngx-table__migration-badge\" title=\"Needs migration\">\u26A0</span>\n }\n </td>\n }\n <!-- Action buttons \u2014 gated by RBAC permissions -->\n @if (permissions.canEdit || permissions.canDelete) {\n <td class=\"ngx-table__td ngx-table__actions\">\n @if (permissions.canEdit) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--edit\"\n (click)=\"onEdit($event, record)\"\n title=\"Edit\"\n >\u270F</button>\n }\n @if (permissions.canDelete) {\n <button\n type=\"button\"\n class=\"ngx-table__action ngx-table__action--delete\"\n (click)=\"onDelete($event, record)\"\n title=\"Delete\"\n >\uD83D\uDDD1</button>\n }\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <div class=\"ngx-table__pagination\">\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage <= 1\"\n (click)=\"onPageChange(currentPage - 1)\"\n aria-label=\"Previous page\"\n >\u2190</button>\n\n <span class=\"ngx-table__page-info\">\n Page {{ currentPage }} of {{ totalPages }} ({{ totalRecords }} records)\n </span>\n\n <button\n type=\"button\"\n class=\"ngx-table__page-btn\"\n [disabled]=\"currentPage >= totalPages\"\n (click)=\"onPageChange(currentPage + 1)\"\n aria-label=\"Next page\"\n >\u2192</button>\n </div>\n }\n\n</div>\n" }]
127
+ }], propDecorators: { config: [{
128
+ type: Input
129
+ }], data: [{
130
+ type: Input
131
+ }], userRoles: [{
132
+ type: Input
133
+ }], language: [{
134
+ type: Input
135
+ }], loading: [{
136
+ type: Input
137
+ }], error: [{
138
+ type: Input
139
+ }], totalRecords: [{
140
+ type: Input
141
+ }], pageSize: [{
142
+ type: Input
143
+ }], currentPage: [{
144
+ type: Input
145
+ }], rowClick: [{
146
+ type: Output
147
+ }], rowEdit: [{
148
+ type: Output
149
+ }], rowDelete: [{
150
+ type: Output
151
+ }], pageChange: [{
152
+ type: Output
153
+ }], pageSizeChange: [{
154
+ type: Output
155
+ }], sortChange: [{
156
+ type: Output
157
+ }], searchChange: [{
158
+ type: Output
159
+ }] } });
160
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHluYW1pYy10YWJsZS5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL3RhYmxlL2R5bmFtaWMtdGFibGUuY29tcG9uZW50LnRzIiwiLi4vLi4vLi4vLi4vc3JjL2xpYi90YWJsZS9keW5hbWljLXRhYmxlLmNvbXBvbmVudC5odG1sIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxTQUFTLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxZQUFZLEVBQUUsTUFBTSxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQTRCLE1BQU0sZUFBZSxDQUFDO0FBRTNILE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sNkJBQTZCLENBQUM7O0FBRTdEOzs7OztHQUtHO0FBT0gsTUFBTSxPQUFPLHFCQUFxQjtJQUNoQyw2RUFBNkU7SUFDcEUsTUFBTSxDQUFnQjtJQUN0QixJQUFJLEdBQXNCLEVBQUUsQ0FBQztJQUM3QixTQUFTLEdBQWEsRUFBRSxDQUFDO0lBQ3pCLFFBQVEsR0FBVyxJQUFJLENBQUM7SUFDeEIsT0FBTyxHQUFZLEtBQUssQ0FBQztJQUN6QixLQUFLLEdBQWtCLElBQUksQ0FBQztJQUM1QixZQUFZLEdBQVcsQ0FBQyxDQUFDO0lBQ3pCLFFBQVEsR0FBVyxFQUFFLENBQUM7SUFDdEIsV0FBVyxHQUFXLENBQUMsQ0FBQztJQUVqQyw2RUFBNkU7SUFDbkUsUUFBUSxHQUFHLElBQUksWUFBWSxFQUFtQixDQUFDO0lBQy9DLE9BQU8sR0FBRyxJQUFJLFlBQVksRUFBbUIsQ0FBQztJQUM5QyxTQUFTLEdBQUcsSUFBSSxZQUFZLEVBQW1CLENBQUM7SUFDaEQsVUFBVSxHQUFHLElBQUksWUFBWSxFQUFVLENBQUM7SUFDeEMsY0FBYyxHQUFHLElBQUksWUFBWSxFQUFVLENBQUM7SUFDNUMsVUFBVSxHQUFHLElBQUksWUFBWSxF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SEE7OzRGRHZHYSxxQkFBcUI7a0JBTmpDLFNBQVM7K0JBQ0UsbUJBQW1CLGNBQ2pCLElBQUksV0FDUCxFQUFFOzhCQUtGLE1BQU07c0JBQWQsS0FBSztnQkFDRyxJQUFJO3NCQUFaLEtBQUs7Z0JBQ0csU0FBUztzQkFBakIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLE9BQU87c0JBQWYsS0FBSztnQkFDRyxLQUFLO3NCQUFiLEtBQUs7Z0JBQ0csWUFBWTtzQkFBcEIsS0FBSztnQkFDRyxRQUFRO3NCQUFoQixLQUFLO2dCQUNHLFdBQVc7c0JBQW5CLEtBQUs7Z0JBR0ksUUFBUTtzQkFBakIsTUFBTTtnQkFDRyxPQUFPO3NCQUFoQixNQUFNO2dCQUNHLFNBQVM7c0JBQWxCLE1BQU07Z0JBQ0csVUFBVTtzQkFBbkIsTUFBTTtnQkFDRyxjQUFjO3NCQUF2QixNQUFNO2dCQUNHLFVBQVU7c0JBQW5CLE1BQU07Z0JBQ0csWUFBWTtzQkFBckIsTUFBTSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IENvbXBvbmVudCwgSW5wdXQsIE91dHB1dCwgRXZlbnRFbWl0dGVyLCBzaWduYWwsIGNvbXB1dGVkLCBpbmplY3QsIE9uQ2hhbmdlcywgU2ltcGxlQ2hhbmdlcyB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHR5cGUgeyBFbnRpdHlDb25maWcsIFZlcnNpb25lZFJlY29yZCB9IGZyb20gJ0BkeW5hbWljLWVudGl0eS9jb3JlJztcbmltcG9ydCB7IFJiYWNTZXJ2aWNlIH0gZnJvbSAnLi4vc2VydmljZXMvcmJhYy5zZXJ2aWNlJztcbmltcG9ydCB7IFZlcnNpb25TZXJ2aWNlIH0gZnJvbSAnLi4vc2VydmljZXMvdmVyc2lvbi5zZXJ2aWNlJztcblxuLyoqXG4gKiBEeW5hbWljVGFibGVDb21wb25lbnQg4oCUIHJlbmRlcnMgZW50aXR5IHJlY29yZHMgYXMgYSBzZWFyY2hhYmxlLCBzb3J0YWJsZSwgcGFnaW5hdGVkIHRhYmxlLlxuICogQ29sdW1ucyBkZXRlcm1pbmVkIGJ5IEZpZWxkQ29uZmlnLnRhYmxlQ29sdW1uID09PSB0cnVlLlxuICogQ2VsbCBtYXNraW5nIGFwcGxpZWQgdmlhIFJiYWNTZXJ2aWNlIChkZWZlbnNlLWluLWRlcHRoKS5cbiAqIFBhZ2luYXRpb24sIHNvcnQsIGFuZCBzZWFyY2ggZW1pdCBldmVudHMg4oCUIHBhcmVudCBjb250cm9scyBkYXRhIGZldGNoaW5nIChzbWFydC9kdW1iIHBhdHRlcm4pLlxuICovXG5AQ29tcG9uZW50KHtcbiAgc2VsZWN0b3I6ICduZ3gtZHluYW1pYy10YWJsZScsXG4gIHN0YW5kYWxvbmU6IHRydWUsXG4gIGltcG9ydHM6IFtdLFxuICB0ZW1wbGF0ZVVybDogJy4vZHluYW1pYy10YWJsZS5jb21wb25lbnQuaHRtbCcsXG59KVxuZXhwb3J0IGNsYXNzIER5bmFtaWNUYWJsZUNvbXBvbmVudCBpbXBsZW1lbnRzIE9uQ2hhbmdlcyB7XG4gIC8vIOKUgOKUgOKUgCBJbnB1dHMg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSAXG4gIEBJbnB1dCgpIGNvbmZpZyE6IEVudGl0eUNvbmZpZztcbiAgQElucHV0KCkgZGF0YTogVmVyc2lvbmVkUmVjb3JkW10gPSBbXTtcbiAgQElucHV0KCkgdXNlclJvbGVzOiBzdHJpbmdbXSA9IFtdO1xuICBASW5wdXQoKSBsYW5ndWFnZTogc3RyaW5nID0gJ2VuJztcbiAgQElucHV0KCkgbG9hZGluZzogYm9vbGVhbiA9IGZhbHNlO1xuICBASW5wdXQoKSBlcnJvcjogc3RyaW5nIHwgbnVsbCA9IG51bGw7XG4gIEBJbnB1dCgpIHRvdGFsUmVjb3JkczogbnVtYmVyID0gMDtcbiAgQElucHV0KCkgcGFnZVNpemU6IG51bWJlciA9IDIwO1xuICBASW5wdXQoKSBjdXJyZW50UGFnZTogbnVtYmVyID0gMTtcblxuICAvLyDilIDilIDilIAgT3V0cHV0cyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgQE91dHB1dCgpIHJvd0NsaWNrID0gbmV3IEV2ZW50RW1pdHRlcjxWZXJzaW9uZWRSZWNvcmQ+KCk7XG4gIEBPdXRwdXQoKSByb3dFZGl0ID0gbmV3IEV2ZW50RW1pdHRlcjxWZXJzaW9uZWRSZWNvcmQ+KCk7XG4gIEBPdXRwdXQoKSByb3dEZWxldGUgPSBuZXcgRXZlbnRFbWl0dGVyPFZlcnNpb25lZFJlY29yZD4oKTtcbiAgQE91dHB1dCgpIHBhZ2VDaGFuZ2UgPSBuZXcgRXZlbnRFbWl0dGVyPG51bWJlcj4oKTtcbiAgQE91dHB1dCgpIHBhZ2VTaXplQ2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjxudW1iZXI+KCk7XG4gIEBPdXRwdXQoKSBzb3J0Q2hhbmdlID0gbmV3IEV2ZW50RW1pdHRlcjx7IGZpZWxkOiBzdHJpbmc7IGRpcjogJ2FzYycgfCAnZGVzYycgfT4oKTtcbiAgQE91dHB1dCgpIHNlYXJjaENoYW5nZSA9IG5ldyBFdmVudEVtaXR0ZXI8c3RyaW5nPigpO1xuXG4gIC8vIOKUgOKUgOKUgCBTZXJ2aWNlcyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIBcbiAgcHJpdmF0ZSByZWFkb25seSByYmFjU2VydmljZSA9IGluamVjdChSYmFjU2VydmljZSk7XG4gIHByaXZhdGUgcmVhZG9ubHkgdmVyc2lvblNlcnZpY2UgPSBpbmplY3QoVmVyc2lvblNlcnZpY2UpO1xuXG4gIC8vIOKUgOKUgOKUgCBTaWduYWxzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICByZWFkb25seSBzb3J0RmllbGQgPSBzaWduYWw8c3RyaW5nPignJyk7XG4gIHJlYWRvbmx5IHNvcnREaXIgPSBzaWduYWw8J2FzYycgfCAnZGVzYyc+KCdkZXNjJyk7XG4gIHJlYWRvbmx5IHNlYXJjaFRlcm0gPSBzaWduYWw8c3RyaW5nPignJyk7XG5cbiAgLy8g4pSA4pSA4pSAIENvbXB1dGVkIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuICByZWFkb25seSB0YWJsZUNvbHVtbnMgPSBjb21wdXRlZCgoKSA9PiBcbiAgICAodGhpcy5jb25maWc/LmZpZWxkcyB8fCBbXSkuZmlsdGVyKGYgPT4gZi50YWJsZUNvbHVtbiA9PT0gdHJ1ZSlcbiAgKTtcblxuICBnZXQgcGVybWlzc2lvbnMoKSB7XG4gICAgcmV0dXJuIHRoaXMucmJhY1NlcnZpY2UuZ2V0UGVybWlzc2lvbnModGhpcy5jb25maWcsIHRoaXMudXNlclJvbGVzKTtcbiAgfVxuXG4gIGdldCB0b3RhbFBhZ2VzKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIE1hdGguY2VpbCh0aGlzLnRvdGFsUmVjb3JkcyAvIHRoaXMucGFnZVNpemUpIHx8IDA7XG4gIH1cblxuICBuZ09uQ2hhbmdlcyhfY2hhbmdlczogU2ltcGxlQ2hhbmdlcyk6IHZvaWQge1xuICAgIC8vIFByZS1yZXNvbHZlIG1hc2tpbmcgcGVyIGNvbHVtbiBvbmNlIChPTkVIRVJNRVMgaGFyZGVuaW5nOiBwcmUtcmVzb2x2ZWQgYXQgZXh0cmFjdCB0aW1lKVxuICAgIHRoaXMuX3Jlc29sdmVDb2x1bW5NYXNrcygpO1xuICB9XG5cbiAgcHJpdmF0ZSBjb2x1bW5NYXNrQ2FjaGUgPSBuZXcgTWFwPHN0cmluZywgYm9vbGVhbj4oKTtcblxuICBwcml2YXRlIF9yZXNvbHZlQ29sdW1uTWFza3MoKTogdm9pZCB7XG4gICAgaWYgKCF0aGlzLmNvbmZpZykgcmV0dXJuO1xuICAgIHRoaXMuY29sdW1uTWFza0NhY2hlLmNsZWFyKCk7XG4gICAgZm9yIChjb25zdCBmaWVsZCBvZiB0aGlzLnRhYmxlQ29sdW1ucygpKSB7XG4gICAgICBjb25zdCB0YWIgPSAodGhpcy5jb25maWcudGFicyB8fCBbXSkuZmluZCh0ID0+IHQuaWQgPT09IGZpZWxkLnRhYik7XG4gICAgICBjb25zdCBtYXNrZWQgPSB0aGlzLnJiYWNTZXJ2aWNlLnNob3VsZE1hc2tGaWVsZChmaWVsZCwgdGFiLCB0aGlzLmNvbmZpZywgdGhpcy51c2VyUm9sZXMpO1xuICAgICAgdGhpcy5jb2x1bW5NYXNrQ2FjaGUuc2V0KGZpZWxkLmlkLCBtYXNrZWQpO1xuICAgIH1cbiAgfVxuXG4gIC8vIOKUgOKUgOKUgCBIZWxwZXJzIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIGlzQ2VsbE1hc2tlZChmaWVsZElkOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy5jb2x1bW5NYXNrQ2FjaGUuZ2V0KGZpZWxkSWQpID8/IGZhbHNlO1xuICB9XG5cbiAgZ2V0Q2VsbFZhbHVlKHJlY29yZDogVmVyc2lvbmVkUmVjb3JkLCBmaWVsZElkOiBzdHJpbmcpOiBzdHJpbmcge1xuICAgIGlmICh0aGlzLmlzQ2VsbE1hc2tlZChmaWVsZElkKSkgcmV0dXJuICdYWFhYWFhYWFgnO1xuICAgIGNvbnN0IHZhbHVlID0gcmVjb3JkW2ZpZWxkSWRdO1xuICAgIGlmICh2YWx1ZSA9PT0gbnVsbCB8fCB2YWx1ZSA9PT0gdW5kZWZpbmVkKSByZXR1cm4gJ+KAlCc7XG4gICAgaWYgKHR5cGVvZiB2YWx1ZSA9PT0gJ2Jvb2xlYW4nKSByZXR1cm4gdmFsdWUgPyAnWWVzJyA6ICdObyc7XG4gICAgaWYgKHZhbHVlIGluc3RhbmNlb2YgRGF0ZSB8fCAodHlwZW9mIHZhbHVlID09PSAnc3RyaW5nJyAmJiB2YWx1ZS5tYXRjaCgvXlxcZHs0fS1cXGR7Mn0tXFxkezJ9LykpKSB7XG4gICAgICB0cnkge1xuICAgICAgICByZXR1cm4gbmV3IERhdGUodmFsdWUpLnRvTG9jYWxlRGF0ZVN0cmluZygpO1xuICAgICAgfSBjYXRjaCB7XG4gICAgICAgIHJldHVybiBTdHJpbmcodmFsdWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAoQXJyYXkuaXNBcnJheSh2YWx1ZSkpIHJldHVybiB2YWx1ZS5qb2luKCcsICcpO1xuICAgIHJldHVybiBTdHJpbmcodmFsdWUpO1xuICB9XG5cbiAgbmVlZHNNaWdyYXRpb24ocmVjb3JkOiBWZXJzaW9uZWRSZWNvcmQpOiBib29sZWFuIHtcbiAgICByZXR1cm4gdGhpcy52ZXJzaW9uU2VydmljZS5uZWVkc01pZ3JhdGlvbihyZWNvcmQsIHRoaXMuY29uZmlnKTtcbiAgfVxuXG4gIGdldENvbHVtbkxhYmVsKGZpZWxkSWQ6IHN0cmluZyk6IHN0cmluZyB7XG4gICAgY29uc3QgZmllbGQgPSAodGhpcy5jb25maWc/LmZpZWxkcyB8fCBbXSkuZmluZChmID0+IGYuaWQgPT09IGZpZWxkSWQpO1xuICAgIHJldHVybiBmaWVsZD8ubGFiZWw/Llt0aGlzLmxhbmd1YWdlXSB8fCBmaWVsZD8ubGFiZWw/LlsnZW4nXSB8fCBmaWVsZElkO1xuICB9XG5cbiAgLy8g4pSA4pSA4pSAIFNvcnQgLyBTZWFyY2ggLyBQYWdpbmF0aW9uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgFxuXG4gIG9uU29ydChmaWVsZElkOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5zb3J0RmllbGQoKSA9PT0gZmllbGRJZCkge1xuICAgICAgY29uc3QgbmV4dCA9IHRoaXMuc29ydERpcigpID09PSAnYXNjJyA/ICdkZXNjJyA6ICdhc2MnO1xuICAgICAgdGhpcy5zb3J0RGlyLnNldChuZXh0KTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5zb3J0RmllbGQuc2V0KGZpZWxkSWQpO1xuICAgICAgdGhpcy5zb3J0RGlyLnNldCgnYXNjJyk7XG4gICAgfVxuICAgIHRoaXMuc29ydENoYW5nZS5lbWl0KHsgZmllbGQ6IHRoaXMuc29ydEZpZWxkKCksIGRpcjogdGhpcy5zb3J0RGlyKCkgfSk7XG4gIH1cblxuICBvblNlYXJjaChldmVudDogRXZlbnQpOiB2b2lkIHtcbiAgICBjb25zdCB2YWx1ZSA9IChldmVudC50YXJnZXQgYXMgSFRNTElucHV0RWxlbWVudCkudmFsdWU7XG4gICAgdGhpcy5zZWFyY2hUZXJtLnNldCh2YWx1ZSk7XG4gICAgdGhpcy5zZWFyY2hDaGFuZ2UuZW1pdCh2YWx1ZSk7XG4gIH1cblxuICBvblBhZ2VDaGFuZ2UocGFnZTogbnVtYmVyKTogdm9pZCB7XG4gICAgaWYgKHBhZ2UgPCAxIHx8IHBhZ2UgPiB0aGlzLnRvdGFsUGFnZXMpIHJldHVybjtcbiAgICB0aGlzLnBhZ2VDaGFuZ2UuZW1pdChwYWdlKTtcbiAgfVxuXG4gIG9uRWRpdChldmVudDogRXZlbnQsIHJlY29yZDogVmVyc2lvbmVkUmVjb3JkKTogdm9pZCB7XG4gICAgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgdGhpcy5yb3dFZGl0LmVtaXQocmVjb3JkKTtcbiAgfVxuXG4gIG9uRGVsZXRlKGV2ZW50OiBFdmVudCwgcmVjb3JkOiBWZXJzaW9uZWRSZWNvcmQpOiB2b2lkIHtcbiAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICB0aGlzLnJvd0RlbGV0ZS5lbWl0KHJlY29yZCk7XG4gIH1cbn1cbiIsIjxkaXYgY2xhc3M9XCJuZ3gtdGFibGVcIj5cblxuICA8IS0tIFNlYXJjaCBiYXIgLS0+XG4gIDxkaXYgY2xhc3M9XCJuZ3gtdGFibGVfX3Rvb2xiYXJcIj5cbiAgICA8aW5wdXRcbiAgICAgIGNsYXNzPVwibmd4LXRhYmxlX19zZWFyY2hcIlxuICAgICAgdHlwZT1cInNlYXJjaFwiXG4gICAgICBwbGFjZWhvbGRlcj1cIlNlYXJjaC4uLlwiXG4gICAgICBbdmFsdWVdPVwic2VhcmNoVGVybSgpXCJcbiAgICAgIChpbnB1dCk9XCJvblNlYXJjaCgkZXZlbnQpXCJcbiAgICAgIGFyaWEtbGFiZWw9XCJTZWFyY2ggcmVjb3Jkc1wiXG4gICAgLz5cbiAgPC9kaXY+XG5cbiAgPCEtLSBFcnJvciAtLT5cbiAgQGlmIChlcnJvcikge1xuICAgIDxkaXYgY2xhc3M9XCJuZ3gtdGFibGVfX2Vycm9yXCIgcm9sZT1cImFsZXJ0XCI+e3sgZXJyb3IgfX08L2Rpdj5cbiAgfVxuXG4gIDwhLS0gVGFibGUgLS0+XG4gIDx0YWJsZSBjbGFzcz1cIm5neC10YWJsZV9fdGFibGVcIj5cbiAgICA8dGhlYWQ+XG4gICAgICA8dHI+XG4gICAgICAgIEBmb3IgKGNvbCBvZiB0YWJsZUNvbHVtbnMoKTsgdHJhY2sgY29sLmlkKSB7XG4gICAgICAgICAgPHRoXG4gICAgICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fdGhcIlxuICAgICAgICAgICAgW3N0eWxlLndpZHRoXT1cImNvbC5jb2x1bW5XaWR0aCB8fCAnYXV0bydcIlxuICAgICAgICAgICAgKGNsaWNrKT1cIm9uU29ydChjb2wuaWQpXCJcbiAgICAgICAgICAgIFthdHRyLmFyaWEtc29ydF09XCJzb3J0RmllbGQoKSA9PT0gY29sLmlkID8gc29ydERpcigpIDogJ25vbmUnXCJcbiAgICAgICAgICAgIHJvbGU9XCJjb2x1bW5oZWFkZXJcIlxuICAgICAgICAgID5cbiAgICAgICAgICAgIHt7IGdldENvbHVtbkxhYmVsKGNvbC5pZCkgfX1cbiAgICAgICAgICAgIEBpZiAoc29ydEZpZWxkKCkgPT09IGNvbC5pZCkge1xuICAgICAgICAgICAgICA8c3BhbiBjbGFzcz1cIm5neC10YWJsZV9fc29ydC1pY29uXCI+e3sgc29ydERpcigpID09PSAnYXNjJyA/ICfihpEnIDogJ+KGkycgfX08L3NwYW4+XG4gICAgICAgICAgICB9XG4gICAgICAgICAgPC90aD5cbiAgICAgICAgfVxuICAgICAgICA8IS0tIEFjdGlvbnMgY29sdW1uIC0tPlxuICAgICAgICBAaWYgKHBlcm1pc3Npb25zLmNhbkVkaXQgfHwgcGVybWlzc2lvbnMuY2FuRGVsZXRlKSB7XG4gICAgICAgICAgPHRoIGNsYXNzPVwibmd4LXRhYmxlX190aCBuZ3gtdGFibGVfX3RoLS1hY3Rpb25zXCI+QWN0aW9uczwvdGg+XG4gICAgICAgIH1cbiAgICAgIDwvdHI+XG4gICAgPC90aGVhZD5cbiAgICA8dGJvZHk+XG4gICAgICBAaWYgKGxvYWRpbmcpIHtcbiAgICAgICAgPHRyPlxuICAgICAgICAgIDx0ZCBbYXR0ci5jb2xzcGFuXT1cInRhYmxlQ29sdW1ucygpLmxlbmd0aCArIDFcIiBjbGFzcz1cIm5neC10YWJsZV9fbG9hZGluZ1wiPkxvYWRpbmfigKY8L3RkPlxuICAgICAgICA8L3RyPlxuICAgICAgfSBAZWxzZSBpZiAoIWRhdGEgfHwgIWRhdGEubGVuZ3RoKSB7XG4gICAgICAgIDx0cj5cbiAgICAgICAgICA8dGQgW2F0dHIuY29sc3Bhbl09XCJ0YWJsZUNvbHVtbnMoKS5sZW5ndGggKyAxXCIgY2xhc3M9XCJuZ3gtdGFibGVfX2VtcHR5XCI+Tm8gcmVjb3JkcyBmb3VuZDwvdGQ+XG4gICAgICAgIDwvdHI+XG4gICAgICB9IEBlbHNlIHtcbiAgICAgICAgQGZvciAocmVjb3JkIG9mIGRhdGE7IHRyYWNrIHJlY29yZFsnX2lkJ10pIHtcbiAgICAgICAgICA8dHIgY2xhc3M9XCJuZ3gtdGFibGVfX3Jvd1wiIChjbGljayk9XCJyb3dDbGljay5lbWl0KHJlY29yZClcIj5cbiAgICAgICAgICAgIEBmb3IgKGNvbCBvZiB0YWJsZUNvbHVtbnMoKTsgdHJhY2sgY29sLmlkKSB7XG4gICAgICAgICAgICAgIDx0ZFxuICAgICAgICAgICAgICAgIGNsYXNzPVwibmd4LXRhYmxlX190ZFwiXG4gICAgICAgICAgICAgICAgW2NsYXNzLm5neC10YWJsZV9fdGQtLW1hc2tlZF09XCJpc0NlbGxNYXNrZWQoY29sLmlkKVwiXG4gICAgICAgICAgICAgID5cbiAgICAgICAgICAgICAgICB7eyBnZXRDZWxsVmFsdWUocmVjb3JkLCBjb2wuaWQpIH19XG4gICAgICAgICAgICAgICAgPCEtLSBNaWdyYXRpb24gYmFkZ2Ug4oCUIHNob3dzIHdoZW4gcmVjb3JkIGlzIHN0YWxlIC0tPlxuICAgICAgICAgICAgICAgIEBpZiAodGFibGVDb2x1bW5zKCkubGVuZ3RoID4gMCAmJiBjb2wuaWQgPT09IHRhYmxlQ29sdW1ucygpWzBdLmlkICYmIG5lZWRzTWlncmF0aW9uKHJlY29yZCkpIHtcbiAgICAgICAgICAgICAgICAgIDxzcGFuIGNsYXNzPVwibmd4LXRhYmxlX19taWdyYXRpb24tYmFkZ2VcIiB0aXRsZT1cIk5lZWRzIG1pZ3JhdGlvblwiPuKaoDwvc3Bhbj5cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgIDwvdGQ+XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICA8IS0tIEFjdGlvbiBidXR0b25zIOKAlCBnYXRlZCBieSBSQkFDIHBlcm1pc3Npb25zIC0tPlxuICAgICAgICAgICAgQGlmIChwZXJtaXNzaW9ucy5jYW5FZGl0IHx8IHBlcm1pc3Npb25zLmNhbkRlbGV0ZSkge1xuICAgICAgICAgICAgICA8dGQgY2xhc3M9XCJuZ3gtdGFibGVfX3RkIG5neC10YWJsZV9fYWN0aW9uc1wiPlxuICAgICAgICAgICAgICAgIEBpZiAocGVybWlzc2lvbnMuY2FuRWRpdCkge1xuICAgICAgICAgICAgICAgICAgPGJ1dHRvblxuICAgICAgICAgICAgICAgICAgICB0eXBlPVwiYnV0dG9uXCJcbiAgICAgICAgICAgICAgICAgICAgY2xhc3M9XCJuZ3gtdGFibGVfX2FjdGlvbiBuZ3gtdGFibGVfX2FjdGlvbi0tZWRpdFwiXG4gICAgICAgICAgICAgICAgICAgIChjbGljayk9XCJvbkVkaXQoJGV2ZW50LCByZWNvcmQpXCJcbiAgICAgICAgICAgICAgICAgICAgdGl0bGU9XCJFZGl0XCJcbiAgICAgICAgICAgICAgICAgID7inI88L2J1dHRvbj5cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgQGlmIChwZXJtaXNzaW9ucy5jYW5EZWxldGUpIHtcbiAgICAgICAgICAgICAgICAgIDxidXR0b25cbiAgICAgICAgICAgICAgICAgICAgdHlwZT1cImJ1dHRvblwiXG4gICAgICAgICAgICAgICAgICAgIGNsYXNzPVwibmd4LXRhYmxlX19hY3Rpb24gbmd4LXRhYmxlX19hY3Rpb24tLWRlbGV0ZVwiXG4gICAgICAgICAgICAgICAgICAgIChjbGljayk9XCJvbkRlbGV0ZSgkZXZlbnQsIHJlY29yZClcIlxuICAgICAgICAgICAgICAgICAgICB0aXRsZT1cIkRlbGV0ZVwiXG4gICAgICAgICAgICAgICAgICA+8J+XkTwvYnV0dG9uPlxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgPC90ZD5cbiAgICAgICAgICAgIH1cbiAgICAgICAgICA8L3RyPlxuICAgICAgICB9XG4gICAgICB9XG4gICAgPC90Ym9keT5cbiAgPC90YWJsZT5cblxuICA8IS0tIFBhZ2luYXRpb24gLS0+XG4gIEBpZiAodG90YWxQYWdlcyA+IDEpIHtcbiAgICA8ZGl2IGNsYXNzPVwibmd4LXRhYmxlX19wYWdpbmF0aW9uXCI+XG4gICAgICA8YnV0dG9uXG4gICAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fcGFnZS1idG5cIlxuICAgICAgICBbZGlzYWJsZWRdPVwiY3VycmVudFBhZ2UgPD0gMVwiXG4gICAgICAgIChjbGljayk9XCJvblBhZ2VDaGFuZ2UoY3VycmVudFBhZ2UgLSAxKVwiXG4gICAgICAgIGFyaWEtbGFiZWw9XCJQcmV2aW91cyBwYWdlXCJcbiAgICAgID7ihpA8L2J1dHRvbj5cblxuICAgICAgPHNwYW4gY2xhc3M9XCJuZ3gtdGFibGVfX3BhZ2UtaW5mb1wiPlxuICAgICAgICBQYWdlIHt7IGN1cnJlbnRQYWdlIH19IG9mIHt7IHRvdGFsUGFnZXMgfX0gKHt7IHRvdGFsUmVjb3JkcyB9fSByZWNvcmRzKVxuICAgICAgPC9zcGFuPlxuXG4gICAgICA8YnV0dG9uXG4gICAgICAgIHR5cGU9XCJidXR0b25cIlxuICAgICAgICBjbGFzcz1cIm5neC10YWJsZV9fcGFnZS1idG5cIlxuICAgICAgICBbZGlzYWJsZWRdPVwiY3VycmVudFBhZ2UgPj0gdG90YWxQYWdlc1wiXG4gICAgICAgIChjbGljayk9XCJvblBhZ2VDaGFuZ2UoY3VycmVudFBhZ2UgKyAxKVwiXG4gICAgICAgIGFyaWEtbGFiZWw9XCJOZXh0IHBhZ2VcIlxuICAgICAgPuKGkjwvYnV0dG9uPlxuICAgIDwvZGl2PlxuICB9XG5cbjwvZGl2PlxuIl19
@@ -0,0 +1,16 @@
1
+ import { InjectionToken } from '@angular/core';
2
+ /** Base URL for the dynamic-entity-server API */
3
+ export const DYNAMIC_ENTITY_API_URL = new InjectionToken('DYNAMIC_ENTITY_API_URL');
4
+ /** Roles that see XXXXXXXXX for masked fields (ADR-003) */
5
+ export const MASKED_ROLES = new InjectionToken('MASKED_ROLES');
6
+ /** Migration strategy for the frontend — 'strict' | 'graceful' only (ADR-005, never 'auto') */
7
+ export const MIGRATION_STRATEGY = new InjectionToken('MIGRATION_STRATEGY');
8
+ /** Registry: fieldType string → Angular component class */
9
+ export const FIELD_TYPE_REGISTRY = new InjectionToken('FIELD_TYPE_REGISTRY');
10
+ /** Registry: entity key → async () => options[] loader function */
11
+ export const ENTITY_REF_REGISTRY = new InjectionToken('ENTITY_REF_REGISTRY');
12
+ /** Registry: validator key → ValidatorFn */
13
+ export const VALIDATOR_REGISTRY = new InjectionToken('VALIDATOR_REGISTRY');
14
+ /** Registry: hook key → async (data, context) => data */
15
+ export const HOOK_REGISTRY = new InjectionToken('HOOK_REGISTRY');
16
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5qZWN0aW9uLXRva2Vucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvdG9rZW5zL2luamVjdGlvbi10b2tlbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUkvQyxpREFBaUQ7QUFDakQsTUFBTSxDQUFDLE1BQU0sc0JBQXNCLEdBQUcsSUFBSSxjQUFjLENBQVMsd0JBQXdCLENBQUMsQ0FBQztBQUUzRiwyREFBMkQ7QUFDM0QsTUFBTSxDQUFDLE1BQU0sWUFBWSxHQUFHLElBQUksY0FBYyxDQUFXLGNBQWMsQ0FBQyxDQUFDO0FBRXpFLCtGQUErRjtBQUMvRixNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLGNBQWMsQ0FBd0Isb0JBQW9CLENBQUMsQ0FBQztBQUVsRywyREFBMkQ7QUFDM0QsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQUcsSUFBSSxjQUFjLENBQXlCLHFCQUFxQixDQUFDLENBQUM7QUFFckcsbUVBQW1FO0FBQ25FLE1BQU0sQ0FBQyxNQUFNLG1CQUFtQixHQUFHLElBQUksY0FBYyxDQUNuRCxxQkFBcUIsQ0FDdEIsQ0FBQztBQUVGLDRDQUE0QztBQUM1QyxNQUFNLENBQUMsTUFBTSxrQkFBa0IsR0FBRyxJQUFJLGNBQWMsQ0FBbUIsb0JBQW9CLENBQUMsQ0FBQztBQUU3Rix5REFBeUQ7QUFDekQsTUFBTSxDQUFDLE1BQU0sYUFBYSxHQUFHLElBQUksY0FBYyxDQUF3QixlQUFlLENBQUMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGlvblRva2VuIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgdHlwZSB7IEVudGl0eUNvbmZpZywgRmllbGRDb25maWcsIE1pZ3JhdGlvblN0cmF0ZWd5IH0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuaW1wb3J0IHR5cGUgeyBUeXBlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5cbi8qKiBCYXNlIFVSTCBmb3IgdGhlIGR5bmFtaWMtZW50aXR5LXNlcnZlciBBUEkgKi9cbmV4cG9ydCBjb25zdCBEWU5BTUlDX0VOVElUWV9BUElfVVJMID0gbmV3IEluamVjdGlvblRva2VuPHN0cmluZz4oJ0RZTkFNSUNfRU5USVRZX0FQSV9VUkwnKTtcblxuLyoqIFJvbGVzIHRoYXQgc2VlIFhYWFhYWFhYWCBmb3IgbWFza2VkIGZpZWxkcyAoQURSLTAwMykgKi9cbmV4cG9ydCBjb25zdCBNQVNLRURfUk9MRVMgPSBuZXcgSW5qZWN0aW9uVG9rZW48c3RyaW5nW10+KCdNQVNLRURfUk9MRVMnKTtcblxuLyoqIE1pZ3JhdGlvbiBzdHJhdGVneSBmb3IgdGhlIGZyb250ZW5kIOKAlCAnc3RyaWN0JyB8ICdncmFjZWZ1bCcgb25seSAoQURSLTAwNSwgbmV2ZXIgJ2F1dG8nKSAqL1xuZXhwb3J0IGNvbnN0IE1JR1JBVElPTl9TVFJBVEVHWSA9IG5ldyBJbmplY3Rpb25Ub2tlbjwnc3RyaWN0JyB8ICdncmFjZWZ1bCc+KCdNSUdSQVRJT05fU1RSQVRFR1knKTtcblxuLyoqIFJlZ2lzdHJ5OiBmaWVsZFR5cGUgc3RyaW5nIOKGkiBBbmd1bGFyIGNvbXBvbmVudCBjbGFzcyAqL1xuZXhwb3J0IGNvbnN0IEZJRUxEX1RZUEVfUkVHSVNUUlkgPSBuZXcgSW5qZWN0aW9uVG9rZW48TWFwPHN0cmluZywgVHlwZTxhbnk+Pj4oJ0ZJRUxEX1RZUEVfUkVHSVNUUlknKTtcblxuLyoqIFJlZ2lzdHJ5OiBlbnRpdHkga2V5IOKGkiBhc3luYyAoKSA9PiBvcHRpb25zW10gbG9hZGVyIGZ1bmN0aW9uICovXG5leHBvcnQgY29uc3QgRU5USVRZX1JFRl9SRUdJU1RSWSA9IG5ldyBJbmplY3Rpb25Ub2tlbjxNYXA8c3RyaW5nLCAoKSA9PiBQcm9taXNlPGFueVtdPj4+KFxuICAnRU5USVRZX1JFRl9SRUdJU1RSWScsXG4pO1xuXG4vKiogUmVnaXN0cnk6IHZhbGlkYXRvciBrZXkg4oaSIFZhbGlkYXRvckZuICovXG5leHBvcnQgY29uc3QgVkFMSURBVE9SX1JFR0lTVFJZID0gbmV3IEluamVjdGlvblRva2VuPE1hcDxzdHJpbmcsIGFueT4+KCdWQUxJREFUT1JfUkVHSVNUUlknKTtcblxuLyoqIFJlZ2lzdHJ5OiBob29rIGtleSDihpIgYXN5bmMgKGRhdGEsIGNvbnRleHQpID0+IGRhdGEgKi9cbmV4cG9ydCBjb25zdCBIT09LX1JFR0lTVFJZID0gbmV3IEluamVjdGlvblRva2VuPE1hcDxzdHJpbmcsIEZ1bmN0aW9uPj4oJ0hPT0tfUkVHSVNUUlknKTtcbiJdfQ==
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWR5bmFtaWMtZW50aXR5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL25neC1keW5hbWljLWVudGl0eS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILGNBQWMsY0FBYyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBHZW5lcmF0ZWQgYnVuZGxlIGluZGV4LiBEbyBub3QgZWRpdC5cbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL3B1YmxpYy1hcGknO1xuIl19
@@ -0,0 +1,26 @@
1
+ // Tokens
2
+ export * from './lib/tokens/injection-tokens';
3
+ // Providers
4
+ export * from './lib/providers/provide-ngx-dynamic-entity';
5
+ // Services
6
+ export * from './lib/services/config.service';
7
+ export * from './lib/services/field-registry.service';
8
+ export * from './lib/services/hook-registry.service';
9
+ export * from './lib/services/validator-registry.service';
10
+ export * from './lib/services/entity-ref-registry.service';
11
+ export * from './lib/services/version.service';
12
+ export * from './lib/services/rbac.service';
13
+ // Components
14
+ export * from './lib/form/dynamic-form.component';
15
+ export * from './lib/form/dynamic-field/dynamic-field.component';
16
+ export * from './lib/table/dynamic-table.component';
17
+ // Field type components (individually importable — reusability rule)
18
+ export * from './lib/field-types/text-field.component';
19
+ export * from './lib/field-types/textarea-field.component';
20
+ export * from './lib/field-types/number-field.component';
21
+ export * from './lib/field-types/checkbox-field.component';
22
+ export * from './lib/field-types/date-field.component';
23
+ export * from './lib/field-types/dropdown-field.component';
24
+ export * from './lib/field-types/multi-select-field.component';
25
+ export * from './lib/field-types/entity-ref-field.component';
26
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9wdWJsaWMtYXBpLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLFNBQVM7QUFDVCxjQUFjLCtCQUErQixDQUFDO0FBRTlDLFlBQVk7QUFDWixjQUFjLDRDQUE0QyxDQUFDO0FBRTNELFdBQVc7QUFDWCxjQUFjLCtCQUErQixDQUFDO0FBQzlDLGNBQWMsdUNBQXVDLENBQUM7QUFDdEQsY0FBYyxzQ0FBc0MsQ0FBQztBQUNyRCxjQUFjLDJDQUEyQyxDQUFDO0FBQzFELGNBQWMsNENBQTRDLENBQUM7QUFDM0QsY0FBYyxnQ0FBZ0MsQ0FBQztBQUMvQyxjQUFjLDZCQUE2QixDQUFDO0FBRTVDLGFBQWE7QUFDYixjQUFjLG1DQUFtQyxDQUFDO0FBQ2xELGNBQWMsa0RBQWtELENBQUM7QUFDakUsY0FBYyxxQ0FBcUMsQ0FBQztBQUVwRCxxRUFBcUU7QUFDckUsY0FBYyx3Q0FBd0MsQ0FBQztBQUN2RCxjQUFjLDRDQUE0QyxDQUFDO0FBQzNELGNBQWMsMENBQTBDLENBQUM7QUFDekQsY0FBYyw0Q0FBNEMsQ0FBQztBQUMzRCxjQUFjLHdDQUF3QyxDQUFDO0FBQ3ZELGNBQWMsNENBQTRDLENBQUM7QUFDM0QsY0FBYyxnREFBZ0QsQ0FBQztBQUMvRCxjQUFjLDhDQUE4QyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gVG9rZW5zXG5leHBvcnQgKiBmcm9tICcuL2xpYi90b2tlbnMvaW5qZWN0aW9uLXRva2Vucyc7XG5cbi8vIFByb3ZpZGVyc1xuZXhwb3J0ICogZnJvbSAnLi9saWIvcHJvdmlkZXJzL3Byb3ZpZGUtbmd4LWR5bmFtaWMtZW50aXR5JztcblxuLy8gU2VydmljZXNcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2NvbmZpZy5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2ZpZWxkLXJlZ2lzdHJ5LnNlcnZpY2UnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvc2VydmljZXMvaG9vay1yZWdpc3RyeS5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL3ZhbGlkYXRvci1yZWdpc3RyeS5zZXJ2aWNlJztcbmV4cG9ydCAqIGZyb20gJy4vbGliL3NlcnZpY2VzL2VudGl0eS1yZWYtcmVnaXN0cnkuc2VydmljZSc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9zZXJ2aWNlcy92ZXJzaW9uLnNlcnZpY2UnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvc2VydmljZXMvcmJhYy5zZXJ2aWNlJztcblxuLy8gQ29tcG9uZW50c1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZm9ybS9keW5hbWljLWZvcm0uY29tcG9uZW50JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2Zvcm0vZHluYW1pYy1maWVsZC9keW5hbWljLWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi90YWJsZS9keW5hbWljLXRhYmxlLmNvbXBvbmVudCc7XG5cbi8vIEZpZWxkIHR5cGUgY29tcG9uZW50cyAoaW5kaXZpZHVhbGx5IGltcG9ydGFibGUg4oCUIHJldXNhYmlsaXR5IHJ1bGUpXG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy90ZXh0LWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy90ZXh0YXJlYS1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvbnVtYmVyLWZpZWxkLmNvbXBvbmVudCc7XG5leHBvcnQgKiBmcm9tICcuL2xpYi9maWVsZC10eXBlcy9jaGVja2JveC1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZGF0ZS1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZHJvcGRvd24tZmllbGQuY29tcG9uZW50JztcbmV4cG9ydCAqIGZyb20gJy4vbGliL2ZpZWxkLXR5cGVzL211bHRpLXNlbGVjdC1maWVsZC5jb21wb25lbnQnO1xuZXhwb3J0ICogZnJvbSAnLi9saWIvZmllbGQtdHlwZXMvZW50aXR5LXJlZi1maWVsZC5jb21wb25lbnQnO1xuXG4vLyBSZS1leHBvcnQgY29yZSB0eXBlcyBzbyBjb25zdW1lcnMgbmVlZCBvbmx5IG9uZSBpbXBvcnRcbmV4cG9ydCB0eXBlIHtcbiAgRW50aXR5Q29uZmlnLFxuICBGaWVsZENvbmZpZyxcbiAgVGFiQ29uZmlnLFxuICBEcm9wZG93bk9wdGlvbixcbiAgRmllbGREZXBlbmRlbmN5LFxuICBFbnRpdHlIb29rcyxcbiAgVmVyc2lvbmVkUmVjb3JkLFxuICBFbnRpdHlQZXJtaXNzaW9ucyxcbiAgUmJhY0NvbnRleHQsXG4gIE1pZ3JhdGlvblN0cmF0ZWd5LFxuICBRdWVyeU9wdGlvbnMsXG4gIFBhZ2luYXRlZFJlc3VsdCxcbn0gZnJvbSAnQGR5bmFtaWMtZW50aXR5L2NvcmUnO1xuIl19