@reactionary/source 0.0.37 → 0.0.38
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/.env-template +10 -0
- package/.github/workflows/pull-request.yml +5 -3
- package/core/src/client/client.ts +4 -2
- package/core/src/index.ts +3 -7
- package/core/src/providers/analytics.provider.ts +6 -1
- package/core/src/providers/base.provider.ts +33 -3
- package/core/src/providers/cart.provider.ts +7 -1
- package/core/src/providers/category.provider.ts +91 -0
- package/core/src/providers/identity.provider.ts +5 -1
- package/core/src/providers/inventory.provider.ts +4 -0
- package/core/src/providers/price.provider.ts +54 -0
- package/core/src/providers/product.provider.ts +4 -0
- package/core/src/providers/search.provider.ts +6 -0
- package/core/src/schemas/capabilities.schema.ts +3 -2
- package/core/src/schemas/models/base.model.ts +42 -1
- package/core/src/schemas/models/cart.model.ts +27 -3
- package/core/src/schemas/models/category.model.ts +23 -0
- package/core/src/schemas/models/identifiers.model.ts +29 -1
- package/core/src/schemas/models/inventory.model.ts +6 -2
- package/core/src/schemas/models/price.model.ts +11 -3
- package/core/src/schemas/models/search.model.ts +4 -2
- package/core/src/schemas/queries/category.query.ts +32 -0
- package/core/src/schemas/queries/index.ts +9 -0
- package/core/src/schemas/queries/inventory.query.ts +18 -3
- package/core/src/schemas/session.schema.ts +13 -2
- package/otel/src/index.ts +2 -2
- package/otel/src/test/otel.spec.ts +8 -0
- package/otel/src/trace-decorator.ts +27 -27
- package/package.json +2 -1
- package/providers/commercetools/package.json +1 -0
- package/providers/commercetools/src/core/initialize.ts +7 -3
- package/providers/commercetools/src/providers/cart.provider.ts +84 -8
- package/providers/commercetools/src/providers/category.provider.ts +244 -0
- package/providers/commercetools/src/providers/index.ts +7 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
- package/providers/commercetools/src/providers/price.provider.ts +74 -18
- package/providers/commercetools/src/providers/product.provider.ts +19 -15
- package/providers/commercetools/src/providers/search.provider.ts +9 -7
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
- package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
- package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
- package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
- package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
- package/providers/commercetools/src/test/test-utils.ts +35 -0
- package/providers/commercetools/tsconfig.lib.json +1 -1
- package/providers/fake/jest.config.ts +10 -0
- package/providers/fake/src/core/initialize.ts +15 -1
- package/providers/fake/src/index.ts +2 -9
- package/providers/fake/src/providers/cart.provider.ts +74 -15
- package/providers/fake/src/providers/category.provider.ts +152 -0
- package/providers/fake/src/providers/index.ts +8 -0
- package/providers/fake/src/providers/inventory.provider.ts +23 -9
- package/providers/fake/src/providers/price.provider.ts +46 -6
- package/providers/fake/src/providers/search.provider.ts +2 -2
- package/providers/fake/src/schema/capabilities.schema.ts +4 -2
- package/providers/fake/src/schema/configuration.schema.ts +5 -0
- package/providers/fake/src/test/cart.provider.spec.ts +126 -0
- package/providers/fake/src/test/category.provider.spec.ts +134 -0
- package/providers/fake/src/test/price.provider.spec.ts +80 -0
- package/providers/fake/src/test/test-utils.ts +42 -0
- package/providers/fake/tsconfig.json +4 -0
- package/providers/fake/tsconfig.lib.json +3 -1
- package/providers/fake/tsconfig.spec.json +16 -0
- package/trpc/src/integration.spec.ts +16 -10
- package/trpc/src/transparent-client.spec.ts +23 -17
package/.env-template
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
COMMERCETOOLS_API_URL=https://api.europe-west1.gcp.commercetools.com
|
|
2
|
+
COMMERCETOOLS_AUTH_URL=https://auth.europe-west1.gcp.commercetools.com
|
|
3
|
+
COMMERCETOOLS_CLIENT_ID=
|
|
4
|
+
COMMERCETOOLS_CLIENT_SECRET=
|
|
5
|
+
COMMERCETOOLS_PROJECT_KEY=
|
|
6
|
+
|
|
7
|
+
OTEL_SERVICE_NAME=reactionary
|
|
8
|
+
OTEL_LOG_LEVEL=info
|
|
9
|
+
OTEL_TRACES_EXPORTER=console
|
|
10
|
+
OTEL_METRICS_EXPORTER=console
|
|
@@ -9,6 +9,8 @@ permissions:
|
|
|
9
9
|
contents: read
|
|
10
10
|
pull-requests: read
|
|
11
11
|
|
|
12
|
+
concurrency: pull-request-${{ github.event.pull_request.number }}
|
|
13
|
+
|
|
12
14
|
env:
|
|
13
15
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
14
16
|
|
|
@@ -24,14 +26,14 @@ jobs:
|
|
|
24
26
|
- uses: pnpm/action-setup@v4
|
|
25
27
|
|
|
26
28
|
- run: pnpm dlx nx-cloud start-ci-run --distribute-on="3 linux-medium-js" --stop-agents-after="build"
|
|
27
|
-
|
|
29
|
+
|
|
28
30
|
- uses: actions/setup-node@v4
|
|
29
31
|
with:
|
|
30
32
|
node-version: 23
|
|
31
33
|
cache: 'pnpm'
|
|
32
|
-
|
|
34
|
+
|
|
33
35
|
- run: pnpm install
|
|
34
|
-
|
|
36
|
+
|
|
35
37
|
- uses: nrwl/nx-set-shas@v4
|
|
36
38
|
|
|
37
39
|
- run: pnpm exec nx affected -t lint build
|
|
@@ -7,6 +7,7 @@ import { PriceProvider } from "../providers/price.provider";
|
|
|
7
7
|
import { InventoryProvider } from "../providers/inventory.provider";
|
|
8
8
|
import { Cache } from "../cache/cache.interface";
|
|
9
9
|
import { RedisCache } from "../cache/redis-cache";
|
|
10
|
+
import { CategoryProvider } from "../providers/category.provider";
|
|
10
11
|
|
|
11
12
|
export interface Client {
|
|
12
13
|
product: ProductProvider,
|
|
@@ -16,7 +17,8 @@ export interface Client {
|
|
|
16
17
|
cart: CartProvider,
|
|
17
18
|
analytics: Array<AnalyticsProvider>,
|
|
18
19
|
price: PriceProvider,
|
|
19
|
-
inventory: InventoryProvider
|
|
20
|
+
inventory: InventoryProvider,
|
|
21
|
+
category: CategoryProvider
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export interface BuildClientOptions {
|
|
@@ -47,7 +49,7 @@ export function buildClient<T extends Partial<Client>>(
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
client.analytics = mergedAnalytics;
|
|
50
|
-
|
|
52
|
+
|
|
51
53
|
// Add cache to complete the client
|
|
52
54
|
const completeClient = {
|
|
53
55
|
...client,
|
package/core/src/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export * from './providers/inventory.provider';
|
|
|
16
16
|
export * from './providers/price.provider';
|
|
17
17
|
export * from './providers/product.provider';
|
|
18
18
|
export * from './providers/search.provider';
|
|
19
|
+
export * from './providers/category.provider';
|
|
19
20
|
|
|
20
21
|
export * from './schemas/capabilities.schema';
|
|
21
22
|
export * from './schemas/session.schema';
|
|
@@ -29,6 +30,7 @@ export * from './schemas/models/inventory.model';
|
|
|
29
30
|
export * from './schemas/models/price.model';
|
|
30
31
|
export * from './schemas/models/product.model';
|
|
31
32
|
export * from './schemas/models/search.model';
|
|
33
|
+
export * from './schemas/models/category.model';
|
|
32
34
|
|
|
33
35
|
export * from './schemas/mutations/base.mutation';
|
|
34
36
|
export * from './schemas/mutations/cart.mutation';
|
|
@@ -38,10 +40,4 @@ export * from './schemas/mutations/price.mutation';
|
|
|
38
40
|
export * from './schemas/mutations/product.mutation';
|
|
39
41
|
export * from './schemas/mutations/search.mutation';
|
|
40
42
|
|
|
41
|
-
export * from './schemas/queries
|
|
42
|
-
export * from './schemas/queries/cart.query';
|
|
43
|
-
export * from './schemas/queries/identity.query';
|
|
44
|
-
export * from './schemas/queries/inventory.query';
|
|
45
|
-
export * from './schemas/queries/price.query';
|
|
46
|
-
export * from './schemas/queries/product.query';
|
|
47
|
-
export * from './schemas/queries/search.query';
|
|
43
|
+
export * from './schemas/queries';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { BaseModel } from '../schemas/models/base.model';
|
|
2
|
+
import { BaseModel, createPaginatedResponseSchema } from '../schemas/models/base.model';
|
|
3
3
|
import { Cache } from '../cache/cache.interface';
|
|
4
|
+
import { Session } from '../schemas/session.schema';
|
|
5
|
+
import { IdentifierType } from '../schemas/models/identifiers.model';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Base capability provider, responsible for mutations (changes) and queries (fetches)
|
|
@@ -10,7 +12,7 @@ export abstract class BaseProvider<
|
|
|
10
12
|
T extends BaseModel = BaseModel
|
|
11
13
|
> {
|
|
12
14
|
protected cache: Cache;
|
|
13
|
-
|
|
15
|
+
|
|
14
16
|
constructor(
|
|
15
17
|
public readonly schema: z.ZodType<T>,
|
|
16
18
|
cache: Cache
|
|
@@ -37,9 +39,37 @@ export abstract class BaseProvider<
|
|
|
37
39
|
* Handler for parsing a response from a remote provider and converting it
|
|
38
40
|
* into the typed domain model.
|
|
39
41
|
*/
|
|
40
|
-
protected parseSingle(_body: unknown): T {
|
|
42
|
+
protected parseSingle(_body: unknown, session: Session): T {
|
|
41
43
|
const model = this.newModel();
|
|
42
44
|
|
|
43
45
|
return this.assert(model);
|
|
44
46
|
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
protected parsePaginatedResult(_body: unknown, session: Session): z.infer<ReturnType<typeof createPaginatedResponseSchema<typeof this.schema>>> {
|
|
50
|
+
return createPaginatedResponseSchema(this.schema).parse({});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
protected generateCacheKeyPaginatedResult(resultSetName: string, res: ReturnType<typeof this.parsePaginatedResult>, session: Session): string {
|
|
54
|
+
const type = this.getResourceName();
|
|
55
|
+
const langPart = session.languageContext.locale;
|
|
56
|
+
const currencyPart = session.languageContext.currencyCode || 'default';
|
|
57
|
+
const storePart = session.storeIdentifier?.key || 'default';
|
|
58
|
+
return `${type}-${resultSetName}-paginated|pageNumber:${res.pageNumber}|pageSize:${res.pageSize}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
protected generateCacheKeySingle(identifier: IdentifierType, session: Session): string {
|
|
63
|
+
const type = this.getResourceName();
|
|
64
|
+
const idPart = Object.entries(identifier).map(([k, v]) => `${k}:${(v as any).key}`).join('#');
|
|
65
|
+
const langPart = session.languageContext.locale;
|
|
66
|
+
const currencyPart = session.languageContext.currencyCode || 'default';
|
|
67
|
+
const storePart = session.storeIdentifier?.key || 'default';
|
|
68
|
+
return `${type}-${idPart}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns the abstract resource name provided by the remote system.
|
|
73
|
+
*/
|
|
74
|
+
protected abstract getResourceName(): string ;
|
|
45
75
|
}
|
|
@@ -11,4 +11,10 @@ export abstract class CartProvider<
|
|
|
11
11
|
public abstract add(payload: CartMutationItemAdd, session: Session): Promise<T>;
|
|
12
12
|
public abstract remove(payload: CartMutationItemRemove, session: Session): Promise<T>;
|
|
13
13
|
public abstract changeQuantity(payload: CartMutationItemQuantityChange, session: Session): Promise<T>;
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
protected override getResourceName(): string {
|
|
17
|
+
return 'cart';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
import { PaginationOptions } from "../schemas/models/base.model";
|
|
4
|
+
import { Category } from "../schemas/models/category.model";
|
|
5
|
+
import { CategoryIdentifier } from "../schemas/models/identifiers.model";
|
|
6
|
+
import { CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "../schemas/queries/category.query";
|
|
7
|
+
|
|
8
|
+
import { Session } from "../schemas/session.schema";
|
|
9
|
+
import { BaseProvider } from "./base.provider";
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* CategoryProvider
|
|
15
|
+
*
|
|
16
|
+
* This provider allows fetching of single or sets of categories.
|
|
17
|
+
*
|
|
18
|
+
* We only allow fetching one hierachy level at a time, for now. This is to avoid development patterns of "fetch 5000 categories in one go.."
|
|
19
|
+
*
|
|
20
|
+
*/
|
|
21
|
+
export abstract class CategoryProvider<
|
|
22
|
+
T extends Category = Category
|
|
23
|
+
> extends BaseProvider<T> {
|
|
24
|
+
/**
|
|
25
|
+
* Get a single category by its ID. Cannot return null, because HOW did you come across a categories ID that does not exist?
|
|
26
|
+
*
|
|
27
|
+
* DISCUSSION: What do you persist in, say, a CMS or Recommendation engine? The seo slug or the ID?
|
|
28
|
+
* We have previous discussed, that the ID is not necessarily the DATABASE id, but rather an externally unique identifier for the category.
|
|
29
|
+
*
|
|
30
|
+
* So, if you persist that externally, you could actually end up with an ID that does not exist in the current system.
|
|
31
|
+
*
|
|
32
|
+
* For now, the result will be en empty category, but we should probably throw an error instead.
|
|
33
|
+
*
|
|
34
|
+
* Use case: You have received a list of category ids from a recommendation engine, and you need to show a tile of this.
|
|
35
|
+
* Future optimization: getByIds(ids: CategoryIdentifier[], session: Session): Promise<T[]>
|
|
36
|
+
* @param id
|
|
37
|
+
* @param session
|
|
38
|
+
*/
|
|
39
|
+
public abstract getById(payload: CategoryQueryById, session: Session): Promise<T>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets a single category by its seo slug
|
|
43
|
+
*
|
|
44
|
+
* Usecase: You are rendering a category page, and you have the slug from the URL.
|
|
45
|
+
* @param slug the slug
|
|
46
|
+
* @param session
|
|
47
|
+
*/
|
|
48
|
+
public abstract getBySlug(payload: CategoryQueryBySlug, session: Session): Promise<T | null>;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Gets the breadcrumb path to the category, i.e. all parents up to the root.
|
|
53
|
+
* The returned order is from root to leaf.
|
|
54
|
+
*
|
|
55
|
+
* Usecase: You are rendering a category or product page, and you need to show the breadcrumb path.
|
|
56
|
+
* @param id
|
|
57
|
+
* @param session
|
|
58
|
+
*/
|
|
59
|
+
public abstract getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb, session: Session): Promise<T[]>;
|
|
60
|
+
|
|
61
|
+
// hmm, this is not really good enough.... We need a type we can pass in that will allow us to specify the precise return type, but otoh we also need
|
|
62
|
+
// to be able to verify and assert the output type. FIXME
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Finds all child categories of a given category.
|
|
66
|
+
*
|
|
67
|
+
* Usecase: You are rendering a top menu, or mega menu, and you need the show the child categories of a given category.
|
|
68
|
+
*
|
|
69
|
+
* NOTE: it is recommended to create a navigational service, that allows combining CMS and Static pages into this, rather than fetching categories directly.
|
|
70
|
+
*
|
|
71
|
+
* @param id The ID of the parent category.
|
|
72
|
+
* @param session The session information.
|
|
73
|
+
*/
|
|
74
|
+
public abstract findChildCategories(payload: CategoryQueryForChildCategories, session: Session): Promise< ReturnType<typeof this.parsePaginatedResult>>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Returns all top categories, i.e. categories without a parent.
|
|
78
|
+
*
|
|
79
|
+
* Usecase: You are rendering a top menu, or mega menu, and you need the show the top level categories.
|
|
80
|
+
* @param paginationOptions
|
|
81
|
+
* @param session
|
|
82
|
+
*/
|
|
83
|
+
public abstract findTopCategories( payload: CategoryQueryForTopCategories, session: Session): Promise<ReturnType<typeof this.parsePaginatedResult>>;
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
protected override getResourceName(): string {
|
|
87
|
+
return 'category';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
}
|
|
91
|
+
|
|
@@ -10,4 +10,8 @@ export abstract class IdentityProvider<
|
|
|
10
10
|
public abstract getSelf(payload: IdentityQuerySelf, session: Session): Promise<T>;
|
|
11
11
|
public abstract login(payload: IdentityMutationLogin, session: Session): Promise<T>;
|
|
12
12
|
public abstract logout(payload: IdentityMutationLogout, session: Session): Promise<T>;
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
protected override getResourceName(): string {
|
|
15
|
+
return 'identity';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -7,4 +7,8 @@ export abstract class InventoryProvider<
|
|
|
7
7
|
T extends Inventory = Inventory
|
|
8
8
|
> extends BaseProvider<T> {
|
|
9
9
|
public abstract getBySKU(payload: InventoryQuery, session: Session): Promise<T>;
|
|
10
|
+
|
|
11
|
+
protected override getResourceName(): string {
|
|
12
|
+
return 'inventory';
|
|
13
|
+
}
|
|
10
14
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Currency } from '../schemas/models/currency.model';
|
|
1
2
|
import { Price } from '../schemas/models/price.model';
|
|
2
3
|
import { PriceQueryBySku } from '../schemas/queries/price.query';
|
|
3
4
|
import { Session } from '../schemas/session.schema';
|
|
@@ -6,5 +7,58 @@ import { BaseProvider } from './base.provider';
|
|
|
6
7
|
export abstract class PriceProvider<
|
|
7
8
|
T extends Price = Price
|
|
8
9
|
> extends BaseProvider<T> {
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get a price by SKU.
|
|
14
|
+
*
|
|
15
|
+
* Note: This does not include any discounts or promotions that may apply.
|
|
16
|
+
* For B2B scenarios, this will be the base price, and any customer specific pricing
|
|
17
|
+
*
|
|
18
|
+
* Usecase: You are rendering a product page, and you need to show the price for a SKU.
|
|
19
|
+
* @param payload The SKU to query
|
|
20
|
+
* @param session The session information
|
|
21
|
+
*/
|
|
9
22
|
public abstract getBySKU(payload: PriceQueryBySku, session: Session): Promise<T>;
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Fetch prices for multiple SKUs in one go.
|
|
27
|
+
*
|
|
28
|
+
* Usecase: You are rendering a product grid, and you need to show prices for multiple SKUs.
|
|
29
|
+
* @param payload The SKUs to query
|
|
30
|
+
* @param session The session information
|
|
31
|
+
*/
|
|
32
|
+
public abstract getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]>;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Utility function to create an empty price result, with a value of -1.
|
|
37
|
+
* This is used when no price is found for a given SKU + currency combination.
|
|
38
|
+
* You should check for meta.placeholder to see if this is a real price or a placeholder.
|
|
39
|
+
* @param sku
|
|
40
|
+
* @param currency
|
|
41
|
+
* @returns
|
|
42
|
+
*/
|
|
43
|
+
protected getEmptyPriceResult(sku: string, currency: Currency): T {
|
|
44
|
+
const base = this.newModel();
|
|
45
|
+
base.identifier = {
|
|
46
|
+
sku: { key: sku }
|
|
47
|
+
};
|
|
48
|
+
base.unitPrice = {
|
|
49
|
+
value: -1,
|
|
50
|
+
currency: currency,
|
|
51
|
+
};
|
|
52
|
+
base.meta = {
|
|
53
|
+
cache: { hit: false, key: `price-${sku}-${currency}` },
|
|
54
|
+
placeholder: true
|
|
55
|
+
};
|
|
56
|
+
return this.assert(base);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
protected override getResourceName(): string {
|
|
62
|
+
return 'price';
|
|
63
|
+
}
|
|
10
64
|
}
|
|
@@ -8,4 +8,8 @@ export abstract class ProductProvider<
|
|
|
8
8
|
> extends BaseProvider<T> {
|
|
9
9
|
public abstract getById(payload: ProductQueryById, session: Session): Promise<T>;
|
|
10
10
|
public abstract getBySlug(payload: ProductQueryBySlug, session: Session): Promise<T>;
|
|
11
|
+
|
|
12
|
+
protected override getResourceName(): string {
|
|
13
|
+
return 'product';
|
|
14
|
+
}
|
|
11
15
|
}
|
|
@@ -7,6 +7,12 @@ export abstract class SearchProvider<
|
|
|
7
7
|
T extends SearchResult = SearchResult
|
|
8
8
|
> extends BaseProvider<T> {
|
|
9
9
|
public abstract queryByTerm(payload: SearchQueryByTerm, session: Session): Promise<SearchResult>;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
protected override getResourceName(): string {
|
|
13
|
+
return 'product-search';
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
|
|
@@ -7,7 +7,8 @@ export const CapabilitiesSchema = z.looseInterface({
|
|
|
7
7
|
identity: z.boolean(),
|
|
8
8
|
cart: z.boolean(),
|
|
9
9
|
inventory: z.boolean(),
|
|
10
|
-
price: z.boolean()
|
|
10
|
+
price: z.boolean(),
|
|
11
|
+
category: z.boolean()
|
|
11
12
|
});
|
|
12
13
|
|
|
13
|
-
export type Capabilities = z.infer<typeof CapabilitiesSchema>;
|
|
14
|
+
export type Capabilities = z.infer<typeof CapabilitiesSchema>;
|
|
@@ -16,4 +16,45 @@ export const BaseModelSchema = z.looseInterface({
|
|
|
16
16
|
|
|
17
17
|
export type CacheInformation = z.infer<typeof CacheInformationSchema>;
|
|
18
18
|
export type Meta = z.infer<typeof MetaSchema>;
|
|
19
|
-
export type BaseModel = z.infer<typeof BaseModelSchema>;
|
|
19
|
+
export type BaseModel = z.infer<typeof BaseModelSchema>;
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export const PaginationOptionsSchema = z.looseInterface({
|
|
23
|
+
pageNumber: z.number().default(1).describe('Current page number, starting from 1'),
|
|
24
|
+
pageSize: z.number().default(20).describe('Number of items per page'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type PaginationOptions = z.infer<typeof PaginationOptionsSchema>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* This seemed like the right way to do it, but we need to be able to pass in the item schema even later than this
|
|
31
|
+
*
|
|
32
|
+
**/
|
|
33
|
+
export function createPaginatedResponseSchema<ItemType extends z.ZodTypeAny>(
|
|
34
|
+
itemSchema: ItemType,
|
|
35
|
+
) {
|
|
36
|
+
return z.object({
|
|
37
|
+
meta: MetaSchema.default(() => MetaSchema.parse({})),
|
|
38
|
+
pageNumber: z.number().min(1).describe('Current page number, starting from 1'),
|
|
39
|
+
pageSize: z.number().min(1).max(50).describe('Number of items per page'),
|
|
40
|
+
totalCount: z.number().min(0).describe('Total number of items available'),
|
|
41
|
+
totalPages: z.number().min(0).describe('Total number of pages available'),
|
|
42
|
+
items: z.array(itemSchema),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* I posit, we should not have final urls in this, but rather assume the frontend has some kind of image transcoding/resizing service it will use to pass the image through, so
|
|
48
|
+
* what we really need is the original source url, and then some metadata about the image.
|
|
49
|
+
* Ie, rather than having distinct thumbnail and image fields, we just have a list of images, and the frontend will generate its own thumbnails as needed?
|
|
50
|
+
*/
|
|
51
|
+
export const ImageSchema = z.looseInterface({
|
|
52
|
+
sourceUrl: z.string().default('').describe('The original source URL of the image. Pass this through your image resizing and transcoding service to get the desired size, and generate thumbnails as needed'),
|
|
53
|
+
altText: z.string().default('').describe('Alternative text for the image, for accessibility purposes. Must always be set, and non-empty'),
|
|
54
|
+
width: z.number().optional().describe('Width of the original image, in pixels, if known'),
|
|
55
|
+
height: z.number().optional().describe('Height of the original image, in pixels, if known'),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
export type Image = z.infer<typeof ImageSchema>;
|
|
59
|
+
|
|
60
|
+
|
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { CartIdentifierSchema, CartItemIdentifierSchema, ProductIdentifierSchema } from '../models/identifiers.model';
|
|
3
3
|
import { BaseModelSchema } from './base.model';
|
|
4
|
+
import { MonetaryAmountSchema } from './price.model';
|
|
5
|
+
|
|
6
|
+
export const CostBreakDownSchema = z.looseObject({
|
|
7
|
+
totalTax: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The amount of tax paid on the cart. This may include VAT, GST, sales tax, etc.'),
|
|
8
|
+
totalDiscount: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The amount of discount applied to the cart.'),
|
|
9
|
+
totalSurcharge: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The amount of surcharge applied to the cart.'),
|
|
10
|
+
totalShipping: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The amount of shipping fees for the cart.'),
|
|
11
|
+
totalProductPrice: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The total price of products in the cart.'),
|
|
12
|
+
grandTotal: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The total price for the cart including all taxes, discounts, and shipping.'),
|
|
13
|
+
});
|
|
14
|
+
export type CostBreakDown = z.infer<typeof CostBreakDownSchema>;
|
|
15
|
+
|
|
16
|
+
export const ItemCostBreakdownSchema = z.looseObject({
|
|
17
|
+
unitPrice: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The price per single unit of the item.'),
|
|
18
|
+
unitDiscount: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The discount applied per single unit of the item.'),
|
|
19
|
+
totalPrice: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The total price for all units of the item.'),
|
|
20
|
+
totalDiscount: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The total discount applied to all units of the item.'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type ItemCostBreakdown = z.infer<typeof ItemCostBreakdownSchema>;
|
|
4
24
|
|
|
5
25
|
export const CartItemSchema = z.looseInterface({
|
|
6
26
|
identifier: CartItemIdentifierSchema.default(() => CartItemIdentifierSchema.parse({})),
|
|
7
27
|
product: ProductIdentifierSchema.default(() => ProductIdentifierSchema.parse({})),
|
|
8
|
-
quantity: z.number().default(0)
|
|
28
|
+
quantity: z.number().default(0),
|
|
29
|
+
price: ItemCostBreakdownSchema.default(() => ItemCostBreakdownSchema.parse({})),
|
|
9
30
|
});
|
|
10
31
|
|
|
11
32
|
export const CartSchema = BaseModelSchema.extend({
|
|
12
33
|
identifier: CartIdentifierSchema.default(() => CartIdentifierSchema.parse({})),
|
|
13
|
-
items: z.array(CartItemSchema).default(() => [])
|
|
34
|
+
items: z.array(CartItemSchema).default(() => []),
|
|
35
|
+
price: CostBreakDownSchema.default(() => CostBreakDownSchema.parse({})),
|
|
36
|
+
name: z.string().default(''),
|
|
37
|
+
description: z.string().default(''),
|
|
14
38
|
});
|
|
15
39
|
|
|
16
40
|
export type CartItem = z.infer<typeof CartItemSchema>;
|
|
17
|
-
export type Cart = z.infer<typeof CartSchema>;
|
|
41
|
+
export type Cart = z.infer<typeof CartSchema>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// getBySeoSlug
|
|
2
|
+
// getBreadcrumbPathToCategory
|
|
3
|
+
// getById
|
|
4
|
+
// getByExternalId
|
|
5
|
+
// getChildCategories
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { BaseModelSchema, createPaginatedResponseSchema, ImageSchema } from './base.model';
|
|
10
|
+
import { CategoryIdentifierSchema } from './identifiers.model';
|
|
11
|
+
export const CategorySchema = BaseModelSchema.extend({
|
|
12
|
+
identifier: CategoryIdentifierSchema.default(() => CategoryIdentifierSchema.parse({})),
|
|
13
|
+
name: z.string().default(''),
|
|
14
|
+
slug: z.string().default(''),
|
|
15
|
+
text: z.string().default(''),
|
|
16
|
+
images: z.array(ImageSchema.required()).default(() => []),
|
|
17
|
+
parentCategory: CategoryIdentifierSchema.optional(),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export type Category = z.infer<typeof CategorySchema>;
|
|
21
|
+
|
|
22
|
+
export const CategoryPaginatedResultSchema = createPaginatedResponseSchema(CategorySchema);
|
|
23
|
+
export type CategoryPaginatedResult = z.infer<typeof CategoryPaginatedResultSchema>;
|
|
@@ -36,10 +36,38 @@ export const PriceIdentifierSchema = z.looseInterface({
|
|
|
36
36
|
sku: SKUIdentifierSchema.default(() => SKUIdentifierSchema.parse({})),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
export const CategoryIdentifierSchema = z.looseInterface({
|
|
40
|
+
key: z.string().default('').nonoptional()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The target store the user is interacting with. Can change over time, and is not necessarily the same as the default store.
|
|
45
|
+
*/
|
|
46
|
+
export const WebStoreIdentifierSchema = z.looseInterface({
|
|
47
|
+
key: z.string().default('').nonoptional()
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export const InventoryChannelIdentifierSchema= z.looseInterface({
|
|
51
|
+
key: z.string().default('online').nonoptional()
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const InventoryIdentifierSchema = z.looseInterface({
|
|
55
|
+
sku: SKUIdentifierSchema.default(() => SKUIdentifierSchema.parse({})),
|
|
56
|
+
channelId: InventoryChannelIdentifierSchema.default(() => InventoryChannelIdentifierSchema.parse({})),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
|
|
39
60
|
export type ProductIdentifier = z.infer<typeof ProductIdentifierSchema>;
|
|
40
61
|
export type SearchIdentifier = z.infer<typeof SearchIdentifierSchema>;
|
|
41
62
|
export type FacetIdentifier = z.infer<typeof FacetIdentifierSchema>;
|
|
42
63
|
export type FacetValueIdentifier = z.infer<typeof FacetValueIdentifierSchema>;
|
|
43
64
|
export type CartIdentifier = z.infer<typeof CartIdentifierSchema>;
|
|
44
65
|
export type CartItemIdentifier = z.infer<typeof CartItemIdentifierSchema>;
|
|
45
|
-
export type PriceIdentifier = z.infer<typeof PriceIdentifierSchema>;
|
|
66
|
+
export type PriceIdentifier = z.infer<typeof PriceIdentifierSchema>;
|
|
67
|
+
export type CategoryIdentifier = z.infer<typeof CategoryIdentifierSchema>;
|
|
68
|
+
export type WebStoreIdentifier = z.infer<typeof WebStoreIdentifierSchema>;
|
|
69
|
+
export type InventoryIdentifier = z.infer<typeof InventoryIdentifierSchema>;
|
|
70
|
+
export type InventoryChannelIdentifier = z.infer<typeof InventoryChannelIdentifierSchema>;
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
export type IdentifierType = ProductIdentifier | SearchIdentifier | FacetIdentifier | FacetValueIdentifier | CartIdentifier | CartItemIdentifier | PriceIdentifier | CategoryIdentifier | WebStoreIdentifier | InventoryIdentifier;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { BaseModelSchema } from './base.model';
|
|
3
|
+
import { InventoryIdentifierSchema } from './identifiers.model';
|
|
3
4
|
|
|
4
5
|
export const InventorySchema = BaseModelSchema.extend({
|
|
5
|
-
|
|
6
|
+
identifier: InventoryIdentifierSchema.default(() => InventoryIdentifierSchema.parse({})),
|
|
7
|
+
sku: z.string().default(''),
|
|
8
|
+
quantity: z.number().default(0),
|
|
9
|
+
status: z.enum(['inStock', 'outOfStock', 'onBackOrder', 'preOrder', 'discontinued']).default('inStock'),
|
|
6
10
|
});
|
|
7
11
|
|
|
8
|
-
export type Inventory = z.infer<typeof InventorySchema>;
|
|
12
|
+
export type Inventory = z.infer<typeof InventorySchema>;
|
|
@@ -4,14 +4,22 @@ import { PriceIdentifierSchema } from './identifiers.model';
|
|
|
4
4
|
import { CurrencySchema } from './currency.model';
|
|
5
5
|
|
|
6
6
|
export const MonetaryAmountSchema = z.looseInterface({
|
|
7
|
-
|
|
7
|
+
value: z.number().default(0).describe('The monetary amount in decimal-precision.'),
|
|
8
8
|
currency: CurrencySchema.default("XXX").describe('The currency associated with the amount, as a ISO 4217 standardized code.')
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
export const TieredPriceSchema = z.looseObject({
|
|
12
|
+
minimumQuantity: z.number().default(0).describe('The minimum quantity required to be eligible for the tiered price.'),
|
|
13
|
+
price: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})).describe('The monetary amount for the tiered price.'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
export const PriceSchema = BaseModelSchema.extend({
|
|
12
18
|
identifier: PriceIdentifierSchema.default(() => PriceIdentifierSchema.parse({})),
|
|
13
|
-
|
|
19
|
+
unitPrice: MonetaryAmountSchema.default(() => MonetaryAmountSchema.parse({})),
|
|
20
|
+
tieredPrices: z.array(TieredPriceSchema).default(() => []),
|
|
14
21
|
});
|
|
15
22
|
|
|
16
23
|
export type MonetaryAmount = z.infer<typeof MonetaryAmountSchema>;
|
|
17
|
-
export type Price = z.infer<typeof PriceSchema>;
|
|
24
|
+
export type Price = z.infer<typeof PriceSchema>;
|
|
25
|
+
export type TieredPrice = z.infer<typeof TieredPriceSchema>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ProductIdentifierSchema, FacetValueIdentifierSchema, FacetIdentifierSchema, SearchIdentifierSchema } from './identifiers.model';
|
|
3
|
-
import { BaseModelSchema } from './base.model';
|
|
3
|
+
import { BaseModelSchema, createPaginatedResponseSchema } from './base.model';
|
|
4
|
+
import { create } from 'domain';
|
|
4
5
|
|
|
5
6
|
export const SearchResultProductSchema = z.looseInterface({
|
|
6
7
|
identifier: ProductIdentifierSchema.default(ProductIdentifierSchema.parse({})),
|
|
@@ -29,7 +30,8 @@ export const SearchResultSchema = BaseModelSchema.extend({
|
|
|
29
30
|
facets: z.array(SearchResultFacetSchema).default(() => [])
|
|
30
31
|
});
|
|
31
32
|
|
|
33
|
+
|
|
32
34
|
export type SearchResultProduct = z.infer<typeof SearchResultProductSchema>;
|
|
33
35
|
export type SearchResult = z.infer<typeof SearchResultSchema>;
|
|
34
36
|
export type SearchResultFacet = z.infer<typeof SearchResultFacetSchema>;
|
|
35
|
-
export type SearchResultFacetValue = z.infer<typeof SearchResultFacetValueSchema>;
|
|
37
|
+
export type SearchResultFacetValue = z.infer<typeof SearchResultFacetValueSchema>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { CategoryIdentifierSchema } from "../models/identifiers.model";
|
|
3
|
+
import { BaseQuerySchema } from "./base.query";
|
|
4
|
+
import { PaginationOptionsSchema } from "../models/base.model";
|
|
5
|
+
|
|
6
|
+
export const CategoryQueryById = BaseQuerySchema.extend({
|
|
7
|
+
id: CategoryIdentifierSchema.default(() => CategoryIdentifierSchema.parse({})),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const CategoryQueryBySlug = BaseQuerySchema.extend({
|
|
11
|
+
slug: z.string().default(''),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const CategoryQueryForBreadcrumb = BaseQuerySchema.extend({
|
|
15
|
+
id: CategoryIdentifierSchema.default(() => CategoryIdentifierSchema.parse({})),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const CategoryQueryForChildCategories = BaseQuerySchema.extend({
|
|
19
|
+
parentId: CategoryIdentifierSchema.default(() => CategoryIdentifierSchema.parse({})),
|
|
20
|
+
paginationOptions: PaginationOptionsSchema.default(() => PaginationOptionsSchema.parse({})),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const CategoryQueryForTopCategories = BaseQuerySchema.extend({
|
|
24
|
+
paginationOptions: PaginationOptionsSchema.default(() => PaginationOptionsSchema.parse({})),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
export type CategoryQueryById = z.infer<typeof CategoryQueryById>;
|
|
29
|
+
export type CategoryQueryBySlug = z.infer<typeof CategoryQueryBySlug>;
|
|
30
|
+
export type CategoryQueryForBreadcrumb = z.infer<typeof CategoryQueryForBreadcrumb>;
|
|
31
|
+
export type CategoryQueryForChildCategories = z.infer<typeof CategoryQueryForChildCategories>;
|
|
32
|
+
export type CategoryQueryForTopCategories = z.infer<typeof CategoryQueryForTopCategories>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './analytics.query';
|
|
2
|
+
export * from './base.query';
|
|
3
|
+
export * from './cart.query';
|
|
4
|
+
export * from './category.query';
|
|
5
|
+
export * from './identity.query';
|
|
6
|
+
export * from './inventory.query';
|
|
7
|
+
export * from './price.query';
|
|
8
|
+
export * from './product.query';
|
|
9
|
+
export * from './search.query';
|