ngx-dynamic-entity 0.0.1 → 1.1.0

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 (84) hide show
  1. package/README.md +129 -0
  2. package/esm2022/lib/field-types/array-field.component.mjs +160 -0
  3. package/esm2022/lib/field-types/boolean-field.component.mjs +100 -0
  4. package/esm2022/lib/field-types/checkbox-field.component.mjs +25 -17
  5. package/esm2022/lib/field-types/currency-field.component.mjs +101 -0
  6. package/esm2022/lib/field-types/date-field.component.mjs +21 -13
  7. package/esm2022/lib/field-types/dropdown-field.component.mjs +120 -29
  8. package/esm2022/lib/field-types/email-field.component.mjs +101 -0
  9. package/esm2022/lib/field-types/entity-ref-field.component.mjs +96 -32
  10. package/esm2022/lib/field-types/file-field.component.mjs +156 -0
  11. package/esm2022/lib/field-types/group-field.component.mjs +89 -0
  12. package/esm2022/lib/field-types/image-field.component.mjs +168 -0
  13. package/esm2022/lib/field-types/month-year-field.component.mjs +151 -0
  14. package/esm2022/lib/field-types/multi-select-field.component.mjs +79 -25
  15. package/esm2022/lib/field-types/number-field.component.mjs +60 -17
  16. package/esm2022/lib/field-types/password-field.component.mjs +109 -0
  17. package/esm2022/lib/field-types/radio-field.component.mjs +152 -0
  18. package/esm2022/lib/field-types/text-field.component.mjs +58 -18
  19. package/esm2022/lib/field-types/textarea-field.component.mjs +26 -15
  20. package/esm2022/lib/form/dynamic-field/dynamic-field.component.mjs +44 -26
  21. package/esm2022/lib/form/dynamic-form.component.mjs +676 -69
  22. package/esm2022/lib/form/dynamic-record-form.component.mjs +494 -0
  23. package/esm2022/lib/providers/provide-field-types.mjs +72 -0
  24. package/esm2022/lib/providers/provide-ngx-dynamic-entity.mjs +4 -5
  25. package/esm2022/lib/services/cascade-data.service.mjs +129 -0
  26. package/esm2022/lib/services/config-source.service.mjs +67 -0
  27. package/esm2022/lib/services/entity-ref-cache.mjs +20 -0
  28. package/esm2022/lib/services/entity-ref-registry.service.mjs +3 -3
  29. package/esm2022/lib/services/entity-ref-selection.service.mjs +27 -0
  30. package/esm2022/lib/services/entity-reference.service.mjs +168 -0
  31. package/esm2022/lib/services/field-registry.service.mjs +35 -30
  32. package/esm2022/lib/services/file-upload.service.mjs +89 -0
  33. package/esm2022/lib/services/lookup-registry.service.mjs +246 -0
  34. package/esm2022/lib/services/rbac.service.mjs +5 -11
  35. package/esm2022/lib/services/rules-evaluation.service.mjs +27 -0
  36. package/esm2022/lib/services/validator-registry.service.mjs +35 -7
  37. package/esm2022/lib/tokens/injection-tokens.mjs +104 -7
  38. package/esm2022/public-api.mjs +31 -6
  39. package/fesm2022/ngx-dynamic-entity.mjs +4094 -759
  40. package/fesm2022/ngx-dynamic-entity.mjs.map +1 -1
  41. package/lib/field-types/array-field.component.d.ts +22 -0
  42. package/lib/field-types/boolean-field.component.d.ts +18 -0
  43. package/lib/field-types/checkbox-field.component.d.ts +3 -2
  44. package/lib/field-types/currency-field.component.d.ts +17 -0
  45. package/lib/field-types/date-field.component.d.ts +3 -2
  46. package/lib/field-types/dropdown-field.component.d.ts +24 -4
  47. package/lib/field-types/email-field.component.d.ts +15 -0
  48. package/lib/field-types/entity-ref-field.component.d.ts +24 -11
  49. package/lib/field-types/file-field.component.d.ts +25 -0
  50. package/lib/field-types/group-field.component.d.ts +17 -0
  51. package/lib/field-types/image-field.component.d.ts +30 -0
  52. package/lib/field-types/month-year-field.component.d.ts +28 -0
  53. package/lib/field-types/multi-select-field.component.d.ts +18 -4
  54. package/lib/field-types/number-field.component.d.ts +5 -2
  55. package/lib/field-types/password-field.component.d.ts +17 -0
  56. package/lib/field-types/radio-field.component.d.ts +33 -0
  57. package/lib/field-types/text-field.component.d.ts +5 -3
  58. package/lib/field-types/textarea-field.component.d.ts +4 -2
  59. package/lib/form/dynamic-field/dynamic-field.component.d.ts +20 -7
  60. package/lib/form/dynamic-form.component.d.ts +203 -24
  61. package/lib/form/dynamic-record-form.component.d.ts +180 -0
  62. package/lib/providers/provide-field-types.d.ts +24 -0
  63. package/lib/providers/provide-ngx-dynamic-entity.d.ts +19 -8
  64. package/lib/services/cascade-data.service.d.ts +45 -0
  65. package/lib/services/config-source.service.d.ts +14 -0
  66. package/lib/services/entity-ref-cache.d.ts +27 -0
  67. package/lib/services/entity-ref-registry.service.d.ts +4 -3
  68. package/lib/services/entity-ref-selection.service.d.ts +24 -0
  69. package/lib/services/entity-reference.service.d.ts +49 -0
  70. package/lib/services/field-registry.service.d.ts +17 -7
  71. package/lib/services/file-upload.service.d.ts +27 -0
  72. package/lib/services/lookup-registry.service.d.ts +94 -0
  73. package/lib/services/rbac.service.d.ts +4 -9
  74. package/lib/services/rules-evaluation.service.d.ts +17 -0
  75. package/lib/services/validator-registry.service.d.ts +5 -4
  76. package/lib/tokens/injection-tokens.d.ts +113 -8
  77. package/package.json +18 -8
  78. package/public-api.d.ts +21 -4
  79. package/esm2022/lib/services/config.service.mjs +0 -58
  80. package/esm2022/lib/services/version.service.mjs +0 -40
  81. package/esm2022/lib/table/dynamic-table.component.mjs +0 -160
  82. package/lib/services/config.service.d.ts +0 -25
  83. package/lib/services/version.service.d.ts +0 -26
  84. package/lib/table/dynamic-table.component.d.ts +0 -56
@@ -1,160 +0,0 @@
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
@@ -1,25 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- import type { EntityConfig } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /**
5
- * ConfigService — fetches entity configs from the dynamic-entity-server API.
6
- * Uses shareReplay(1) to cache per config key — avoids duplicate HTTP requests.
7
- * Following ONEHERMES smart service pattern: Observable$ + shareReplay.
8
- */
9
- export declare class ConfigService {
10
- private readonly http;
11
- private readonly apiUrl;
12
- private readonly cache;
13
- /** Get a config by entity key. Cached via shareReplay(1). */
14
- getConfig(entity: string): Observable<EntityConfig>;
15
- /** List all configs */
16
- listConfigs(): Observable<EntityConfig[]>;
17
- /** Create a new config */
18
- saveConfig(config: EntityConfig): Observable<EntityConfig>;
19
- /** Update an existing config */
20
- updateConfig(entity: string, updates: Partial<EntityConfig>): Observable<EntityConfig>;
21
- /** Invalidate cache for an entity (call after config update) */
22
- invalidate(entity: string): void;
23
- static ɵfac: i0.ɵɵFactoryDeclaration<ConfigService, never>;
24
- static ɵprov: i0.ɵɵInjectableDeclaration<ConfigService>;
25
- }
@@ -1,26 +0,0 @@
1
- import type { VersionedRecord, EntityConfig } from '@dynamic-entity/core';
2
- import * as i0 from "@angular/core";
3
- /**
4
- * VersionService — checks config version staleness and migration strategy.
5
- * ADR-005: Frontend strategy is 'strict' | 'graceful' only. Never 'auto'.
6
- */
7
- export declare class VersionService {
8
- private readonly strategy;
9
- /**
10
- * Check if a record is stale compared to the current config version.
11
- * Returns true if the record needs migration.
12
- */
13
- needsMigration(record: Partial<VersionedRecord>, config: EntityConfig): boolean;
14
- /**
15
- * Get the effective migration strategy for frontend use.
16
- * Only 'strict' or 'graceful' — never 'auto' (ADR-005).
17
- */
18
- getStrategy(): 'strict' | 'graceful';
19
- /**
20
- * Determine if form submission should be blocked for a stale record.
21
- * 'strict' → block; 'graceful' → allow with warning.
22
- */
23
- shouldBlockSubmit(record: Partial<VersionedRecord>, config: EntityConfig): boolean;
24
- static ɵfac: i0.ɵɵFactoryDeclaration<VersionService, never>;
25
- static ɵprov: i0.ɵɵInjectableDeclaration<VersionService>;
26
- }
@@ -1,56 +0,0 @@
1
- import { EventEmitter, OnChanges, SimpleChanges } from '@angular/core';
2
- import type { EntityConfig, VersionedRecord } from '@dynamic-entity/core';
3
- import * as i0 from "@angular/core";
4
- /**
5
- * DynamicTableComponent — renders entity records as a searchable, sortable, paginated table.
6
- * Columns determined by FieldConfig.tableColumn === true.
7
- * Cell masking applied via RbacService (defense-in-depth).
8
- * Pagination, sort, and search emit events — parent controls data fetching (smart/dumb pattern).
9
- */
10
- export declare class DynamicTableComponent implements OnChanges {
11
- config: EntityConfig;
12
- data: VersionedRecord[];
13
- userRoles: string[];
14
- language: string;
15
- loading: boolean;
16
- error: string | null;
17
- totalRecords: number;
18
- pageSize: number;
19
- currentPage: number;
20
- rowClick: EventEmitter<VersionedRecord>;
21
- rowEdit: EventEmitter<VersionedRecord>;
22
- rowDelete: EventEmitter<VersionedRecord>;
23
- pageChange: EventEmitter<number>;
24
- pageSizeChange: EventEmitter<number>;
25
- sortChange: EventEmitter<{
26
- field: string;
27
- dir: 'asc' | 'desc';
28
- }>;
29
- searchChange: EventEmitter<string>;
30
- private readonly rbacService;
31
- private readonly versionService;
32
- readonly sortField: import("@angular/core").WritableSignal<string>;
33
- readonly sortDir: import("@angular/core").WritableSignal<"asc" | "desc">;
34
- readonly searchTerm: import("@angular/core").WritableSignal<string>;
35
- readonly tableColumns: import("@angular/core").Signal<import("@dynamic-entity/core").FieldConfig[]>;
36
- get permissions(): {
37
- canView: boolean;
38
- canEdit: boolean;
39
- canDelete: boolean;
40
- };
41
- get totalPages(): number;
42
- ngOnChanges(_changes: SimpleChanges): void;
43
- private columnMaskCache;
44
- private _resolveColumnMasks;
45
- isCellMasked(fieldId: string): boolean;
46
- getCellValue(record: VersionedRecord, fieldId: string): string;
47
- needsMigration(record: VersionedRecord): boolean;
48
- getColumnLabel(fieldId: string): string;
49
- onSort(fieldId: string): void;
50
- onSearch(event: Event): void;
51
- onPageChange(page: number): void;
52
- onEdit(event: Event, record: VersionedRecord): void;
53
- onDelete(event: Event, record: VersionedRecord): void;
54
- static ɵfac: i0.ɵɵFactoryDeclaration<DynamicTableComponent, never>;
55
- static ɵcmp: i0.ɵɵComponentDeclaration<DynamicTableComponent, "ngx-dynamic-table", never, { "config": { "alias": "config"; "required": false; }; "data": { "alias": "data"; "required": false; }; "userRoles": { "alias": "userRoles"; "required": false; }; "language": { "alias": "language"; "required": false; }; "loading": { "alias": "loading"; "required": false; }; "error": { "alias": "error"; "required": false; }; "totalRecords": { "alias": "totalRecords"; "required": false; }; "pageSize": { "alias": "pageSize"; "required": false; }; "currentPage": { "alias": "currentPage"; "required": false; }; }, { "rowClick": "rowClick"; "rowEdit": "rowEdit"; "rowDelete": "rowDelete"; "pageChange": "pageChange"; "pageSizeChange": "pageSizeChange"; "sortChange": "sortChange"; "searchChange": "searchChange"; }, never, never, true, never>;
56
- }