@comity/storefront 0.9.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/dist/cjs/composers/category.js +45 -0
  4. package/dist/cjs/composers/content.js +45 -0
  5. package/dist/cjs/composers/product.js +46 -0
  6. package/dist/cjs/composers/search.js +52 -0
  7. package/dist/cjs/contracts/category-page.js +3 -0
  8. package/dist/cjs/contracts/content-page.js +3 -0
  9. package/dist/cjs/contracts/context.js +3 -0
  10. package/dist/cjs/contracts/page.js +3 -0
  11. package/dist/cjs/contracts/product-page.js +3 -0
  12. package/dist/cjs/contracts/search-page.js +3 -0
  13. package/dist/cjs/errors/index.js +6 -0
  14. package/dist/cjs/errors/storefront-error.js +54 -0
  15. package/dist/cjs/index.js +12 -0
  16. package/dist/cjs/setup/composition.js +45 -0
  17. package/dist/cjs/setup/constants.js +8 -0
  18. package/dist/cjs/setup/index.js +14 -0
  19. package/dist/cjs/setup/types.js +3 -0
  20. package/dist/esm/composers/category.js +41 -0
  21. package/dist/esm/composers/content.js +41 -0
  22. package/dist/esm/composers/product.js +42 -0
  23. package/dist/esm/composers/search.js +48 -0
  24. package/dist/esm/contracts/category-page.js +2 -0
  25. package/dist/esm/contracts/content-page.js +2 -0
  26. package/dist/esm/contracts/context.js +2 -0
  27. package/dist/esm/contracts/page.js +2 -0
  28. package/dist/esm/contracts/product-page.js +2 -0
  29. package/dist/esm/contracts/search-page.js +2 -0
  30. package/dist/esm/errors/index.js +2 -0
  31. package/dist/esm/errors/storefront-error.js +50 -0
  32. package/dist/esm/index.js +5 -0
  33. package/dist/esm/setup/composition.js +43 -0
  34. package/dist/esm/setup/constants.js +5 -0
  35. package/dist/esm/setup/index.js +3 -0
  36. package/dist/esm/setup/types.js +2 -0
  37. package/dist/types/composers/category.d.ts +20 -0
  38. package/dist/types/composers/content.d.ts +20 -0
  39. package/dist/types/composers/product.d.ts +20 -0
  40. package/dist/types/composers/search.d.ts +26 -0
  41. package/dist/types/contracts/category-page.d.ts +48 -0
  42. package/dist/types/contracts/content-page.d.ts +42 -0
  43. package/dist/types/contracts/context.d.ts +29 -0
  44. package/dist/types/contracts/page.d.ts +8 -0
  45. package/dist/types/contracts/product-page.d.ts +45 -0
  46. package/dist/types/contracts/search-page.d.ts +49 -0
  47. package/dist/types/errors/index.d.ts +2 -0
  48. package/dist/types/errors/storefront-error.d.ts +43 -0
  49. package/dist/types/index.d.ts +10 -0
  50. package/dist/types/setup/composition.d.ts +20 -0
  51. package/dist/types/setup/constants.d.ts +4 -0
  52. package/dist/types/setup/index.d.ts +3 -0
  53. package/dist/types/setup/types.d.ts +70 -0
  54. package/package.json +105 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Filippo Bovo and contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @comity/storefront
2
+
3
+ Storefront page composition capability module for Comity.
4
+
5
+ ---
6
+
7
+ ## Purpose
8
+
9
+ Provides reusable page composition capabilities for storefront applications. Defines page composer and enricher contracts, page models for categories, content, products, and search, and default composer implementations.
10
+
11
+ ---
12
+
13
+ ## Scope
14
+
15
+ This package:
16
+
17
+ - ✅ defines page composer and enricher contracts
18
+ - ✅ defines page model contracts for categories, content, products, and search
19
+ - ✅ exposes default page composers implementing the contracts
20
+ - ✅ provides setup tokens for composer wiring
21
+ - ✅ provides `StorefrontContext` and `StorefrontContextResolver` for locale/currency/tenant resolution
22
+
23
+ This package does NOT:
24
+
25
+ - ❌ implement checkout orchestration or workflows
26
+ - ❌ implement payment, inventory, order, or pricing operations
27
+ - ❌ implement customer or address loading
28
+ - ❌ implement application-level security concepts (Principal, Permission, Scope)
29
+ - ❌ render HTML or UI components
30
+ - ❌ execute routing decisions
31
+
32
+ ---
33
+
34
+ ## Public API
35
+
36
+ - Page composer contracts — category, content, product, and search page composers
37
+ - Page model contracts — corresponding models and enrichers for each page type
38
+ - Context contracts — storefront context and resolver interfaces
39
+ - Default composers — canonical composer implementations
40
+ - Setup — kernel module metadata, DI tokens, and wiring (`@comity/storefront/setup`)
41
+ - Errors — storefront error types (`@comity/storefront/errors`)
42
+
43
+ No exhaustive reference; see docs for constraints.
44
+ ---
45
+
46
+ ## Documentation
47
+
48
+ - docs/overview.md
49
+ - docs/conventions.md
50
+ - docs/architecture.md
51
+
52
+ ---
53
+
54
+ ## Related Packages
55
+
56
+ - @comity/catalog — product and category models
57
+ - @comity/search — search criteria models
58
+ - @comity/content — page content models
59
+ - @comity/taxonomy — category taxonomy models
60
+
61
+ ---
62
+
63
+ ## Status
64
+
65
+ Stable
66
+
67
+ _Review Completed: 2026-07-25_
68
+ _Reviewer: Hobiri MAGI (DeepSeek v4 Pro)_
69
+ _Compliance Score: 99.5% (Green)_
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultCategoryPageComposer = void 0;
4
+ const result_1 = require("@comity/primitives/result");
5
+ /**
6
+ * Default implementation of the CategoryPageComposer interface, responsible for presenting category pages in the storefront.
7
+ */
8
+ class DefaultCategoryPageComposer {
9
+ #repository;
10
+ #enrichers;
11
+ /**
12
+ * @param repository - The taxonomy repository to use for fetching category data.
13
+ * @param enrichers - Optional array of enrichers to apply to the category page model.
14
+ */
15
+ constructor(repository, enrichers) {
16
+ this.#repository = repository;
17
+ this.#enrichers = enrichers ?? [];
18
+ }
19
+ /**
20
+ * @inheritdoc
21
+ */
22
+ async compose(id, ctx) {
23
+ const result = await this.#repository.getById(id, ctx);
24
+ if ((0, result_1.isSuccess)(result)) {
25
+ let page = {
26
+ type: "category",
27
+ category: result.value,
28
+ id: result.value?.id ?? "",
29
+ url: result.value?.url ?? "",
30
+ title: result.value?.name ?? "",
31
+ products: [],
32
+ };
33
+ for (const enricher of this.#enrichers) {
34
+ const enriched = await enricher.enrich(page, ctx);
35
+ if ((0, result_1.isSuccess)(enriched)) {
36
+ page = enriched.value;
37
+ }
38
+ }
39
+ return (0, result_1.success)(page);
40
+ }
41
+ return result;
42
+ }
43
+ }
44
+ exports.DefaultCategoryPageComposer = DefaultCategoryPageComposer;
45
+ //# sourceMappingURL=category.js.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultContentPageComposer = void 0;
4
+ const result_1 = require("@comity/primitives/result");
5
+ /**
6
+ * Default implementation of the ContentPageComposer interface.
7
+ */
8
+ class DefaultContentPageComposer {
9
+ /** Page repository. */
10
+ #repository;
11
+ /** Page enrichers. */
12
+ #enrichers;
13
+ /**
14
+ * @param repository - Page repository used to fetch content page data.
15
+ * @param enrichers - Optional enrichers to apply after base composition.
16
+ */
17
+ constructor(repository, enrichers) {
18
+ this.#repository = repository;
19
+ this.#enrichers = enrichers ?? [];
20
+ }
21
+ /**
22
+ * @inheritdoc
23
+ */
24
+ async compose(id, ctx) {
25
+ const result = await this.#repository.getById(id, ctx);
26
+ if ((0, result_1.isSuccess)(result)) {
27
+ let page = {
28
+ type: "content",
29
+ id: result.value?.id ?? "",
30
+ url: result.value?.url ?? "",
31
+ title: result.value?.title ?? "",
32
+ };
33
+ for (const enricher of this.#enrichers) {
34
+ const enriched = await enricher.enrich(page, ctx);
35
+ if ((0, result_1.isSuccess)(enriched)) {
36
+ page = enriched.value;
37
+ }
38
+ }
39
+ return (0, result_1.success)(page);
40
+ }
41
+ return result;
42
+ }
43
+ }
44
+ exports.DefaultContentPageComposer = DefaultContentPageComposer;
45
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultProductPageComposer = void 0;
4
+ const result_1 = require("@comity/primitives/result");
5
+ /**
6
+ * Default implementation of the ProductPageComposer interface.
7
+ */
8
+ class DefaultProductPageComposer {
9
+ /** Product repository. */
10
+ #repository;
11
+ /** Page enrichers. */
12
+ #enrichers;
13
+ /**
14
+ * @param repository - Product repository used to fetch product data.
15
+ * @param enrichers - Optional enrichers to apply after base composition.
16
+ */
17
+ constructor(repository, enrichers) {
18
+ this.#repository = repository;
19
+ this.#enrichers = enrichers ?? [];
20
+ }
21
+ /**
22
+ * @inheritdoc
23
+ */
24
+ async compose(id, ctx) {
25
+ const result = await this.#repository.getById(id, ctx);
26
+ if ((0, result_1.isSuccess)(result)) {
27
+ let page = {
28
+ type: "product",
29
+ product: result.value,
30
+ id: result.value?.id ?? "",
31
+ url: result.value?.url ?? "",
32
+ title: result.value?.name ?? "",
33
+ };
34
+ for (const enricher of this.#enrichers) {
35
+ const enriched = await enricher.enrich(page, ctx);
36
+ if ((0, result_1.isSuccess)(enriched)) {
37
+ page = enriched.value;
38
+ }
39
+ }
40
+ return (0, result_1.success)(page);
41
+ }
42
+ return result;
43
+ }
44
+ }
45
+ exports.DefaultProductPageComposer = DefaultProductPageComposer;
46
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultSearchPageComposer = void 0;
4
+ const result_1 = require("@comity/primitives/result");
5
+ /**
6
+ * Default implementation of the SearchPageComposer interface.
7
+ *
8
+ * Phase 15 — product search is consumed via the search-owned
9
+ * `SearchPort<ProductProjection>` port, NOT via `ProductRepository.search`.
10
+ * The storefront composition layer is the only place that wires a
11
+ * `ProductRepository` together with a `SearchPort<ProductProjection>`.
12
+ */
13
+ class DefaultSearchPageComposer {
14
+ /** Product search port. */
15
+ #search;
16
+ /** Page enrichers. */
17
+ #enrichers;
18
+ /**
19
+ * @param search - Search port used to execute product search queries.
20
+ * @param enrichers - Optional enrichers to apply after base composition.
21
+ */
22
+ constructor(search, enrichers) {
23
+ this.#search = search;
24
+ this.#enrichers = enrichers ?? [];
25
+ }
26
+ /**
27
+ * @inheritdoc
28
+ */
29
+ async compose(query, ctx) {
30
+ const result = await this.#search.search({ query });
31
+ if ((0, result_1.isSuccess)(result)) {
32
+ let page = {
33
+ type: "search",
34
+ id: "search",
35
+ url: `/search?q=${encodeURIComponent(query)}`,
36
+ title: query ? `Search: ${query}` : "Search",
37
+ query,
38
+ result: result.value,
39
+ };
40
+ for (const enricher of this.#enrichers) {
41
+ const enriched = await enricher.enrich(page, ctx);
42
+ if ((0, result_1.isSuccess)(enriched)) {
43
+ page = enriched.value;
44
+ }
45
+ }
46
+ return (0, result_1.success)(page);
47
+ }
48
+ return result;
49
+ }
50
+ }
51
+ exports.DefaultSearchPageComposer = DefaultSearchPageComposer;
52
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=category-page.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=content-page.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=product-page.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=search-page.js.map
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorefrontError = void 0;
4
+ var storefront_error_js_1 = require("./storefront-error.js");
5
+ Object.defineProperty(exports, "StorefrontError", { enumerable: true, get: function () { return storefront_error_js_1.StorefrontError; } });
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorefrontError = void 0;
4
+ const errors_1 = require("@comity/primitives/errors");
5
+ /**
6
+ * Human-friendly messages mapped by reason.
7
+ */
8
+ const REASON_MESSAGES = {
9
+ invalid_input: "Invalid input provided",
10
+ customer_not_found: "Customer not found",
11
+ address_not_found: "Address not found",
12
+ product_not_found: "Product not found",
13
+ payment_failed: "Payment processing failed",
14
+ order_failed: "Order processing failed",
15
+ internal_error: "Internal storefront error",
16
+ compensation_failed: "Payment compensation failed",
17
+ };
18
+ /**
19
+ * Default HTTP status mapped by reason.
20
+ */
21
+ const REASON_HTTP_STATUS = {
22
+ invalid_input: 400,
23
+ customer_not_found: 404,
24
+ address_not_found: 404,
25
+ product_not_found: 404,
26
+ payment_failed: 402,
27
+ order_failed: 500,
28
+ internal_error: 500,
29
+ compensation_failed: 500,
30
+ };
31
+ /**
32
+ * Storefront application error.
33
+ *
34
+ * Represents an error at the Application layer that wraps domain errors
35
+ * from Core Modules and adds application-level context.
36
+ */
37
+ class StorefrontError extends errors_1.BaseError {
38
+ /** Namespaced error code. */
39
+ code;
40
+ /**
41
+ * @param reason - The reason for the storefront error.
42
+ * @param meta - Additional metadata for the error.
43
+ */
44
+ constructor(reason, meta) {
45
+ super(REASON_MESSAGES[reason], {
46
+ httpStatus: REASON_HTTP_STATUS[reason],
47
+ ...meta,
48
+ reason,
49
+ });
50
+ this.code = `storefront:${reason}`;
51
+ }
52
+ }
53
+ exports.StorefrontError = StorefrontError;
54
+ //# sourceMappingURL=storefront-error.js.map
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultSearchPageComposer = exports.DefaultProductPageComposer = exports.DefaultContentPageComposer = exports.DefaultCategoryPageComposer = void 0;
4
+ var category_js_1 = require("./composers/category.js");
5
+ Object.defineProperty(exports, "DefaultCategoryPageComposer", { enumerable: true, get: function () { return category_js_1.DefaultCategoryPageComposer; } });
6
+ var content_js_1 = require("./composers/content.js");
7
+ Object.defineProperty(exports, "DefaultContentPageComposer", { enumerable: true, get: function () { return content_js_1.DefaultContentPageComposer; } });
8
+ var product_js_1 = require("./composers/product.js");
9
+ Object.defineProperty(exports, "DefaultProductPageComposer", { enumerable: true, get: function () { return product_js_1.DefaultProductPageComposer; } });
10
+ var search_js_1 = require("./composers/search.js");
11
+ Object.defineProperty(exports, "DefaultSearchPageComposer", { enumerable: true, get: function () { return search_js_1.DefaultSearchPageComposer; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const result_1 = require("@comity/primitives/result");
4
+ const category_js_1 = require("../composers/category.js");
5
+ const content_js_1 = require("../composers/content.js");
6
+ const product_js_1 = require("../composers/product.js");
7
+ const search_js_1 = require("../composers/search.js");
8
+ const constants_js_1 = require("./constants.js");
9
+ exports.default = {
10
+ name: "@comity/storefront",
11
+ version: "0.9.0",
12
+ dependsOn: {
13
+ "@comity/catalog": { optional: false },
14
+ "@comity/taxonomy": { optional: false },
15
+ "@comity/content": { optional: false },
16
+ },
17
+ incompatibleWith: [],
18
+ /** @inheritdoc */
19
+ setup: async (ctx, options = {}) => {
20
+ const initial = {
21
+ product: [],
22
+ category: [],
23
+ content: [],
24
+ search: [],
25
+ };
26
+ let enrichers = initial;
27
+ // Execute configuring hook to allow enrichment customization
28
+ enrichers = await ctx.hooks.execute("@comity/storefront:configuring", enrichers);
29
+ // Product
30
+ ctx.services.define(constants_js_1.PRODUCT_PAGE_COMPOSER_TOKEN, () => new product_js_1.DefaultProductPageComposer(options.productRepository, enrichers.product));
31
+ // Category
32
+ ctx.services.define(constants_js_1.CATEGORY_PAGE_COMPOSER_TOKEN, () => new category_js_1.DefaultCategoryPageComposer(options.taxonomyRepository, enrichers.category));
33
+ // Content
34
+ ctx.services.define(constants_js_1.CONTENT_PAGE_COMPOSER_TOKEN, () => new content_js_1.DefaultContentPageComposer(options.pageRepository, enrichers.content));
35
+ // Search — Phase 15: consumes `SearchPort<ProductProjection>`,
36
+ // not `ProductRepository.search()`. The composition layer is
37
+ // the only place that wires a repository together with a search port.
38
+ ctx.services.define(constants_js_1.SEARCH_PAGE_COMPOSER_TOKEN, () => new search_js_1.DefaultSearchPageComposer(options.productSearchPort, enrichers.search));
39
+ return (0, result_1.success)(async () => {
40
+ await ctx.hooks.execute("@comity/storefront:initialized", undefined);
41
+ return (0, result_1.success)(undefined);
42
+ });
43
+ },
44
+ };
45
+ //# sourceMappingURL=composition.js.map
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SEARCH_PAGE_COMPOSER_TOKEN = exports.PRODUCT_PAGE_COMPOSER_TOKEN = exports.CONTENT_PAGE_COMPOSER_TOKEN = exports.CATEGORY_PAGE_COMPOSER_TOKEN = void 0;
4
+ exports.CATEGORY_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:category-page");
5
+ exports.CONTENT_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:content-page");
6
+ exports.PRODUCT_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:product-page");
7
+ exports.SEARCH_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:search-page");
8
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SEARCH_PAGE_COMPOSER_TOKEN = exports.PRODUCT_PAGE_COMPOSER_TOKEN = exports.CONTENT_PAGE_COMPOSER_TOKEN = exports.CATEGORY_PAGE_COMPOSER_TOKEN = exports.default = void 0;
7
+ var composition_js_1 = require("./composition.js");
8
+ Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(composition_js_1).default; } });
9
+ var constants_js_1 = require("./constants.js");
10
+ Object.defineProperty(exports, "CATEGORY_PAGE_COMPOSER_TOKEN", { enumerable: true, get: function () { return constants_js_1.CATEGORY_PAGE_COMPOSER_TOKEN; } });
11
+ Object.defineProperty(exports, "CONTENT_PAGE_COMPOSER_TOKEN", { enumerable: true, get: function () { return constants_js_1.CONTENT_PAGE_COMPOSER_TOKEN; } });
12
+ Object.defineProperty(exports, "PRODUCT_PAGE_COMPOSER_TOKEN", { enumerable: true, get: function () { return constants_js_1.PRODUCT_PAGE_COMPOSER_TOKEN; } });
13
+ Object.defineProperty(exports, "SEARCH_PAGE_COMPOSER_TOKEN", { enumerable: true, get: function () { return constants_js_1.SEARCH_PAGE_COMPOSER_TOKEN; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,41 @@
1
+ import { isSuccess, success } from "@comity/primitives/result";
2
+ /**
3
+ * Default implementation of the CategoryPageComposer interface, responsible for presenting category pages in the storefront.
4
+ */
5
+ export class DefaultCategoryPageComposer {
6
+ #repository;
7
+ #enrichers;
8
+ /**
9
+ * @param repository - The taxonomy repository to use for fetching category data.
10
+ * @param enrichers - Optional array of enrichers to apply to the category page model.
11
+ */
12
+ constructor(repository, enrichers) {
13
+ this.#repository = repository;
14
+ this.#enrichers = enrichers ?? [];
15
+ }
16
+ /**
17
+ * @inheritdoc
18
+ */
19
+ async compose(id, ctx) {
20
+ const result = await this.#repository.getById(id, ctx);
21
+ if (isSuccess(result)) {
22
+ let page = {
23
+ type: "category",
24
+ category: result.value,
25
+ id: result.value?.id ?? "",
26
+ url: result.value?.url ?? "",
27
+ title: result.value?.name ?? "",
28
+ products: [],
29
+ };
30
+ for (const enricher of this.#enrichers) {
31
+ const enriched = await enricher.enrich(page, ctx);
32
+ if (isSuccess(enriched)) {
33
+ page = enriched.value;
34
+ }
35
+ }
36
+ return success(page);
37
+ }
38
+ return result;
39
+ }
40
+ }
41
+ //# sourceMappingURL=category.js.map
@@ -0,0 +1,41 @@
1
+ import { isSuccess, success } from "@comity/primitives/result";
2
+ /**
3
+ * Default implementation of the ContentPageComposer interface.
4
+ */
5
+ export class DefaultContentPageComposer {
6
+ /** Page repository. */
7
+ #repository;
8
+ /** Page enrichers. */
9
+ #enrichers;
10
+ /**
11
+ * @param repository - Page repository used to fetch content page data.
12
+ * @param enrichers - Optional enrichers to apply after base composition.
13
+ */
14
+ constructor(repository, enrichers) {
15
+ this.#repository = repository;
16
+ this.#enrichers = enrichers ?? [];
17
+ }
18
+ /**
19
+ * @inheritdoc
20
+ */
21
+ async compose(id, ctx) {
22
+ const result = await this.#repository.getById(id, ctx);
23
+ if (isSuccess(result)) {
24
+ let page = {
25
+ type: "content",
26
+ id: result.value?.id ?? "",
27
+ url: result.value?.url ?? "",
28
+ title: result.value?.title ?? "",
29
+ };
30
+ for (const enricher of this.#enrichers) {
31
+ const enriched = await enricher.enrich(page, ctx);
32
+ if (isSuccess(enriched)) {
33
+ page = enriched.value;
34
+ }
35
+ }
36
+ return success(page);
37
+ }
38
+ return result;
39
+ }
40
+ }
41
+ //# sourceMappingURL=content.js.map
@@ -0,0 +1,42 @@
1
+ import { isSuccess, success } from "@comity/primitives/result";
2
+ /**
3
+ * Default implementation of the ProductPageComposer interface.
4
+ */
5
+ export class DefaultProductPageComposer {
6
+ /** Product repository. */
7
+ #repository;
8
+ /** Page enrichers. */
9
+ #enrichers;
10
+ /**
11
+ * @param repository - Product repository used to fetch product data.
12
+ * @param enrichers - Optional enrichers to apply after base composition.
13
+ */
14
+ constructor(repository, enrichers) {
15
+ this.#repository = repository;
16
+ this.#enrichers = enrichers ?? [];
17
+ }
18
+ /**
19
+ * @inheritdoc
20
+ */
21
+ async compose(id, ctx) {
22
+ const result = await this.#repository.getById(id, ctx);
23
+ if (isSuccess(result)) {
24
+ let page = {
25
+ type: "product",
26
+ product: result.value,
27
+ id: result.value?.id ?? "",
28
+ url: result.value?.url ?? "",
29
+ title: result.value?.name ?? "",
30
+ };
31
+ for (const enricher of this.#enrichers) {
32
+ const enriched = await enricher.enrich(page, ctx);
33
+ if (isSuccess(enriched)) {
34
+ page = enriched.value;
35
+ }
36
+ }
37
+ return success(page);
38
+ }
39
+ return result;
40
+ }
41
+ }
42
+ //# sourceMappingURL=product.js.map
@@ -0,0 +1,48 @@
1
+ import { isSuccess, success } from "@comity/primitives/result";
2
+ /**
3
+ * Default implementation of the SearchPageComposer interface.
4
+ *
5
+ * Phase 15 — product search is consumed via the search-owned
6
+ * `SearchPort<ProductProjection>` port, NOT via `ProductRepository.search`.
7
+ * The storefront composition layer is the only place that wires a
8
+ * `ProductRepository` together with a `SearchPort<ProductProjection>`.
9
+ */
10
+ export class DefaultSearchPageComposer {
11
+ /** Product search port. */
12
+ #search;
13
+ /** Page enrichers. */
14
+ #enrichers;
15
+ /**
16
+ * @param search - Search port used to execute product search queries.
17
+ * @param enrichers - Optional enrichers to apply after base composition.
18
+ */
19
+ constructor(search, enrichers) {
20
+ this.#search = search;
21
+ this.#enrichers = enrichers ?? [];
22
+ }
23
+ /**
24
+ * @inheritdoc
25
+ */
26
+ async compose(query, ctx) {
27
+ const result = await this.#search.search({ query });
28
+ if (isSuccess(result)) {
29
+ let page = {
30
+ type: "search",
31
+ id: "search",
32
+ url: `/search?q=${encodeURIComponent(query)}`,
33
+ title: query ? `Search: ${query}` : "Search",
34
+ query,
35
+ result: result.value,
36
+ };
37
+ for (const enricher of this.#enrichers) {
38
+ const enriched = await enricher.enrich(page, ctx);
39
+ if (isSuccess(enriched)) {
40
+ page = enriched.value;
41
+ }
42
+ }
43
+ return success(page);
44
+ }
45
+ return result;
46
+ }
47
+ }
48
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=category-page.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=content-page.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=product-page.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=search-page.js.map