@cobrastyle/adapter-magento2 1.1.0 → 1.2.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/dist/adapter.js +10 -0
- package/dist/client.js +27 -2
- package/dist/config.d.ts +3 -0
- package/dist/config.js +28 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/queries.d.ts +1 -1
- package/dist/queries.js +59 -0
- package/package.json +1 -1
package/dist/adapter.js
CHANGED
|
@@ -232,6 +232,16 @@ export const magento2Adapter = {
|
|
|
232
232
|
return mapCartWithUrls(data.cart);
|
|
233
233
|
}
|
|
234
234
|
catch (error) {
|
|
235
|
+
// An ownership refusal means the id is unusable for this session (e.g. a
|
|
236
|
+
// guest cart id left in the cookie after login). Reporting it as `null`
|
|
237
|
+
// would render an empty cart while the quote still exists and leave the bad
|
|
238
|
+
// id in place, so let it propagate; callers can drop the id and start over.
|
|
239
|
+
if (error instanceof Error &&
|
|
240
|
+
error.message.toLowerCase().includes('cannot perform operations on cart')) {
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
// Anything else (missing cart, transport failure) stays soft: the cart is
|
|
244
|
+
// simply unavailable and the page must still render.
|
|
235
245
|
return null;
|
|
236
246
|
}
|
|
237
247
|
},
|
package/dist/client.js
CHANGED
|
@@ -254,10 +254,15 @@ export async function graphqlFetch(query, variables, options = {}) {
|
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
256
|
logger.error(operationName, new Error(`HTTP ${response.status}: ${text}`), duration);
|
|
257
|
+
// Carry the backend's reason, not just the status: callers need it to tell
|
|
258
|
+
// an expired cart from an ownership problem, and without it a failure can
|
|
259
|
+
// only be diagnosed by reproducing the request by hand.
|
|
260
|
+
const reason = extractErrorReason(text);
|
|
261
|
+
const detail = reason ? `: ${reason}` : '';
|
|
257
262
|
if (response.status === 401) {
|
|
258
|
-
throw new GraphQLAuthError(`GraphQL request failed: 401 ${response.statusText} [${requestId}]
|
|
263
|
+
throw new GraphQLAuthError(`GraphQL request failed: 401 ${response.statusText}${detail} [${requestId}]`.replace(/ +/g, ' '));
|
|
259
264
|
}
|
|
260
|
-
throw new Error(`GraphQL request failed: ${response.status} ${response.statusText} [${requestId}]
|
|
265
|
+
throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}${detail} [${requestId}]`.replace(/ +/g, ' '));
|
|
261
266
|
}
|
|
262
267
|
const text = await response.text();
|
|
263
268
|
// Check if response is HTML (likely an error page)
|
|
@@ -324,6 +329,26 @@ export async function graphqlFetch(query, variables, options = {}) {
|
|
|
324
329
|
? lastError
|
|
325
330
|
: new Error(`GraphQL request failed after ${maxAttempts} attempts for ${operationName} [${requestId}]`);
|
|
326
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Pull a human-readable reason out of a non-2xx response body: the first GraphQL
|
|
334
|
+
* error message when the body is a GraphQL envelope, otherwise a trimmed excerpt
|
|
335
|
+
* of whatever the server sent (proxy errors, HTML pages, empty bodies).
|
|
336
|
+
*/
|
|
337
|
+
function extractErrorReason(body) {
|
|
338
|
+
const text = body?.trim();
|
|
339
|
+
if (!text)
|
|
340
|
+
return undefined;
|
|
341
|
+
try {
|
|
342
|
+
const parsed = JSON.parse(text);
|
|
343
|
+
const message = parsed.errors?.find((e) => e?.message)?.message;
|
|
344
|
+
if (message)
|
|
345
|
+
return message;
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// Not JSON — fall through to the excerpt.
|
|
349
|
+
}
|
|
350
|
+
return text.slice(0, 200);
|
|
351
|
+
}
|
|
327
352
|
/** Sleep helper for retry backoff. */
|
|
328
353
|
function delay(ms) {
|
|
329
354
|
return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
package/dist/config.d.ts
CHANGED
|
@@ -42,6 +42,9 @@ export interface MagentoConfig extends AdapterConfig {
|
|
|
42
42
|
*/
|
|
43
43
|
storeResolver?: () => string | undefined;
|
|
44
44
|
}
|
|
45
|
+
export declare const isConfigured: () => boolean;
|
|
46
|
+
/** Warn once per process that the client is running on defaults only. */
|
|
47
|
+
export declare function warnIfUnconfigured(context: string): void;
|
|
45
48
|
export declare const getConfig: () => MagentoConfig;
|
|
46
49
|
export declare const setConfig: (newConfig: Partial<MagentoConfig>) => void;
|
|
47
50
|
export declare const createConfig: (overrides?: Partial<MagentoConfig>) => MagentoConfig;
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
let config = {
|
|
2
|
-
|
|
2
|
+
// Deliberately empty rather than a placeholder host: an unset MAGENTO_URL is a
|
|
3
|
+
// configuration error, and pointing it at a domain nobody owns turns that into
|
|
4
|
+
// confusing request failures instead.
|
|
5
|
+
baseUrl: process.env.MAGENTO_URL || '',
|
|
3
6
|
storeCode: process.env.MAGENTO_STORE || 'default',
|
|
4
7
|
skipSsl: process.env.SKIP_SSL === 'true',
|
|
5
8
|
allowInsecure: process.env.ALLOW_INSECURE === 'true',
|
|
@@ -7,9 +10,32 @@ let config = {
|
|
|
7
10
|
// If not set, will be fetched dynamically from storeConfig
|
|
8
11
|
rootCategoryId: process.env.MAGENTO_ROOT_CATEGORY_ID ? parseInt(process.env.MAGENTO_ROOT_CATEGORY_ID, 10) : undefined,
|
|
9
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Whether the consuming app has supplied its configuration yet.
|
|
15
|
+
*
|
|
16
|
+
* The env-derived defaults above make an *unconfigured* client look usable, but
|
|
17
|
+
* the injected callbacks — `customerTokenGetter`, `tokenRefresher`,
|
|
18
|
+
* `storeResolver` — have no defaults. Requests then go out unauthenticated and
|
|
19
|
+
* without per-request store resolution, which surfaces far downstream as an
|
|
20
|
+
* authorization error on customer-owned data. Consumers of the exported client
|
|
21
|
+
* (`graphqlFetch`, `withCustomerAuth`) need to be told.
|
|
22
|
+
*/
|
|
23
|
+
let configured = false;
|
|
24
|
+
let warnedUnconfigured = false;
|
|
25
|
+
export const isConfigured = () => configured;
|
|
26
|
+
/** Warn once per process that the client is running on defaults only. */
|
|
27
|
+
export function warnIfUnconfigured(context) {
|
|
28
|
+
if (configured || warnedUnconfigured)
|
|
29
|
+
return;
|
|
30
|
+
warnedUnconfigured = true;
|
|
31
|
+
console.warn(`[magento2] ${context} before setConfig() ran: requests will be sent unauthenticated ` +
|
|
32
|
+
'and without per-request store resolution. Call setConfig() (or load the adapter ' +
|
|
33
|
+
'through configureStorefront/getAdapter) before using the exported GraphQL client.');
|
|
34
|
+
}
|
|
10
35
|
export const getConfig = () => config;
|
|
11
36
|
export const setConfig = (newConfig) => {
|
|
12
37
|
config = { ...config, ...newConfig };
|
|
38
|
+
configured = true;
|
|
13
39
|
};
|
|
14
40
|
export const createConfig = (overrides = {}) => ({
|
|
15
41
|
...config,
|
|
@@ -20,6 +46,7 @@ export const createConfig = (overrides = {}) => ({
|
|
|
20
46
|
* Merges with any extra options passed in.
|
|
21
47
|
*/
|
|
22
48
|
export async function withCustomerAuth(extra = {}) {
|
|
49
|
+
warnIfUnconfigured('withCustomerAuth() was called');
|
|
23
50
|
const token = await config.customerTokenGetter?.();
|
|
24
51
|
if (token) {
|
|
25
52
|
return { ...extra, customerToken: token };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { magento2Adapter, magento2Adapter as default } from './adapter';
|
|
2
|
-
export { getConfig, setConfig, createConfig, withCustomerAuth } from './config';
|
|
2
|
+
export { getConfig, setConfig, createConfig, withCustomerAuth, isConfigured } from './config';
|
|
3
3
|
export type { MagentoConfig } from './config';
|
|
4
4
|
export { graphqlFetch, GraphQLAuthError } from './client';
|
|
5
5
|
export { logger, MagentoLogger } from './logger';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { magento2Adapter, magento2Adapter as default } from './adapter';
|
|
2
|
-
export { getConfig, setConfig, createConfig, withCustomerAuth } from './config';
|
|
2
|
+
export { getConfig, setConfig, createConfig, withCustomerAuth, isConfigured } from './config';
|
|
3
3
|
export { graphqlFetch, GraphQLAuthError } from './client';
|
|
4
4
|
export { logger, MagentoLogger } from './logger';
|
|
5
5
|
export * as queries from './queries';
|
package/dist/queries.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const GET_PRODUCT_BY_URL_KEY = "\n query GetProductByUrlKey($urlKey: String!) {\n products(filter: { url_key: { eq: $urlKey } }) {\n items {\n __typename\n id\n sku\n name\n url_key\n description { html }\n short_description { html }\n meta_title\n meta_description\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n discount { amount_off percent_off }\n }\n maximum_price {\n final_price { value currency }\n }\n }\n price_tiers {\n quantity\n final_price { value currency }\n discount { amount_off percent_off }\n }\n media_gallery {\n url\n label\n position\n }\n categories {\n id\n name\n url_key\n breadcrumbs {\n category_url_key\n category_level\n }\n }\n custom_attributesV2(filters: { is_visible_on_front: true }) {\n items {\n code\n __typename\n ... on AttributeValue {\n value\n }\n ... on AttributeSelectedOptions {\n selected_options {\n label\n value\n }\n }\n }\n errors {\n message\n }\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_code\n label\n values {\n uid\n label\n swatch_data {\n __typename\n value\n }\n }\n }\n variants {\n product {\n id\n sku\n name\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n }\n }\n }\n attributes {\n code\n label\n value_index\n }\n }\n }\n ... on GroupedProduct {\n items {\n qty\n position\n product {\n ... on ProductInterface {\n __typename\n id\n uid\n sku\n name\n url_key\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n discount { amount_off percent_off }\n }\n }\n thumbnail { url label }\n }\n }\n }\n }\n ... on BundleProduct {\n dynamic_price\n price_view\n bundleItems: items {\n uid\n option_id\n title\n type\n required\n position\n options {\n uid\n id\n label\n quantity\n can_change_quantity\n is_default\n price\n price_type\n product {\n sku\n price_range { minimum_price { final_price { value currency } } }\n }\n }\n }\n }\n }\n }\n }\n";
|
|
2
|
-
export declare const GET_PRODUCT_BY_ID = "\n query GetProductBySku($sku: String!) {\n products(filter: { sku: { eq: $sku } }) {\n items {\n __typename\n id\n sku\n name\n url_key\n description { html }\n short_description { html }\n meta_title\n meta_description\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n discount { amount_off percent_off }\n }\n maximum_price {\n final_price { value currency }\n }\n }\n price_tiers {\n quantity\n final_price { value currency }\n discount { amount_off percent_off }\n }\n media_gallery {\n url\n label\n position\n }\n categories {\n id\n name\n url_key\n breadcrumbs {\n category_url_key\n category_level\n }\n }\n custom_attributesV2(filters: { is_visible_on_front: true }) {\n items {\n code\n __typename\n ... on AttributeValue {\n value\n }\n ... on AttributeSelectedOptions {\n selected_options {\n label\n value\n }\n }\n }\n errors {\n message\n }\n }\n ... on BundleProduct {\n dynamic_price\n price_view\n bundleItems: items {\n uid\n option_id\n title\n type\n required\n position\n options {\n uid\n id\n label\n quantity\n can_change_quantity\n is_default\n price\n price_type\n product {\n sku\n price_range { minimum_price { final_price { value currency } } }\n }\n }\n }\n }\n }\n }\n }\n";
|
|
2
|
+
export declare const GET_PRODUCT_BY_ID = "\n query GetProductBySku($sku: String!) {\n products(filter: { sku: { eq: $sku } }) {\n items {\n __typename\n id\n sku\n name\n url_key\n description { html }\n short_description { html }\n meta_title\n meta_description\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n discount { amount_off percent_off }\n }\n maximum_price {\n final_price { value currency }\n }\n }\n price_tiers {\n quantity\n final_price { value currency }\n discount { amount_off percent_off }\n }\n media_gallery {\n url\n label\n position\n }\n categories {\n id\n name\n url_key\n breadcrumbs {\n category_url_key\n category_level\n }\n }\n custom_attributesV2(filters: { is_visible_on_front: true }) {\n items {\n code\n __typename\n ... on AttributeValue {\n value\n }\n ... on AttributeSelectedOptions {\n selected_options {\n label\n value\n }\n }\n }\n errors {\n message\n }\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_code\n label\n values {\n uid\n label\n swatch_data {\n __typename\n value\n }\n }\n }\n variants {\n product {\n id\n sku\n name\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n }\n }\n }\n attributes {\n code\n label\n value_index\n }\n }\n }\n ... on GroupedProduct {\n items {\n qty\n position\n product {\n ... on ProductInterface {\n __typename\n id\n uid\n sku\n name\n url_key\n stock_status\n price_range {\n minimum_price {\n regular_price { value currency }\n final_price { value currency }\n discount { amount_off percent_off }\n }\n }\n thumbnail { url label }\n }\n }\n }\n }\n ... on BundleProduct {\n dynamic_price\n price_view\n bundleItems: items {\n uid\n option_id\n title\n type\n required\n position\n options {\n uid\n id\n label\n quantity\n can_change_quantity\n is_default\n price\n price_type\n product {\n sku\n price_range { minimum_price { final_price { value currency } } }\n }\n }\n }\n }\n }\n }\n }\n";
|
|
3
3
|
export declare const GET_PRODUCT_ATTRIBUTE_LABELS = "\n query GetProductAttributeLabels {\n attributesList(entityType: CATALOG_PRODUCT, filters: { is_visible_on_front: true }) {\n items {\n code\n label\n }\n errors {\n message\n }\n }\n }\n";
|
|
4
4
|
export declare const GET_CATEGORY_BY_URL_KEY = "\n query GetCategoryByUrlKey($urlKey: String!) {\n categoryList(filters: { url_path: { eq: $urlKey } }) {\n id\n uid\n name\n url_key\n url_path\n description\n image\n meta_title\n meta_description\n product_count\n breadcrumbs {\n category_id\n category_uid\n category_name\n category_url_key\n category_url_path\n category_level\n }\n children {\n id\n uid\n name\n url_key\n url_path\n product_count\n }\n }\n }\n";
|
|
5
5
|
export declare const GET_CATEGORY_BY_URL_KEY_SINGLE = "\n query GetCategoryByUrlKeySingle($urlKey: String!) {\n categoryList(filters: { url_key: { eq: $urlKey } }) {\n id\n uid\n name\n url_key\n url_path\n description\n image\n meta_title\n meta_description\n product_count\n breadcrumbs {\n category_id\n category_uid\n category_name\n category_url_key\n category_url_path\n category_level\n }\n children {\n id\n uid\n name\n url_key\n url_path\n product_count\n }\n }\n }\n";
|
package/dist/queries.js
CHANGED
|
@@ -210,6 +210,65 @@ export const GET_PRODUCT_BY_ID = `
|
|
|
210
210
|
message
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
+
... on ConfigurableProduct {
|
|
214
|
+
configurable_options {
|
|
215
|
+
id
|
|
216
|
+
attribute_code
|
|
217
|
+
label
|
|
218
|
+
values {
|
|
219
|
+
uid
|
|
220
|
+
label
|
|
221
|
+
swatch_data {
|
|
222
|
+
__typename
|
|
223
|
+
value
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
variants {
|
|
228
|
+
product {
|
|
229
|
+
id
|
|
230
|
+
sku
|
|
231
|
+
name
|
|
232
|
+
stock_status
|
|
233
|
+
price_range {
|
|
234
|
+
minimum_price {
|
|
235
|
+
regular_price { value currency }
|
|
236
|
+
final_price { value currency }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
attributes {
|
|
241
|
+
code
|
|
242
|
+
label
|
|
243
|
+
value_index
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
... on GroupedProduct {
|
|
248
|
+
items {
|
|
249
|
+
qty
|
|
250
|
+
position
|
|
251
|
+
product {
|
|
252
|
+
... on ProductInterface {
|
|
253
|
+
__typename
|
|
254
|
+
id
|
|
255
|
+
uid
|
|
256
|
+
sku
|
|
257
|
+
name
|
|
258
|
+
url_key
|
|
259
|
+
stock_status
|
|
260
|
+
price_range {
|
|
261
|
+
minimum_price {
|
|
262
|
+
regular_price { value currency }
|
|
263
|
+
final_price { value currency }
|
|
264
|
+
discount { amount_off percent_off }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
thumbnail { url label }
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
213
272
|
... on BundleProduct {
|
|
214
273
|
dynamic_price
|
|
215
274
|
price_view
|