@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,116 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { Redis } from 'ioredis';
3
+ import type { AssetReferenceRegistryPort, CmsExternalReferenceScanner, DictionaryValidator } from '@endora-commerce/contracts';
4
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
5
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
6
+ import { MegamenuCache, type MegamenuCacheOptions } from './services/megamenu-cache.js';
7
+ import { MegamenuReferenceRegistry } from './services/megamenu-reference-registry.js';
8
+ import { MegamenuService } from './services/megamenu-service.js';
9
+ import { MegamenuItemService } from './services/megamenu-item-service.js';
10
+ import { StorefrontResolver } from './services/storefront-resolver.js';
11
+ import { MegamenuBinding } from './entities/megamenu-binding.entity.js';
12
+ import { MegamenuItem } from './entities/megamenu-item.entity.js';
13
+ import { Megamenu } from './entities/megamenu.entity.js';
14
+ /**
15
+ * `megamenu` — the module reads its own cross-module targets
16
+ * (`specs/110-instance-repository/` T118c).
17
+ *
18
+ * Until T118c this block said the opposite, and said it firmly: two dependency
19
+ * bundles stayed in the composition roots because both were raw SQL against
20
+ * `categories`, `cms_pages`, `cms_blocks` and `assets`, and moving *that* into
21
+ * the module would have given it direct reads of three other modules' storage.
22
+ * The reasoning was right and the conclusion had an expiry date the block wrote
23
+ * down itself: *"it belongs to whichever of `catalog`, `cms` and
24
+ * `assets_library` grows the existence-check port first."* All three have.
25
+ * `catalogCategoryReadPort`, `cmsPageReadPort` and `assetReadPort` came out of
26
+ * `specs/075-cross-module-decoupling-sweep/` Phase C, and `cmsBlockReadPort` is
27
+ * this merge request's — the one port the drain still needed and the reason
28
+ * T118c's row picks this target.
29
+ *
30
+ * So the eight closures are gone from both roots and their two interfaces with
31
+ * them. What replaces them is `MegamenuCrossModulePorts`: five published ports,
32
+ * resolved per call by `lazyPort`, with the four judgements that used to sit
33
+ * inside the closures — a storefront URL's shape, which category counts as live,
34
+ * which asset kinds a menu may embed, what an absent language means — in
35
+ * `services/cross-module-ports.ts`, where they have a test.
36
+ *
37
+ * **What the roots were getting wrong, which is the argument for the drain
38
+ * rather than a bonus.** A root's contribution is nobody's declared dependency,
39
+ * so none of these edges appeared in a manifest: an operator switching `cms` off
40
+ * was told nothing about the menu embeds that stop. And the two roots did not
41
+ * agree — production dropped a deactivated or soft-deleted category from the
42
+ * menu where the harness kept it, and production built `/c/<slug>` where the
43
+ * harness built `/catalog/<slug>`, which the storefront serves from nowhere. No
44
+ * test in the tree read either value, so both divergences were invisible.
45
+ *
46
+ * `requireAdmin` stops being optional — `routes.admin.ts` defaulted an absent
47
+ * gate to `?? (async () => {})`, the permissive form, which both roots happened
48
+ * to pass.
49
+ *
50
+ * `megamenuCacheOptions` is a contribution point that **neither** composition
51
+ * currently overrides, and that is deliberate rather than an oversight. Unlike
52
+ * `seo`'s sitemap options, the harness wants this module's cache *on*:
53
+ * `test/integration/megamenu/storefront-cache.test.ts` exists to prove the
54
+ * payload is cached and dropped on write, so a zero TTL there would make the
55
+ * suite assert nothing. The seam stays for a deployment that wants to tune the
56
+ * TTL; the default is the behaviour both compositions actually want.
57
+ */
58
+ interface MegamenuServices {
59
+ readonly menuService: MegamenuService;
60
+ readonly itemService: MegamenuItemService;
61
+ readonly storefrontResolver: StorefrontResolver;
62
+ readonly cache: MegamenuCache | undefined;
63
+ }
64
+ export interface MegamenuCradle {
65
+ readonly emFactory: () => EntityManager;
66
+ readonly redis: Redis | undefined;
67
+ readonly requireAdmin: RequireAdminFactory;
68
+ readonly dictionaryValidator: DictionaryValidator;
69
+ /** Composition-specific cache tuning; `{}` in production. */
70
+ readonly megamenuCacheOptions: MegamenuCacheOptions;
71
+ /**
72
+ * Owned by `assets_library`: the registry that refuses to delete an asset a
73
+ * menu item points at, whether as the item's target or as its icon. Named by
74
+ * its published contract since Phase C, not by that module's class.
75
+ */
76
+ readonly assetReferenceRegistry: AssetReferenceRegistryPort;
77
+ /**
78
+ * Owned by `cms`: the registry that refuses to delete a page or a block a
79
+ * menu item links to. This module contributes the scanner; `cms` calls it.
80
+ */
81
+ readonly cmsReferenceRegistry: {
82
+ register: (scanner: CmsExternalReferenceScanner) => void;
83
+ };
84
+ readonly megamenuServices: MegamenuServices;
85
+ readonly megamenuReferenceRegistry: MegamenuReferenceRegistry;
86
+ }
87
+ export declare function registerModule(ctx: ModuleContext): void;
88
+ /**
89
+ * The module's persisted entity classes, on the `./backend` subpath, as one
90
+ * array and **no named class export** (D-168).
91
+ *
92
+ * This is the shape the platform reads when the package is *installed*: the
93
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
94
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
95
+ * which is the third source of `check:module-boundary`'s `table->owner` map and
96
+ * the package pass of `check-entity-tenant-classification`. A missing array is
97
+ * answered with `[]` — zero entities registered, no error anywhere.
98
+ *
99
+ * The order is the one `db/entities-registry.generated.ts` declared before this
100
+ * module became a package, so the registered set is the same list in the same
101
+ * sequence.
102
+ */
103
+ export declare const entities: (typeof MegamenuBinding | typeof MegamenuItem | typeof Megamenu)[];
104
+ export {};
105
+ /**
106
+ * **Nothing is published for a composition root any more**
107
+ * (`specs/110-instance-repository/` T118c).
108
+ *
109
+ * `TargetValidatorDeps` and `StorefrontDeps` were exported here so
110
+ * `backend/src/composition.ts` and `backend/test/helpers/test-server.ts` could
111
+ * write objects that `satisfies` them. Both roots have stopped, both interfaces
112
+ * are deleted rather than relocated, and this module's outward reach is
113
+ * `MegamenuCrossModulePorts` — module-private, and a bundle of five published
114
+ * ports with no closure in it, so there is nothing left for a root to build.
115
+ */
116
+ //# 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;AACrC,OAAO,KAAK,EAEV,0BAA0B,EAI1B,2BAA2B,EAE3B,mBAAmB,EAEpB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACxF,OAAO,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAMvE,OAAO,EAAE,eAAe,EAAE,MAAM,uCAAuC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,UAAU,gBAAgB;IACxB,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,mBAAmB,CAAC;IAC1C,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,CAAC;IAChD,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;CAC3C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;IAClD,6DAA6D;IAC7D,QAAQ,CAAC,oBAAoB,EAAE,oBAAoB,CAAC;IACpD;;;;OAIG;IACH,QAAQ,CAAC,sBAAsB,EAAE,0BAA0B,CAAC;IAC5D;;;OAGG;IACH,QAAQ,CAAC,oBAAoB,EAAE;QAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAA;KAAE,CAAC;IAC5F,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,yBAAyB,EAAE,yBAAyB,CAAC;CAC/D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAmHvD;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,QAAQ,oEAIpB,CAAC;;AAEF;;;;;;;;;;GAUG"}
@@ -0,0 +1,147 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { MegamenuCache } from './services/megamenu-cache.js';
3
+ import { MegamenuReferenceRegistry } from './services/megamenu-reference-registry.js';
4
+ import { MegamenuService } from './services/megamenu-service.js';
5
+ import { MegamenuItemService } from './services/megamenu-item-service.js';
6
+ import { StorefrontResolver } from './services/storefront-resolver.js';
7
+ import { registerMegamenuAdminRoutes } from './routes.admin.js';
8
+ import { registerMegamenuStorefrontRoutes } from './routes.storefront.js';
9
+ import { registerMegamenuAssetReferences } from './services/asset-references.js';
10
+ import { registerMegamenuCmsReferences } from './services/cms-references.js';
11
+ import { registerMegamenuLanguageReferences } from './services/megamenu-language-reference.js';
12
+ import { MegamenuBinding } from './entities/megamenu-binding.entity.js';
13
+ import { MegamenuItem } from './entities/megamenu-item.entity.js';
14
+ import { Megamenu } from './entities/megamenu.entity.js';
15
+ export function registerModule(ctx) {
16
+ ctx.di.register({
17
+ megamenuCacheOptions: ctx.asFunction(() => ({})).singleton(),
18
+ megamenuServices: ctx
19
+ .asFunction(({ emFactory, redis, megamenuCacheOptions }) => {
20
+ const cache = redis ? new MegamenuCache(redis, megamenuCacheOptions) : undefined;
21
+ const menuService = new MegamenuService(emFactory, cache, lazyPort(ctx, 'dictionaryValidator'));
22
+ /**
23
+ * The five published ports this module reads outside itself
24
+ * (`specs/110-instance-repository/` T118c). Every one is a `lazyPort`
25
+ * proxy that resolves on the call rather than here, so nothing
26
+ * captures a gated registration — a captured gate keeps answering
27
+ * after its owner is switched off.
28
+ *
29
+ * Two of them are `assets_library`'s, because neither answers the
30
+ * whole question: `AssetRecord` carries the stored `kind` and no
31
+ * resolved URL, `AssetDetail` carries the URL and no `kind`, and
32
+ * `resolveUrl` is published on no port (D-223).
33
+ */
34
+ const ports = {
35
+ categories: lazyPort(ctx, 'catalogCategoryReadPort'),
36
+ pages: lazyPort(ctx, 'cmsPageReadPort'),
37
+ blocks: lazyPort(ctx, 'cmsBlockReadPort'),
38
+ assets: lazyPort(ctx, 'assetReadPort'),
39
+ assetLibrary: lazyPort(ctx, 'assetsLibraryPort'),
40
+ };
41
+ return {
42
+ menuService,
43
+ cache,
44
+ itemService: new MegamenuItemService(emFactory, menuService, ports, cache),
45
+ storefrontResolver: new StorefrontResolver(emFactory, ports, cache),
46
+ };
47
+ })
48
+ .singleton(),
49
+ });
50
+ /**
51
+ * The lookup behind this module's own CMS scanner, deliberately **ungated**
52
+ * (feature 072, D-39).
53
+ *
54
+ * It was a `providePort`, and the only thing that resolves it is this
55
+ * module's own boot hook, twenty lines down — so switching `megamenu` off
56
+ * made `megamenu` throw `MODULE_DISABLED` at its own registration and the
57
+ * backend stopped starting. The gate protected nothing: the sole consumer is
58
+ * the module the gate was about.
59
+ *
60
+ * It stays ungated rather than merely moving: what it feeds is a scanner
61
+ * `cms` walks on delete, and `cms` honours that edge while `megamenu` is off
62
+ * on purpose — the menu items still hold the reference. A gate here would be
63
+ * a second, contradictory answer to a question `cms` has already answered.
64
+ */
65
+ ctx.di.register({
66
+ megamenuReferenceRegistry: ctx
67
+ .asFunction(({ emFactory }) => new MegamenuReferenceRegistry(emFactory))
68
+ .singleton(),
69
+ });
70
+ /**
71
+ * The two reference edges this module holds against other modules' entities
72
+ * (T143a): an asset a menu item targets or uses as an icon, and a CMS page or
73
+ * block a menu item links to. Both refuse the upstream delete with a 409 that
74
+ * names the menu.
75
+ *
76
+ * Both roots used to cross-register these, which had the failure this cluster
77
+ * exists to remove: a root's push survives `megamenu` being switched off, so
78
+ * an operator who had disabled the module still could not delete a page a
79
+ * megamenu item referenced, and the 409 named a menu the platform was no
80
+ * longer serving.
81
+ *
82
+ * `ctx.onBoot` rather than a registration: both registries are *read* by
83
+ * their owners on delete, and a registration declares without resolving.
84
+ */
85
+ ctx.onBoot(() => {
86
+ const { assetReferenceRegistry, cmsReferenceRegistry, megamenuReferenceRegistry, emFactory } = ctx.cradle();
87
+ registerMegamenuAssetReferences(assetReferenceRegistry, emFactory);
88
+ registerMegamenuCmsReferences(cmsReferenceRegistry, megamenuReferenceRegistry);
89
+ });
90
+ ctx.routes(async (app) => {
91
+ const { megamenuServices, requireAdmin } = ctx.cradle();
92
+ await registerMegamenuAdminRoutes(app, {
93
+ menuService: megamenuServices.menuService,
94
+ itemService: megamenuServices.itemService,
95
+ requireAdmin,
96
+ });
97
+ await registerMegamenuStorefrontRoutes(app, {
98
+ storefrontResolver: megamenuServices.storefrontResolver,
99
+ });
100
+ });
101
+ /**
102
+ * This module's rows carry a language code, so it answers "who still points at
103
+ * this language?" about its own tables (feature 077, D-87), where the owner used
104
+ * to count them with SQL naming this module's tables.
105
+ *
106
+ * A **contribution** hook: it pushes an inert descriptor into `languageReferenceRegistry`,
107
+ * an ungated registry, and carries no presence probe (D-62/D-68). Probing
108
+ * would be wrong in the dangerous direction — a switched-off module still owns
109
+ * the rows, so its language must still refuse the delete, which is the
110
+ * enumeration policy the registry states.
111
+ */
112
+ ctx.onBoot(() => {
113
+ registerMegamenuLanguageReferences(lazyPort(ctx, 'languageReferenceRegistry'), ctx.cradle().emFactory);
114
+ });
115
+ }
116
+ /**
117
+ * The module's persisted entity classes, on the `./backend` subpath, as one
118
+ * array and **no named class export** (D-168).
119
+ *
120
+ * This is the shape the platform reads when the package is *installed*: the
121
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
122
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
123
+ * which is the third source of `check:module-boundary`'s `table->owner` map and
124
+ * the package pass of `check-entity-tenant-classification`. A missing array is
125
+ * answered with `[]` — zero entities registered, no error anywhere.
126
+ *
127
+ * The order is the one `db/entities-registry.generated.ts` declared before this
128
+ * module became a package, so the registered set is the same list in the same
129
+ * sequence.
130
+ */
131
+ export const entities = [
132
+ MegamenuBinding,
133
+ MegamenuItem,
134
+ Megamenu,
135
+ ];
136
+ /**
137
+ * **Nothing is published for a composition root any more**
138
+ * (`specs/110-instance-repository/` T118c).
139
+ *
140
+ * `TargetValidatorDeps` and `StorefrontDeps` were exported here so
141
+ * `backend/src/composition.ts` and `backend/test/helpers/test-server.ts` could
142
+ * write objects that `satisfies` them. Both roots have stopped, both interfaces
143
+ * are deleted rather than relocated, and this module's outward reach is
144
+ * `MegamenuCrossModulePorts` — module-private, and a bundle of five published
145
+ * ports with no closure in it, so there is nothing left for a root to build.
146
+ */
147
+ //# 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,10 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { MegamenuService } from './services/megamenu-service.js';
3
+ import type { MegamenuItemService } from './services/megamenu-item-service.js';
4
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ export declare function registerMegamenuAdminRoutes(app: FastifyInstance, deps: {
6
+ menuService: MegamenuService;
7
+ itemService: MegamenuItemService;
8
+ requireAdmin: RequireAdminFactory;
9
+ }): Promise<void>;
10
+ //# 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;AAQ/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAC/E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE;IACJ,WAAW,EAAE,eAAe,CAAC;IAC7B,WAAW,EAAE,mBAAmB,CAAC;IACjC,YAAY,EAAE,mBAAmB,CAAC;CACnC,GACA,OAAO,CAAC,IAAI,CAAC,CAoHf"}
@@ -0,0 +1,50 @@
1
+ import { activateBindingRequestSchema, addBindingRequestSchema, createMegamenuRequestSchema, patchMegamenuRequestSchema, putItemsRequestSchema, } from '@endora-commerce/contracts';
2
+ export async function registerMegamenuAdminRoutes(app, deps) {
3
+ const requireRead = deps.requireAdmin('megamenu.read');
4
+ const requireWrite = deps.requireAdmin('megamenu.write');
5
+ app.get('/api/v1/admin/megamenu/menus', { preHandler: requireRead }, async () => deps.menuService.list());
6
+ app.post('/api/v1/admin/megamenu/menus', { preHandler: requireWrite }, async (request, reply) => {
7
+ const body = createMegamenuRequestSchema.parse(request.body);
8
+ const created = await deps.menuService.create(body);
9
+ return reply.code(201).send({ data: created });
10
+ });
11
+ app.get('/api/v1/admin/megamenu/menus/:id', { preHandler: requireRead }, async (request) => ({ data: await deps.menuService.get(request.params.id) }));
12
+ app.patch('/api/v1/admin/megamenu/menus/:id', { preHandler: requireWrite }, async (request) => {
13
+ const body = patchMegamenuRequestSchema.parse(request.body);
14
+ return { data: await deps.menuService.patch(request.params.id, body) };
15
+ });
16
+ app.delete('/api/v1/admin/megamenu/menus/:id', { preHandler: requireWrite }, async (request, reply) => {
17
+ await deps.menuService.delete(request.params.id);
18
+ return reply.code(204).send();
19
+ });
20
+ app.put('/api/v1/admin/megamenu/menus/:id/items', { preHandler: requireWrite }, async (request) => {
21
+ const body = putItemsRequestSchema.parse(request.body);
22
+ const result = await deps.itemService.setTree(request.params.id, body);
23
+ return {
24
+ data: result.detail,
25
+ ...(result.warnings.length > 0 ? { meta: { warnings: result.warnings } } : {}),
26
+ };
27
+ });
28
+ // ── Bindings ──────────────────────────────────────────────────────
29
+ app.get('/api/v1/admin/megamenu/menus/:id/bindings', { preHandler: requireRead }, async (request) => ({ data: await deps.menuService.listBindings(request.params.id) }));
30
+ app.post('/api/v1/admin/megamenu/menus/:id/bindings', { preHandler: requireWrite }, async (request, reply) => {
31
+ const body = addBindingRequestSchema.parse(request.body);
32
+ const created = await deps.menuService.addBinding(request.params.id, body.salesChannelId, body.language);
33
+ return reply.code(201).send({ data: created });
34
+ });
35
+ app.delete('/api/v1/admin/megamenu/menus/:id/bindings/:salesChannelId/:language', { preHandler: requireWrite }, async (request, reply) => {
36
+ await deps.menuService.removeBinding(request.params.id, request.params.salesChannelId, request.params.language);
37
+ return reply.code(204).send();
38
+ });
39
+ app.post('/api/v1/admin/megamenu/menus/:id/activate', { preHandler: requireWrite }, async (request) => {
40
+ const body = activateBindingRequestSchema.parse(request.body);
41
+ const result = await deps.menuService.activate(request.params.id, body.salesChannelId, body.language);
42
+ return { data: result };
43
+ });
44
+ app.post('/api/v1/admin/megamenu/menus/:id/deactivate', { preHandler: requireWrite }, async (request, reply) => {
45
+ const body = activateBindingRequestSchema.parse(request.body);
46
+ await deps.menuService.deactivate(request.params.id, body.salesChannelId, body.language);
47
+ return reply.code(200).send({ data: { deactivated: body } });
48
+ });
49
+ }
50
+ //# 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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"}
@@ -0,0 +1,6 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { StorefrontResolver } from './services/storefront-resolver.js';
3
+ export declare function registerMegamenuStorefrontRoutes(app: FastifyInstance, deps: {
4
+ storefrontResolver: StorefrontResolver;
5
+ }): Promise<void>;
6
+ //# sourceMappingURL=routes.storefront.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.d.ts","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,SAAS,CAAC;AAI/D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,wBAAsB,gCAAgC,CACpD,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE;IAAE,kBAAkB,EAAE,kBAAkB,CAAA;CAAE,GAC/C,OAAO,CAAC,IAAI,CAAC,CAcf"}
@@ -0,0 +1,26 @@
1
+ import { ERROR_CODES } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { getResolvedChannel } from '@endora-commerce/platform/kernel';
4
+ export async function registerMegamenuStorefrontRoutes(app, deps) {
5
+ app.get('/api/v1/megamenu/by-channel', async (request) => {
6
+ const query = (request.query ?? {});
7
+ const language = query['language'] ?? readHeader(request.headers['accept-language'])?.split(',')[0];
8
+ const resolved = await deps.storefrontResolver.resolveByChannelAndLanguage({
9
+ resolvedChannel: resolvedChannel(request),
10
+ ...(language !== undefined ? { language } : {}),
11
+ });
12
+ if (!resolved) {
13
+ throw new HttpError(404, ERROR_CODES.MEGAMENU_NOT_FOUND, 'No active Megamenu for the requested scope.');
14
+ }
15
+ return { data: resolved };
16
+ });
17
+ }
18
+ /** Reads the request's already-resolved sales channel (set by the resolver middleware). */
19
+ function resolvedChannel(request) {
20
+ const ch = getResolvedChannel(request);
21
+ return { id: ch.id, code: ch.code, defaultLanguage: ch.defaultLanguage };
22
+ }
23
+ function readHeader(value) {
24
+ return Array.isArray(value) ? value[0] : value;
25
+ }
26
+ //# sourceMappingURL=routes.storefront.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.storefront.js","sourceRoot":"","sources":["../../src/backend/routes.storefront.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AAGtE,MAAM,CAAC,KAAK,UAAU,gCAAgC,CACpD,GAAoB,EACpB,IAAgD;IAEhD,GAAG,CAAC,GAAG,CAAC,6BAA6B,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QACvD,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAuC,CAAC;QAC1E,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAEpG,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,2BAA2B,CAAC;YACzE,eAAe,EAAE,eAAe,CAAC,OAAO,CAAC;YACzC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChD,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,kBAAkB,EAAE,6CAA6C,CAAC,CAAC;QAC1G,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2FAA2F;AAC3F,SAAS,eAAe,CAAC,OAAuB;IAC9C,MAAM,EAAE,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,EAAE,CAAC,eAAe,EAAE,CAAC;AAC3E,CAAC;AAED,SAAS,UAAU,CAAC,KAAoC;IACtD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AACjD,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AssetReferenceRegistryPort } from '@endora-commerce/contracts';
3
+ export declare function registerMegamenuAssetReferences(registry: AssetReferenceRegistryPort, emFactory: () => EntityManager): void;
4
+ //# sourceMappingURL=asset-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-references.d.ts","sourceRoot":"","sources":["../../../src/backend/services/asset-references.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAGV,0BAA0B,EAC3B,MAAM,4BAA4B,CAAC;AAEpC,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,0BAA0B,EACpC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAEN"}
@@ -0,0 +1,36 @@
1
+ // Megamenu → AssetReferenceRegistry descriptor — feature 015 / T015.
2
+ //
3
+ // Scans `megamenu_items.target` for both `assetId` (asset items) and
4
+ // `iconAssetId` (link items with an icon). Both edges block deletion of
5
+ // the upstream Library Asset with a 409.
6
+ //
7
+ // The registry and the descriptor are named by their `@endora-commerce/contracts` shapes
8
+ // since feature 075's Phase C: this file describes what it contributes, and
9
+ // `assets_library` decides what enumerating it means — including the policy
10
+ // that honours this scanner while `megamenu` is switched off, which is why the
11
+ // seam stays an ungated registration rather than becoming a port.
12
+ export function registerMegamenuAssetReferences(registry, emFactory) {
13
+ registry.register(megamenuAssetReferenceDescriptor(emFactory));
14
+ }
15
+ function megamenuAssetReferenceDescriptor(emFactory) {
16
+ return {
17
+ ownerModuleId: 'megamenu',
18
+ async findReferences(assetIds) {
19
+ if (assetIds.length === 0)
20
+ return [];
21
+ const em = emFactory();
22
+ const placeholders = assetIds.map(() => '?').join(', ');
23
+ const rows = (await em.execute(`select mi.id::text as item_id, m.name as menu_name
24
+ from megamenu_items mi
25
+ join megamenus m on m.id = mi.megamenu_id
26
+ where mi.target->>'assetId' in (${placeholders})
27
+ or mi.target->>'iconAssetId' in (${placeholders})`, [...assetIds, ...assetIds]));
28
+ return rows.map((r) => ({
29
+ kind: 'megamenu_item_target',
30
+ entityId: r.item_id,
31
+ label: `Megamenu "${r.menu_name}"`,
32
+ }));
33
+ },
34
+ };
35
+ }
36
+ //# sourceMappingURL=asset-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-references.js","sourceRoot":"","sources":["../../../src/backend/services/asset-references.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AACxE,yCAAyC;AACzC,EAAE;AACF,yFAAyF;AACzF,4EAA4E;AAC5E,4EAA4E;AAC5E,+EAA+E;AAC/E,kEAAkE;AASlE,MAAM,UAAU,+BAA+B,CAC7C,QAAoC,EACpC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC,gCAAgC,CAAC,SAAS,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,gCAAgC,CACvC,SAA8B;IAE9B,OAAO;QACL,aAAa,EAAE,UAAU;QACzB,KAAK,CAAC,cAAc,CAAC,QAAkB;YACrC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;YACrC,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;YACvB,MAAM,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC,OAAO,CAC5B;;;gDAGwC,YAAY;gDACZ,YAAY,GAAG,EACvD,CAAC,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC,CAC3B,CAAkD,CAAC;YACpD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtB,IAAI,EAAE,sBAAsB;gBAC5B,QAAQ,EAAE,CAAC,CAAC,OAAO;gBACnB,KAAK,EAAE,aAAa,CAAC,CAAC,SAAS,GAAG;aACnC,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { CmsExternalReferenceScanner } from '@endora-commerce/contracts';
2
+ import type { MegamenuReferenceRegistry } from './megamenu-reference-registry.js';
3
+ export declare function registerMegamenuCmsReferences(cmsRegistry: {
4
+ register: (scanner: CmsExternalReferenceScanner) => void;
5
+ }, megamenuRegistry: MegamenuReferenceRegistry): void;
6
+ //# sourceMappingURL=cms-references.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-references.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cms-references.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,2BAA2B,EAAgB,MAAM,4BAA4B,CAAC;AAC5F,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,kCAAkC,CAAC;AAElF,wBAAgB,6BAA6B,CAK3C,WAAW,EAAE;IAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAA;CAAE,EACzE,gBAAgB,EAAE,yBAAyB,GAC1C,IAAI,CAoBN"}
@@ -0,0 +1,39 @@
1
+ // Megamenu → CmsReferenceRegistry external scanner — feature 015 / T047.
2
+ //
3
+ // Adapts the in-process `MegamenuReferenceRegistry` into the shape the
4
+ // CMS module's `CmsReferenceRegistry` registers. When the CMS module's
5
+ // page or block delete endpoints consult their registry, our scanner
6
+ // surfaces megamenu items that hold the reference, so the delete is
7
+ // refused with the merged-holder list.
8
+ //
9
+ // Both shapes are read from `@endora-commerce/contracts` since feature 075's Phase C, so
10
+ // this module no longer names a file in `cms`. The seam itself is unchanged:
11
+ // an ungated registration `cms` enumerates on delete, honoured while `megamenu`
12
+ // is off because the items still hold the reference.
13
+ export function registerMegamenuCmsReferences(
14
+ // `CmsReferenceRegistryPort` is the whole seam; a contributor calls one
15
+ // method of it, and `cms`' registry spells the other one `externalOwners`.
16
+ // Naming just `register` keeps this signature what it has always been while
17
+ // the scanner shape comes from the contract.
18
+ cmsRegistry, megamenuRegistry) {
19
+ cmsRegistry.register({
20
+ ownerModuleId: 'megamenu',
21
+ async findPageReferences(pageId) {
22
+ const refs = await megamenuRegistry.findCmsPageReferences(pageId);
23
+ return refs.map((ref) => ({
24
+ kind: 'megamenu',
25
+ entityId: ref.menuId,
26
+ label: `Megamenu "${ref.menuName}"`,
27
+ }));
28
+ },
29
+ async findBlockReferences(blockId) {
30
+ const refs = await megamenuRegistry.findCmsBlockReferences(blockId);
31
+ return refs.map((ref) => ({
32
+ kind: 'megamenu',
33
+ entityId: ref.menuId,
34
+ label: `Megamenu "${ref.menuName}"`,
35
+ }));
36
+ },
37
+ });
38
+ }
39
+ //# sourceMappingURL=cms-references.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cms-references.js","sourceRoot":"","sources":["../../../src/backend/services/cms-references.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,EAAE;AACF,uEAAuE;AACvE,uEAAuE;AACvE,qEAAqE;AACrE,oEAAoE;AACpE,uCAAuC;AACvC,EAAE;AACF,yFAAyF;AACzF,6EAA6E;AAC7E,gFAAgF;AAChF,qDAAqD;AAKrD,MAAM,UAAU,6BAA6B;AAC3C,wEAAwE;AACxE,2EAA2E;AAC3E,4EAA4E;AAC5E,6CAA6C;AAC7C,WAAyE,EACzE,gBAA2C;IAE3C,WAAW,CAAC,QAAQ,CAAC;QACnB,aAAa,EAAE,UAAU;QACzB,KAAK,CAAC,kBAAkB,CAAC,MAAc;YACrC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAClE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACxB,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,GAAG,CAAC,MAAM;gBACpB,KAAK,EAAE,aAAa,GAAG,CAAC,QAAQ,GAAG;aACpC,CAAC,CAAC,CAAC;QACN,CAAC;QACD,KAAK,CAAC,mBAAmB,CAAC,OAAe;YACvC,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACpE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;gBACxB,IAAI,EAAE,UAAU;gBAChB,QAAQ,EAAE,GAAG,CAAC,MAAM;gBACpB,KAAK,EAAE,aAAa,GAAG,CAAC,QAAQ,GAAG;aACpC,CAAC,CAAC,CAAC;QACN,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,112 @@
1
+ import type { AssetReadPort, AssetsLibraryPort, CatalogCategoryReadPort, CmsBlockReadPort, CmsPageReadPort, MegamenuAssetKind, MegamenuResolvedBlock } from '@endora-commerce/contracts';
2
+ /**
3
+ * Everything this module reads outside itself, in one declaration
4
+ * (`specs/110-instance-repository/` T118c).
5
+ *
6
+ * Until T118c this was two objects a composition root built —
7
+ * `megamenuValidatorDeps` and `megamenuStorefrontDeps`, eight closures written
8
+ * twice, once in `backend/src/composition.ts` and once in
9
+ * `backend/test/helpers/test-server.ts`. Every one of them was a
10
+ * `select … from categories | cms_pages | cms_blocks | assets` issued from a file
11
+ * this module does not own, by a root that is nobody's declared dependency: an
12
+ * operator switching `cms` off was told nothing about what stops, because the
13
+ * edge existed nowhere a manifest could see it. The two roots also disagreed —
14
+ * production dropped a deactivated category from the menu and the harness did
15
+ * not, and production built `/c/<slug>` where the harness built `/catalog/<slug>`,
16
+ * a path the storefront serves from nowhere.
17
+ *
18
+ * Every member is now its owner's **published** port, resolved per call by
19
+ * `lazyPort` in `backend/index.ts`, so the gate is the registration's and the
20
+ * edges are in this module's manifest `dependencies`.
21
+ *
22
+ * The four functions below are the judgements that used to live inside those
23
+ * closures — a storefront URL's shape, which category counts as live, which
24
+ * asset kinds a menu may embed, and what an absent language means. They are
25
+ * functions of their ports rather than of a `ModuleContext`, so each is testable
26
+ * over a stub with nothing composed.
27
+ */
28
+ export interface MegamenuCrossModulePorts {
29
+ /** `catalog` — a menu item's category target and its storefront URL. */
30
+ readonly categories: Pick<CatalogCategoryReadPort, 'findById'>;
31
+ /** `cms` — a menu item's page target and its storefront URL. */
32
+ readonly pages: Pick<CmsPageReadPort, 'findById'>;
33
+ /** `cms` — a menu item's block target, and the tree a panel inlines. */
34
+ readonly blocks: Pick<CmsBlockReadPort, 'findById' | 'findLocalizedById'>;
35
+ /** `assets_library` — the stored kind, which is what an item's target declares. */
36
+ readonly assets: Pick<AssetReadPort, 'findById'>;
37
+ /**
38
+ * `assets_library` again, for the URL alone.
39
+ *
40
+ * Two ports for one asset because neither answers the whole question:
41
+ * `AssetRecord` carries the stored `kind` and no resolved URL, and
42
+ * `AssetDetail` carries the URL and no `kind`. `resolveUrl`, which is what
43
+ * both roots called, **is published on no port** — D-223, in as many words —
44
+ * so `getAsset` is the sanctioned way to turn an asset id into a URL and this
45
+ * is the same two reads the roots issued.
46
+ */
47
+ readonly assetLibrary: Pick<AssetsLibraryPort, 'getAsset'>;
48
+ }
49
+ /** What a menu item renders an asset target or an icon as. */
50
+ export interface MegamenuAssetTarget {
51
+ url: string;
52
+ label: string | null;
53
+ kind: MegamenuAssetKind;
54
+ }
55
+ /**
56
+ * A category target's public storefront URL, or `null` for one a shopper may not
57
+ * reach — which drops the item, and its children with it.
58
+ *
59
+ * `/c/<slug>` is the storefront's category route
60
+ * (`storefront/app/(catalog)/c/[slug]`), and a module naming it is the tree's
61
+ * established shape: `seo`'s sitemap builds `/c/`, `/p/` and `/<slug>` in its own
62
+ * sources for the same three things. The composition roots disagreed about it —
63
+ * the harness built `/catalog/<slug>`, which no route serves — and no test in the
64
+ * tree read the value, so the harness's answer was wrong for as long as it
65
+ * existed.
66
+ *
67
+ * **Deactivated and soft-deleted are both refusals**, which is production's
68
+ * `is_active = true and deleted_at is null`. `liveOnly` is the port's word for
69
+ * the second; `isActive` rides on the record because it is the caller's
70
+ * judgement, and the admin-side existence check below deliberately makes the
71
+ * opposite one.
72
+ */
73
+ export declare function resolveCategoryUrl(categories: MegamenuCrossModulePorts['categories'], categoryId: string): Promise<string | null>;
74
+ /**
75
+ * A CMS page target's public storefront URL, or `null` when the page is gone.
76
+ *
77
+ * **No status or `active` narrowing**, which is what both roots did and is an
78
+ * asymmetry with the category case above rather than an oversight of this drain:
79
+ * a draft or deactivated page keeps its menu item and answers for itself when the
80
+ * shopper follows the link. Changing it is a product decision, and T118c moves a
81
+ * value rather than redesigning it.
82
+ */
83
+ export declare function resolveCmsPageUrl(pages: MegamenuCrossModulePorts['pages'], pageId: string): Promise<string | null>;
84
+ /**
85
+ * An asset target or icon, resolved to the three fields a menu payload carries.
86
+ *
87
+ * `null` for an asset that is gone, and for one whose stored kind a menu cannot
88
+ * render: the library holds `pdf`, `certificate` and `other` as well, and
89
+ * `MegamenuAssetKind` is the narrower pair. The narrowing is the roots' own
90
+ * (`if (row.kind !== 'image' && row.kind !== 'video') return null`) and it is what
91
+ * keeps the wire shape's union honest.
92
+ *
93
+ * **No `catch` around `getAsset`, deliberately.** It throws 404 for a row that is
94
+ * gone, which the read above has already answered `null` for, so the only way to
95
+ * reach it is an asset deleted between the two reads — and swallowing it would
96
+ * also swallow `ModuleDisabledError`, which is the fail-open composition
97
+ * checklist item 7 refuses. Neither root caught here either.
98
+ */
99
+ export declare function resolveMenuAsset(ports: Pick<MegamenuCrossModulePorts, 'assets' | 'assetLibrary'>, assetId: string): Promise<MegamenuAssetTarget | null>;
100
+ /**
101
+ * The block a panel inlines, in the language the menu is being resolved for, or
102
+ * `null` when there is nothing to inline — which drops the item.
103
+ *
104
+ * `schemaVersion: 1` is **this module's wire contract**
105
+ * (`megamenuResolvedBlockSchema`) and not the CMS envelope's deprecated
106
+ * `schema_version`, which is normalized away on read. It is a constant here for
107
+ * the same reason it was a constant in the root closures: nothing produces a
108
+ * second version of this payload shape, and the field belongs to whoever owns the
109
+ * response.
110
+ */
111
+ export declare function resolveMenuBlock(blocks: MegamenuCrossModulePorts['blocks'], blockId: string, language: string): Promise<MegamenuResolvedBlock | null>;
112
+ //# sourceMappingURL=cross-module-ports.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-module-ports.d.ts","sourceRoot":"","sources":["../../../src/backend/services/cross-module-ports.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,uBAAuB,EACvB,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,wBAAwB;IACvC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC,CAAC;IAC/D,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;IAClD,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,mBAAmB,CAAC,CAAC;IAC1E,mFAAmF;IACnF,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;IACjD;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,EAAE,IAAI,CAAC,iBAAiB,EAAE,UAAU,CAAC,CAAC;CAC5D;AAED,8DAA8D;AAC9D,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,wBAAwB,CAAC,YAAY,CAAC,EAClD,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,wBAAwB,CAAC,OAAO,CAAC,EACxC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAIxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,IAAI,CAAC,wBAAwB,EAAE,QAAQ,GAAG,cAAc,CAAC,EAChE,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CASrC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,wBAAwB,CAAC,QAAQ,CAAC,EAC1C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CASvC"}