@decocms/apps-magento 8.1.0-next.0 → 8.1.0-next.1
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/package.json +10 -22
- package/src/index.ts +1 -28
- package/src/README.md +0 -65
- package/src/__tests__/cart.test.ts +0 -139
- package/src/__tests__/client.test.ts +0 -252
- package/src/__tests__/features.test.ts +0 -51
- package/src/__tests__/graphql.test.ts +0 -183
- package/src/__tests__/magentoClient.test.ts +0 -166
- package/src/__tests__/newsletter-subscribe.test.ts +0 -86
- package/src/__tests__/product-stockAlert.test.ts +0 -80
- package/src/__tests__/stringifySearchCriteria.test.ts +0 -56
- package/src/__tests__/transform.test.ts +0 -346
- package/src/__tests__/user-loader.test.ts +0 -134
- package/src/__tests__/wishlist-actions.test.ts +0 -140
- package/src/__tests__/wishlist-loader.test.ts +0 -97
- package/src/actions/newsletter/subscribe.ts +0 -40
- package/src/actions/product/stockAlert.ts +0 -67
- package/src/actions/wishlist/addItem.ts +0 -57
- package/src/actions/wishlist/removeItem.ts +0 -59
- package/src/client.ts +0 -252
- package/src/loaders/cart.ts +0 -64
- package/src/loaders/features.ts +0 -16
- package/src/loaders/user.ts +0 -60
- package/src/loaders/wishlist.ts +0 -29
- package/src/middleware.ts +0 -19
- package/src/types.ts +0 -51
- package/src/utils/__tests__/instrumentation.test.ts +0 -59
- package/src/utils/cacheTimeControl.ts +0 -69
- package/src/utils/client/types.ts +0 -270
- package/src/utils/constants.ts +0 -126
- package/src/utils/fetchCache.ts +0 -55
- package/src/utils/graphql-types.ts +0 -99
- package/src/utils/graphql.ts +0 -155
- package/src/utils/instrumentedFetch.ts +0 -57
- package/src/utils/operationRouter.ts +0 -43
- package/src/utils/stringifySearchCriteria.ts +0 -62
- package/src/utils/transform.ts +0 -283
- package/src/utils/user.ts +0 -16
- package/tsconfig.json +0 -7
package/src/utils/fetchCache.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Magento SWR fetch cache — a thin binding over the shared, instrumented
|
|
3
|
-
* `createFetchCache` in `@decocms/blocks/sdk/fetchCache`.
|
|
4
|
-
*
|
|
5
|
-
* Same shared implementation VTEX uses (in-flight dedup, stale-while-
|
|
6
|
-
* revalidate, stale-if-error, inflight backstop), wired with Magento's tuning
|
|
7
|
-
* constants and the `provider: "magento"` label. Every call emits
|
|
8
|
-
* `deco.cache.requests{layer="swr",profile="magento"}` automatically.
|
|
9
|
-
*
|
|
10
|
-
* Cache key is caller-supplied (not required to be a URL): REST GETs key by
|
|
11
|
-
* their URL; GraphQL POSTs can key by a hash of `query + variables`. Callers
|
|
12
|
-
* pass the closure that performs the actual `magentoFetch`, so a HIT never
|
|
13
|
-
* touches the network.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
createFetchCache,
|
|
18
|
-
type FetchCacheOptions as SharedFetchCacheOptions,
|
|
19
|
-
} from "@decocms/blocks/sdk/fetchCache";
|
|
20
|
-
import {
|
|
21
|
-
FETCH_CACHE_FRESH_TTL_MS,
|
|
22
|
-
FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
23
|
-
FETCH_CACHE_MAX_ENTRIES,
|
|
24
|
-
FETCH_CACHE_STALE_IF_ERROR_MS,
|
|
25
|
-
} from "./constants";
|
|
26
|
-
|
|
27
|
-
export type FetchCacheOptions = SharedFetchCacheOptions;
|
|
28
|
-
|
|
29
|
-
const cache = createFetchCache({
|
|
30
|
-
provider: "magento",
|
|
31
|
-
maxEntries: FETCH_CACHE_MAX_ENTRIES,
|
|
32
|
-
freshTtlMs: FETCH_CACHE_FRESH_TTL_MS,
|
|
33
|
-
staleIfErrorMs: FETCH_CACHE_STALE_IF_ERROR_MS,
|
|
34
|
-
inflightBackstopMs: FETCH_CACHE_INFLIGHT_BACKSTOP_MS,
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Wrap a Magento GET with SWR caching + in-flight dedup. Returns the parsed
|
|
39
|
-
* JSON body, or `null` for cacheable non-2xx responses (e.g. 404). 5xx throw.
|
|
40
|
-
*/
|
|
41
|
-
export function magentoCachedFetch<T>(
|
|
42
|
-
cacheKey: string,
|
|
43
|
-
doFetch: () => Promise<Response>,
|
|
44
|
-
opts?: FetchCacheOptions,
|
|
45
|
-
): Promise<T | null> {
|
|
46
|
-
return cache.fetchWithCache<T>(cacheKey, doFetch, opts);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function clearFetchCache() {
|
|
50
|
-
cache.clear();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function getFetchCacheStats() {
|
|
54
|
-
return cache.getStats();
|
|
55
|
-
}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Magento GraphQL request input types used by the product loaders.
|
|
3
|
-
*
|
|
4
|
-
* Subset of `deco-cx/apps/magento/utils/clientGraphql/types.ts` that the
|
|
5
|
-
* port has reached so far — extended as more loaders land. These mirror
|
|
6
|
-
* Magento's GraphQL schema for product search/sort/filter so the
|
|
7
|
-
* storefront's URL params translate cleanly into GraphQL variables.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Magento ProductAttributeFilterInput shape — keys are Magento
|
|
12
|
-
* attribute codes; values are one of the three filter-type unions.
|
|
13
|
-
* Maps to: `filter: ProductAttributeFilterInput!`
|
|
14
|
-
*/
|
|
15
|
-
export interface ProductFilterInput {
|
|
16
|
-
[key: string]: FilterEqualTypeInput | FilterMatchTypeInput | FilterRangeTypeInput;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Magento `FilterEqualTypeInput` — for exact-match attributes (sku,
|
|
21
|
-
* sale, color, size, etc.). `in` lets you OR multiple values; the
|
|
22
|
-
* single-value `eq` form is also accepted by the Magento schema and is
|
|
23
|
-
* the shape `transformFilterValueGraphQL` emits.
|
|
24
|
-
*/
|
|
25
|
-
export interface FilterEqualTypeInput {
|
|
26
|
-
in?: string[];
|
|
27
|
-
eq?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Magento `FilterMatchTypeInput` — substring-match attributes (name,
|
|
32
|
-
* description, short_description).
|
|
33
|
-
*/
|
|
34
|
-
export interface FilterMatchTypeInput {
|
|
35
|
-
match: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Magento `FilterRangeTypeInput` — numeric ranges (price). Both bounds
|
|
40
|
-
* are strings in the schema; ranges in URL params come as `from_to`.
|
|
41
|
-
*/
|
|
42
|
-
export interface FilterRangeTypeInput {
|
|
43
|
-
from: string;
|
|
44
|
-
to: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Magento `ProductAttributeSortInput` — one entry per sortable
|
|
49
|
-
* attribute keyed by attribute code, ordered ASC or DESC.
|
|
50
|
-
*/
|
|
51
|
-
export interface ProductSortInput {
|
|
52
|
-
[key: string]: "ASC" | "DESC";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Built-in sort options surfaced in the CMS admin. Custom options can
|
|
57
|
-
* be added by sites via `CustomProductSortOption`.
|
|
58
|
-
*/
|
|
59
|
-
export interface DefaultProductSortOption {
|
|
60
|
-
value: "name" | "position" | "price" | "relevance";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface CustomProductSortOption {
|
|
64
|
-
value: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Shared sort-prop shape used by PLP / list / relatedProducts loaders.
|
|
69
|
-
*/
|
|
70
|
-
export interface ProductSort {
|
|
71
|
-
/** @title Order by */
|
|
72
|
-
sortBy: DefaultProductSortOption | CustomProductSortOption;
|
|
73
|
-
/** @title Sequency */
|
|
74
|
-
order: "ASC" | "DESC";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Loader-supplied filter (vs URL-derived). The site can hard-code
|
|
79
|
-
* filters in the CMS section config and they layer on top of whatever
|
|
80
|
-
* the user picked from URL params.
|
|
81
|
-
*/
|
|
82
|
-
export interface FilterProps {
|
|
83
|
-
name: string;
|
|
84
|
-
type: FilterEqualTypeInput | FilterMatchTypeInput | FilterRangeTypeInput;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Custom-fields toggle used by `getCustomFields()` to decide which
|
|
89
|
-
* Magento product attributes to project from a query.
|
|
90
|
-
*/
|
|
91
|
-
export interface CustomFields {
|
|
92
|
-
/**
|
|
93
|
-
* @description Search for global custom fields defined in App settings
|
|
94
|
-
* @default false
|
|
95
|
-
*/
|
|
96
|
-
active: boolean;
|
|
97
|
-
/** @description Will override global custom fields defined in App settings */
|
|
98
|
-
overrideList?: string[];
|
|
99
|
-
}
|
package/src/utils/graphql.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GraphQL helpers for Magento product loaders.
|
|
3
|
-
*
|
|
4
|
-
* Ported from `deco-cx/apps/magento/utils/graphql.ts` (Fresh/Deno
|
|
5
|
-
* prod). Pure functions — no I/O, no client state — so behavior is
|
|
6
|
-
* pinned by `__tests__/graphql.test.ts` and shared across PDP, PLP,
|
|
7
|
-
* list, and relatedProducts loaders.
|
|
8
|
-
*
|
|
9
|
-
* - `transformSortGraphQL(ProductSort)` → `ProductSortInput`
|
|
10
|
-
* - `transformFilterGraphQL(url, customFilters, fromLoader)` →
|
|
11
|
-
* `ProductFilterInput` — merges URL-derived + loader-derived filters.
|
|
12
|
-
* - `transformFilterValueGraphQL(value, type)` → typed Filter*Input.
|
|
13
|
-
* - `formatUrlSuffix(str)` → ensures a path ends with `/` (used as
|
|
14
|
-
* `defaultPath` for the Magento URL rewrite resolver).
|
|
15
|
-
* - `getCustomFields(CustomFields, fallback)` → resolved custom
|
|
16
|
-
* attribute list for product projection.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import type { FiltersGraphQL } from "../client";
|
|
20
|
-
import { DEFAULT_GRAPHQL_FILTERS } from "./constants";
|
|
21
|
-
import type {
|
|
22
|
-
CustomFields,
|
|
23
|
-
FilterEqualTypeInput,
|
|
24
|
-
FilterMatchTypeInput,
|
|
25
|
-
FilterProps,
|
|
26
|
-
FilterRangeTypeInput,
|
|
27
|
-
ProductFilterInput,
|
|
28
|
-
ProductSort,
|
|
29
|
-
ProductSortInput,
|
|
30
|
-
} from "./graphql-types";
|
|
31
|
-
|
|
32
|
-
export const typeChecker = <T extends object>(v: T, prop: keyof T): boolean => prop in v;
|
|
33
|
-
|
|
34
|
-
export const transformSortGraphQL = ({
|
|
35
|
-
sortBy,
|
|
36
|
-
order,
|
|
37
|
-
}: Partial<ProductSort>): ProductSortInput | undefined => {
|
|
38
|
-
if (!sortBy) {
|
|
39
|
-
return undefined;
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
[sortBy.value]: order ?? "ASC",
|
|
43
|
-
};
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Compose the GraphQL `filter` payload from two sources, in order:
|
|
48
|
-
*
|
|
49
|
-
* 1. URL search params crossed against `DEFAULT_GRAPHQL_FILTERS`
|
|
50
|
-
* (+ any `customFilters` the site extends with).
|
|
51
|
-
* 2. Explicit `fromLoader` filters the CMS section pinned at config
|
|
52
|
-
* time.
|
|
53
|
-
*
|
|
54
|
-
* Loader-derived filters shadow URL-derived ones on key collisions
|
|
55
|
-
* (intentional — a section that hard-codes `sale=true` should ignore
|
|
56
|
-
* any conflicting URL hint).
|
|
57
|
-
*/
|
|
58
|
-
export const transformFilterGraphQL = (
|
|
59
|
-
url: URL,
|
|
60
|
-
customFilters?: Array<FiltersGraphQL>,
|
|
61
|
-
fromLoader?: Array<FilterProps>,
|
|
62
|
-
): ProductFilterInput | undefined => ({
|
|
63
|
-
...filtersFromUrlGraphQL(url, customFilters),
|
|
64
|
-
...filtersFromLoaderGraphQL(fromLoader),
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
export const filtersFromLoaderGraphQL = (
|
|
68
|
-
fromLoader?: Array<FilterProps>,
|
|
69
|
-
): ProductFilterInput | undefined =>
|
|
70
|
-
fromLoader?.reduce<ProductFilterInput>(
|
|
71
|
-
(acc, f) => ({
|
|
72
|
-
...acc,
|
|
73
|
-
[f.name]: f.type,
|
|
74
|
-
}),
|
|
75
|
-
{},
|
|
76
|
-
) ?? {};
|
|
77
|
-
|
|
78
|
-
export const filtersFromUrlGraphQL = (
|
|
79
|
-
url: URL,
|
|
80
|
-
customFilters?: Array<FiltersGraphQL>,
|
|
81
|
-
): ProductFilterInput =>
|
|
82
|
-
DEFAULT_GRAPHQL_FILTERS.concat(customFilters ?? []).reduce<ProductFilterInput>(
|
|
83
|
-
(acc, { type, value }) => {
|
|
84
|
-
const fromUrl = url.searchParams.get(value);
|
|
85
|
-
if (!fromUrl) {
|
|
86
|
-
return acc;
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
...acc,
|
|
90
|
-
[value]: transformFilterValueGraphQL(fromUrl, type),
|
|
91
|
-
};
|
|
92
|
-
},
|
|
93
|
-
{},
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
export const transformFilterValueGraphQL = (
|
|
97
|
-
value: string,
|
|
98
|
-
type: "EQUAL" | "MATCH" | "RANGE",
|
|
99
|
-
): FilterEqualTypeInput | FilterMatchTypeInput | FilterRangeTypeInput => {
|
|
100
|
-
if (type === "EQUAL") {
|
|
101
|
-
return { eq: value } as FilterEqualTypeInput;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (type === "MATCH") {
|
|
105
|
-
return { match: value } as FilterMatchTypeInput;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (type === "RANGE") {
|
|
109
|
-
const splitterIndex = value.indexOf("_");
|
|
110
|
-
return {
|
|
111
|
-
from: value.substring(0, splitterIndex),
|
|
112
|
-
to: value.substring(splitterIndex + 1),
|
|
113
|
-
} as FilterRangeTypeInput;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {} as FilterEqualTypeInput;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Normalize a URL path into the form Magento's
|
|
121
|
-
* `urlResolver(url: "<path>/")` expects:
|
|
122
|
-
* - Strip a single leading slash (the resolver doesn't want it).
|
|
123
|
-
* - Ensure the path ends with `/`.
|
|
124
|
-
*
|
|
125
|
-
* Used by PLP / PDP / list / relatedProducts loaders as `defaultPath`
|
|
126
|
-
* when `useSuffix` is enabled.
|
|
127
|
-
*/
|
|
128
|
-
export const formatUrlSuffix = (str: string): string => {
|
|
129
|
-
let s = str;
|
|
130
|
-
if (s.startsWith("/")) s = s.slice(1);
|
|
131
|
-
if (!s.endsWith("/")) s = `${s}/`;
|
|
132
|
-
return s;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Resolve which custom-attribute list a loader should request:
|
|
137
|
-
*
|
|
138
|
-
* - disabled (`active: false`) → undefined (loader projects nothing custom).
|
|
139
|
-
* - explicit override list set → return that list as-is.
|
|
140
|
-
* - otherwise → fall back to the global list provided by the loader.
|
|
141
|
-
*/
|
|
142
|
-
export const getCustomFields = (
|
|
143
|
-
{ active, overrideList }: CustomFields = { active: false, overrideList: [] },
|
|
144
|
-
customFields?: Array<string>,
|
|
145
|
-
): Array<string> | undefined => {
|
|
146
|
-
if (!active) {
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (overrideList && overrideList.length > 0) {
|
|
151
|
-
return overrideList;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return customFields;
|
|
155
|
-
};
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-wired instrumented fetch factory for Magento.
|
|
3
|
-
*
|
|
4
|
-
* Mirrors `vtex/utils/instrumentedFetch.ts` and `shopify/utils/instrumentedFetch.ts`.
|
|
5
|
-
* Bundles:
|
|
6
|
-
*
|
|
7
|
-
* 1. `createInstrumentedFetch` from `@decocms/blocks/sdk/instrumentedFetch`
|
|
8
|
-
* (spans, traceparent injection, URL redaction, cache-header span attrs).
|
|
9
|
-
* 2. `magentoOperationRouter` as the URL→operation fallback.
|
|
10
|
-
* 3. An `onComplete` that records the canonical
|
|
11
|
-
* `http.client.request.duration` histogram via the framework's
|
|
12
|
-
* `recordCommerceMetric(...)` helper with `provider: "magento"`.
|
|
13
|
-
*
|
|
14
|
-
* Sites opt in once at startup:
|
|
15
|
-
*
|
|
16
|
-
* ```ts
|
|
17
|
-
* import { createMagentoFetch, setMagentoFetch } from "@decocms/apps/magento";
|
|
18
|
-
* setMagentoFetch(createMagentoFetch());
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* With this wired, every Magento egress call (GraphQL + REST) funnels through
|
|
22
|
-
* one instrumented boundary, so upstream latency/status lands in ClickHouse.
|
|
23
|
-
* SWR hit/miss for cached GETs is emitted separately by
|
|
24
|
-
* `@decocms/blocks/sdk/fetchCache` (see `./fetchCache.ts`).
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
import {
|
|
28
|
-
createInstrumentedFetch,
|
|
29
|
-
type InstrumentedFetch,
|
|
30
|
-
} from "@decocms/blocks/sdk/instrumentedFetch";
|
|
31
|
-
import { recordCommerceMetric, statusClassFor } from "@decocms/blocks/sdk/observability";
|
|
32
|
-
import { magentoOperationRouter } from "./operationRouter";
|
|
33
|
-
|
|
34
|
-
export interface CreateMagentoFetchOptions {
|
|
35
|
-
/** Underlying fetch to wrap. Defaults to `globalThis.fetch`. */
|
|
36
|
-
baseFetch?: typeof fetch;
|
|
37
|
-
/**
|
|
38
|
-
* Disable the `http.client.request.duration` histogram for Magento calls.
|
|
39
|
-
* Spans + structured logs still emit. Default: false.
|
|
40
|
-
*/
|
|
41
|
-
disableHistogram?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Construct a pre-wired Magento `InstrumentedFetch`. Pass the result to
|
|
46
|
-
* `setMagentoFetch(...)`.
|
|
47
|
-
*/
|
|
48
|
-
export function createMagentoFetch(options: CreateMagentoFetchOptions = {}): InstrumentedFetch {
|
|
49
|
-
const { baseFetch, disableHistogram = false } = options;
|
|
50
|
-
return createInstrumentedFetch({
|
|
51
|
-
name: "magento",
|
|
52
|
-
baseFetch,
|
|
53
|
-
resolveOperation: magentoOperationRouter,
|
|
54
|
-
onComplete: disableHistogram ? undefined : (r) =>
|
|
55
|
-
recordCommerceMetric(r.durationMs, { provider: "magento", operation: r.operation, status_class: statusClassFor(r.status), cached: r.cached }),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* URL-derived operation name router for Magento API calls.
|
|
3
|
-
*
|
|
4
|
-
* Plugged into `@decocms/blocks/sdk/instrumentedFetch`'s `resolveOperation`
|
|
5
|
-
* option. Mirrors the VTEX/Shopify routers. Magento's surface from this repo is
|
|
6
|
-
* a mix of GraphQL (`/graphql`) and REST (`/rest/<store>/V1/...`); the URL alone
|
|
7
|
-
* can name the REST resource, while GraphQL calls fall back to a single
|
|
8
|
-
* `graphql` operation (the semantic name lives in the document body, which
|
|
9
|
-
* callers may stamp as `init.operation` to override this router).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
type OperationResolver = string | ((match: RegExpMatchArray, method: string) => string);
|
|
13
|
-
|
|
14
|
-
interface Matcher {
|
|
15
|
-
pattern: RegExp;
|
|
16
|
-
operation: OperationResolver;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const m = (pattern: RegExp, operation: OperationResolver): Matcher => ({ pattern, operation });
|
|
20
|
-
|
|
21
|
-
const MATCHERS: ReadonlyArray<Matcher> = [
|
|
22
|
-
m(/\/graphql\b/, "graphql"),
|
|
23
|
-
// REST: /rest/<storeCode>/V1/<resource>/... — name by the first resource segment.
|
|
24
|
-
m(/\/rest\/[^/]+\/V1\/([^/?#]+)/, (match) => `rest.${match[1]}`),
|
|
25
|
-
m(/\/rest\/V1\/([^/?#]+)/, (match) => `rest.${match[1]}`),
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve an operation name for a Magento URL. Returns `undefined` when no
|
|
30
|
-
* matcher fires, so the framework falls back to `magento.fetch`.
|
|
31
|
-
*/
|
|
32
|
-
import { extractPathname } from "@decocms/blocks/sdk/urlUtils";
|
|
33
|
-
|
|
34
|
-
export function magentoOperationRouter(url: string, _method: string): string | undefined {
|
|
35
|
-
const pathname = extractPathname(url);
|
|
36
|
-
|
|
37
|
-
for (const { pattern, operation } of MATCHERS) {
|
|
38
|
-
const match = pathname.match(pattern);
|
|
39
|
-
if (!match) continue;
|
|
40
|
-
return typeof operation === "function" ? operation(match, _method) : operation;
|
|
41
|
-
}
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Flatten a Magento REST `searchCriteria` object into a bracketed
|
|
3
|
-
* query-string key/value map.
|
|
4
|
-
*
|
|
5
|
-
* Magento's search-criteria payload is deeply nested (filterGroups →
|
|
6
|
-
* filters → field/value), but the REST API accepts it only as flat
|
|
7
|
-
* `searchCriteria[filterGroups][0][filters][0][field]=name` style
|
|
8
|
-
* query parameters. This helper walks the tree once and emits that
|
|
9
|
-
* shape.
|
|
10
|
-
*
|
|
11
|
-
* Ported verbatim from `deco-cx/apps/magento/utils/stringifySearchCriteria.ts`
|
|
12
|
-
* — the Fresh implementation is self-contained (no Deno-isms) and
|
|
13
|
-
* produces output that prod consumers already depend on. Behavior
|
|
14
|
-
* pinned by `__tests__/stringifySearchCriteria.test.ts`.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* stringifySearchCriteria({
|
|
18
|
-
* filterGroups: [{ filters: [{ field: "sku", value: "ABC" }] }],
|
|
19
|
-
* })
|
|
20
|
-
* // ⇒ { "searchCriteria[filterGroups][0][filters][0][field]": "sku",
|
|
21
|
-
* // "searchCriteria[filterGroups][0][filters][0][value]": "ABC" }
|
|
22
|
-
*/
|
|
23
|
-
interface Filter {
|
|
24
|
-
field: string;
|
|
25
|
-
value: string;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface FilterGroup {
|
|
29
|
-
[key: string]: Filter[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface SearchCriteria {
|
|
33
|
-
[key: string]: string | number | FilterGroup[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
type Path = string;
|
|
37
|
-
type TraverseObj = SearchCriteria | FilterGroup[];
|
|
38
|
-
|
|
39
|
-
function traverse(data: TraverseObj, result: Record<Path, string>, path: Path) {
|
|
40
|
-
if (Array.isArray(data)) {
|
|
41
|
-
data.forEach((item, index) => {
|
|
42
|
-
traverse(item as unknown as TraverseObj, result, `${path}[${index}]`);
|
|
43
|
-
});
|
|
44
|
-
} else if (typeof data === "object" && data !== null) {
|
|
45
|
-
for (const key in data) {
|
|
46
|
-
if (Object.hasOwn(data, key)) {
|
|
47
|
-
// @ts-expect-error recursive function with heterogeneous values
|
|
48
|
-
traverse(data[key], result, `${path}[${key}]`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
52
|
-
result[path] = data;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default function stringifySearchCriteria(
|
|
57
|
-
searchCriteriaObj: SearchCriteria,
|
|
58
|
-
): Record<Path, string> {
|
|
59
|
-
const result: Record<Path, string> = {};
|
|
60
|
-
traverse(searchCriteriaObj, result, "searchCriteria");
|
|
61
|
-
return result;
|
|
62
|
-
}
|