@endora-commerce/mod-custom-fields 0.0.0-stage → 0.100.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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/admin/api/custom-fields-client.d.ts +10 -0
  4. package/dist/admin/api/custom-fields-client.d.ts.map +1 -0
  5. package/dist/admin/api/custom-fields-client.js +11 -0
  6. package/dist/admin/api/custom-fields-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +44 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +51 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/CustomFieldsPage.d.ts +10 -0
  12. package/dist/admin/pages/CustomFieldsPage.d.ts.map +1 -0
  13. package/dist/admin/pages/CustomFieldsPage.js +118 -0
  14. package/dist/admin/pages/CustomFieldsPage.js.map +1 -0
  15. package/dist/backend/commands/definition-commands.d.ts +22 -0
  16. package/dist/backend/commands/definition-commands.d.ts.map +1 -0
  17. package/dist/backend/commands/definition-commands.js +115 -0
  18. package/dist/backend/commands/definition-commands.js.map +1 -0
  19. package/dist/backend/entities/custom-field-definition.entity.d.ts +37 -0
  20. package/dist/backend/entities/custom-field-definition.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/custom-field-definition.entity.js +98 -0
  22. package/dist/backend/entities/custom-field-definition.entity.js.map +1 -0
  23. package/dist/backend/entities/custom-field-option.entity.d.ts +25 -0
  24. package/dist/backend/entities/custom-field-option.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/custom-field-option.entity.js +79 -0
  26. package/dist/backend/entities/custom-field-option.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +79 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +94 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/routes.admin.d.ts +14 -0
  32. package/dist/backend/routes.admin.d.ts.map +1 -0
  33. package/dist/backend/routes.admin.js +179 -0
  34. package/dist/backend/routes.admin.js.map +1 -0
  35. package/dist/backend/services/custom-field-definition-apply.d.ts +63 -0
  36. package/dist/backend/services/custom-field-definition-apply.d.ts.map +1 -0
  37. package/dist/backend/services/custom-field-definition-apply.js +221 -0
  38. package/dist/backend/services/custom-field-definition-apply.js.map +1 -0
  39. package/dist/backend/services/custom-field-definition.service.d.ts +68 -0
  40. package/dist/backend/services/custom-field-definition.service.d.ts.map +1 -0
  41. package/dist/backend/services/custom-field-definition.service.js +205 -0
  42. package/dist/backend/services/custom-field-definition.service.js.map +1 -0
  43. package/dist/backend/services/custom-field-definitions-cache.d.ts +46 -0
  44. package/dist/backend/services/custom-field-definitions-cache.d.ts.map +1 -0
  45. package/dist/backend/services/custom-field-definitions-cache.js +82 -0
  46. package/dist/backend/services/custom-field-definitions-cache.js.map +1 -0
  47. package/dist/backend/services/custom-field-read-port.d.ts +61 -0
  48. package/dist/backend/services/custom-field-read-port.d.ts.map +1 -0
  49. package/dist/backend/services/custom-field-read-port.js +82 -0
  50. package/dist/backend/services/custom-field-read-port.js.map +1 -0
  51. package/dist/backend/services/custom-field-registry.d.ts +41 -0
  52. package/dist/backend/services/custom-field-registry.d.ts.map +1 -0
  53. package/dist/backend/services/custom-field-registry.js +24 -0
  54. package/dist/backend/services/custom-field-registry.js.map +1 -0
  55. package/dist/backend/services/custom-field-value.service.d.ts +56 -0
  56. package/dist/backend/services/custom-field-value.service.d.ts.map +1 -0
  57. package/dist/backend/services/custom-field-value.service.js +175 -0
  58. package/dist/backend/services/custom-field-value.service.js.map +1 -0
  59. package/dist/manifest.d.ts +177 -0
  60. package/dist/manifest.d.ts.map +1 -0
  61. package/dist/manifest.js +130 -0
  62. package/dist/manifest.js.map +1 -0
  63. package/dist/migrations/20260718T200338_custom_fields_init.d.ts +20 -0
  64. package/dist/migrations/20260718T200338_custom_fields_init.d.ts.map +1 -0
  65. package/dist/migrations/20260718T200338_custom_fields_init.js +60 -0
  66. package/dist/migrations/20260718T200338_custom_fields_init.js.map +1 -0
  67. package/dist/migrations/index.d.ts +27 -0
  68. package/dist/migrations/index.d.ts.map +1 -0
  69. package/dist/migrations/index.js +29 -0
  70. package/dist/migrations/index.js.map +1 -0
  71. package/dist/ports/index.d.ts +123 -0
  72. package/dist/ports/index.d.ts.map +1 -0
  73. package/dist/ports/index.js +2 -0
  74. package/dist/ports/index.js.map +1 -0
  75. package/i18n/en.json +15 -0
  76. package/i18n/pl.json +15 -0
  77. package/package.json +99 -3
  78. package/tailwind.css +14 -0
@@ -0,0 +1,98 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * CustomFieldDefinition — an operator-defined field for one host entity type
15
+ * (feature 055, FR-001/FR-002).
16
+ *
17
+ * Entity-agnostic by construction (FR-006): it carries a `key`, a per-locale
18
+ * `label` + `labelDefault`, a `valueType`, a `required` flag, ordering, and an
19
+ * opaque `config` JSONB for host-capability opt-in that the generic core stores
20
+ * but never interprets. It deliberately omits the catalog-only capability flags
21
+ * that made the product-attribute registry un-reusable — those stay behind host
22
+ * extension points. Platform-global (`@GlobalEntity`), like the product-attribute
23
+ * registry; the VALUES live on the host row's JSONB column and inherit that
24
+ * host's tenant scope.
25
+ *
26
+ * Unique on `(entity_type, key)` so a key is a clean identifier within an
27
+ * entity type. Options for `select` / `multiselect` live on
28
+ * `custom_field_options` (FK ON DELETE CASCADE).
29
+ */
30
+ let CustomFieldDefinition = class CustomFieldDefinition {
31
+ [OptionalProps];
32
+ id = randomUUID();
33
+ entityType;
34
+ key;
35
+ /** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
36
+ label = {};
37
+ /** Fallback label used when the active locale is missing from `label` (US1 scenario 4). */
38
+ labelDefault;
39
+ valueType;
40
+ required = false;
41
+ sortOrder = 0;
42
+ /** Opaque host-capability flags (FR-006). The core stores but never reads this for meaning. */
43
+ config = {};
44
+ createdAt = new Date();
45
+ updatedAt = new Date();
46
+ };
47
+ __decorate([
48
+ PrimaryKey({ type: 'uuid' }),
49
+ __metadata("design:type", String)
50
+ ], CustomFieldDefinition.prototype, "id", void 0);
51
+ __decorate([
52
+ Property({ type: 'string', length: 32 }),
53
+ Index(),
54
+ __metadata("design:type", String)
55
+ ], CustomFieldDefinition.prototype, "entityType", void 0);
56
+ __decorate([
57
+ Property({ type: 'string', length: 64 }),
58
+ __metadata("design:type", String)
59
+ ], CustomFieldDefinition.prototype, "key", void 0);
60
+ __decorate([
61
+ Property({ type: 'json' }),
62
+ __metadata("design:type", Object)
63
+ ], CustomFieldDefinition.prototype, "label", void 0);
64
+ __decorate([
65
+ Property({ type: 'string', length: 200 }),
66
+ __metadata("design:type", String)
67
+ ], CustomFieldDefinition.prototype, "labelDefault", void 0);
68
+ __decorate([
69
+ Property({ type: 'string', length: 16 }),
70
+ __metadata("design:type", String)
71
+ ], CustomFieldDefinition.prototype, "valueType", void 0);
72
+ __decorate([
73
+ Property({ type: 'boolean' }),
74
+ __metadata("design:type", Boolean)
75
+ ], CustomFieldDefinition.prototype, "required", void 0);
76
+ __decorate([
77
+ Property({ type: 'integer' }),
78
+ __metadata("design:type", Number)
79
+ ], CustomFieldDefinition.prototype, "sortOrder", void 0);
80
+ __decorate([
81
+ Property({ type: 'json' }),
82
+ __metadata("design:type", Object)
83
+ ], CustomFieldDefinition.prototype, "config", void 0);
84
+ __decorate([
85
+ Property({ type: 'datetime', onCreate: () => new Date() }),
86
+ __metadata("design:type", Date)
87
+ ], CustomFieldDefinition.prototype, "createdAt", void 0);
88
+ __decorate([
89
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
90
+ __metadata("design:type", Date)
91
+ ], CustomFieldDefinition.prototype, "updatedAt", void 0);
92
+ CustomFieldDefinition = __decorate([
93
+ GlobalEntity(),
94
+ Entity({ tableName: 'custom_field_definitions' }),
95
+ Unique({ properties: ['entityType', 'key'] })
96
+ ], CustomFieldDefinition);
97
+ export { CustomFieldDefinition };
98
+ //# sourceMappingURL=custom-field-definition.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-field-definition.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-definition.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAGpC;;;;;;;;;;;;;;;;GAgBG;AAII,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,CAAC,aAAa,CAAC,CAOF;IAGb,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,UAAU,CAAuB;IAGjC,GAAG,CAAU;IAEb,qGAAqG;IAErG,KAAK,GAA2B,EAAE,CAAC;IAEnC,2FAA2F;IAE3F,YAAY,CAAU;IAGtB,SAAS,CAAwB;IAGjC,QAAQ,GAAY,KAAK,CAAC;IAG1B,SAAS,GAAW,CAAC,CAAC;IAEtB,+FAA+F;IAE/F,MAAM,GAA4B,EAAE,CAAC;IAGrC,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AAnCC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;iDACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,KAAK,EAAE;;yDACyB;AAGjC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;kDAC5B;AAIb;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;oDACQ;AAInC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;2DACpB;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;;wDACR;AAGjC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;uDACJ;AAG1B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;wDACR;AAItB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;qDACU;AAGrC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;wDAAc;AA7ClB,qBAAqB;IAHjC,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,0BAA0B,EAAE,CAAC;IACjD,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;GACjC,qBAAqB,CA8CjC"}
@@ -0,0 +1,25 @@
1
+ import { OptionalProps } from '@mikro-orm/core';
2
+ /**
3
+ * CustomFieldOption — one selectable value for a `select` / `multiselect`
4
+ * custom-field definition (feature 055, FR-002). Shape lifted from the catalog
5
+ * `AttributeOption`: stable `value`, per-locale label + default fallback,
6
+ * `isDefault`, `sortOrder`.
7
+ *
8
+ * Unique on `(definitionId, value)`. FK-by-id to `custom_field_definitions`
9
+ * with ON DELETE CASCADE (enforced at migration level, mirroring
10
+ * `attribute_options`). Platform-global (`@GlobalEntity`).
11
+ */
12
+ export declare class CustomFieldOption {
13
+ [OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'isDefault' | 'sortOrder' | 'label';
14
+ id: string;
15
+ definitionId: string;
16
+ value: string;
17
+ /** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
18
+ label: Record<string, string>;
19
+ labelDefault: string;
20
+ isDefault: boolean;
21
+ sortOrder: number;
22
+ createdAt: Date;
23
+ updatedAt: Date;
24
+ }
25
+ //# sourceMappingURL=custom-field-option.entity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-field-option.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-option.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAI7F;;;;;;;;;GASG;AACH,qBAGa,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,WAAW,GACX,WAAW,GACX,OAAO,CAAC;IAGZ,EAAE,EAAE,MAAM,CAAgB;IAI1B,YAAY,EAAG,MAAM,CAAC;IAGtB,KAAK,EAAG,MAAM,CAAC;IAEf,qGAAqG;IAErG,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAM;IAGnC,YAAY,EAAG,MAAM,CAAC;IAGtB,SAAS,EAAE,OAAO,CAAS;IAG3B,SAAS,EAAE,MAAM,CAAK;IAGtB,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
@@ -0,0 +1,79 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
10
+ import { Entity, Index, OptionalProps, PrimaryKey, Property, Unique } from '@mikro-orm/core';
11
+ import { GlobalEntity } from '@endora-commerce/platform/tenancy';
12
+ import { randomUUID } from 'crypto';
13
+ /**
14
+ * CustomFieldOption — one selectable value for a `select` / `multiselect`
15
+ * custom-field definition (feature 055, FR-002). Shape lifted from the catalog
16
+ * `AttributeOption`: stable `value`, per-locale label + default fallback,
17
+ * `isDefault`, `sortOrder`.
18
+ *
19
+ * Unique on `(definitionId, value)`. FK-by-id to `custom_field_definitions`
20
+ * with ON DELETE CASCADE (enforced at migration level, mirroring
21
+ * `attribute_options`). Platform-global (`@GlobalEntity`).
22
+ */
23
+ let CustomFieldOption = class CustomFieldOption {
24
+ [OptionalProps];
25
+ id = randomUUID();
26
+ definitionId;
27
+ value;
28
+ /** Per-locale label map (BCP-47-keyed). Empty `{}` is allowed when only `labelDefault` is filled. */
29
+ label = {};
30
+ labelDefault;
31
+ isDefault = false;
32
+ sortOrder = 0;
33
+ createdAt = new Date();
34
+ updatedAt = new Date();
35
+ };
36
+ __decorate([
37
+ PrimaryKey({ type: 'uuid' }),
38
+ __metadata("design:type", String)
39
+ ], CustomFieldOption.prototype, "id", void 0);
40
+ __decorate([
41
+ Property({ type: 'uuid' }),
42
+ Index(),
43
+ __metadata("design:type", String)
44
+ ], CustomFieldOption.prototype, "definitionId", void 0);
45
+ __decorate([
46
+ Property({ type: 'string', length: 200 }),
47
+ __metadata("design:type", String)
48
+ ], CustomFieldOption.prototype, "value", void 0);
49
+ __decorate([
50
+ Property({ type: 'json' }),
51
+ __metadata("design:type", Object)
52
+ ], CustomFieldOption.prototype, "label", void 0);
53
+ __decorate([
54
+ Property({ type: 'string', length: 200 }),
55
+ __metadata("design:type", String)
56
+ ], CustomFieldOption.prototype, "labelDefault", void 0);
57
+ __decorate([
58
+ Property({ type: 'boolean' }),
59
+ __metadata("design:type", Boolean)
60
+ ], CustomFieldOption.prototype, "isDefault", void 0);
61
+ __decorate([
62
+ Property({ type: 'integer' }),
63
+ __metadata("design:type", Number)
64
+ ], CustomFieldOption.prototype, "sortOrder", void 0);
65
+ __decorate([
66
+ Property({ type: 'datetime', onCreate: () => new Date() }),
67
+ __metadata("design:type", Date)
68
+ ], CustomFieldOption.prototype, "createdAt", void 0);
69
+ __decorate([
70
+ Property({ type: 'datetime', onUpdate: () => new Date() }),
71
+ __metadata("design:type", Date)
72
+ ], CustomFieldOption.prototype, "updatedAt", void 0);
73
+ CustomFieldOption = __decorate([
74
+ GlobalEntity(),
75
+ Entity({ tableName: 'custom_field_options' }),
76
+ Unique({ properties: ['definitionId', 'value'] })
77
+ ], CustomFieldOption);
78
+ export { CustomFieldOption };
79
+ //# sourceMappingURL=custom-field-option.entity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"custom-field-option.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/custom-field-option.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC;;;;;;;;;GASG;AAII,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAMH;IAGZ,EAAE,GAAW,UAAU,EAAE,CAAC;IAI1B,YAAY,CAAU;IAGtB,KAAK,CAAU;IAEf,qGAAqG;IAErG,KAAK,GAA2B,EAAE,CAAC;IAGnC,YAAY,CAAU;IAGtB,SAAS,GAAY,KAAK,CAAC;IAG3B,SAAS,GAAW,CAAC,CAAC;IAGtB,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;IAG7B,SAAS,GAAS,IAAI,IAAI,EAAE,CAAC;CAC9B,CAAA;AA3BC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;6CACH;AAI1B;IAFC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE;;uDACc;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;gDAC3B;AAIf;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;gDACQ;AAGnC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC;;uDACpB;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;oDACH;AAG3B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;;oDACR;AAGtB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;oDAAc;AAG7B;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAChD,IAAI;oDAAc;AApClB,iBAAiB;IAH7B,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,sBAAsB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC;GACrC,iBAAiB,CAqC7B"}
@@ -0,0 +1,79 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { Redis } from 'ioredis';
3
+ import type { CommandBus } from '@endora-commerce/platform/commands';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ import { CustomFieldDefinitionsCache } from './services/custom-field-definitions-cache.js';
7
+ import { CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
8
+ import { CustomFieldValueService } from './services/custom-field-value.service.js';
9
+ import { CustomFieldDefinition } from './entities/custom-field-definition.entity.js';
10
+ import { CustomFieldOption } from './entities/custom-field-option.entity.js';
11
+ /**
12
+ * `custom_fields` — the module that cannot be switched off (feature 072, wave 1,
13
+ * T087).
14
+ *
15
+ * Mechanically this is one of the plainer conversions: one factory call per
16
+ * root, one admin route file, three services with a cycle between two of them.
17
+ * What it forced was a decision the wave had so far been able to defer.
18
+ *
19
+ * **Why the activation control is `nonDeactivatable`.** Every other converted
20
+ * module got a Setting an operator can flip. This one gets a locked control and
21
+ * a reason, because the alternative is a switch that half-works. Custom-field
22
+ * *values* live in the host's own JSONB column — `orders.custom_field_values`,
23
+ * `products.attribute_values` — so switching the module off does not hide the
24
+ * data; it removes the definitions that give the data meaning. `validateAndMerge`
25
+ * with no definitions silently ignores every incoming key and `project` returns
26
+ * an empty bag, so a host write would drop the operator's custom fields and a
27
+ * host read would stop showing them, with a 200 either way. `catalog`,
28
+ * `product_feeds` and `pim_ergonode` already declare this module a hard
29
+ * dependency for exactly that reason; `catalog`'s manifest says outright that
30
+ * it "must not hard-uninstall it under a live catalog". Declaring the reality
31
+ * beats shipping a control whose off position corrupts.
32
+ *
33
+ * **The cache is the module's spine, and there is one.** `getForEntity` caches
34
+ * per entity type for 5 s, invalidated across processes on a Redis channel. The
35
+ * admin service publishes an invalidation after every definition write, and the
36
+ * value service reads through the same instance — which is what makes a write
37
+ * visible to the next validation instead of up to five seconds later. The two
38
+ * registrations below therefore resolve one `CustomFieldDefinitionService`, and
39
+ * `customFieldValueService` is built *from* it rather than beside it.
40
+ *
41
+ * **The hard-uninstall cleanup stays in `manifest.ts`.** That is the only place
42
+ * the lifecycle orchestrator looks: it reads `uninstallHook` off the registry
43
+ * entry the composer generates into the manifest index. There is no
44
+ * container-side equivalent — `ctx.onInstall`/`ctx.onUninstall` existed, were
45
+ * never run by anything, and were deleted by D-46 precisely because a hook
46
+ * placed there would compile, pass every test, and quietly stop dropping
47
+ * definitions on a hard uninstall.
48
+ */
49
+ export interface CustomFieldsCradle {
50
+ readonly emFactory: () => EntityManager;
51
+ readonly commandBus: CommandBus;
52
+ readonly requireAdmin: RequireAdminFactory;
53
+ readonly redis: Redis;
54
+ /** The shared subscriber connection; see the note on `onBoot` below. */
55
+ readonly redisSubscriber: Redis;
56
+ readonly customFieldDefinitionsCache: CustomFieldDefinitionsCache;
57
+ readonly customFieldServices: CustomFieldServices;
58
+ readonly customFieldDefinitionService: CustomFieldDefinitionService;
59
+ readonly customFieldValueService: CustomFieldValueService;
60
+ }
61
+ interface CustomFieldServices {
62
+ readonly definitionService: CustomFieldDefinitionService;
63
+ readonly valueService: CustomFieldValueService;
64
+ }
65
+ export declare function registerModule(ctx: ModuleContext): void;
66
+ /**
67
+ * The module's persisted entity classes, on the `./backend` subpath, as one
68
+ * array and **no named class export** (D-168).
69
+ *
70
+ * This is the shape the platform reads when the package is *installed*: the
71
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
72
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
73
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
74
+ * the package pass of `check-entity-tenant-classification`. A missing array is
75
+ * answered with `[]` — zero entities registered, no error anywhere.
76
+ */
77
+ export declare const entities: (typeof CustomFieldDefinition | typeof CustomFieldOption)[];
78
+ export {};
79
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAC;AACrE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAC3F,OAAO,EAAE,4BAA4B,EAAE,MAAM,+CAA+C,CAAC;AAC7F,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAGnF,OAAO,EAAE,qBAAqB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,MAAM,0CAA0C,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,KAAK,CAAC;IAChC,QAAQ,CAAC,2BAA2B,EAAE,2BAA2B,CAAC;IAClE,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,QAAQ,CAAC,4BAA4B,EAAE,4BAA4B,CAAC;IACpE,QAAQ,CAAC,uBAAuB,EAAE,uBAAuB,CAAC;CAC3D;AAED,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;IACzD,QAAQ,CAAC,YAAY,EAAE,uBAAuB,CAAC;CAChD;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAkGvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,6DAGpB,CAAC"}
@@ -0,0 +1,94 @@
1
+ import { CustomFieldDefinitionsCache } from './services/custom-field-definitions-cache.js';
2
+ import { CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
3
+ import { CustomFieldValueService } from './services/custom-field-value.service.js';
4
+ import { CustomFieldDefinitionReadService } from './services/custom-field-read-port.js';
5
+ import { registerCustomFieldsAdminRoutes } from './routes.admin.js';
6
+ import { CustomFieldDefinition } from './entities/custom-field-definition.entity.js';
7
+ import { CustomFieldOption } from './entities/custom-field-option.entity.js';
8
+ export function registerModule(ctx) {
9
+ ctx.di.register({
10
+ // Internal: the definition and value services are the module's public
11
+ // surface, and the cache is how they agree. Nothing outside resolves it.
12
+ customFieldDefinitionsCache: ctx
13
+ .asFunction(({ redis }) => new CustomFieldDefinitionsCache(redis))
14
+ .singleton(),
15
+ // The two services are **one registration** because they are one object
16
+ // graph: the definition service holds the value service to run its
17
+ // change-guards, and the value service reads definitions back through it.
18
+ // Registering them separately means the guard's value service and the one
19
+ // hosts validate against are different instances — harmless today, since
20
+ // the value service is a stateless wrapper, and exactly the divergence that
21
+ // stops being harmless the first time one of them gains state.
22
+ customFieldServices: ctx
23
+ .asFunction(({ emFactory, customFieldDefinitionsCache, commandBus, }) => {
24
+ const definitionService = new CustomFieldDefinitionService(emFactory, customFieldDefinitionsCache, commandBus);
25
+ const valueService = new CustomFieldValueService(definitionService);
26
+ definitionService.setValueService(valueService);
27
+ return { definitionService, valueService };
28
+ })
29
+ .singleton(),
30
+ });
31
+ /**
32
+ * The module's own CRUD surface — its admin routes resolve it, and `catalog`
33
+ * reaches it for one thing only: the transactional apply seam
34
+ * (`CustomFieldDefinitionApplyApi`, declared in this module's `ports/`
35
+ * directory), whose every `apply*` method takes the caller's `EntityManager`
36
+ * so that a product attribute and its definition are written under one
37
+ * transaction. D-77 ruled that seam permanent and FR-034 keeps a MikroORM
38
+ * type out of `@endora-commerce/contracts`, so it is the one name this module publishes
39
+ * no contract for; it is ledgered as such in
40
+ * `RESOLUTIONS_OF_UNPUBLISHED_NAMES`. Every definition **read** goes through
41
+ * `customFieldDefinitionReadPort` below — the per-entity lists since issue
42
+ * #209, and the single definition by id since feature 080's T053(b).
43
+ */
44
+ ctx.di.providePort('customFieldDefinitionService', ctx
45
+ .asFunction(({ customFieldServices }) => customFieldServices.definitionService)
46
+ .singleton());
47
+ ctx.di.providePort('customFieldValueService', ctx
48
+ .asFunction(({ customFieldServices }) => customFieldServices.valueService)
49
+ .singleton());
50
+ /**
51
+ * Feature 075, Phase P — the definition read model, without the two ORM
52
+ * entities `CachedDefinition` carries.
53
+ *
54
+ * `customFieldDefinitionService` above stays for the reason its own note
55
+ * gives. This is the read seven modules actually make — `catalog`'s composed
56
+ * attribute read model most of all, because since feature 061 the definition
57
+ * half of a product attribute *is* a custom-field definition, and since issue
58
+ * #209 that read names this port rather than the CRUD registration.
59
+ */
60
+ ctx.di.providePort('customFieldDefinitionReadPort', ctx
61
+ .asFunction(({ customFieldServices }) => new CustomFieldDefinitionReadService(customFieldServices.definitionService))
62
+ .singleton());
63
+ ctx.onBoot(async () => {
64
+ // The subscriber is one connection shared by the whole composition —
65
+ // ioredis puts a connection in subscriber mode, so a per-module one would
66
+ // cost a socket per module for no benefit. `start` is idempotent and only
67
+ // adds a listener for this module's channel.
68
+ const { customFieldDefinitionsCache, redisSubscriber } = ctx.cradle();
69
+ await customFieldDefinitionsCache.start(redisSubscriber);
70
+ });
71
+ ctx.routes(async (app) => {
72
+ const { customFieldDefinitionService, requireAdmin } = ctx.cradle();
73
+ await registerCustomFieldsAdminRoutes(app, {
74
+ definitionService: customFieldDefinitionService,
75
+ requireAdmin,
76
+ });
77
+ });
78
+ }
79
+ /**
80
+ * The module's persisted entity classes, on the `./backend` subpath, as one
81
+ * array and **no named class export** (D-168).
82
+ *
83
+ * This is the shape the platform reads when the package is *installed*: the
84
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
85
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
86
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
87
+ * the package pass of `check-entity-tenant-classification`. A missing array is
88
+ * answered with `[]` — zero entities registered, no error anywhere.
89
+ */
90
+ export const entities = [
91
+ CustomFieldDefinition,
92
+ CustomFieldOption,
93
+ ];
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import { type CustomFieldDefinitionService } from './services/custom-field-definition.service.js';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ export interface CustomFieldsAdminDeps {
5
+ definitionService: CustomFieldDefinitionService;
6
+ requireAdmin: RequireAdminFactory;
7
+ }
8
+ /**
9
+ * Admin API for custom-field definitions + options (feature 055). All mutations
10
+ * run through the service, which dispatches Commands (Principle XIII). Reads are
11
+ * cached per entity type.
12
+ */
13
+ export declare function registerCustomFieldsAdminRoutes(app: FastifyInstance, deps: CustomFieldsAdminDeps): Promise<void>;
14
+ //# sourceMappingURL=routes.admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.d.ts","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/C,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,+CAA+C,CAAC;AAMvD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,qBAAqB;IACpC,iBAAiB,EAAE,4BAA4B,CAAC;IAChD,YAAY,EAAE,mBAAmB,CAAC;CACnC;AA8DD;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,qBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA4Jf"}
@@ -0,0 +1,179 @@
1
+ import { ERROR_CODES, createCustomFieldDefinitionSchema, customFieldOptionSchema, supportedEntityTypeSchema, updateCustomFieldDefinitionSchema, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { CustomFieldDefinitionError, } from './services/custom-field-definition.service.js';
4
+ import { isSupportedEntityType, SUPPORTED_ENTITIES, } from './services/custom-field-registry.js';
5
+ /** Map a service error to the HTTP envelope. */
6
+ function toHttp(err) {
7
+ if (err instanceof CustomFieldDefinitionError) {
8
+ switch (err.code) {
9
+ case 'not_found':
10
+ throw new HttpError(404, ERROR_CODES.CUSTOM_FIELD_NOT_FOUND, err.message);
11
+ case 'duplicate_key':
12
+ throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_KEY_CONFLICT, err.message);
13
+ case 'value_type_locked':
14
+ case 'option_in_use':
15
+ throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_DEFINITION_INVALID, err.message);
16
+ default:
17
+ throw new HttpError(422, ERROR_CODES.CUSTOM_FIELD_DEFINITION_INVALID, err.message);
18
+ }
19
+ }
20
+ throw err;
21
+ }
22
+ /**
23
+ * Feature 061 — generic host-managed refusal. When an entity type's registry
24
+ * entry declares `managedBy`, its definitions are mutated only through the
25
+ * owning host module's own surface; the generic mutation routes refuse with
26
+ * 409. Entity-agnostic: only the marker's presence is checked, never which
27
+ * module manages.
28
+ */
29
+ function assertNotHostManaged(entityType) {
30
+ const meta = isSupportedEntityType(entityType) ? SUPPORTED_ENTITIES[entityType] : undefined;
31
+ if (meta?.managedBy) {
32
+ throw new HttpError(409, ERROR_CODES.CUSTOM_FIELD_HOST_MANAGED, `Definitions for entity type "${entityType}" are managed by the "${meta.managedBy.moduleId}" module and cannot be modified here.`);
33
+ }
34
+ }
35
+ function serialize({ definition, options }) {
36
+ return {
37
+ id: definition.id,
38
+ entityType: definition.entityType,
39
+ key: definition.key,
40
+ label: definition.label,
41
+ labelDefault: definition.labelDefault,
42
+ valueType: definition.valueType,
43
+ required: definition.required,
44
+ sortOrder: definition.sortOrder,
45
+ config: definition.config,
46
+ options: options.map((o) => ({
47
+ id: o.id,
48
+ value: o.value,
49
+ label: o.label,
50
+ labelDefault: o.labelDefault,
51
+ isDefault: o.isDefault,
52
+ sortOrder: o.sortOrder,
53
+ })),
54
+ createdAt: definition.createdAt.toISOString(),
55
+ updatedAt: definition.updatedAt.toISOString(),
56
+ };
57
+ }
58
+ /**
59
+ * Admin API for custom-field definitions + options (feature 055). All mutations
60
+ * run through the service, which dispatches Commands (Principle XIII). Reads are
61
+ * cached per entity type.
62
+ */
63
+ export async function registerCustomFieldsAdminRoutes(app, deps) {
64
+ const { definitionService, requireAdmin } = deps;
65
+ /** Refuse mutating an existing definition whose entity type is host-managed (feature 061). */
66
+ async function assertDefinitionNotHostManaged(id) {
67
+ const def = await definitionService.getById(id);
68
+ if (def)
69
+ assertNotHostManaged(def.definition.entityType);
70
+ }
71
+ // Feature 061 T037 — supported entity types + host-managed metadata, so the
72
+ // generic admin UI can render managed types read-only with a link to the
73
+ // owning module's surface (no hard-coded entity list in the frontend).
74
+ app.get('/api/v1/admin/custom-fields/entity-types', { preHandler: requireAdmin('custom_fields:read') }, async () => {
75
+ const data = Object.entries(SUPPORTED_ENTITIES).map(([entityType, meta]) => ({
76
+ entityType,
77
+ labelKey: meta.labelKey,
78
+ ...(meta.managedBy
79
+ ? {
80
+ managedBy: {
81
+ moduleId: meta.managedBy.moduleId,
82
+ labelKey: meta.managedBy.labelKey,
83
+ route: meta.managedBy.route,
84
+ },
85
+ }
86
+ : {}),
87
+ }));
88
+ return { data };
89
+ });
90
+ app.get('/api/v1/admin/custom-fields/definitions', { preHandler: requireAdmin('custom_fields:read') }, async (request) => {
91
+ const raw = request.query.entityType;
92
+ const entityType = raw ? supportedEntityTypeSchema.parse(raw) : undefined;
93
+ const defs = await definitionService.listAll(entityType);
94
+ return { data: defs.map(serialize) };
95
+ });
96
+ app.get('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:read') }, async (request) => {
97
+ const def = await definitionService.getById(request.params.id);
98
+ if (!def)
99
+ throw new HttpError(404, ERROR_CODES.CUSTOM_FIELD_NOT_FOUND, 'Custom field not found.');
100
+ return { data: serialize(def) };
101
+ });
102
+ app.post('/api/v1/admin/custom-fields/definitions', { preHandler: requireAdmin('custom_fields:write'), schema: { body: createCustomFieldDefinitionSchema } }, async (request, reply) => {
103
+ const body = createCustomFieldDefinitionSchema.parse(request.body);
104
+ assertNotHostManaged(body.entityType);
105
+ try {
106
+ const created = await definitionService.create(body);
107
+ const def = await definitionService.getById(created.id);
108
+ reply.status(201);
109
+ return { data: serialize(def) };
110
+ }
111
+ catch (err) {
112
+ toHttp(err);
113
+ }
114
+ });
115
+ app.patch('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:write'), schema: { body: updateCustomFieldDefinitionSchema } }, async (request) => {
116
+ const body = updateCustomFieldDefinitionSchema.parse(request.body);
117
+ await assertDefinitionNotHostManaged(request.params.id);
118
+ try {
119
+ const updated = await definitionService.update(request.params.id, body);
120
+ const def = await definitionService.getById(updated.id);
121
+ return { data: serialize(def) };
122
+ }
123
+ catch (err) {
124
+ toHttp(err);
125
+ }
126
+ });
127
+ app.delete('/api/v1/admin/custom-fields/definitions/:id', { preHandler: requireAdmin('custom_fields:write') }, async (request, reply) => {
128
+ await assertDefinitionNotHostManaged(request.params.id);
129
+ try {
130
+ await definitionService.delete(request.params.id);
131
+ reply.status(204);
132
+ return null;
133
+ }
134
+ catch (err) {
135
+ toHttp(err);
136
+ }
137
+ });
138
+ // ---- Option sub-routes (select / multiselect) ---------------------------
139
+ app.post('/api/v1/admin/custom-fields/definitions/:id/options', { preHandler: requireAdmin('custom_fields:write'), schema: { body: customFieldOptionSchema } }, async (request, reply) => {
140
+ const body = customFieldOptionSchema.parse(request.body);
141
+ await assertDefinitionNotHostManaged(request.params.id);
142
+ try {
143
+ const opt = await definitionService.createOption(request.params.id, body);
144
+ reply.status(201);
145
+ return { data: { id: opt.id, value: opt.value } };
146
+ }
147
+ catch (err) {
148
+ toHttp(err);
149
+ }
150
+ });
151
+ app.patch('/api/v1/admin/custom-fields/definitions/:id/options/:optionId', { preHandler: requireAdmin('custom_fields:write'), schema: { body: customFieldOptionSchema.partial() } }, async (request) => {
152
+ const body = customFieldOptionSchema.partial().parse(request.body);
153
+ await assertDefinitionNotHostManaged(request.params.id);
154
+ try {
155
+ const opt = await definitionService.updateOption(request.params.id, request.params.optionId, {
156
+ ...(body.label !== undefined ? { label: body.label } : {}),
157
+ ...(body.labelDefault !== undefined ? { labelDefault: body.labelDefault } : {}),
158
+ ...(body.isDefault !== undefined ? { isDefault: body.isDefault } : {}),
159
+ ...(body.sortOrder !== undefined ? { sortOrder: body.sortOrder } : {}),
160
+ });
161
+ return { data: { id: opt.id, value: opt.value } };
162
+ }
163
+ catch (err) {
164
+ toHttp(err);
165
+ }
166
+ });
167
+ app.delete('/api/v1/admin/custom-fields/definitions/:id/options/:optionId', { preHandler: requireAdmin('custom_fields:write') }, async (request, reply) => {
168
+ await assertDefinitionNotHostManaged(request.params.id);
169
+ try {
170
+ await definitionService.deleteOption(request.params.id, request.params.optionId);
171
+ reply.status(204);
172
+ return null;
173
+ }
174
+ catch (err) {
175
+ toHttp(err);
176
+ }
177
+ });
178
+ }
179
+ //# sourceMappingURL=routes.admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.admin.js","sourceRoot":"","sources":["../../src/backend/routes.admin.ts"],"names":[],"mappings":"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"}