@decocms/apps-magento 8.1.0-next.0 → 8.1.0-next.2
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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/magentoClient.d.ts +62 -0
- package/dist/magentoClient.d.ts.map +1 -0
- package/dist/magentoClient.js +96 -0
- package/dist/magentoClient.js.map +1 -0
- package/package.json +19 -27
- package/src/index.ts +2 -29
- 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
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Magento REST API response shapes — the subset of types from
|
|
3
|
-
* `deco-cx/apps/magento/utils/client/types.ts` that the port has
|
|
4
|
-
* reached so far. Extended as more loaders/actions land.
|
|
5
|
-
*
|
|
6
|
-
* Keep field names **exactly** as Magento returns them (mostly
|
|
7
|
-
* snake_case, occasional camelCase from carbono-customer). Consumer
|
|
8
|
-
* sites already render against these shapes — any rename is a
|
|
9
|
-
* breaking change at the storefront, not just the API boundary.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Customer / user section payloads (added by the user+wishlist port)
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* `customer` slice of `/customer/section/load?sections=customer,…`.
|
|
18
|
-
* Magento returns this on every authenticated section call.
|
|
19
|
-
*/
|
|
20
|
-
export interface Customer {
|
|
21
|
-
data_id: number;
|
|
22
|
-
fullname?: string;
|
|
23
|
-
firstname?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* `carbono-customer` slice. A storefront-specific overlay that mirrors the
|
|
28
|
-
* `customer` slice plus a website/store id pair and a normalized email.
|
|
29
|
-
* Other magento sites that don't run the Carbono module will get this
|
|
30
|
-
* absent; loaders/user.ts checks for it before mapping to a Person.
|
|
31
|
-
*/
|
|
32
|
-
export interface CarbonoCustomer {
|
|
33
|
-
websiteId?: string;
|
|
34
|
-
email?: string;
|
|
35
|
-
customerId?: string;
|
|
36
|
-
data_id: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* `cart` slice of the customer section bundle — minimal projection of
|
|
41
|
-
* the cart that the minicart island renders before the full cart loader
|
|
42
|
-
* has resolved. Not the same as the full Cart payload from
|
|
43
|
-
* `/V1/carts/:cartId` (which lives in MagentoCart in types.ts).
|
|
44
|
-
*/
|
|
45
|
-
export interface CartUser {
|
|
46
|
-
summary_count: number;
|
|
47
|
-
subtotalAmount: number | null;
|
|
48
|
-
subtotal: string;
|
|
49
|
-
possible_onepage_checkout: boolean;
|
|
50
|
-
items: [];
|
|
51
|
-
isGuestCheckoutAllowed: boolean;
|
|
52
|
-
website_id: string;
|
|
53
|
-
storeId: string;
|
|
54
|
-
adyen_payment_methods: unknown[];
|
|
55
|
-
extra_actions: string;
|
|
56
|
-
cart_empty_message: string;
|
|
57
|
-
subtotal_incl_tax: string;
|
|
58
|
-
subtotal_excl_tax: string;
|
|
59
|
-
mpFSBCartTotal: unknown | null;
|
|
60
|
-
data_id: number;
|
|
61
|
-
minicart_improvements: MinicartImprovements;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface MinicartImprovements {
|
|
65
|
-
coupon_code: string | null;
|
|
66
|
-
country_id: string;
|
|
67
|
-
api_base_url: string;
|
|
68
|
-
is_logged_in: boolean;
|
|
69
|
-
quote_id: string;
|
|
70
|
-
base_url: string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Bundle shape returned by
|
|
75
|
-
* `GET /:site/customer/section/load?sections=customer,carbono-customer,wishlist,…`.
|
|
76
|
-
* Keys are optional because the caller picks which sections to request.
|
|
77
|
-
*/
|
|
78
|
-
export interface CustomerSectionLoad {
|
|
79
|
-
customer?: Customer;
|
|
80
|
-
"carbono-customer"?: CarbonoCustomer;
|
|
81
|
-
cart?: CartUser;
|
|
82
|
-
wishlist?: Wishlist;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// Wishlist payloads
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
88
|
-
|
|
89
|
-
export interface Wishlist {
|
|
90
|
-
counter: string;
|
|
91
|
-
items: WishlistItem[];
|
|
92
|
-
counter_number: number;
|
|
93
|
-
data_id: number;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export interface WishlistItem {
|
|
97
|
-
image: WishlistItemImage;
|
|
98
|
-
product_sku: string;
|
|
99
|
-
product_id: string;
|
|
100
|
-
product_url: string;
|
|
101
|
-
product_name: string;
|
|
102
|
-
product_price: string;
|
|
103
|
-
product_is_saleable_and_visible: boolean;
|
|
104
|
-
product_has_required_options: boolean;
|
|
105
|
-
add_to_cart_params: string;
|
|
106
|
-
delete_item_params: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export interface WishlistItemImage {
|
|
110
|
-
template: string;
|
|
111
|
-
src: string;
|
|
112
|
-
width: number;
|
|
113
|
-
height: number;
|
|
114
|
-
alt: string;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Shared attribute/category shapes (added by the transform port)
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
export interface CustomAttribute {
|
|
122
|
-
attribute_code: string;
|
|
123
|
-
value: string | string[];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface CategoryLink {
|
|
127
|
-
position: number;
|
|
128
|
-
category_id: string;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface MagentoCategory {
|
|
132
|
-
id: number;
|
|
133
|
-
parent_id: number;
|
|
134
|
-
name: string;
|
|
135
|
-
is_active: boolean;
|
|
136
|
-
position: number;
|
|
137
|
-
level: number;
|
|
138
|
-
children: string;
|
|
139
|
-
created_at: string;
|
|
140
|
-
updated_at: string;
|
|
141
|
-
path: string;
|
|
142
|
-
include_in_menu: boolean;
|
|
143
|
-
custom_attributes: CustomAttribute[];
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ---------------------------------------------------------------------------
|
|
147
|
-
// Product detail shapes (used by PDP / PLP / list loaders)
|
|
148
|
-
// ---------------------------------------------------------------------------
|
|
149
|
-
|
|
150
|
-
export interface MagentoPriceInfo {
|
|
151
|
-
final_price: number;
|
|
152
|
-
max_price: number;
|
|
153
|
-
max_regular_price: number;
|
|
154
|
-
minimal_regular_price: number;
|
|
155
|
-
special_price: number | null;
|
|
156
|
-
minimal_price: number;
|
|
157
|
-
regular_price: number;
|
|
158
|
-
formatted_prices: {
|
|
159
|
-
final_price: string;
|
|
160
|
-
max_price: string;
|
|
161
|
-
minimal_price: string;
|
|
162
|
-
max_regular_price: string;
|
|
163
|
-
minimal_regular_price: string | null;
|
|
164
|
-
special_price: string | null;
|
|
165
|
-
regular_price: string;
|
|
166
|
-
};
|
|
167
|
-
extension_attributes: {
|
|
168
|
-
msrp: {
|
|
169
|
-
msrp_price: string;
|
|
170
|
-
is_applicable: string;
|
|
171
|
-
is_shown_price_on_gesture: string;
|
|
172
|
-
msrp_message: string;
|
|
173
|
-
explanation_message: string;
|
|
174
|
-
};
|
|
175
|
-
tax_adjustments: {
|
|
176
|
-
final_price: number;
|
|
177
|
-
max_price: number;
|
|
178
|
-
max_regular_price: number;
|
|
179
|
-
minimal_regular_price: number;
|
|
180
|
-
special_price: number;
|
|
181
|
-
minimal_price: number;
|
|
182
|
-
regular_price: number;
|
|
183
|
-
formatted_prices: {
|
|
184
|
-
final_price: string;
|
|
185
|
-
max_price: string;
|
|
186
|
-
minimal_price: string;
|
|
187
|
-
max_regular_price: string;
|
|
188
|
-
minimal_regular_price: string | null;
|
|
189
|
-
special_price: string;
|
|
190
|
-
regular_price: string;
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
weee_attributes: unknown[];
|
|
194
|
-
weee_adjustment: string;
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export interface MagentoStock {
|
|
199
|
-
item_id: number;
|
|
200
|
-
product_id: number;
|
|
201
|
-
stock_id: number;
|
|
202
|
-
qty?: number;
|
|
203
|
-
is_in_stock?: boolean;
|
|
204
|
-
is_qty_decimal?: boolean;
|
|
205
|
-
show_default_notification_message?: boolean;
|
|
206
|
-
use_config_min_qty?: boolean;
|
|
207
|
-
min_qty?: number;
|
|
208
|
-
use_config_min_sale_qty?: boolean;
|
|
209
|
-
min_sale_qty?: number;
|
|
210
|
-
use_config_max_sale_qty?: boolean;
|
|
211
|
-
max_sale_qty?: number;
|
|
212
|
-
use_config_backorders?: boolean;
|
|
213
|
-
backorders?: number;
|
|
214
|
-
use_config_notify_stock_qty?: boolean;
|
|
215
|
-
notify_stock_qty?: number;
|
|
216
|
-
use_config_qty_increments?: boolean;
|
|
217
|
-
qty_increments?: number;
|
|
218
|
-
use_config_enable_qty_inc?: boolean;
|
|
219
|
-
enable_qty_increments?: boolean;
|
|
220
|
-
use_config_manage_stock?: boolean;
|
|
221
|
-
manage_stock?: boolean;
|
|
222
|
-
low_stock_date?: string | null;
|
|
223
|
-
is_decimal_divided?: boolean;
|
|
224
|
-
stock_status_changed_auto?: number;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
export interface MagentoImage {
|
|
228
|
-
url: string;
|
|
229
|
-
code: string;
|
|
230
|
-
height: number;
|
|
231
|
-
width: number;
|
|
232
|
-
label: string;
|
|
233
|
-
resized_width: number;
|
|
234
|
-
resized_height: number;
|
|
235
|
-
disabled: boolean;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export interface MediaEntry {
|
|
239
|
-
id: number;
|
|
240
|
-
media_type: string;
|
|
241
|
-
label: string | null;
|
|
242
|
-
position: number;
|
|
243
|
-
disabled: boolean;
|
|
244
|
-
types: string[];
|
|
245
|
-
file: string;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
export interface MagentoProduct {
|
|
249
|
-
id: number;
|
|
250
|
-
sku: string;
|
|
251
|
-
name: string;
|
|
252
|
-
price: number;
|
|
253
|
-
status: number;
|
|
254
|
-
visibility: number;
|
|
255
|
-
type_id: string;
|
|
256
|
-
created_at: string;
|
|
257
|
-
updated_at: string;
|
|
258
|
-
weight: number;
|
|
259
|
-
url: string;
|
|
260
|
-
extension_attributes: {
|
|
261
|
-
website_ids?: number[];
|
|
262
|
-
category_links: CategoryLink[];
|
|
263
|
-
stock_item?: MagentoStock;
|
|
264
|
-
};
|
|
265
|
-
custom_attributes: CustomAttribute[];
|
|
266
|
-
price_info?: MagentoPriceInfo;
|
|
267
|
-
currency_code?: string;
|
|
268
|
-
images?: MagentoImage[];
|
|
269
|
-
media_gallery_entries?: MediaEntry[];
|
|
270
|
-
}
|
package/src/utils/constants.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Magento constants — ported verbatim from
|
|
3
|
-
* `deco-cx/apps/magento/utils/constants.ts`.
|
|
4
|
-
*
|
|
5
|
-
* Keep this file boring and append-only. Magento's REST and GraphQL
|
|
6
|
-
* payloads rely on these identifiers as-is (URL_KEY for PDP slug
|
|
7
|
-
* matching, GRAND_TOTAL/SUBTOTAL/… for the totals composition that
|
|
8
|
-
* cart.ts pulls, IN_STOCK/OUT_OF_STOCK for schema.org availability).
|
|
9
|
-
* Mutating any of these values would silently break consumer sites
|
|
10
|
-
* that already render against them.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { FiltersGraphQL } from "../client";
|
|
14
|
-
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
// SWR fetch-cache tuning (consumed by utils/fetchCache.ts via the shared
|
|
17
|
-
// `createFetchCache` in @decocms/blocks/sdk/fetchCache). Mirrors the shape of
|
|
18
|
-
// VTEX's constants so Magento's cache posture is tuned in one place.
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
/** Max distinct cache keys kept in memory; oldest `createdAt` evicted first. */
|
|
22
|
-
export const FETCH_CACHE_MAX_ENTRIES = 500;
|
|
23
|
-
|
|
24
|
-
/** How long a cached Magento response stays FRESH, keyed by status class. */
|
|
25
|
-
export const FETCH_CACHE_FRESH_TTL_MS = {
|
|
26
|
-
/** 2xx — 3 min. Catalog/price data doesn't need to be second-fresh. */
|
|
27
|
-
success: 180_000,
|
|
28
|
-
/** 404 — 10s. A just-published product shouldn't 404 for long. */
|
|
29
|
-
notFound: 10_000,
|
|
30
|
-
/** 5xx — never treated as a good cache hit. */
|
|
31
|
-
serverError: 0,
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
/** Stale-if-error window: serve last-good this long past freshness on outage. */
|
|
35
|
-
export const FETCH_CACHE_STALE_IF_ERROR_MS = 86_400_000; // 24h
|
|
36
|
-
|
|
37
|
-
/** Inflight-slot backstop: bounds how long one hung fetch holds a dedup slot. */
|
|
38
|
-
export const FETCH_CACHE_INFLIGHT_BACKSTOP_MS = 15_000;
|
|
39
|
-
|
|
40
|
-
export const URL_KEY = "url_key";
|
|
41
|
-
|
|
42
|
-
// Schema.org availability mapping (used by utils/transform.ts to
|
|
43
|
-
// produce Offer.availability — kept here so transform doesn't import
|
|
44
|
-
// schema.org strings as magic literals).
|
|
45
|
-
export const IN_STOCK = "https://schema.org/InStock";
|
|
46
|
-
export const OUT_OF_STOCK = "https://schema.org/OutOfStock";
|
|
47
|
-
|
|
48
|
-
// Rating bounds used by `utils/transform.ts` (and the review/rating
|
|
49
|
-
// loaders that follow) when mapping Magento's integer-rating scale
|
|
50
|
-
// into schema.org `AggregateRating`'s 1–5 range.
|
|
51
|
-
export const MAX_RATING_VALUE = 5;
|
|
52
|
-
export const MIN_RATING_VALUE = 1;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Default filter mapping consumed by `utils/graphql.ts:filtersFromUrlGraphQL`.
|
|
56
|
-
* Each entry pairs a Magento attribute slug with the comparison operator
|
|
57
|
-
* the storefront's URL filters use. Sites can extend this via the
|
|
58
|
-
* `customFilters` prop on PLP/list loaders without forking the array.
|
|
59
|
-
*/
|
|
60
|
-
export const DEFAULT_GRAPHQL_FILTERS: FiltersGraphQL[] = [
|
|
61
|
-
{ value: "activity", type: "EQUAL" },
|
|
62
|
-
{ value: "category_gear", type: "EQUAL" },
|
|
63
|
-
{ value: "category_id", type: "EQUAL" },
|
|
64
|
-
{ value: "category_uid", type: "EQUAL" },
|
|
65
|
-
{ value: "category_url_path", type: "EQUAL" },
|
|
66
|
-
{ value: "climate", type: "EQUAL" },
|
|
67
|
-
{ value: "collar", type: "EQUAL" },
|
|
68
|
-
{ value: "color", type: "EQUAL" },
|
|
69
|
-
{ value: "description", type: "MATCH" },
|
|
70
|
-
{ value: "eco_collection", type: "EQUAL" },
|
|
71
|
-
{ value: "erin_recommends", type: "EQUAL" },
|
|
72
|
-
{ value: "features_bags", type: "EQUAL" },
|
|
73
|
-
{ value: "format", type: "EQUAL" },
|
|
74
|
-
{ value: "gender", type: "EQUAL" },
|
|
75
|
-
{ value: "material", type: "EQUAL" },
|
|
76
|
-
{ value: "name", type: "MATCH" },
|
|
77
|
-
{ value: "new", type: "EQUAL" },
|
|
78
|
-
{ value: "pattern", type: "EQUAL" },
|
|
79
|
-
{ value: "performance_fabric", type: "EQUAL" },
|
|
80
|
-
{ value: "price", type: "RANGE" },
|
|
81
|
-
{ value: "purpose", type: "EQUAL" },
|
|
82
|
-
{ value: "sale", type: "EQUAL" },
|
|
83
|
-
{ value: "short_description", type: "MATCH" },
|
|
84
|
-
{ value: "size", type: "EQUAL" },
|
|
85
|
-
{ value: "sku", type: "EQUAL" },
|
|
86
|
-
{ value: "sleeve", type: "EQUAL" },
|
|
87
|
-
{ value: "strap_bags", type: "EQUAL" },
|
|
88
|
-
{ value: "style_bags", type: "EQUAL" },
|
|
89
|
-
{ value: "style_bottom", type: "EQUAL" },
|
|
90
|
-
{ value: "style_general", type: "EQUAL" },
|
|
91
|
-
{ value: "url_key", type: "EQUAL" },
|
|
92
|
-
];
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Query-string keys that should be stripped before forwarding a request
|
|
96
|
-
* URL to Magento (e.g. when computing a cache key or building a
|
|
97
|
-
* paginated request). Mirrors the Fresh-era REMOVABLE_URL_SEARCHPARAMS.
|
|
98
|
-
*/
|
|
99
|
-
export const REMOVABLE_URL_SEARCHPARAMS = ["p", "product_list_order"];
|
|
100
|
-
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Cart totals composition — field names sent to Magento's
|
|
103
|
-
// /V1/carts/:cartId/totals?fields=… endpoint. The cart loader joins
|
|
104
|
-
// these into the `fields` query param to keep the response narrow.
|
|
105
|
-
// ---------------------------------------------------------------------------
|
|
106
|
-
|
|
107
|
-
export const GRAND_TOTAL = "grand_total";
|
|
108
|
-
export const SUBTOTAL = "subtotal";
|
|
109
|
-
export const DISCOUNT_AMOUNT = "discount_amount";
|
|
110
|
-
export const BASE_DISCOUNT_AMOUNT = "base_discount_amount";
|
|
111
|
-
export const SHIPPING_AMOUNT = "shipping_amount";
|
|
112
|
-
export const BASE_SHIPPING_AMOUNT = "base_shipping_amount";
|
|
113
|
-
export const SHIPPING_DISCOUNT_AMOUNT = "shipping_discount_amount";
|
|
114
|
-
export const COUPON_CODE = "coupon_code";
|
|
115
|
-
export const BASE_CURRENCY_CODE = "base_currency_code";
|
|
116
|
-
|
|
117
|
-
// ---------------------------------------------------------------------------
|
|
118
|
-
// Cookie names — single source of truth so loaders/actions/middleware
|
|
119
|
-
// don't drift on string literals (Magento is case-sensitive about
|
|
120
|
-
// these and a typo produces a silent anonymous-session bug).
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
|
|
123
|
-
export const SESSION_COOKIE = "PHPSESSID";
|
|
124
|
-
export const CUSTOMER_COOKIE = "dataservices_customer_id";
|
|
125
|
-
export const CART_COOKIE = "dataservices_cart_id";
|
|
126
|
-
export const FORM_KEY_COOKIE = "form_key";
|
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
|
-
};
|