@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.
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/dist/cjs/composers/category.js +45 -0
- package/dist/cjs/composers/content.js +45 -0
- package/dist/cjs/composers/product.js +46 -0
- package/dist/cjs/composers/search.js +52 -0
- package/dist/cjs/contracts/category-page.js +3 -0
- package/dist/cjs/contracts/content-page.js +3 -0
- package/dist/cjs/contracts/context.js +3 -0
- package/dist/cjs/contracts/page.js +3 -0
- package/dist/cjs/contracts/product-page.js +3 -0
- package/dist/cjs/contracts/search-page.js +3 -0
- package/dist/cjs/errors/index.js +6 -0
- package/dist/cjs/errors/storefront-error.js +54 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/setup/composition.js +45 -0
- package/dist/cjs/setup/constants.js +8 -0
- package/dist/cjs/setup/index.js +14 -0
- package/dist/cjs/setup/types.js +3 -0
- package/dist/esm/composers/category.js +41 -0
- package/dist/esm/composers/content.js +41 -0
- package/dist/esm/composers/product.js +42 -0
- package/dist/esm/composers/search.js +48 -0
- package/dist/esm/contracts/category-page.js +2 -0
- package/dist/esm/contracts/content-page.js +2 -0
- package/dist/esm/contracts/context.js +2 -0
- package/dist/esm/contracts/page.js +2 -0
- package/dist/esm/contracts/product-page.js +2 -0
- package/dist/esm/contracts/search-page.js +2 -0
- package/dist/esm/errors/index.js +2 -0
- package/dist/esm/errors/storefront-error.js +50 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/setup/composition.js +43 -0
- package/dist/esm/setup/constants.js +5 -0
- package/dist/esm/setup/index.js +3 -0
- package/dist/esm/setup/types.js +2 -0
- package/dist/types/composers/category.d.ts +20 -0
- package/dist/types/composers/content.d.ts +20 -0
- package/dist/types/composers/product.d.ts +20 -0
- package/dist/types/composers/search.d.ts +26 -0
- package/dist/types/contracts/category-page.d.ts +48 -0
- package/dist/types/contracts/content-page.d.ts +42 -0
- package/dist/types/contracts/context.d.ts +29 -0
- package/dist/types/contracts/page.d.ts +8 -0
- package/dist/types/contracts/product-page.d.ts +45 -0
- package/dist/types/contracts/search-page.d.ts +49 -0
- package/dist/types/errors/index.d.ts +2 -0
- package/dist/types/errors/storefront-error.d.ts +43 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/setup/composition.d.ts +20 -0
- package/dist/types/setup/constants.d.ts +4 -0
- package/dist/types/setup/index.d.ts +3 -0
- package/dist/types/setup/types.d.ts +70 -0
- package/package.json +105 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { BaseError } from "@comity/primitives/errors";
|
|
2
|
+
/**
|
|
3
|
+
* Human-friendly messages mapped by reason.
|
|
4
|
+
*/
|
|
5
|
+
const REASON_MESSAGES = {
|
|
6
|
+
invalid_input: "Invalid input provided",
|
|
7
|
+
customer_not_found: "Customer not found",
|
|
8
|
+
address_not_found: "Address not found",
|
|
9
|
+
product_not_found: "Product not found",
|
|
10
|
+
payment_failed: "Payment processing failed",
|
|
11
|
+
order_failed: "Order processing failed",
|
|
12
|
+
internal_error: "Internal storefront error",
|
|
13
|
+
compensation_failed: "Payment compensation failed",
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Default HTTP status mapped by reason.
|
|
17
|
+
*/
|
|
18
|
+
const REASON_HTTP_STATUS = {
|
|
19
|
+
invalid_input: 400,
|
|
20
|
+
customer_not_found: 404,
|
|
21
|
+
address_not_found: 404,
|
|
22
|
+
product_not_found: 404,
|
|
23
|
+
payment_failed: 402,
|
|
24
|
+
order_failed: 500,
|
|
25
|
+
internal_error: 500,
|
|
26
|
+
compensation_failed: 500,
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Storefront application error.
|
|
30
|
+
*
|
|
31
|
+
* Represents an error at the Application layer that wraps domain errors
|
|
32
|
+
* from Core Modules and adds application-level context.
|
|
33
|
+
*/
|
|
34
|
+
export class StorefrontError extends BaseError {
|
|
35
|
+
/** Namespaced error code. */
|
|
36
|
+
code;
|
|
37
|
+
/**
|
|
38
|
+
* @param reason - The reason for the storefront error.
|
|
39
|
+
* @param meta - Additional metadata for the error.
|
|
40
|
+
*/
|
|
41
|
+
constructor(reason, meta) {
|
|
42
|
+
super(REASON_MESSAGES[reason], {
|
|
43
|
+
httpStatus: REASON_HTTP_STATUS[reason],
|
|
44
|
+
...meta,
|
|
45
|
+
reason,
|
|
46
|
+
});
|
|
47
|
+
this.code = `storefront:${reason}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=storefront-error.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { DefaultCategoryPageComposer } from "./composers/category.js";
|
|
2
|
+
export { DefaultContentPageComposer } from "./composers/content.js";
|
|
3
|
+
export { DefaultProductPageComposer } from "./composers/product.js";
|
|
4
|
+
export { DefaultSearchPageComposer } from "./composers/search.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { success } from "@comity/primitives/result";
|
|
2
|
+
import { DefaultCategoryPageComposer } from "../composers/category.js";
|
|
3
|
+
import { DefaultContentPageComposer } from "../composers/content.js";
|
|
4
|
+
import { DefaultProductPageComposer } from "../composers/product.js";
|
|
5
|
+
import { DefaultSearchPageComposer } from "../composers/search.js";
|
|
6
|
+
import { CATEGORY_PAGE_COMPOSER_TOKEN, CONTENT_PAGE_COMPOSER_TOKEN, PRODUCT_PAGE_COMPOSER_TOKEN, SEARCH_PAGE_COMPOSER_TOKEN, } from "./constants.js";
|
|
7
|
+
export default {
|
|
8
|
+
name: "@comity/storefront",
|
|
9
|
+
version: "0.9.0",
|
|
10
|
+
dependsOn: {
|
|
11
|
+
"@comity/catalog": { optional: false },
|
|
12
|
+
"@comity/taxonomy": { optional: false },
|
|
13
|
+
"@comity/content": { optional: false },
|
|
14
|
+
},
|
|
15
|
+
incompatibleWith: [],
|
|
16
|
+
/** @inheritdoc */
|
|
17
|
+
setup: async (ctx, options = {}) => {
|
|
18
|
+
const initial = {
|
|
19
|
+
product: [],
|
|
20
|
+
category: [],
|
|
21
|
+
content: [],
|
|
22
|
+
search: [],
|
|
23
|
+
};
|
|
24
|
+
let enrichers = initial;
|
|
25
|
+
// Execute configuring hook to allow enrichment customization
|
|
26
|
+
enrichers = await ctx.hooks.execute("@comity/storefront:configuring", enrichers);
|
|
27
|
+
// Product
|
|
28
|
+
ctx.services.define(PRODUCT_PAGE_COMPOSER_TOKEN, () => new DefaultProductPageComposer(options.productRepository, enrichers.product));
|
|
29
|
+
// Category
|
|
30
|
+
ctx.services.define(CATEGORY_PAGE_COMPOSER_TOKEN, () => new DefaultCategoryPageComposer(options.taxonomyRepository, enrichers.category));
|
|
31
|
+
// Content
|
|
32
|
+
ctx.services.define(CONTENT_PAGE_COMPOSER_TOKEN, () => new DefaultContentPageComposer(options.pageRepository, enrichers.content));
|
|
33
|
+
// Search — Phase 15: consumes `SearchPort<ProductProjection>`,
|
|
34
|
+
// not `ProductRepository.search()`. The composition layer is
|
|
35
|
+
// the only place that wires a repository together with a search port.
|
|
36
|
+
ctx.services.define(SEARCH_PAGE_COMPOSER_TOKEN, () => new DefaultSearchPageComposer(options.productSearchPort, enrichers.search));
|
|
37
|
+
return success(async () => {
|
|
38
|
+
await ctx.hooks.execute("@comity/storefront:initialized", undefined);
|
|
39
|
+
return success(undefined);
|
|
40
|
+
});
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=composition.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const CATEGORY_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:category-page");
|
|
2
|
+
export const CONTENT_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:content-page");
|
|
3
|
+
export const PRODUCT_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:product-page");
|
|
4
|
+
export const SEARCH_PAGE_COMPOSER_TOKEN = Symbol("@comity/storefront:search-page");
|
|
5
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TaxonomyRepository } from "@comity/taxonomy";
|
|
2
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
3
|
+
import { type Result } from "@comity/primitives/result";
|
|
4
|
+
import type { CategoryPageComposer, CategoryPageEnricher, CategoryPageModel } from "../contracts/category-page.js";
|
|
5
|
+
import type { StorefrontContext } from "../contracts/context.js";
|
|
6
|
+
/**
|
|
7
|
+
* Default implementation of the CategoryPageComposer interface, responsible for presenting category pages in the storefront.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DefaultCategoryPageComposer implements CategoryPageComposer {
|
|
10
|
+
#private;
|
|
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: TaxonomyRepository, enrichers?: ReadonlyArray<CategoryPageEnricher>);
|
|
16
|
+
/**
|
|
17
|
+
* @inheritdoc
|
|
18
|
+
*/
|
|
19
|
+
compose(id: string, ctx: StorefrontContext): Promise<Result<CategoryPageModel, RepositoryError>>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PageRepository } from "@comity/content";
|
|
2
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
3
|
+
import { type Result } from "@comity/primitives/result";
|
|
4
|
+
import type { ContentPageComposer, ContentPageEnricher, ContentPageModel } from "../contracts/content-page.js";
|
|
5
|
+
import type { StorefrontContext } from "../contracts/context.js";
|
|
6
|
+
/**
|
|
7
|
+
* Default implementation of the ContentPageComposer interface.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DefaultContentPageComposer implements ContentPageComposer {
|
|
10
|
+
#private;
|
|
11
|
+
/**
|
|
12
|
+
* @param repository - Page repository used to fetch content page data.
|
|
13
|
+
* @param enrichers - Optional enrichers to apply after base composition.
|
|
14
|
+
*/
|
|
15
|
+
constructor(repository: PageRepository, enrichers?: ReadonlyArray<ContentPageEnricher>);
|
|
16
|
+
/**
|
|
17
|
+
* @inheritdoc
|
|
18
|
+
*/
|
|
19
|
+
compose(id: string, ctx: StorefrontContext): Promise<Result<ContentPageModel, RepositoryError>>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ProductRepository } from "@comity/catalog";
|
|
2
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
3
|
+
import { type Result } from "@comity/primitives/result";
|
|
4
|
+
import type { StorefrontContext } from "../contracts/context.js";
|
|
5
|
+
import type { ProductPageComposer, ProductPageEnricher, ProductPageModel } from "../contracts/product-page.js";
|
|
6
|
+
/**
|
|
7
|
+
* Default implementation of the ProductPageComposer interface.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DefaultProductPageComposer implements ProductPageComposer {
|
|
10
|
+
#private;
|
|
11
|
+
/**
|
|
12
|
+
* @param repository - Product repository used to fetch product data.
|
|
13
|
+
* @param enrichers - Optional enrichers to apply after base composition.
|
|
14
|
+
*/
|
|
15
|
+
constructor(repository: ProductRepository, enrichers?: ReadonlyArray<ProductPageEnricher>);
|
|
16
|
+
/**
|
|
17
|
+
* @inheritdoc
|
|
18
|
+
*/
|
|
19
|
+
compose(id: string, ctx: StorefrontContext): Promise<Result<ProductPageModel, RepositoryError>>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ProductProjection } from "@comity/catalog";
|
|
2
|
+
import type { SearchPort } from "@comity/search";
|
|
3
|
+
import type { SearchError } from "@comity/search/errors";
|
|
4
|
+
import { type Result } from "@comity/primitives/result";
|
|
5
|
+
import type { StorefrontContext } from "../contracts/context.js";
|
|
6
|
+
import type { SearchPageComposer, SearchPageEnricher, SearchPageModel } from "../contracts/search-page.js";
|
|
7
|
+
/**
|
|
8
|
+
* Default implementation of the SearchPageComposer interface.
|
|
9
|
+
*
|
|
10
|
+
* Phase 15 — product search is consumed via the search-owned
|
|
11
|
+
* `SearchPort<ProductProjection>` port, NOT via `ProductRepository.search`.
|
|
12
|
+
* The storefront composition layer is the only place that wires a
|
|
13
|
+
* `ProductRepository` together with a `SearchPort<ProductProjection>`.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DefaultSearchPageComposer implements SearchPageComposer {
|
|
16
|
+
#private;
|
|
17
|
+
/**
|
|
18
|
+
* @param search - Search port used to execute product search queries.
|
|
19
|
+
* @param enrichers - Optional enrichers to apply after base composition.
|
|
20
|
+
*/
|
|
21
|
+
constructor(search: SearchPort<ProductProjection>, enrichers?: ReadonlyArray<SearchPageEnricher>);
|
|
22
|
+
/**
|
|
23
|
+
* @inheritdoc
|
|
24
|
+
*/
|
|
25
|
+
compose(query: string, ctx: StorefrontContext): Promise<Result<SearchPageModel, SearchError>>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ProductProjection } from "@comity/catalog";
|
|
2
|
+
import type { BreadcrumbModel } from "@comity/content";
|
|
3
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
4
|
+
import type { Result } from "@comity/primitives/result";
|
|
5
|
+
import type { CategoryModel } from "@comity/taxonomy";
|
|
6
|
+
import type { StorefrontContext } from "./context.js";
|
|
7
|
+
import type { StorefrontPageModel } from "./page.js";
|
|
8
|
+
/**
|
|
9
|
+
* Category page model for storefront.
|
|
10
|
+
*/
|
|
11
|
+
export interface CategoryPageModel extends StorefrontPageModel {
|
|
12
|
+
/** The type of the page, fixed to "category" for category pages. */
|
|
13
|
+
readonly type: "category";
|
|
14
|
+
/** Category model for the category page. */
|
|
15
|
+
readonly category: CategoryModel | null;
|
|
16
|
+
/** Product models for the category page. */
|
|
17
|
+
readonly products: ReadonlyArray<ProductProjection>;
|
|
18
|
+
/** Breadcrumbs for the category page. */
|
|
19
|
+
readonly breadcrumbs?: readonly BreadcrumbModel[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Composer for category pages in the storefront.
|
|
23
|
+
*/
|
|
24
|
+
export interface CategoryPageComposer {
|
|
25
|
+
/**
|
|
26
|
+
* Compose a category page by ID and context.
|
|
27
|
+
*
|
|
28
|
+
* @param id - The identifier of the category to compose.
|
|
29
|
+
* @param context - The storefront context for the composition, including locale, currency, and tenant information.
|
|
30
|
+
*
|
|
31
|
+
* @returns The composed category page model or an error if the composition fails.
|
|
32
|
+
*/
|
|
33
|
+
compose(id: string, context: StorefrontContext): Promise<Result<CategoryPageModel, RepositoryError>>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Enricher for category pages in the storefront.
|
|
37
|
+
*/
|
|
38
|
+
export interface CategoryPageEnricher {
|
|
39
|
+
/**
|
|
40
|
+
* Enrich an existing page model.
|
|
41
|
+
*
|
|
42
|
+
* @param page - The category page model to enrich.
|
|
43
|
+
* @param ctx - The storefront context for the enrichment, including locale, currency, and tenant information.
|
|
44
|
+
*
|
|
45
|
+
* @returns The enriched category page model or an error if the enrichment fails.
|
|
46
|
+
*/
|
|
47
|
+
enrich(page: CategoryPageModel, ctx: StorefrontContext): Promise<Result<CategoryPageModel, RepositoryError>>;
|
|
48
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { BreadcrumbModel } from "@comity/content";
|
|
2
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
3
|
+
import type { Result } from "@comity/primitives/result";
|
|
4
|
+
import type { StorefrontContext } from "./context.js";
|
|
5
|
+
import type { StorefrontPageModel } from "./page.js";
|
|
6
|
+
/**
|
|
7
|
+
* Content page model for storefront.
|
|
8
|
+
*/
|
|
9
|
+
export interface ContentPageModel extends StorefrontPageModel {
|
|
10
|
+
/** The type of the page, fixed to "content" for content pages. */
|
|
11
|
+
readonly type: "content";
|
|
12
|
+
/** Breadcrumbs for the content page. */
|
|
13
|
+
readonly breadcrumbs?: readonly BreadcrumbModel[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Composer for content pages in the storefront.
|
|
17
|
+
*/
|
|
18
|
+
export interface ContentPageComposer {
|
|
19
|
+
/**
|
|
20
|
+
* Compose a content page by ID and context.
|
|
21
|
+
*
|
|
22
|
+
* @param id - The identifier of the content to compose.
|
|
23
|
+
* @param context - The storefront context for the composition, including locale, currency, and tenant information.
|
|
24
|
+
*
|
|
25
|
+
* @returns The composed content page model or an error if the composition fails.
|
|
26
|
+
*/
|
|
27
|
+
compose(id: string, context: StorefrontContext): Promise<Result<ContentPageModel, RepositoryError>>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Enricher for content pages in the storefront.
|
|
31
|
+
*/
|
|
32
|
+
export interface ContentPageEnricher {
|
|
33
|
+
/**
|
|
34
|
+
* Enrich an existing content page model.
|
|
35
|
+
*
|
|
36
|
+
* @param page - The content page model to enrich.
|
|
37
|
+
* @param ctx - The storefront context for enrichment.
|
|
38
|
+
*
|
|
39
|
+
* @returns The enriched content page model or an error if enrichment fails.
|
|
40
|
+
*/
|
|
41
|
+
enrich(page: ContentPageModel, ctx: StorefrontContext): Promise<Result<ContentPageModel, RepositoryError>>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storefront context
|
|
3
|
+
*/
|
|
4
|
+
export interface StorefrontContext {
|
|
5
|
+
/** Locale for the request. */
|
|
6
|
+
readonly locale?: string;
|
|
7
|
+
/** Currency for the request. */
|
|
8
|
+
readonly currency?: string;
|
|
9
|
+
/** Optional tenant ID to fetch catalog data for a specific tenant. */
|
|
10
|
+
readonly tenant?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Transport-neutral request input used to resolve the storefront context.
|
|
14
|
+
*/
|
|
15
|
+
export interface StorefrontContextInput {
|
|
16
|
+
/** Request URL. */
|
|
17
|
+
readonly url: URL;
|
|
18
|
+
/** Request headers. */
|
|
19
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
20
|
+
/** Request cookies. */
|
|
21
|
+
readonly cookies: Readonly<Record<string, string>>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the storefront context for incoming requests, including locale, currency, and tenant information.
|
|
25
|
+
*/
|
|
26
|
+
export interface StorefrontContextResolver {
|
|
27
|
+
/** Resolves the storefront context for the given request input. */
|
|
28
|
+
resolve(input: StorefrontContextInput): Promise<StorefrontContext>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ProductProjection } from "@comity/catalog";
|
|
2
|
+
import type { BreadcrumbModel } from "@comity/content";
|
|
3
|
+
import type { RepositoryError } from "@comity/primitives/errors";
|
|
4
|
+
import type { Result } from "@comity/primitives/result";
|
|
5
|
+
import type { StorefrontContext } from "./context.js";
|
|
6
|
+
import type { StorefrontPageModel } from "./page.js";
|
|
7
|
+
/**
|
|
8
|
+
* Product page model for storefront.
|
|
9
|
+
*/
|
|
10
|
+
export interface ProductPageModel extends StorefrontPageModel {
|
|
11
|
+
/** The type of the page, fixed to "product" for product pages. */
|
|
12
|
+
readonly type: "product";
|
|
13
|
+
/** Product model for the product page. */
|
|
14
|
+
readonly product: ProductProjection | null;
|
|
15
|
+
/** Breadcrumbs for the product page. */
|
|
16
|
+
readonly breadcrumbs?: readonly BreadcrumbModel[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Composer for product pages in the storefront.
|
|
20
|
+
*/
|
|
21
|
+
export interface ProductPageComposer {
|
|
22
|
+
/**
|
|
23
|
+
* Compose a product page by ID and context.
|
|
24
|
+
*
|
|
25
|
+
* @param id - The identifier of the product to compose.
|
|
26
|
+
* @param context - The storefront context for the composition, including locale, currency, and tenant information.
|
|
27
|
+
*
|
|
28
|
+
* @returns The composed product page model or an error if the composition fails.
|
|
29
|
+
*/
|
|
30
|
+
compose(id: string, context: StorefrontContext): Promise<Result<ProductPageModel, RepositoryError>>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Enricher for product pages in the storefront.
|
|
34
|
+
*/
|
|
35
|
+
export interface ProductPageEnricher {
|
|
36
|
+
/**
|
|
37
|
+
* Enrich an existing product page model.
|
|
38
|
+
*
|
|
39
|
+
* @param page - The product page model to enrich.
|
|
40
|
+
* @param ctx - The storefront context for enrichment.
|
|
41
|
+
*
|
|
42
|
+
* @returns The enriched product page model or an error if enrichment fails.
|
|
43
|
+
*/
|
|
44
|
+
enrich(page: ProductPageModel, ctx: StorefrontContext): Promise<Result<ProductPageModel, RepositoryError>>;
|
|
45
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ProductProjection } from "@comity/catalog";
|
|
2
|
+
import type { Result } from "@comity/primitives/result";
|
|
3
|
+
import type { SearchError } from "@comity/search/errors";
|
|
4
|
+
import type { SearchResultModel } from "@comity/search";
|
|
5
|
+
import type { StorefrontContext } from "./context.js";
|
|
6
|
+
import type { StorefrontPageModel } from "./page.js";
|
|
7
|
+
/**
|
|
8
|
+
* Search page model for storefront.
|
|
9
|
+
*/
|
|
10
|
+
export interface SearchPageModel extends StorefrontPageModel {
|
|
11
|
+
/** The type of the page, fixed to "search" for search pages. */
|
|
12
|
+
readonly type: "search";
|
|
13
|
+
/** The search query for the search page. */
|
|
14
|
+
readonly query: string;
|
|
15
|
+
/** Product models for the search page. */
|
|
16
|
+
readonly result: SearchResultModel<ProductProjection>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Composer for search pages in the storefront.
|
|
20
|
+
*
|
|
21
|
+
* Phase 15 — failures surface as `SearchError` from `@comity/search/errors`,
|
|
22
|
+
* not `RepositoryError`. The composer delegates execution to a
|
|
23
|
+
* `SearchPort<ProductProjection>` and never reaches into a domain repository.
|
|
24
|
+
*/
|
|
25
|
+
export interface SearchPageComposer {
|
|
26
|
+
/**
|
|
27
|
+
* Compose a search page by query and context.
|
|
28
|
+
*
|
|
29
|
+
* @param query - The search query to compose.
|
|
30
|
+
* @param context - The storefront context for the composition, including locale, currency, and tenant information.
|
|
31
|
+
*
|
|
32
|
+
* @returns The composed search page model or an error if the composition fails.
|
|
33
|
+
*/
|
|
34
|
+
compose(query: string, context: StorefrontContext): Promise<Result<SearchPageModel, SearchError>>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Enricher for search pages in the storefront.
|
|
38
|
+
*/
|
|
39
|
+
export interface SearchPageEnricher {
|
|
40
|
+
/**
|
|
41
|
+
* Enrich an existing search page model.
|
|
42
|
+
*
|
|
43
|
+
* @param page - The search page model to enrich.
|
|
44
|
+
* @param ctx - The storefront context for enrichment.
|
|
45
|
+
*
|
|
46
|
+
* @returns The enriched search page model or an error if enrichment fails.
|
|
47
|
+
*/
|
|
48
|
+
enrich(page: SearchPageModel, ctx: StorefrontContext): Promise<Result<SearchPageModel, SearchError>>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ErrorMeta } from "@comity/primitives/errors";
|
|
2
|
+
import { BaseError } from "@comity/primitives/errors";
|
|
3
|
+
/**
|
|
4
|
+
* Reasons for storefront errors.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* These reasons categorize application-level failures in the storefront.
|
|
8
|
+
* Core module errors are wrapped and re-exposed with their original reasons.
|
|
9
|
+
*/
|
|
10
|
+
export type StorefrontErrorReason = "invalid_input" | "customer_not_found" | "address_not_found" | "product_not_found" | "payment_failed" | "order_failed" | "internal_error" | "compensation_failed";
|
|
11
|
+
/**
|
|
12
|
+
* Metadata attached to storefront errors.
|
|
13
|
+
*/
|
|
14
|
+
export interface StorefrontErrorMeta extends ErrorMeta {
|
|
15
|
+
/** Error reason. */
|
|
16
|
+
readonly reason: StorefrontErrorReason;
|
|
17
|
+
/** Optional diagnostic details. */
|
|
18
|
+
readonly details?: Readonly<{
|
|
19
|
+
/** Affected entity identifier. */
|
|
20
|
+
entityId?: string;
|
|
21
|
+
/** Entity type (e.g., "customer", "address", "product", "order"). */
|
|
22
|
+
entityType?: string;
|
|
23
|
+
/** Wrapped error code, if any. */
|
|
24
|
+
wrappedCode?: string;
|
|
25
|
+
/** Wrapped error reason, if any. */
|
|
26
|
+
wrappedReason?: string;
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Storefront application error.
|
|
31
|
+
*
|
|
32
|
+
* Represents an error at the Application layer that wraps domain errors
|
|
33
|
+
* from Core Modules and adds application-level context.
|
|
34
|
+
*/
|
|
35
|
+
export declare class StorefrontError extends BaseError<StorefrontErrorMeta> {
|
|
36
|
+
/** Namespaced error code. */
|
|
37
|
+
readonly code: `storefront:${StorefrontErrorReason}`;
|
|
38
|
+
/**
|
|
39
|
+
* @param reason - The reason for the storefront error.
|
|
40
|
+
* @param meta - Additional metadata for the error.
|
|
41
|
+
*/
|
|
42
|
+
constructor(reason: StorefrontErrorReason, meta?: Omit<StorefrontErrorMeta, "reason">);
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { CategoryPageComposer, CategoryPageEnricher, CategoryPageModel, } from "./contracts/category-page.js";
|
|
2
|
+
export type { ContentPageComposer, ContentPageEnricher, ContentPageModel, } from "./contracts/content-page.js";
|
|
3
|
+
export type { StorefrontContext, StorefrontContextInput, StorefrontContextResolver, } from "./contracts/context.js";
|
|
4
|
+
export type { StorefrontPageModel } from "./contracts/page.js";
|
|
5
|
+
export type { ProductPageComposer, ProductPageEnricher, ProductPageModel, } from "./contracts/product-page.js";
|
|
6
|
+
export type { SearchPageComposer, SearchPageEnricher, SearchPageModel, } from "./contracts/search-page.js";
|
|
7
|
+
export { DefaultCategoryPageComposer } from "./composers/category.js";
|
|
8
|
+
export { DefaultContentPageComposer } from "./composers/content.js";
|
|
9
|
+
export { DefaultProductPageComposer } from "./composers/product.js";
|
|
10
|
+
export { DefaultSearchPageComposer } from "./composers/search.js";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { StorefrontModuleContext, StorefrontModuleOptions } from "./types.js";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
name: string;
|
|
4
|
+
version: string;
|
|
5
|
+
dependsOn: {
|
|
6
|
+
"@comity/catalog": {
|
|
7
|
+
optional: false;
|
|
8
|
+
};
|
|
9
|
+
"@comity/taxonomy": {
|
|
10
|
+
optional: false;
|
|
11
|
+
};
|
|
12
|
+
"@comity/content": {
|
|
13
|
+
optional: false;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
incompatibleWith: never[];
|
|
17
|
+
/** @inheritdoc */
|
|
18
|
+
setup: (ctx: StorefrontModuleContext, options?: StorefrontModuleOptions) => Promise<import("@comity/primitives/result").ResultSuccess<() => Promise<import("@comity/primitives/result").ResultSuccess<undefined>>>>;
|
|
19
|
+
};
|
|
20
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type { StorefrontModuleContext, StorefrontModuleEvents, StorefrontModuleHooks, StorefrontModuleOptions, StorefrontModuleServices, } from "./types.js";
|
|
2
|
+
export { default } from "./composition.js";
|
|
3
|
+
export { CATEGORY_PAGE_COMPOSER_TOKEN, CONTENT_PAGE_COMPOSER_TOKEN, PRODUCT_PAGE_COMPOSER_TOKEN, SEARCH_PAGE_COMPOSER_TOKEN, } from "./constants.js";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ModuleSetupContext } from "@comity/composition/setup";
|
|
2
|
+
import type { ProductProjection, ProductRepository } from "@comity/catalog";
|
|
3
|
+
import type { SearchPort } from "@comity/search";
|
|
4
|
+
import type { PageRepository } from "@comity/content";
|
|
5
|
+
import type { TaxonomyRepository } from "@comity/taxonomy";
|
|
6
|
+
import type { CategoryPageComposer, CategoryPageEnricher } from "../contracts/category-page.js";
|
|
7
|
+
import type { ContentPageComposer, ContentPageEnricher } from "../contracts/content-page.js";
|
|
8
|
+
import type { ProductPageComposer, ProductPageEnricher } from "../contracts/product-page.js";
|
|
9
|
+
import type { SearchPageComposer, SearchPageEnricher } from "../contracts/search-page.js";
|
|
10
|
+
import type { CATEGORY_PAGE_COMPOSER_TOKEN, CONTENT_PAGE_COMPOSER_TOKEN, PRODUCT_PAGE_COMPOSER_TOKEN, SEARCH_PAGE_COMPOSER_TOKEN } from "./constants.js";
|
|
11
|
+
/**
|
|
12
|
+
* Options for setting up the storefront module.
|
|
13
|
+
*/
|
|
14
|
+
export interface StorefrontOptions {
|
|
15
|
+
/** Enrichers for the product page. */
|
|
16
|
+
readonly product: ReadonlyArray<ProductPageEnricher>;
|
|
17
|
+
/** Enrichers for the category page. */
|
|
18
|
+
readonly category: ReadonlyArray<CategoryPageEnricher>;
|
|
19
|
+
/** Enrichers for the content page. */
|
|
20
|
+
readonly content: ReadonlyArray<ContentPageEnricher>;
|
|
21
|
+
/** Enrichers for the search page. */
|
|
22
|
+
readonly search: ReadonlyArray<SearchPageEnricher>;
|
|
23
|
+
}
|
|
24
|
+
/** Hooks exposed by the module */
|
|
25
|
+
export type StorefrontModuleHooks = {
|
|
26
|
+
/** Executed during module setup, allows modifying initial configuration */
|
|
27
|
+
"@comity/storefront:configuring": StorefrontOptions;
|
|
28
|
+
/** Executed when the module is initialized. */
|
|
29
|
+
"@comity/storefront:initialized": undefined;
|
|
30
|
+
};
|
|
31
|
+
/** Events emitted by the module */
|
|
32
|
+
export type StorefrontModuleEvents = {};
|
|
33
|
+
/**
|
|
34
|
+
* Services exposed by the module
|
|
35
|
+
*/
|
|
36
|
+
export type StorefrontModuleServices = {
|
|
37
|
+
/** Category page composer token */
|
|
38
|
+
[CATEGORY_PAGE_COMPOSER_TOKEN]: CategoryPageComposer;
|
|
39
|
+
/** Content page composer token */
|
|
40
|
+
[CONTENT_PAGE_COMPOSER_TOKEN]: ContentPageComposer;
|
|
41
|
+
/** Product page composer token */
|
|
42
|
+
[PRODUCT_PAGE_COMPOSER_TOKEN]: ProductPageComposer;
|
|
43
|
+
/** Search page composer token */
|
|
44
|
+
[SEARCH_PAGE_COMPOSER_TOKEN]: SearchPageComposer;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Context provided to the storefront module setup function.
|
|
48
|
+
*/
|
|
49
|
+
export interface StorefrontModuleContext extends ModuleSetupContext<StorefrontModuleServices, StorefrontModuleEvents, StorefrontModuleHooks> {
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for setting up the storefront module.
|
|
53
|
+
*/
|
|
54
|
+
export interface StorefrontModuleOptions extends Record<string, unknown> {
|
|
55
|
+
/** Product repository for page composition */
|
|
56
|
+
readonly productRepository?: ProductRepository;
|
|
57
|
+
/**
|
|
58
|
+
* Product search port.
|
|
59
|
+
*
|
|
60
|
+
* Phase 15 — product search is provided via `@comity/search`'s
|
|
61
|
+
* `SearchPort<ProductProjection>`, NOT via `ProductRepository.search`.
|
|
62
|
+
* The application/composition layer wires a `ProductRepository`
|
|
63
|
+
* together with this search port.
|
|
64
|
+
*/
|
|
65
|
+
readonly productSearchPort?: SearchPort<ProductProjection>;
|
|
66
|
+
/** Taxonomy repository for category page composition */
|
|
67
|
+
readonly taxonomyRepository?: TaxonomyRepository;
|
|
68
|
+
/** Page repository for content page composition */
|
|
69
|
+
readonly pageRepository?: PageRepository;
|
|
70
|
+
}
|