@decocms/apps-salesforce 7.31.6 → 7.32.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/package.json +3 -3
- package/src/index.ts +4 -0
- package/src/utils/httpClient.ts +16 -1
- package/src/utils/instrumentedFetch.ts +41 -0
- package/src/utils/operationRouter.ts +25 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-salesforce",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.32.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco commerce app: Salesforce Commerce Cloud integration",
|
|
6
6
|
"repository": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"lint:unused": "knip"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@decocms/blocks": "7.
|
|
26
|
-
"@decocms/apps-commerce": "7.
|
|
25
|
+
"@decocms/blocks": "7.32.0",
|
|
26
|
+
"@decocms/apps-commerce": "7.32.0"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@tanstack/react-start": ">=1.0.0",
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,10 @@ export type {
|
|
|
25
25
|
SalesforceProduct,
|
|
26
26
|
} from "./types";
|
|
27
27
|
export { createHttpClient, type HttpClientOptions } from "./utils/httpClient";
|
|
28
|
+
export {
|
|
29
|
+
type CreateSalesforceFetchOptions,
|
|
30
|
+
createSalesforceFetch,
|
|
31
|
+
} from "./utils/instrumentedFetch";
|
|
28
32
|
export { parseUserCookie } from "./utils/parseUserCookie";
|
|
29
33
|
export {
|
|
30
34
|
createProductTransformer,
|
package/src/utils/httpClient.ts
CHANGED
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* loaders that call `.then(res => res.json())` keep working).
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
+
import { createSalesforceFetch } from "./instrumentedFetch";
|
|
20
|
+
|
|
19
21
|
export interface HttpClientOptions {
|
|
20
22
|
base: string;
|
|
21
23
|
headers?: Record<string, string> | Headers;
|
|
@@ -29,9 +31,22 @@ interface IndexedRouteResponse {
|
|
|
29
31
|
headers: Headers;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
// Lazily-created, module-shared instrumented fetch used as the default
|
|
35
|
+
// transport. Every Salesforce loader instantiates its own `createHttpClient`
|
|
36
|
+
// inline, so defaulting the `fetcher` here is the single chokepoint that
|
|
37
|
+
// instruments ALL Salesforce egress (upstream latency/status → ClickHouse,
|
|
38
|
+
// `provider:"salesforce"`) without touching each loader. Sites that pass an
|
|
39
|
+
// explicit `fetcher` opt out; wrap it with `createSalesforceFetch({ baseFetch })`
|
|
40
|
+
// to keep instrumentation.
|
|
41
|
+
let _defaultFetch: typeof fetch | undefined;
|
|
42
|
+
function defaultInstrumentedFetch(): typeof fetch {
|
|
43
|
+
if (!_defaultFetch) _defaultFetch = createSalesforceFetch();
|
|
44
|
+
return _defaultFetch;
|
|
45
|
+
}
|
|
46
|
+
|
|
32
47
|
export function createHttpClient<_Routes = unknown>(options: HttpClientOptions) {
|
|
33
48
|
const base = options.base.replace(/\/$/, "");
|
|
34
|
-
const fetchImpl = options.fetcher ??
|
|
49
|
+
const fetchImpl = options.fetcher ?? defaultInstrumentedFetch();
|
|
35
50
|
const defaultHeaders: Record<string, string> =
|
|
36
51
|
options.headers instanceof Headers
|
|
37
52
|
? Object.fromEntries(options.headers.entries())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-wired instrumented fetch factory for Salesforce Commerce Cloud.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `vtex` / `shopify` / `magento`. Bundles `createInstrumentedFetch`
|
|
5
|
+
* (spans, traceparent, URL redaction), the `salesforceOperationRouter`, and an
|
|
6
|
+
* `onComplete` that records `http.client.request.duration` with
|
|
7
|
+
* `provider: "salesforce"`.
|
|
8
|
+
*
|
|
9
|
+
* Salesforce has no module-global `_fetch`; instead `createHttpClient` defaults
|
|
10
|
+
* its `fetcher` to {@link createSalesforceFetch}, so EVERY loader's egress is
|
|
11
|
+
* instrumented automatically without per-loader wiring. Sites can still pass an
|
|
12
|
+
* explicit `fetcher` to `createHttpClient` (e.g. for cookie passthrough) — wrap
|
|
13
|
+
* it with `createSalesforceFetch({ baseFetch })` to keep the instrumentation.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
createInstrumentedFetch,
|
|
18
|
+
type InstrumentedFetch,
|
|
19
|
+
} from "@decocms/blocks/sdk/instrumentedFetch";
|
|
20
|
+
import { recordCommerceMetric, statusClassFor } from "@decocms/blocks/sdk/observability";
|
|
21
|
+
import { salesforceOperationRouter } from "./operationRouter";
|
|
22
|
+
|
|
23
|
+
export interface CreateSalesforceFetchOptions {
|
|
24
|
+
/** Underlying fetch to wrap. Defaults to `globalThis.fetch`. */
|
|
25
|
+
baseFetch?: typeof fetch;
|
|
26
|
+
/** Disable the histogram (spans + logs still emit). Default: false. */
|
|
27
|
+
disableHistogram?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createSalesforceFetch(
|
|
31
|
+
options: CreateSalesforceFetchOptions = {},
|
|
32
|
+
): InstrumentedFetch {
|
|
33
|
+
const { baseFetch, disableHistogram = false } = options;
|
|
34
|
+
return createInstrumentedFetch({
|
|
35
|
+
name: "salesforce",
|
|
36
|
+
baseFetch,
|
|
37
|
+
resolveOperation: salesforceOperationRouter,
|
|
38
|
+
onComplete: disableHistogram ? undefined : (r) =>
|
|
39
|
+
recordCommerceMetric(r.durationMs, { provider: "salesforce", operation: r.operation, status_class: statusClassFor(r.status), cached: r.cached }),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL-derived operation name router for Salesforce Commerce Cloud calls.
|
|
3
|
+
*
|
|
4
|
+
* Plugged into `@decocms/blocks/sdk/instrumentedFetch`'s `resolveOperation`.
|
|
5
|
+
* SFCC's OCAPI/SCAPI surface is wide; naming by the first meaningful path
|
|
6
|
+
* segment keeps the histogram label low-cardinality without hand-listing every
|
|
7
|
+
* endpoint. Returns `undefined` when nothing meaningful is found, so the
|
|
8
|
+
* framework falls back to `salesforce.fetch`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { extractPathname } from "@decocms/blocks/sdk/urlUtils";
|
|
12
|
+
|
|
13
|
+
export function salesforceOperationRouter(url: string, _method: string): string | undefined {
|
|
14
|
+
const pathname = extractPathname(url);
|
|
15
|
+
|
|
16
|
+
// SCAPI: /shopper/<api>/v1/... ; OCAPI: /s/<site>/dw/shop/<version>/<resource>/...
|
|
17
|
+
const scapi = pathname.match(/\/shopper\/([^/]+)/);
|
|
18
|
+
if (scapi) return `scapi.${scapi[1]}`;
|
|
19
|
+
const ocapi = pathname.match(/\/dw\/(?:shop|data)\/[^/]+\/([^/?#]+)/);
|
|
20
|
+
if (ocapi) return `ocapi.${ocapi[1]}`;
|
|
21
|
+
|
|
22
|
+
// Fallback: first non-empty path segment.
|
|
23
|
+
const seg = pathname.split("/").find(Boolean);
|
|
24
|
+
return seg ? `path.${seg}` : undefined;
|
|
25
|
+
}
|