@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,KAAK,aAAa,EAClB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,YAAY,EACZ,KAAK,kBAAkB,GACxB,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,OAAO,EACL,mBAAmB,EAInB,YAAY,GAEb,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Magento client: a thin client for a Magento store's REST and GraphQL
|
|
3
|
+
* APIs, over the framework's instrumented fetch (provider "magento"). See
|
|
4
|
+
* /next/upstream-clients.
|
|
5
|
+
*
|
|
6
|
+
* Configuration comes in as arguments; the site reads its environment (or a
|
|
7
|
+
* `secret` block) where it creates the client. Converters, hooks, cart and
|
|
8
|
+
* session flows, caching and feature toggles belong to the site (platform
|
|
9
|
+
* templates), not here.
|
|
10
|
+
*/
|
|
11
|
+
import { createInstrumentedFetch } from "@decocms/blocks/fetch";
|
|
12
|
+
export interface MagentoClientConfig {
|
|
13
|
+
/** The store's origin, e.g. `https://store.example.com`. Every request goes here. */
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
/** Integration access token, sent as `Authorization: Bearer` unless a call opts out. */
|
|
16
|
+
apiKey?: string;
|
|
17
|
+
/** Optional value sent as `x-origin-header`, for stores behind an origin guard. */
|
|
18
|
+
originHeader?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface MagentoClientOptions {
|
|
21
|
+
/** The fetch underneath, e.g. a fake in tests. Defaults to `globalThis.fetch`. */
|
|
22
|
+
fetch?: Parameters<typeof createInstrumentedFetch>[0]["fetch"];
|
|
23
|
+
}
|
|
24
|
+
export interface MagentoRequestInit extends RequestInit {
|
|
25
|
+
/** The operation label, the API's own name for the call (e.g. `getCart`), never a URL. */
|
|
26
|
+
operation: string;
|
|
27
|
+
/**
|
|
28
|
+
* Send the `apiKey` bearer token. Default true for `request`/`rest`, false
|
|
29
|
+
* for `graphql`. Never replaces an `Authorization` header the call sets
|
|
30
|
+
* (e.g. a customer token).
|
|
31
|
+
*/
|
|
32
|
+
authenticated?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Thrown on a non-2xx response or a GraphQL `errors` payload. Never carries bodies or tokens. */
|
|
35
|
+
export declare class MagentoError extends Error {
|
|
36
|
+
readonly operation: string;
|
|
37
|
+
readonly status: number;
|
|
38
|
+
/** How many GraphQL errors the response carried (0 for an HTTP failure). */
|
|
39
|
+
readonly graphqlErrors: number;
|
|
40
|
+
constructor(operation: string, status: number,
|
|
41
|
+
/** How many GraphQL errors the response carried (0 for an HTTP failure). */
|
|
42
|
+
graphqlErrors?: number);
|
|
43
|
+
}
|
|
44
|
+
export type MagentoClient = ReturnType<typeof createMagentoClient>;
|
|
45
|
+
export declare function createMagentoClient(config: MagentoClientConfig, options?: MagentoClientOptions): {
|
|
46
|
+
request: (path: string, init: MagentoRequestInit) => Promise<Response>;
|
|
47
|
+
/** A REST call (`/rest/<store>/V1/...`) returning its parsed JSON body. */
|
|
48
|
+
rest<T>(path: string, init: MagentoRequestInit): Promise<T>;
|
|
49
|
+
/**
|
|
50
|
+
* A GraphQL operation on `/graphql`. `operationName` is sent with the
|
|
51
|
+
* request and is the operation label. `headers` adds per-call headers,
|
|
52
|
+
* such as `Store` or a customer `Authorization` token. Storefront
|
|
53
|
+
* GraphQL is public, so the `apiKey` is sent only with
|
|
54
|
+
* `authenticated: true`, and never over a customer token.
|
|
55
|
+
*/
|
|
56
|
+
graphql<TData, TVariables = Record<string, unknown>>(query: string, variables: TVariables | undefined, init: {
|
|
57
|
+
operationName: string;
|
|
58
|
+
headers?: Record<string, string>;
|
|
59
|
+
authenticated?: boolean;
|
|
60
|
+
}): Promise<TData>;
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=magentoClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magentoClient.d.ts","sourceRoot":"","sources":["../src/magentoClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,MAAM,WAAW,mBAAmB;IAClC,qFAAqF;IACrF,OAAO,EAAE,MAAM,CAAC;IAChB,wFAAwF;IACxF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,kFAAkF;IAClF,KAAK,CAAC,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;CAChE;AAED,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,kGAAkG;AAClG,qBAAa,YAAa,SAAQ,KAAK;IAEnC,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,4EAA4E;IAC5E,QAAQ,CAAC,aAAa;IAJxB,YACW,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM;IACvB,4EAA4E;IACnE,aAAa,SAAI,EAQ3B;CACF;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEnE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,GAAE,oBAAyB;oBAUL,MAAM,QAAQ,kBAAkB,KAAG,OAAO,CAAC,QAAQ,CAAC;IAiB/E,2EAA2E;SAChE,CAAC,QAAQ,MAAM,QAAQ,kBAAkB,GAAG,OAAO,CAAC,CAAC,CAAC;IAWjE;;;;;;OAMG;YACW,KAAK,EAAE,UAAU,mCACtB,MAAM,aACF,UAAU,GAAG,SAAS,QAC3B;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,GACzF,OAAO,CAAC,KAAK,CAAC;EAoBpB"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Magento client: a thin client for a Magento store's REST and GraphQL
|
|
3
|
+
* APIs, over the framework's instrumented fetch (provider "magento"). See
|
|
4
|
+
* /next/upstream-clients.
|
|
5
|
+
*
|
|
6
|
+
* Configuration comes in as arguments; the site reads its environment (or a
|
|
7
|
+
* `secret` block) where it creates the client. Converters, hooks, cart and
|
|
8
|
+
* session flows, caching and feature toggles belong to the site (platform
|
|
9
|
+
* templates), not here.
|
|
10
|
+
*/
|
|
11
|
+
import { createInstrumentedFetch } from "@decocms/blocks/fetch";
|
|
12
|
+
/** Thrown on a non-2xx response or a GraphQL `errors` payload. Never carries bodies or tokens. */
|
|
13
|
+
export class MagentoError extends Error {
|
|
14
|
+
operation;
|
|
15
|
+
status;
|
|
16
|
+
graphqlErrors;
|
|
17
|
+
constructor(operation, status,
|
|
18
|
+
/** How many GraphQL errors the response carried (0 for an HTTP failure). */
|
|
19
|
+
graphqlErrors = 0) {
|
|
20
|
+
super(graphqlErrors > 0
|
|
21
|
+
? `magento ${operation} returned ${graphqlErrors} GraphQL error(s)`
|
|
22
|
+
: `magento ${operation} failed with HTTP ${status}`);
|
|
23
|
+
this.operation = operation;
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.graphqlErrors = graphqlErrors;
|
|
26
|
+
this.name = "MagentoError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function createMagentoClient(config, options = {}) {
|
|
30
|
+
const instrumented = createInstrumentedFetch({ provider: "magento", fetch: options.fetch });
|
|
31
|
+
const origin = new URL(config.baseUrl).origin;
|
|
32
|
+
/**
|
|
33
|
+
* A request to a path on the store, e.g. `/rest/default/V1/carts/mine` or
|
|
34
|
+
* `/customer/section/load`, returning the raw response. Paths only: the
|
|
35
|
+
* store's credentials never go to another origin.
|
|
36
|
+
*/
|
|
37
|
+
async function request(path, init) {
|
|
38
|
+
const { operation, authenticated = true, ...rest } = init;
|
|
39
|
+
const url = new URL(path, origin);
|
|
40
|
+
if (url.origin !== origin) {
|
|
41
|
+
throw new Error(`magento ${operation}: only paths on the configured store are allowed`);
|
|
42
|
+
}
|
|
43
|
+
const headers = new Headers(rest.headers);
|
|
44
|
+
if (authenticated && config.apiKey && !headers.has("authorization")) {
|
|
45
|
+
headers.set("authorization", `Bearer ${config.apiKey}`);
|
|
46
|
+
}
|
|
47
|
+
if (config.originHeader)
|
|
48
|
+
headers.set("x-origin-header", config.originHeader);
|
|
49
|
+
return instrumented(url, { ...rest, headers, operation });
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
request,
|
|
53
|
+
/** A REST call (`/rest/<store>/V1/...`) returning its parsed JSON body. */
|
|
54
|
+
async rest(path, init) {
|
|
55
|
+
const headers = new Headers(init.headers);
|
|
56
|
+
if (init.body !== undefined && !headers.has("content-type")) {
|
|
57
|
+
headers.set("content-type", "application/json");
|
|
58
|
+
}
|
|
59
|
+
const response = await request(path, { ...init, headers });
|
|
60
|
+
if (!response.ok)
|
|
61
|
+
throw new MagentoError(init.operation, response.status);
|
|
62
|
+
if (response.status === 204)
|
|
63
|
+
return undefined;
|
|
64
|
+
return (await response.json());
|
|
65
|
+
},
|
|
66
|
+
/**
|
|
67
|
+
* A GraphQL operation on `/graphql`. `operationName` is sent with the
|
|
68
|
+
* request and is the operation label. `headers` adds per-call headers,
|
|
69
|
+
* such as `Store` or a customer `Authorization` token. Storefront
|
|
70
|
+
* GraphQL is public, so the `apiKey` is sent only with
|
|
71
|
+
* `authenticated: true`, and never over a customer token.
|
|
72
|
+
*/
|
|
73
|
+
async graphql(query, variables, init) {
|
|
74
|
+
const { operationName, ...rest } = init;
|
|
75
|
+
const headers = new Headers(rest.headers);
|
|
76
|
+
headers.set("content-type", "application/json");
|
|
77
|
+
const response = await request("/graphql", {
|
|
78
|
+
operation: operationName,
|
|
79
|
+
authenticated: rest.authenticated ?? false,
|
|
80
|
+
method: "POST",
|
|
81
|
+
headers,
|
|
82
|
+
body: JSON.stringify({ query, variables, operationName }),
|
|
83
|
+
});
|
|
84
|
+
if (!response.ok)
|
|
85
|
+
throw new MagentoError(operationName, response.status);
|
|
86
|
+
const body = (await response.json());
|
|
87
|
+
if (body.errors?.length) {
|
|
88
|
+
throw new MagentoError(operationName, response.status, body.errors.length);
|
|
89
|
+
}
|
|
90
|
+
if (body.data === undefined)
|
|
91
|
+
throw new MagentoError(operationName, response.status);
|
|
92
|
+
return body.data;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=magentoClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"magentoClient.js","sourceRoot":"","sources":["../src/magentoClient.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,44 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-magento",
|
|
3
|
-
"version": "8.1.0-next.
|
|
3
|
+
"version": "8.1.0-next.2",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Thin Magento (Adobe Commerce) API client for Deco sites",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/decocms/blocks.git",
|
|
9
9
|
"directory": "packages/apps-magento"
|
|
10
10
|
},
|
|
11
|
-
"
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src",
|
|
14
|
+
"!src/**/*.test.ts",
|
|
15
|
+
"!src/**/__tests__"
|
|
16
|
+
],
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
12
19
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"./actions/*": "./src/actions/*.ts",
|
|
19
|
-
"./utils/*": "./src/utils/*.ts",
|
|
20
|
-
"./hooks/*": "./src/hooks/*.ts"
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"source": "./src/index.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
"scripts": {
|
|
23
|
-
"build": "tsc",
|
|
27
|
+
"build": "node ../../scripts/tsc.mjs --clean dist -p tsconfig.build.json",
|
|
28
|
+
"prepack": "npm run build",
|
|
24
29
|
"test": "vitest run --root ../.. packages/apps-magento/",
|
|
25
|
-
"typecheck": "tsc
|
|
26
|
-
"lint:unused": "knip"
|
|
30
|
+
"typecheck": "node ../../scripts/tsc.mjs -p tsconfig.json"
|
|
27
31
|
},
|
|
28
32
|
"dependencies": {
|
|
29
|
-
"@decocms/blocks": "8.1.0-next.
|
|
30
|
-
"@decocms/apps-commerce": "8.1.0-next.0",
|
|
31
|
-
"@decocms/tanstack": "8.1.0-next.0"
|
|
32
|
-
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"react": "^19.0.0",
|
|
35
|
-
"react-dom": "^19.0.0"
|
|
36
|
-
},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@types/react": "^19.0.0",
|
|
39
|
-
"@types/react-dom": "^19.0.0",
|
|
40
|
-
"knip": "^5.86.0",
|
|
41
|
-
"typescript": "^5.9.0"
|
|
33
|
+
"@decocms/blocks": "8.1.0-next.2"
|
|
42
34
|
},
|
|
43
35
|
"publishConfig": {
|
|
44
36
|
"registry": "https://registry.npmjs.org",
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// v7 (kept for v7 consumers until the v7 modules are dropped)
|
|
4
|
-
/**
|
|
5
|
-
* Magento app entry point for @decocms/apps.
|
|
6
|
-
* Re-exports client config + initializer.
|
|
7
|
-
*
|
|
8
|
-
* For actions/loaders/utils, use sub-path imports:
|
|
9
|
-
* import { features } from "@decocms/apps/magento/loaders/features"
|
|
10
|
-
* import { cart } from "@decocms/apps/magento/loaders/cart"
|
|
11
|
-
* import { magentoFetch } from "@decocms/apps/magento/client"
|
|
12
|
-
*/
|
|
13
|
-
export * from "./client";
|
|
1
|
+
// `@decocms/apps-magento`: the thin Magento client (see /next/upstream-clients).
|
|
14
2
|
export {
|
|
15
3
|
createMagentoClient,
|
|
16
4
|
type MagentoClient,
|
|
@@ -18,19 +6,4 @@ export {
|
|
|
18
6
|
type MagentoClientOptions,
|
|
19
7
|
MagentoError,
|
|
20
8
|
type MagentoRequestInit,
|
|
21
|
-
} from "./magentoClient";
|
|
22
|
-
export type { MagentoCart } from "./types";
|
|
23
|
-
export {
|
|
24
|
-
clearFetchCache,
|
|
25
|
-
type FetchCacheOptions,
|
|
26
|
-
getFetchCacheStats,
|
|
27
|
-
magentoCachedFetch,
|
|
28
|
-
} from "./utils/fetchCache";
|
|
29
|
-
// Observability wiring — sites call `setMagentoFetch(createMagentoFetch())` at
|
|
30
|
-
// boot to route every Magento egress call through the instrumented fetch
|
|
31
|
-
// (upstream latency/status), and use `magentoCachedFetch` for cacheable GETs
|
|
32
|
-
// (SWR hit/miss → `deco.cache.requests{layer="swr",profile="magento"}`).
|
|
33
|
-
export {
|
|
34
|
-
type CreateMagentoFetchOptions,
|
|
35
|
-
createMagentoFetch,
|
|
36
|
-
} from "./utils/instrumentedFetch";
|
|
9
|
+
} from "./magentoClient.ts";
|
package/src/README.md
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# Magento app — initial scaffold
|
|
2
|
-
|
|
3
|
-
This folder ports the Magento integration from `deco-cx/apps/magento`
|
|
4
|
-
(Fresh/Deno) to `@decocms/apps/magento` (TanStack Start/Node), following
|
|
5
|
-
the same shape as the existing `vtex/` and `shopify/` packages.
|
|
6
|
-
|
|
7
|
-
## Status
|
|
8
|
-
|
|
9
|
-
**Initial scaffold** — covers the configure/client surface and 2 reference
|
|
10
|
-
loaders (`features`, `cart`) so that downstream sites can wire magento at
|
|
11
|
-
all. The remaining 20+ loaders/actions exist as production-grade code in
|
|
12
|
-
the original deco-cx/apps repo and need adaptation passes (Deno → Node,
|
|
13
|
-
ctx-based to client-based state access, cookie helpers from
|
|
14
|
-
`@decocms/start/sdk/cookie`).
|
|
15
|
-
|
|
16
|
-
A real-world consumer (a production Magento storefront) is wiring magento
|
|
17
|
-
in-site today using a thin adapter that wraps the legacy `magento/mod.ts`
|
|
18
|
-
shape. Their adapter is the migration target — once this package covers
|
|
19
|
-
the surface area they need, the in-site copy goes away.
|
|
20
|
-
|
|
21
|
-
## What's here
|
|
22
|
-
|
|
23
|
-
- `client.ts` — `configureMagento({ baseUrl, apiKey, storeId, ... })` +
|
|
24
|
-
`getMagentoConfig()` global accessor. Mirrors the `configureVtex`
|
|
25
|
-
pattern.
|
|
26
|
-
- `types.ts` — request/response shapes shared between loaders.
|
|
27
|
-
- `loaders/features.ts` — returns the feature flags block. The simplest
|
|
28
|
-
loader, used as a smoke test.
|
|
29
|
-
- `loaders/cart.ts` — fetches the customer's active cart by cookie.
|
|
30
|
-
Pulls cookies via `@decocms/start/sdk/cookie`.
|
|
31
|
-
- `middleware.ts` — passthrough today; real-world cart-id reconciliation
|
|
32
|
-
lives in the consumer site for now.
|
|
33
|
-
- `index.ts` — re-export entry.
|
|
34
|
-
|
|
35
|
-
## Pending port (PR follow-ups)
|
|
36
|
-
|
|
37
|
-
| Path | Original location |
|
|
38
|
-
|---|---|
|
|
39
|
-
| `loaders/product/{detailsPage,detailsPageGQL,listingPage,list,relatedProducts}.ts` | `deco-cx/apps/magento/loaders/product/*` |
|
|
40
|
-
| `loaders/{proxy,user,wishlist}.ts` | `deco-cx/apps/magento/loaders/*` |
|
|
41
|
-
| `loaders/routes/getRouteType.ts` | idem |
|
|
42
|
-
| `actions/cart/{addCoupon,addItem,removeCoupon,removeItem,setSimulation,simulation,updateItem}.ts` | `deco-cx/apps/magento/actions/cart/*` |
|
|
43
|
-
| `actions/newsletter/subscribe.ts` | idem |
|
|
44
|
-
| `actions/product/stockAlert.ts` | idem |
|
|
45
|
-
| `actions/wishlist/{addItem,removeItem}.ts` | idem |
|
|
46
|
-
| `utils/{clientGraphql,client,transform,cache,graphql,cart}.ts` | `deco-cx/apps/magento/utils/*` |
|
|
47
|
-
| `hooks/{useCart,useUser,useWishlist}.ts` | `deco-cx/apps/magento/hooks/*` (refactor to react-query, like `vtex/hooks/`) |
|
|
48
|
-
| `inline-loaders/*` | new, follow `vtex/inline-loaders/` shape |
|
|
49
|
-
|
|
50
|
-
**Site-specific extensions (Livelo, Amasty)** in the deco-cx/apps repo
|
|
51
|
-
should stay out of this package — they belong in consumer sites via the
|
|
52
|
-
`ExtensionOf` pattern. Preserving that pattern in the generic loaders
|
|
53
|
-
above is a hard requirement of this port.
|
|
54
|
-
|
|
55
|
-
## Why a stub now
|
|
56
|
-
|
|
57
|
-
A production Magento storefront's migration hit a HIGH parity finding:
|
|
58
|
-
|
|
59
|
-
```
|
|
60
|
-
invoke(magento/loaders/features) failed: handler not found
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
…because no `@decocms/apps/magento/*` resolver existed. The site is
|
|
64
|
-
working around it locally; this PR begins the upstream fix so future
|
|
65
|
-
magento sites don't have to repeat the in-site adapter.
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the cart loader.
|
|
3
|
-
*
|
|
4
|
-
* Parity goals against deco-cx/apps/magento/loaders/cart.ts (Fresh/Deno,
|
|
5
|
-
* prod):
|
|
6
|
-
*
|
|
7
|
-
* - Reads `dataservices_cart_id` cookie when caller doesn't supply
|
|
8
|
-
* `cartId` in props (matches `getCartCookie(req.headers)` in prod).
|
|
9
|
-
* - Honors props.cartId override (matches `_cartId ?? getCartCookie()`).
|
|
10
|
-
* - Hits /rest/:site/V1/carts/:cartId — the /rest/ prefix is what the
|
|
11
|
-
* Magento REST API exposes; the legacy clientAdmin's typed key was
|
|
12
|
-
* "GET /rest/:site/V1/carts/:cartId".
|
|
13
|
-
* - Encodes site + cartId so a malicious cookie can't escape the path
|
|
14
|
-
* segment and reach another admin endpoint with the Bearer token.
|
|
15
|
-
* - Returns null when no cookie (anonymous visitor — matches prod's
|
|
16
|
-
* `if (!cartId) return null`).
|
|
17
|
-
* - Returns null on 404 (expired cookie — prod hits this via the same
|
|
18
|
-
* branch but throws on other non-2xx; we surface a plain Error).
|
|
19
|
-
*
|
|
20
|
-
* NOT covered by this initial port (and intentionally not tested here):
|
|
21
|
-
* - Pre-cart `GET /rest/:site/V1/carts/mine` warm-up for logged-in
|
|
22
|
-
* users (the original does it inside a try/catch and discards the
|
|
23
|
-
* result — it's a cache primer, not a correctness invariant).
|
|
24
|
-
* - Parallel /totals fetch + image-pipeline transform — those land in
|
|
25
|
-
* follow-up PRs that port utils/cart.ts + utils/cache.ts.
|
|
26
|
-
*/
|
|
27
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
28
|
-
import { configureMagento } from "../client";
|
|
29
|
-
import cart from "../loaders/cart";
|
|
30
|
-
|
|
31
|
-
function mockResponse(body: unknown, status = 200): Response {
|
|
32
|
-
return new Response(JSON.stringify(body), {
|
|
33
|
-
status,
|
|
34
|
-
headers: { "content-type": "application/json" },
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function requestWithCookie(cookie: string): Request {
|
|
39
|
-
const r = new Request("http://localhost/");
|
|
40
|
-
// Headers in a freshly-constructed Request enter "request" guard mode
|
|
41
|
-
// which silently drops `cookie`. Build a Headers manually then attach.
|
|
42
|
-
const headers = new Headers();
|
|
43
|
-
headers.set("cookie", cookie);
|
|
44
|
-
Object.defineProperty(r, "headers", { value: headers, configurable: true });
|
|
45
|
-
return r;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
describe("cart loader", () => {
|
|
49
|
-
let fetchSpy: ReturnType<typeof vi.spyOn>;
|
|
50
|
-
|
|
51
|
-
beforeEach(() => {
|
|
52
|
-
configureMagento({
|
|
53
|
-
baseUrl: "https://loja.example.com/",
|
|
54
|
-
apiKey: "secret",
|
|
55
|
-
storeId: 1,
|
|
56
|
-
site: "example",
|
|
57
|
-
});
|
|
58
|
-
fetchSpy = vi.spyOn(globalThis, "fetch");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
afterEach(() => {
|
|
62
|
-
vi.restoreAllMocks();
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it("returns null when no cart cookie is present", async () => {
|
|
66
|
-
const req = new Request("http://localhost/");
|
|
67
|
-
const result = await cart(undefined, req);
|
|
68
|
-
expect(result).toBeNull();
|
|
69
|
-
expect(fetchSpy).not.toHaveBeenCalled();
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it("reads cartId from the dataservices_cart_id cookie (JSON-encoded)", async () => {
|
|
73
|
-
fetchSpy.mockResolvedValue(mockResponse({ id: "abc123", items: [] }));
|
|
74
|
-
const req = requestWithCookie('dataservices_cart_id="abc123"');
|
|
75
|
-
await cart(undefined, req);
|
|
76
|
-
const [target] = fetchSpy.mock.calls[0] as [URL];
|
|
77
|
-
expect(target.toString()).toBe("https://loja.example.com/rest/example/V1/carts/abc123");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("reads cartId from the cookie when it's not JSON-quoted", async () => {
|
|
81
|
-
fetchSpy.mockResolvedValue(mockResponse({ id: "raw-id", items: [] }));
|
|
82
|
-
const req = requestWithCookie("dataservices_cart_id=raw-id");
|
|
83
|
-
await cart(undefined, req);
|
|
84
|
-
const [target] = fetchSpy.mock.calls[0] as [URL];
|
|
85
|
-
expect(target.toString()).toBe("https://loja.example.com/rest/example/V1/carts/raw-id");
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("honors props.cartId override (cookie ignored)", async () => {
|
|
89
|
-
fetchSpy.mockResolvedValue(mockResponse({ id: "from-props", items: [] }));
|
|
90
|
-
const req = requestWithCookie('dataservices_cart_id="from-cookie"');
|
|
91
|
-
await cart({ cartId: "from-props" }, req);
|
|
92
|
-
const [target] = fetchSpy.mock.calls[0] as [URL];
|
|
93
|
-
expect(target.toString()).toContain("/rest/example/V1/carts/from-props");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("URL-encodes the cartId to prevent path injection", async () => {
|
|
97
|
-
// A malicious cookie value tries to break out of the path segment
|
|
98
|
-
// and reach a different admin endpoint with the Bearer attached.
|
|
99
|
-
fetchSpy.mockResolvedValue(mockResponse(null, 404));
|
|
100
|
-
const req = requestWithCookie('dataservices_cart_id="../admin/leak?secret=1"');
|
|
101
|
-
await cart(undefined, req);
|
|
102
|
-
const [target] = fetchSpy.mock.calls[0] as [URL];
|
|
103
|
-
// Encoded: %2F (slash), %3F (?), %3D (=). The trailing
|
|
104
|
-
// "/admin/leak?secret=1" stays inside the path segment.
|
|
105
|
-
expect(target.pathname).toMatch(
|
|
106
|
-
/^\/rest\/example\/V1\/carts\/(\.\.|%2E%2E)%2Fadmin%2Fleak%3Fsecret%3D1$/i,
|
|
107
|
-
);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("returns null on 404 (expired cart cookie)", async () => {
|
|
111
|
-
fetchSpy.mockResolvedValue(mockResponse(null, 404));
|
|
112
|
-
const req = requestWithCookie('dataservices_cart_id="stale"');
|
|
113
|
-
const result = await cart(undefined, req);
|
|
114
|
-
expect(result).toBeNull();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("throws on non-404 errors", async () => {
|
|
118
|
-
fetchSpy.mockResolvedValue(mockResponse(null, 500));
|
|
119
|
-
const req = requestWithCookie('dataservices_cart_id="abc"');
|
|
120
|
-
await expect(cart(undefined, req)).rejects.toThrow(/cart loader: 500/);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it("attaches Bearer + x-origin-header (same-origin) on the cart fetch", async () => {
|
|
124
|
-
configureMagento({
|
|
125
|
-
baseUrl: "https://loja.example.com/",
|
|
126
|
-
apiKey: "secret",
|
|
127
|
-
storeId: 1,
|
|
128
|
-
site: "example",
|
|
129
|
-
originHeader: "origin-secret",
|
|
130
|
-
});
|
|
131
|
-
fetchSpy.mockResolvedValue(mockResponse({ id: "abc", items: [] }));
|
|
132
|
-
const req = requestWithCookie('dataservices_cart_id="abc"');
|
|
133
|
-
await cart(undefined, req);
|
|
134
|
-
const [, init] = fetchSpy.mock.calls[0] as [URL, RequestInit];
|
|
135
|
-
const headers = init.headers as Headers;
|
|
136
|
-
expect(headers.get("authorization")).toBe("Bearer secret");
|
|
137
|
-
expect(headers.get("x-origin-header")).toBe("origin-secret");
|
|
138
|
-
});
|
|
139
|
-
});
|