@endora-commerce/mod-megamenu 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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -0
  3. package/dist/admin/api/megamenu-client.d.ts +28 -0
  4. package/dist/admin/api/megamenu-client.d.ts.map +1 -0
  5. package/dist/admin/api/megamenu-client.js +43 -0
  6. package/dist/admin/api/megamenu-client.js.map +1 -0
  7. package/dist/admin/components/BindingsPanel.d.ts +10 -0
  8. package/dist/admin/components/BindingsPanel.d.ts.map +1 -0
  9. package/dist/admin/components/BindingsPanel.js +120 -0
  10. package/dist/admin/components/BindingsPanel.js.map +1 -0
  11. package/dist/admin/components/MenuItemConfigPanel.d.ts +22 -0
  12. package/dist/admin/components/MenuItemConfigPanel.d.ts.map +1 -0
  13. package/dist/admin/components/MenuItemConfigPanel.js +77 -0
  14. package/dist/admin/components/MenuItemConfigPanel.js.map +1 -0
  15. package/dist/admin/components/MenuItemTree.d.ts +13 -0
  16. package/dist/admin/components/MenuItemTree.d.ts.map +1 -0
  17. package/dist/admin/components/MenuItemTree.js +46 -0
  18. package/dist/admin/components/MenuItemTree.js.map +1 -0
  19. package/dist/admin/index.d.ts +31 -0
  20. package/dist/admin/index.d.ts.map +1 -0
  21. package/dist/admin/index.js +47 -0
  22. package/dist/admin/index.js.map +1 -0
  23. package/dist/admin/pages/MegamenuEditor.d.ts +4 -0
  24. package/dist/admin/pages/MegamenuEditor.d.ts.map +1 -0
  25. package/dist/admin/pages/MegamenuEditor.js +151 -0
  26. package/dist/admin/pages/MegamenuEditor.js.map +1 -0
  27. package/dist/admin/pages/MegamenuListPage.d.ts +4 -0
  28. package/dist/admin/pages/MegamenuListPage.d.ts.map +1 -0
  29. package/dist/admin/pages/MegamenuListPage.js +51 -0
  30. package/dist/admin/pages/MegamenuListPage.js.map +1 -0
  31. package/dist/backend/entities/megamenu-binding.entity.d.ts +18 -0
  32. package/dist/backend/entities/megamenu-binding.entity.d.ts.map +1 -0
  33. package/dist/backend/entities/megamenu-binding.entity.js +61 -0
  34. package/dist/backend/entities/megamenu-binding.entity.js.map +1 -0
  35. package/dist/backend/entities/megamenu-item.entity.d.ts +27 -0
  36. package/dist/backend/entities/megamenu-item.entity.d.ts.map +1 -0
  37. package/dist/backend/entities/megamenu-item.entity.js +83 -0
  38. package/dist/backend/entities/megamenu-item.entity.js.map +1 -0
  39. package/dist/backend/entities/megamenu.entity.d.ts +17 -0
  40. package/dist/backend/entities/megamenu.entity.d.ts.map +1 -0
  41. package/dist/backend/entities/megamenu.entity.js +57 -0
  42. package/dist/backend/entities/megamenu.entity.js.map +1 -0
  43. package/dist/backend/index.d.ts +116 -0
  44. package/dist/backend/index.d.ts.map +1 -0
  45. package/dist/backend/index.js +147 -0
  46. package/dist/backend/index.js.map +1 -0
  47. package/dist/backend/routes.admin.d.ts +10 -0
  48. package/dist/backend/routes.admin.d.ts.map +1 -0
  49. package/dist/backend/routes.admin.js +50 -0
  50. package/dist/backend/routes.admin.js.map +1 -0
  51. package/dist/backend/routes.storefront.d.ts +6 -0
  52. package/dist/backend/routes.storefront.d.ts.map +1 -0
  53. package/dist/backend/routes.storefront.js +26 -0
  54. package/dist/backend/routes.storefront.js.map +1 -0
  55. package/dist/backend/services/asset-references.d.ts +4 -0
  56. package/dist/backend/services/asset-references.d.ts.map +1 -0
  57. package/dist/backend/services/asset-references.js +36 -0
  58. package/dist/backend/services/asset-references.js.map +1 -0
  59. package/dist/backend/services/cms-references.d.ts +6 -0
  60. package/dist/backend/services/cms-references.d.ts.map +1 -0
  61. package/dist/backend/services/cms-references.js +39 -0
  62. package/dist/backend/services/cms-references.js.map +1 -0
  63. package/dist/backend/services/cross-module-ports.d.ts +112 -0
  64. package/dist/backend/services/cross-module-ports.d.ts.map +1 -0
  65. package/dist/backend/services/cross-module-ports.js +88 -0
  66. package/dist/backend/services/cross-module-ports.js.map +1 -0
  67. package/dist/backend/services/megamenu-cache.d.ts +36 -0
  68. package/dist/backend/services/megamenu-cache.d.ts.map +1 -0
  69. package/dist/backend/services/megamenu-cache.js +70 -0
  70. package/dist/backend/services/megamenu-cache.js.map +1 -0
  71. package/dist/backend/services/megamenu-item-service.d.ts +39 -0
  72. package/dist/backend/services/megamenu-item-service.d.ts.map +1 -0
  73. package/dist/backend/services/megamenu-item-service.js +151 -0
  74. package/dist/backend/services/megamenu-item-service.js.map +1 -0
  75. package/dist/backend/services/megamenu-language-reference.d.ts +12 -0
  76. package/dist/backend/services/megamenu-language-reference.d.ts.map +1 -0
  77. package/dist/backend/services/megamenu-language-reference.js +24 -0
  78. package/dist/backend/services/megamenu-language-reference.js.map +1 -0
  79. package/dist/backend/services/megamenu-reference-registry.d.ts +24 -0
  80. package/dist/backend/services/megamenu-reference-registry.d.ts.map +1 -0
  81. package/dist/backend/services/megamenu-reference-registry.js +69 -0
  82. package/dist/backend/services/megamenu-reference-registry.js.map +1 -0
  83. package/dist/backend/services/megamenu-service.d.ts +70 -0
  84. package/dist/backend/services/megamenu-service.d.ts.map +1 -0
  85. package/dist/backend/services/megamenu-service.js +299 -0
  86. package/dist/backend/services/megamenu-service.js.map +1 -0
  87. package/dist/backend/services/storefront-resolver.d.ts +46 -0
  88. package/dist/backend/services/storefront-resolver.d.ts.map +1 -0
  89. package/dist/backend/services/storefront-resolver.js +162 -0
  90. package/dist/backend/services/storefront-resolver.js.map +1 -0
  91. package/dist/backend/services/target-validator.d.ts +24 -0
  92. package/dist/backend/services/target-validator.d.ts.map +1 -0
  93. package/dist/backend/services/target-validator.js +105 -0
  94. package/dist/backend/services/target-validator.js.map +1 -0
  95. package/dist/manifest.d.ts +167 -0
  96. package/dist/manifest.d.ts.map +1 -0
  97. package/dist/manifest.js +170 -0
  98. package/dist/manifest.js.map +1 -0
  99. package/dist/migrations/20260505T193836_megamenu_init.d.ts +23 -0
  100. package/dist/migrations/20260505T193836_megamenu_init.d.ts.map +1 -0
  101. package/dist/migrations/20260505T193836_megamenu_init.js +91 -0
  102. package/dist/migrations/20260505T193836_megamenu_init.js.map +1 -0
  103. package/dist/migrations/index.d.ts +27 -0
  104. package/dist/migrations/index.d.ts.map +1 -0
  105. package/dist/migrations/index.js +29 -0
  106. package/dist/migrations/index.js.map +1 -0
  107. package/docs/megamenu/index.md +159 -0
  108. package/i18n/en.json +96 -0
  109. package/i18n/pl.json +96 -0
  110. package/package.json +99 -0
  111. package/tailwind.css +14 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"megamenu-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/megamenu-service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAGL,KAAK,uBAAuB,EAC5B,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,eAAe,IAAI,kBAAkB,EAC1C,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACtB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,KAAK,WAAW,GAAG;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,UAAU,EAAE,IAAI,GAAG,MAAM,CAAC;CAC3B,CAAC;AAuBF;;;;;GAKG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBAFpB,SAAS,EAAE,MAAM,aAAa,EAC9B,KAAK,CAAC,EAAE,aAAa,YAAA,EACrB,mBAAmB,CAAC,EAAE,mBAAmB,YAAA;IAOtD,IAAI,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,eAAe,EAAE,CAAC;QAAC,UAAU,EAAE,IAAI,CAAA;KAAE,CAAC;IAQ9D,MAAM,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,cAAc,CAAC;IAY7D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAMxC,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC;IA8BvE,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBjC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAS3D,UAAU,CACd,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC;YA2ChB,gBAAgB;IAmBxB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc5F;;;;;OAKG;IACG,QAAQ,CACZ,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,uBAAuB,CAAC;IA0E7B,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BnF,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;YAM5D,gBAAgB;IAK9B,OAAO,CAAC,aAAa;IAMrB;;;;;;;;;;;OAWG;YACW,YAAY;YAIZ,UAAU;YASV,aAAa;YAOb,SAAS;YAcT,QAAQ;IAStB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,SAAS;IAcjB,OAAO,CAAC,GAAG;CAGZ;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,IAAI,gBAAgB,EAAE,CAEvD"}
@@ -0,0 +1,299 @@
1
+ import { randomUUID } from 'crypto';
2
+ import { DictionaryReferenceError, ERROR_CODES, } from '@endora-commerce/contracts';
3
+ import { HttpError } from '@endora-commerce/platform/http';
4
+ import { SalesChannel } from '@endora-commerce/platform/kernel';
5
+ /**
6
+ * Megamenu CRUD + binding lifecycle. The activation transaction enforces
7
+ * "exactly one active megamenu per (channel, language)" via the partial
8
+ * unique index on `megamenu_bindings(sales_channel_id, language) WHERE
9
+ * active = true` (R3 / FR-008).
10
+ */
11
+ export class MegamenuService {
12
+ emFactory;
13
+ cache;
14
+ dictionaryValidator;
15
+ constructor(emFactory, cache, dictionaryValidator) {
16
+ this.emFactory = emFactory;
17
+ this.cache = cache;
18
+ this.dictionaryValidator = dictionaryValidator;
19
+ }
20
+ // ────────────────────────────────────────────────────────────────────
21
+ // CRUD
22
+ // ────────────────────────────────────────────────────────────────────
23
+ async list() {
24
+ const rows = (await this.emFactory().execute(`select * from megamenus order by updated_at desc limit 100`));
25
+ const data = await Promise.all(rows.map((row) => this.toSummary(row)));
26
+ return { data, nextCursor: null };
27
+ }
28
+ async create(input) {
29
+ const em = this.emFactory();
30
+ const id = randomUUID();
31
+ const now = new Date();
32
+ await em.execute(`insert into megamenus (id, name, description, version, created_at, updated_at)
33
+ values (?, ?, ?, 1, ?, ?)`, [id, input.name, input.description ?? null, now, now]);
34
+ return this.get(id);
35
+ }
36
+ async get(id) {
37
+ const row = await this.findRow(id);
38
+ if (!row)
39
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
40
+ return this.toDetail(row);
41
+ }
42
+ async patch(id, input) {
43
+ const em = this.emFactory();
44
+ await em.transactional(async (tx) => {
45
+ const row = await this.findRow(id, tx);
46
+ if (!row)
47
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
48
+ this.assertVersion(row.version, input.version);
49
+ const sets = [];
50
+ const params = [];
51
+ if (input.name !== undefined) {
52
+ sets.push('name = ?');
53
+ params.push(input.name);
54
+ }
55
+ if (input.description !== undefined) {
56
+ sets.push('description = ?');
57
+ params.push(input.description);
58
+ }
59
+ if (sets.length === 0)
60
+ return;
61
+ params.push(id);
62
+ await tx.execute(`update megamenus
63
+ set ${sets.join(', ')}, version = version + 1, updated_at = now()
64
+ where id = ?`, params);
65
+ });
66
+ if (this.cache)
67
+ await this.cache.invalidateAll();
68
+ return this.get(id);
69
+ }
70
+ async delete(id) {
71
+ const em = this.emFactory();
72
+ const activeRows = (await em.execute(`select 1 from megamenu_bindings where megamenu_id = ? and active = true limit 1`, [id]));
73
+ if (activeRows.length > 0) {
74
+ throw new HttpError(409, ERROR_CODES.MEGAMENU_HAS_ACTIVE_BINDINGS, 'Megamenu has at least one active binding; deactivate before deleting.');
75
+ }
76
+ await em.execute('delete from megamenus where id = ?', [id]);
77
+ if (this.cache)
78
+ await this.cache.invalidateAll();
79
+ }
80
+ // ────────────────────────────────────────────────────────────────────
81
+ // Bindings
82
+ // ────────────────────────────────────────────────────────────────────
83
+ async listBindings(menuId) {
84
+ await this.assertMenuExists(menuId);
85
+ const rows = (await this.emFactory().execute(`select * from megamenu_bindings where megamenu_id = ? order by created_at asc`, [menuId]));
86
+ return rows.map((row) => this.toBindingDto(row));
87
+ }
88
+ async addBinding(menuId, salesChannelId, language) {
89
+ const em = this.emFactory();
90
+ await this.assertMenuExists(menuId);
91
+ await this.validateLanguage(language);
92
+ const channel = await this.fetchChannel(em, salesChannelId);
93
+ if (!channel) {
94
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Sales channel ${salesChannelId} not found.`);
95
+ }
96
+ if (!channel.languages.includes(language)) {
97
+ throw new HttpError(400, ERROR_CODES.MEGAMENU_LANGUAGE_NOT_IN_CHANNEL_SCOPE, `Language "${language}" is not in the channel's configured language set.`);
98
+ }
99
+ const existing = (await em.execute(`select 1 from megamenu_bindings
100
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
101
+ if (existing.length > 0) {
102
+ throw new HttpError(409, ERROR_CODES.MEGAMENU_BINDING_ALREADY_EXISTS, 'Binding for this (channel, language) already exists.');
103
+ }
104
+ const now = new Date();
105
+ await em.execute(`insert into megamenu_bindings
106
+ (megamenu_id, sales_channel_id, language, active, version, created_at, updated_at)
107
+ values (?, ?, ?, false, 1, ?, ?)`, [menuId, salesChannelId, language, now, now]);
108
+ if (this.cache)
109
+ await this.cache.invalidateScope(channel.code, language);
110
+ const rows = (await em.execute(`select * from megamenu_bindings
111
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
112
+ return this.toBindingDto(rows[0]);
113
+ }
114
+ async validateLanguage(language) {
115
+ if (!this.dictionaryValidator)
116
+ return;
117
+ try {
118
+ await this.dictionaryValidator.validateLanguageCode(language, 'create-or-change');
119
+ }
120
+ catch (err) {
121
+ if (err instanceof DictionaryReferenceError) {
122
+ throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
123
+ ? `Language code ${err.entryCode} is no longer available for megamenu bindings.`
124
+ : `Language code ${err.entryCode} is not recognised.`, [{ path: 'language', issue: err.code }]);
125
+ }
126
+ throw err;
127
+ }
128
+ }
129
+ async removeBinding(menuId, salesChannelId, language) {
130
+ const em = this.emFactory();
131
+ await this.assertMenuExists(menuId);
132
+ const channel = await this.fetchChannel(em, salesChannelId);
133
+ await em.execute(`delete from megamenu_bindings
134
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
135
+ // DELETE is idempotent — a missing row is fine. Cache invalidation
136
+ // runs unconditionally so a stale entry never lingers.
137
+ if (this.cache && channel)
138
+ await this.cache.invalidateScope(channel.code, language);
139
+ }
140
+ /**
141
+ * Atomic activation swap (FR-008 / R3): deactivate the prior holder
142
+ * for the scope (if any) inside the same transaction, then upsert the
143
+ * new active row. The partial unique index refuses dual-active state
144
+ * even if this method's body regresses.
145
+ */
146
+ async activate(menuId, salesChannelId, language) {
147
+ const em = this.emFactory();
148
+ let response;
149
+ await em.transactional(async (tx) => {
150
+ // Verify the binding exists.
151
+ const binding = (await tx.execute(`select * from megamenu_bindings
152
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]));
153
+ if (binding.length === 0) {
154
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_BINDING_NOT_FOUND, 'Binding not found.');
155
+ }
156
+ // Refuse on empty tree (R10).
157
+ const items = (await tx.execute(`select 1 from megamenu_items where megamenu_id = ? limit 1`, [menuId]));
158
+ if (items.length === 0) {
159
+ throw new HttpError(400, ERROR_CODES.MEGAMENU_EMPTY_TREE, 'Megamenu has no items; populate the tree before activating.');
160
+ }
161
+ // Identify any prior holder for the (channel, language) pair.
162
+ const prior = (await tx.execute(`select b.megamenu_id, m.name
163
+ from megamenu_bindings b
164
+ join megamenus m on m.id = b.megamenu_id
165
+ where b.sales_channel_id = ? and b.language = ? and b.active = true
166
+ and b.megamenu_id <> ?
167
+ limit 1`, [salesChannelId, language, menuId]));
168
+ // Deactivate any existing active binding in the scope (this menu or another).
169
+ await tx.execute(`update megamenu_bindings
170
+ set active = false, version = version + 1, updated_at = now()
171
+ where sales_channel_id = ? and language = ? and active = true`, [salesChannelId, language]);
172
+ // Activate the requested binding.
173
+ await tx.execute(`update megamenu_bindings
174
+ set active = true, version = version + 1, updated_at = now()
175
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
176
+ response = {
177
+ activated: { megamenuId: menuId, salesChannelId, language },
178
+ previouslyActive: prior.length > 0 && prior[0]
179
+ ? { megamenuId: prior[0].megamenu_id, name: prior[0].name }
180
+ : null,
181
+ };
182
+ });
183
+ if (this.cache) {
184
+ const channel = await this.fetchChannel(em, salesChannelId);
185
+ if (channel)
186
+ await this.cache.invalidateScope(channel.code, language);
187
+ }
188
+ return response;
189
+ }
190
+ async deactivate(menuId, salesChannelId, language) {
191
+ const em = this.emFactory();
192
+ const existing = (await em.execute(`select 1 from megamenu_bindings
193
+ where megamenu_id = ? and sales_channel_id = ? and language = ? limit 1`, [menuId, salesChannelId, language]));
194
+ if (existing.length === 0) {
195
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_BINDING_NOT_FOUND, 'Binding not found.');
196
+ }
197
+ await em.execute(`update megamenu_bindings
198
+ set active = false, version = version + 1, updated_at = now()
199
+ where megamenu_id = ? and sales_channel_id = ? and language = ?`, [menuId, salesChannelId, language]);
200
+ if (this.cache) {
201
+ const channel = await this.fetchChannel(em, salesChannelId);
202
+ if (channel)
203
+ await this.cache.invalidateScope(channel.code, language);
204
+ }
205
+ }
206
+ // ────────────────────────────────────────────────────────────────────
207
+ // Helpers
208
+ // ────────────────────────────────────────────────────────────────────
209
+ async findRow(id, em) {
210
+ const targetEm = (em ?? this.emFactory());
211
+ const rows = (await targetEm.execute('select * from megamenus where id = ?', [id]));
212
+ return rows[0] ?? null;
213
+ }
214
+ async assertMenuExists(id) {
215
+ const row = await this.findRow(id);
216
+ if (!row)
217
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'Megamenu not found.');
218
+ }
219
+ assertVersion(current, supplied) {
220
+ if (supplied !== undefined && supplied !== current) {
221
+ throw new HttpError(409, ERROR_CODES.VERSION_CONFLICT, 'Megamenu was updated concurrently.');
222
+ }
223
+ }
224
+ /**
225
+ * The kernel's own entity, not `select … from sales_channels` (feature 075,
226
+ * D-87). `sales_channels` is the kernel's table since feature 072 moved the
227
+ * resolution machinery there, and a module relating into the kernel by ORM is
228
+ * the sanctioned access.
229
+ *
230
+ * Read through the caller's `em`, not through
231
+ * `salesChannelResolutionPort.getById`: every caller here is inside
232
+ * `em.transactional`, and that accessor forks its own EntityManager, so the
233
+ * binding checks below would validate against rows outside the transaction
234
+ * they are about to write into.
235
+ */
236
+ async fetchChannel(em, id) {
237
+ return em.findOne(SalesChannel, { id });
238
+ }
239
+ async fetchItems(menuId) {
240
+ return (await this.emFactory().execute(`select * from megamenu_items
241
+ where megamenu_id = ?
242
+ order by parent_id nulls first, position asc, id asc`, [menuId]));
243
+ }
244
+ async fetchBindings(menuId) {
245
+ return (await this.emFactory().execute(`select * from megamenu_bindings where megamenu_id = ? order by created_at asc`, [menuId]));
246
+ }
247
+ async toSummary(row) {
248
+ const bindings = (await this.fetchBindings(row.id)).map((b) => this.toBindingDto(b));
249
+ return {
250
+ id: row.id,
251
+ name: row.name,
252
+ description: row.description,
253
+ version: row.version,
254
+ bindings,
255
+ activeIn: bindings.filter((b) => b.active).length,
256
+ createdAt: this.iso(row.created_at),
257
+ updatedAt: this.iso(row.updated_at),
258
+ };
259
+ }
260
+ async toDetail(row) {
261
+ const summary = await this.toSummary(row);
262
+ const itemRows = await this.fetchItems(row.id);
263
+ return {
264
+ ...summary,
265
+ items: itemRows.map((item) => this.toItemDto(item)),
266
+ };
267
+ }
268
+ toBindingDto(row) {
269
+ return {
270
+ salesChannelId: row.sales_channel_id,
271
+ language: row.language,
272
+ active: row.active,
273
+ version: row.version,
274
+ createdAt: this.iso(row.created_at),
275
+ updatedAt: this.iso(row.updated_at),
276
+ };
277
+ }
278
+ toItemDto(row) {
279
+ // The Zod discriminated union enforces shape on the way in; the way
280
+ // out we trust the persisted data and re-shape into the contract type.
281
+ return {
282
+ id: row.id,
283
+ parentId: row.parent_id,
284
+ position: row.position,
285
+ kind: row.kind,
286
+ labels: row.labels,
287
+ descriptions: row.descriptions,
288
+ target: row.target,
289
+ };
290
+ }
291
+ iso(value) {
292
+ return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
293
+ }
294
+ }
295
+ /** Small helper for tests / smoke flows that need to assemble a tree. */
296
+ export function emptyResolvedItems() {
297
+ return [];
298
+ }
299
+ //# sourceMappingURL=megamenu-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"megamenu-service.js","sourceRoot":"","sources":["../../../src/backend/services/megamenu-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { ResolvedMegamenu } from '@endora-commerce/contracts';
3
+ import type { MegamenuCache } from './megamenu-cache.js';
4
+ import { type MegamenuCrossModulePorts } from './cross-module-ports.js';
5
+ /** Request-resolved sales channel handed in by the resolver middleware. */
6
+ export type ResolvedChannel = {
7
+ id: string;
8
+ code: string;
9
+ defaultLanguage: string;
10
+ };
11
+ /**
12
+ * Storefront resolution for the Megamenu module — feature 015 / T028.
13
+ *
14
+ * `resolveByChannelAndLanguage` is the single read endpoint behind
15
+ * `GET /api/v1/megamenu/by-channel`. It looks up the active binding via
16
+ * the partial unique index, fetches every item in one query, batches
17
+ * cross-module URL resolution, and assembles a recursive payload.
18
+ * Optional Redis read-through wraps the whole call.
19
+ */
20
+ export declare class StorefrontResolver {
21
+ private readonly emFactory;
22
+ /**
23
+ * The four cross-module reads a resolved menu needs, as their owners'
24
+ * published ports (`specs/110-instance-repository/` T118c). This was
25
+ * `StorefrontDeps`, an interface this module exported so a composition root
26
+ * could build four closures against it — twice, and not identically.
27
+ */
28
+ private readonly ports;
29
+ private readonly cache?;
30
+ constructor(emFactory: () => EntityManager,
31
+ /**
32
+ * The four cross-module reads a resolved menu needs, as their owners'
33
+ * published ports (`specs/110-instance-repository/` T118c). This was
34
+ * `StorefrontDeps`, an interface this module exported so a composition root
35
+ * could build four closures against it — twice, and not identically.
36
+ */
37
+ ports: MegamenuCrossModulePorts, cache?: MegamenuCache | undefined);
38
+ resolveByChannelAndLanguage(input: {
39
+ resolvedChannel: ResolvedChannel;
40
+ language?: string | undefined;
41
+ }): Promise<ResolvedMegamenu | null>;
42
+ private buildOne;
43
+ private resolveIcon;
44
+ private resolveLabel;
45
+ }
46
+ //# sourceMappingURL=storefront-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storefront-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/storefront-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EACV,gBAAgB,EAOjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAKL,KAAK,wBAAwB,EAC9B,MAAM,yBAAyB,CAAC;AAQjC,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,CAAC;AAqBpF;;;;;;;;GAQG;AACH,qBAAa,kBAAkB;IAE3B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;gBARN,SAAS,EAAE,MAAM,aAAa;IAC/C;;;;;OAKG;IACc,KAAK,EAAE,wBAAwB,EAC/B,KAAK,CAAC,EAAE,aAAa,YAAA;IAGlC,2BAA2B,CAAC,KAAK,EAAE;QACvC,eAAe,EAAE,eAAe,CAAC;QACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAC/B,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;YA6DtB,QAAQ;YA4ER,WAAW;IAUzB,OAAO,CAAC,YAAY;CAiBrB"}
@@ -0,0 +1,162 @@
1
+ import { resolveCategoryUrl, resolveCmsPageUrl, resolveMenuAsset, resolveMenuBlock, } from './cross-module-ports.js';
2
+ function toChannelRow(channel) {
3
+ return { id: channel.id, code: channel.code, default_language: channel.defaultLanguage };
4
+ }
5
+ /**
6
+ * Storefront resolution for the Megamenu module — feature 015 / T028.
7
+ *
8
+ * `resolveByChannelAndLanguage` is the single read endpoint behind
9
+ * `GET /api/v1/megamenu/by-channel`. It looks up the active binding via
10
+ * the partial unique index, fetches every item in one query, batches
11
+ * cross-module URL resolution, and assembles a recursive payload.
12
+ * Optional Redis read-through wraps the whole call.
13
+ */
14
+ export class StorefrontResolver {
15
+ emFactory;
16
+ ports;
17
+ cache;
18
+ constructor(emFactory,
19
+ /**
20
+ * The four cross-module reads a resolved menu needs, as their owners'
21
+ * published ports (`specs/110-instance-repository/` T118c). This was
22
+ * `StorefrontDeps`, an interface this module exported so a composition root
23
+ * could build four closures against it — twice, and not identically.
24
+ */
25
+ ports, cache) {
26
+ this.emFactory = emFactory;
27
+ this.ports = ports;
28
+ this.cache = cache;
29
+ }
30
+ async resolveByChannelAndLanguage(input) {
31
+ const em = this.emFactory();
32
+ const channel = toChannelRow(input.resolvedChannel);
33
+ const requestedLanguage = input.language ?? channel.default_language;
34
+ if (this.cache) {
35
+ const cached = await this.cache.get(channel.code, requestedLanguage);
36
+ if (cached)
37
+ return cached;
38
+ }
39
+ const menuRows = (await em.execute(`select m.id::text as id, m.name as name
40
+ from megamenu_bindings b
41
+ join megamenus m on m.id = b.megamenu_id
42
+ where b.sales_channel_id = ? and b.language = ? and b.active = true
43
+ limit 1`, [channel.id, requestedLanguage]));
44
+ const active = menuRows[0];
45
+ if (!active)
46
+ return null;
47
+ const itemRows = (await em.execute(`select id::text, parent_id::text, position, kind, labels, descriptions, target
48
+ from megamenu_items
49
+ where megamenu_id = ?
50
+ order by parent_id nulls first, position asc, id asc`, [active.id]));
51
+ const byParent = new Map();
52
+ for (const row of itemRows) {
53
+ const list = byParent.get(row.parent_id) ?? [];
54
+ list.push(row);
55
+ byParent.set(row.parent_id, list);
56
+ }
57
+ const buildSubtree = async (parentId) => {
58
+ const children = byParent.get(parentId) ?? [];
59
+ const out = [];
60
+ for (const row of children) {
61
+ const built = await this.buildOne(row, requestedLanguage, channel.default_language, buildSubtree);
62
+ if (built)
63
+ out.push(built);
64
+ }
65
+ return out;
66
+ };
67
+ const tree = await buildSubtree(null);
68
+ const payload = {
69
+ megamenuId: active.id,
70
+ name: active.name,
71
+ language: requestedLanguage,
72
+ items: tree,
73
+ };
74
+ if (this.cache) {
75
+ await this.cache.set(channel.code, requestedLanguage, payload);
76
+ }
77
+ return payload;
78
+ }
79
+ async buildOne(row, language, fallbackLanguage, buildSubtree) {
80
+ const label = this.resolveLabel(row.labels, language, fallbackLanguage);
81
+ if (label === null)
82
+ return null;
83
+ const description = this.resolveLabel(row.descriptions ?? {}, language, fallbackLanguage);
84
+ const children = await buildSubtree(row.id);
85
+ const base = {
86
+ id: row.id,
87
+ parentId: row.parent_id,
88
+ kind: row.kind,
89
+ label,
90
+ ...(description !== null ? { description } : {}),
91
+ children,
92
+ };
93
+ switch (row.kind) {
94
+ case 'category-link': {
95
+ const target = row.target;
96
+ const url = await resolveCategoryUrl(this.ports.categories, target.categoryId);
97
+ if (!url)
98
+ return null;
99
+ const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
100
+ return { ...base, url, ...(icon ? { icon } : {}) };
101
+ }
102
+ case 'cms-page-link': {
103
+ const target = row.target;
104
+ const url = await resolveCmsPageUrl(this.ports.pages, target.pageId);
105
+ if (!url)
106
+ return null;
107
+ const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
108
+ return { ...base, url, ...(icon ? { icon } : {}) };
109
+ }
110
+ case 'external-link': {
111
+ const target = row.target;
112
+ const icon = await this.resolveIcon(target.iconAssetId, target.iconPosition);
113
+ return { ...base, url: target.url, ...(icon ? { icon } : {}) };
114
+ }
115
+ case 'button': {
116
+ const target = row.target;
117
+ return { ...base, url: target.url, variant: target.variant };
118
+ }
119
+ case 'asset': {
120
+ const target = row.target;
121
+ if (!target.assetId)
122
+ return null;
123
+ const resolved = await resolveMenuAsset(this.ports, target.assetId);
124
+ if (!resolved)
125
+ return null;
126
+ return {
127
+ ...base,
128
+ asset: { id: target.assetId, kind: resolved.kind, url: resolved.url, label: resolved.label },
129
+ };
130
+ }
131
+ case 'cms-block-embed': {
132
+ const target = row.target;
133
+ const block = await resolveMenuBlock(this.ports.blocks, target.blockId, language);
134
+ if (!block)
135
+ return null;
136
+ return { ...base, block, embedSide: target.embedSide };
137
+ }
138
+ }
139
+ }
140
+ async resolveIcon(iconAssetId, iconPosition) {
141
+ if (!iconAssetId)
142
+ return null;
143
+ const asset = await resolveMenuAsset(this.ports, iconAssetId);
144
+ if (!asset)
145
+ return null;
146
+ return { assetId: iconAssetId, url: asset.url, position: iconPosition ?? 'left' };
147
+ }
148
+ resolveLabel(labels, language, fallbackLanguage) {
149
+ const direct = labels[language];
150
+ if (typeof direct === 'string' && direct.length > 0) {
151
+ return direct;
152
+ }
153
+ if (language !== fallbackLanguage) {
154
+ const fallback = labels[fallbackLanguage];
155
+ if (typeof fallback === 'string' && fallback.length > 0) {
156
+ return fallback;
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+ }
162
+ //# sourceMappingURL=storefront-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storefront-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/storefront-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { type MegamenuItem } from '@endora-commerce/contracts';
2
+ import type { MegamenuCrossModulePorts } from './cross-module-ports.js';
3
+ /**
4
+ * Validates a single MegamenuItem against the cross-module ports. Throws
5
+ * `HttpError` on the first failure so callers (`MegamenuItemService.setTree`)
6
+ * can re-raise unchanged.
7
+ *
8
+ * The four checks used to arrive as `TargetValidatorDeps`, an interface this
9
+ * module exported for a composition root to build closures against
10
+ * (`specs/110-instance-repository/` T118c). They are their owners' published read
11
+ * ports now, resolved by `backend/index.ts` and declared in this module's
12
+ * manifest.
13
+ *
14
+ * **`_channelIds` is threaded and read by nothing, which is what the roots' SQL
15
+ * did.** `setTree` computes the union of the menu's bound channels and passes it
16
+ * here; neither root's closure ever looked at it, so `MEGAMENU_TARGET_OUT_OF_SCOPE`
17
+ * has only ever meant *the target does not exist*. That is a documented v1
18
+ * narrowing — `megamenu-item-service.ts` says so at the call site, and the scope
19
+ * refusal is US3's — and the parameter is kept so US3 lands inside this function
20
+ * rather than re-plumbing its caller. Naming it `_` is the only change: the
21
+ * ignoring used to happen two files away in a root nobody reads.
22
+ */
23
+ export declare function validateTarget(item: MegamenuItem, _channelIds: string[], ports: MegamenuCrossModulePorts): Promise<void>;
24
+ //# sourceMappingURL=target-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"target-validator.d.ts","sourceRoot":"","sources":["../../../src/backend/services/target-validator.ts"],"names":[],"mappings":"AAQA,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE5E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAIxE;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,YAAY,EAClB,WAAW,EAAE,MAAM,EAAE,EACrB,KAAK,EAAE,wBAAwB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAwGf"}