@decocms/apps-vtex 8.0.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.
Files changed (133) hide show
  1. package/package.json +7 -52
  2. package/src/index.ts +12 -15
  3. package/src/utils/constants.ts +2 -56
  4. package/src/utils/cookieSanitizer.ts +59 -59
  5. package/src/utils/operationRouter.ts +76 -72
  6. package/src/utils/types.ts +709 -1557
  7. package/src/vtexClient.ts +444 -0
  8. package/src/README.md +0 -6
  9. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  10. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  11. package/src/__tests__/schemas.test.ts +0 -71
  12. package/src/actions/address.ts +0 -250
  13. package/src/actions/analytics/sendEvent.ts +0 -86
  14. package/src/actions/auth.ts +0 -333
  15. package/src/actions/checkout.ts +0 -659
  16. package/src/actions/index.ts +0 -11
  17. package/src/actions/masterData.ts +0 -168
  18. package/src/actions/misc.ts +0 -188
  19. package/src/actions/newsletter.ts +0 -105
  20. package/src/actions/orders.ts +0 -34
  21. package/src/actions/profile.ts +0 -201
  22. package/src/actions/session.ts +0 -85
  23. package/src/actions/trigger.ts +0 -43
  24. package/src/actions/wishlist.ts +0 -114
  25. package/src/client.ts +0 -667
  26. package/src/commerceLoaders.ts +0 -267
  27. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  28. package/src/hooks/__tests__/createCart.test.ts +0 -92
  29. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  30. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  31. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  32. package/src/hooks/cartQuery.ts +0 -204
  33. package/src/hooks/createCart.ts +0 -427
  34. package/src/hooks/createUseCart.ts +0 -360
  35. package/src/hooks/createUseUser.ts +0 -153
  36. package/src/hooks/createUseWishlist.ts +0 -242
  37. package/src/hooks/index.ts +0 -25
  38. package/src/hooks/useAutocomplete.ts +0 -83
  39. package/src/hooks/useCart.ts +0 -243
  40. package/src/hooks/useUser.ts +0 -78
  41. package/src/hooks/useWishlist.ts +0 -119
  42. package/src/invoke.ts +0 -235
  43. package/src/loaders/ProductDetailsPage.ts +0 -1
  44. package/src/loaders/ProductList.ts +0 -1
  45. package/src/loaders/ProductListingPage.ts +0 -1
  46. package/src/loaders/address.ts +0 -115
  47. package/src/loaders/autocomplete.ts +0 -58
  48. package/src/loaders/brands.ts +0 -44
  49. package/src/loaders/cart/attachments.ts +0 -53
  50. package/src/loaders/cart/full.ts +0 -67
  51. package/src/loaders/cart/gifts.ts +0 -43
  52. package/src/loaders/cart/orderFormId.ts +0 -31
  53. package/src/loaders/cart/shipping.test.ts +0 -125
  54. package/src/loaders/cart/shipping.ts +0 -136
  55. package/src/loaders/cart/summary.ts +0 -36
  56. package/src/loaders/cart.ts +0 -52
  57. package/src/loaders/catalog.ts +0 -163
  58. package/src/loaders/collections.ts +0 -55
  59. package/src/loaders/index.ts +0 -20
  60. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  61. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -113
  62. package/src/loaders/intelligentSearch/productList.ts +0 -154
  63. package/src/loaders/intelligentSearch/productListingPage.ts +0 -526
  64. package/src/loaders/intelligentSearch/suggestions.ts +0 -46
  65. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  66. package/src/loaders/legacy/productList.ts +0 -1
  67. package/src/loaders/legacy/relatedProductsLoader.ts +0 -81
  68. package/src/loaders/legacy.ts +0 -635
  69. package/src/loaders/logistics.ts +0 -111
  70. package/src/loaders/minicart.ts +0 -78
  71. package/src/loaders/navbar.ts +0 -26
  72. package/src/loaders/orders.ts +0 -92
  73. package/src/loaders/pageType.ts +0 -68
  74. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  75. package/src/loaders/payment.ts +0 -97
  76. package/src/loaders/productListFull.ts +0 -159
  77. package/src/loaders/profile.ts +0 -138
  78. package/src/loaders/promotion.ts +0 -30
  79. package/src/loaders/search.ts +0 -124
  80. package/src/loaders/session.ts +0 -83
  81. package/src/loaders/user.ts +0 -87
  82. package/src/loaders/wishlist.ts +0 -89
  83. package/src/loaders/wishlistProducts.ts +0 -69
  84. package/src/loaders/workflow/products.ts +0 -64
  85. package/src/loaders/workflow.ts +0 -316
  86. package/src/logo.png +0 -0
  87. package/src/manifest.gen.ts +0 -89
  88. package/src/middleware.ts +0 -240
  89. package/src/mod.ts +0 -157
  90. package/src/registry.ts +0 -9
  91. package/src/schemas.gen.ts +0 -3191
  92. package/src/schemas.ts +0 -30
  93. package/src/types.ts +0 -248
  94. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  95. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  96. package/src/utils/__tests__/fetch.test.ts +0 -80
  97. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  98. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  99. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  100. package/src/utils/__tests__/minicart.test.ts +0 -184
  101. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  102. package/src/utils/__tests__/resilience.test.ts +0 -205
  103. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  104. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  105. package/src/utils/__tests__/sitemap.test.ts +0 -185
  106. package/src/utils/__tests__/slugify.test.ts +0 -31
  107. package/src/utils/__tests__/transform.test.ts +0 -769
  108. package/src/utils/accountLoaders.ts +0 -203
  109. package/src/utils/authHelpers.ts +0 -85
  110. package/src/utils/batch.ts +0 -18
  111. package/src/utils/cartProjection.ts +0 -96
  112. package/src/utils/cookies.ts +0 -167
  113. package/src/utils/enrichment.ts +0 -560
  114. package/src/utils/fetch.ts +0 -107
  115. package/src/utils/fetchCache.ts +0 -199
  116. package/src/utils/index.ts +0 -19
  117. package/src/utils/instrumentedFetch.ts +0 -97
  118. package/src/utils/intelligentSearch.ts +0 -96
  119. package/src/utils/legacy.ts +0 -139
  120. package/src/utils/minicart.ts +0 -139
  121. package/src/utils/pickAndOmit.ts +0 -25
  122. package/src/utils/proxy.ts +0 -435
  123. package/src/utils/resilience.ts +0 -352
  124. package/src/utils/resourceRange.ts +0 -10
  125. package/src/utils/segment.ts +0 -152
  126. package/src/utils/similars.ts +0 -37
  127. package/src/utils/simulationCache.ts +0 -73
  128. package/src/utils/sitemap.ts +0 -282
  129. package/src/utils/slugCache.ts +0 -28
  130. package/src/utils/slugify.ts +0 -13
  131. package/src/utils/transform.ts +0 -1522
  132. package/src/utils/vtexId.ts +0 -138
  133. package/tsconfig.json +0 -7
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@decocms/apps-vtex",
3
- "version": "8.0.0",
3
+ "version": "8.1.0-next.1",
4
4
  "type": "module",
5
- "description": "Deco commerce app: VTEX integration",
5
+ "description": "Thin VTEX API client for Deco sites",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/decocms/blocks.git",
@@ -10,67 +10,22 @@
10
10
  },
11
11
  "files": [
12
12
  "src",
13
- "tsconfig.json"
13
+ "!src/**/*.test.ts",
14
+ "!src/**/__tests__"
14
15
  ],
15
16
  "main": "./src/index.ts",
16
17
  "exports": {
17
- ".": "./src/index.ts",
18
- "./commerceLoaders": "./src/commerceLoaders.ts",
19
- "./mod": "./src/mod.ts",
20
- "./schemas": "./src/schemas.ts",
21
- "./client": "./src/client.ts",
22
- "./types": "./src/types.ts",
23
- "./actions": "./src/actions/index.ts",
24
- "./actions/*": "./src/actions/*.ts",
25
- "./actions/analytics/*": "./src/actions/analytics/*.ts",
26
- "./loaders": "./src/loaders/index.ts",
27
- "./loaders/*": "./src/loaders/*.ts",
28
- "./utils": "./src/utils/index.ts",
29
- "./utils/*": "./src/utils/*.ts",
30
- "./loaders/intelligentSearch/*": "./src/loaders/intelligentSearch/*.ts",
31
- "./loaders/legacy/*": "./src/loaders/legacy/*.ts",
32
- "./loaders/workflow/*": "./src/loaders/workflow/*.ts",
33
- "./inline-loaders/productDetailsPage": "./src/loaders/intelligentSearch/productDetailsPage.ts",
34
- "./inline-loaders/productListingPage": "./src/loaders/intelligentSearch/productListingPage.ts",
35
- "./inline-loaders/productList": "./src/loaders/productListFull.ts",
36
- "./inline-loaders/productListShelf": "./src/loaders/intelligentSearch/productList.ts",
37
- "./inline-loaders/relatedProducts": "./src/loaders/legacy/relatedProductsLoader.ts",
38
- "./inline-loaders/suggestions": "./src/loaders/intelligentSearch/suggestions.ts",
39
- "./inline-loaders/minicart": "./src/loaders/minicart.ts",
40
- "./inline-loaders/workflowProducts": "./src/loaders/workflow/products.ts",
41
- "./hooks": "./src/hooks/index.ts",
42
- "./hooks/*": "./src/hooks/*.ts",
43
- "./middleware": "./src/middleware.ts",
44
- "./registry": "./src/registry.ts"
18
+ ".": "./src/index.ts"
45
19
  },
46
20
  "scripts": {
47
21
  "build": "tsc",
48
- "generate:schemas": "bun ../blocks-cli/scripts/generate-app-schemas.ts",
49
22
  "test": "vitest run --root ../.. packages/apps-vtex/",
50
- "typecheck": "tsc --noEmit",
51
- "lint:unused": "knip"
23
+ "typecheck": "tsc --noEmit"
52
24
  },
53
25
  "dependencies": {
54
- "@decocms/blocks": "8.0.0",
55
- "@decocms/apps-commerce": "8.0.0",
56
- "@decocms/apps-website": "8.0.0",
57
- "@decocms/tanstack": "8.0.0"
58
- },
59
- "peerDependencies": {
60
- "@tanstack/react-query": ">=5.0.0",
61
- "react": "^19.0.0",
62
- "react-dom": "^19.0.0"
63
- },
64
- "peerDependenciesMeta": {
65
- "@tanstack/react-query": {
66
- "optional": true
67
- }
26
+ "@decocms/blocks": "8.1.0-next.1"
68
27
  },
69
28
  "devDependencies": {
70
- "@tanstack/react-query": "^5.96.0",
71
- "@types/react": "^19.0.0",
72
- "@types/react-dom": "^19.0.0",
73
- "knip": "^5.86.0",
74
29
  "typescript": "^5.9.0"
75
30
  },
76
31
  "publishConfig": {
package/src/index.ts CHANGED
@@ -1,17 +1,14 @@
1
1
  /**
2
- * VTEX app entry point for @decocms/apps.
3
- * Re-exports client config + initializer + app contract.
4
- *
5
- * For actions/loaders/utils, use sub-path imports:
6
- * import { addItemsToCart } from "@decocms/apps/vtex/actions/checkout"
7
- * import { searchProducts } from "@decocms/apps/vtex/loaders/catalog"
8
- * import { slugify } from "@decocms/apps/vtex/utils/slugify"
9
- *
10
- * Or barrel imports:
11
- * import { addItemsToCart } from "@decocms/apps/vtex/actions"
12
- * import { searchProducts } from "@decocms/apps/vtex/loaders"
2
+ * `@decocms/apps-vtex`: a thin, instrumented VTEX client (`createVtexClient`).
3
+ * See ./vtexClient.ts and /next/upstream-clients.
13
4
  */
14
- export * from "./client";
15
- export { configure, type VtexState } from "./mod";
16
- export { type CreateVtexFetchOptions, createVtexFetch } from "./utils/instrumentedFetch";
17
- export { vtexOperationRouter } from "./utils/operationRouter";
5
+ export {
6
+ createVtexClient,
7
+ type VtexCatalogSearchArgs,
8
+ type VtexClient,
9
+ type VtexClientConfig,
10
+ VtexError,
11
+ type VtexRequestOptions,
12
+ type VtexResponse,
13
+ type VtexSearchArgs,
14
+ } from "./vtexClient";
@@ -1,62 +1,8 @@
1
1
  /**
2
- * Tuning knobs for the VTEX fetch resilience + cache layer.
3
- *
4
- * Every timeout, limit, and threshold that governs how `resilience.ts`
5
- * (abort/timeout, circuit breaker, retry budget) and `fetchCache.ts` (SWR +
6
- * stale-if-error) behave under VTEX slowness/outages lives HERE, in one file,
7
- * with the reasoning for each number next to it. Auditing or tuning the
8
- * resilience posture means reading one file, not hunting across two.
2
+ * Tuning knobs for VTEX request resilience (timeouts, retries, circuit
3
+ * breaker), with the reasoning for each number next to it.
9
4
  */
10
5
 
11
- // ---------------------------------------------------------------------------
12
- // fetchCache.ts — SWR + stale-if-error cache for VTEX GET responses
13
- // ---------------------------------------------------------------------------
14
-
15
- /** Max distinct cache keys kept in memory; oldest `createdAt` evicted first. */
16
- export const FETCH_CACHE_MAX_ENTRIES = 500;
17
-
18
- /**
19
- * How long a cached response is considered FRESH, keyed by status class.
20
- * Past this the entry is stale — served via SWR (2xx) while a background
21
- * refresh runs, or treated as expired (404/5xx). See `ttlForStatus` in
22
- * `fetchCache.ts`.
23
- */
24
- export const FETCH_CACHE_FRESH_TTL_MS = {
25
- /** 2xx — 3 min. Catalog/price data doesn't need to be second-fresh. */
26
- success: 180_000,
27
- /** 404 — 10s. Short on purpose: a just-published SKU shouldn't 404 long. */
28
- notFound: 10_000,
29
- /** 5xx — never "fresh". A server error is never treated as a good cache hit. */
30
- serverError: 0,
31
- } as const;
32
-
33
- /**
34
- * Stale-if-error window: how long PAST the freshness TTL a last-good 2xx entry
35
- * may still be served when the origin is failing (5xx / timeout / circuit
36
- * open). This is the "availability first" knob — a warm key survives a full
37
- * VTEX outage for up to this long with zero upstream pressure; once an entry
38
- * is older than `freshTtl + this`, it's considered too stale to serve and the
39
- * error surfaces (→ the degraded / branded-error path takes over). Mirrors the
40
- * edge `sie` window sites configure for the product/listing cache profiles.
41
- */
42
- export const FETCH_CACHE_STALE_IF_ERROR_MS = 86_400_000; // 24h
43
-
44
- /**
45
- * Inflight-slot backstop timeout. Bounds how long a single hung `fetch()` can
46
- * hold a dedup entry alive. Without this, a VTEX subrequest that never
47
- * settles leaks the inflight Map slot forever and every subsequent request
48
- * for the same cache key joins the zombie Promise, pinning memory until
49
- * `exceededMemory` (observed in prod: 514 hard crashes / 24h on a PLP route).
50
- *
51
- * MUST stay ABOVE `RESILIENCE_CONFIG.totalTimeoutMs` (see below) — the
52
- * resilient fetch already owns real per-call abort/timeout, so this is only a
53
- * last-resort backstop. If it fired first it would kill a slow-but-recoverable
54
- * response the resilience layer was about to deliver, discard the good body
55
- * (it never reaches the cache), and free the dedup slot early so a concurrent
56
- * request launches a duplicate upstream fetch.
57
- */
58
- export const FETCH_CACHE_INFLIGHT_BACKSTOP_MS = 15_000;
59
-
60
6
  // ---------------------------------------------------------------------------
61
7
  // resilience.ts — abort/timeout, retry budget, circuit breaker
62
8
  // ---------------------------------------------------------------------------
@@ -29,36 +29,36 @@
29
29
  * `*.vtexcommercestable.com.br` / `*.myvtex.com` / `secure.<storefront>` APIs.
30
30
  */
31
31
  export const VTEX_COOKIE_PREFIXES: readonly string[] = [
32
- "VtexIdclientAutCookie",
33
- "checkout.vtex.com",
34
- "CheckoutOrderFormOwnership",
35
- "vtex_session",
36
- "vtex_segment",
37
- "vtex_is_",
38
- "janus_sid",
32
+ "VtexIdclientAutCookie",
33
+ "checkout.vtex.com",
34
+ "CheckoutOrderFormOwnership",
35
+ "vtex_session",
36
+ "vtex_segment",
37
+ "vtex_is_",
38
+ "janus_sid",
39
39
  ];
40
40
 
41
41
  export type DropReason = "non_ascii" | "not_in_allowlist" | "malformed";
42
42
 
43
43
  export interface DroppedCookie {
44
- name: string;
45
- reason: DropReason;
44
+ name: string;
45
+ reason: DropReason;
46
46
  }
47
47
 
48
48
  export interface CookieSanitizeResult {
49
- /** Cookie header string ready to forward (may be empty). */
50
- cookies: string;
51
- /** Cookies that were filtered out, with the reason per cookie. */
52
- dropped: DroppedCookie[];
49
+ /** Cookie header string ready to forward (may be empty). */
50
+ cookies: string;
51
+ /** Cookies that were filtered out, with the reason per cookie. */
52
+ dropped: DroppedCookie[];
53
53
  }
54
54
 
55
55
  export interface CookieSanitizeOptions {
56
- /**
57
- * When true, additionally enforces an allowlist: only cookies whose name
58
- * starts with one of `VTEX_COOKIE_PREFIXES` are kept.
59
- * @default false
60
- */
61
- allowlist?: boolean;
56
+ /**
57
+ * When true, additionally enforces an allowlist: only cookies whose name
58
+ * starts with one of `VTEX_COOKIE_PREFIXES` are kept.
59
+ * @default false
60
+ */
61
+ allowlist?: boolean;
62
62
  }
63
63
 
64
64
  /**
@@ -93,47 +93,47 @@ const ASCII_VALUE_RE = /^[\x20-\x7E]*$/;
93
93
  * ```
94
94
  */
95
95
  export function sanitizeOutboundCookieHeader(
96
- raw: string,
97
- opts: CookieSanitizeOptions = {},
96
+ raw: string,
97
+ opts: CookieSanitizeOptions = {},
98
98
  ): CookieSanitizeResult {
99
- if (!raw) return { cookies: "", dropped: [] };
99
+ if (!raw) return { cookies: "", dropped: [] };
100
100
 
101
- const kept: string[] = [];
102
- const dropped: DroppedCookie[] = [];
103
- const allowlist = opts.allowlist === true;
101
+ const kept: string[] = [];
102
+ const dropped: DroppedCookie[] = [];
103
+ const allowlist = opts.allowlist === true;
104
104
 
105
- for (const segment of raw.split(";")) {
106
- const pair = segment.trim();
107
- if (!pair) continue;
105
+ for (const segment of raw.split(";")) {
106
+ const pair = segment.trim();
107
+ if (!pair) continue;
108
108
 
109
- const eq = pair.indexOf("=");
110
- if (eq <= 0) {
111
- dropped.push({ name: pair.slice(0, 32), reason: "malformed" });
112
- continue;
113
- }
109
+ const eq = pair.indexOf("=");
110
+ if (eq <= 0) {
111
+ dropped.push({ name: pair.slice(0, 32), reason: "malformed" });
112
+ continue;
113
+ }
114
114
 
115
- const name = pair.slice(0, eq);
116
- const value = pair.slice(eq + 1);
115
+ const name = pair.slice(0, eq);
116
+ const value = pair.slice(eq + 1);
117
117
 
118
- if (!TOKEN_RE.test(name)) {
119
- dropped.push({ name, reason: "malformed" });
120
- continue;
121
- }
118
+ if (!TOKEN_RE.test(name)) {
119
+ dropped.push({ name, reason: "malformed" });
120
+ continue;
121
+ }
122
122
 
123
- if (!ASCII_VALUE_RE.test(value)) {
124
- dropped.push({ name, reason: "non_ascii" });
125
- continue;
126
- }
123
+ if (!ASCII_VALUE_RE.test(value)) {
124
+ dropped.push({ name, reason: "non_ascii" });
125
+ continue;
126
+ }
127
127
 
128
- if (allowlist && !VTEX_COOKIE_PREFIXES.some((p) => name.startsWith(p))) {
129
- dropped.push({ name, reason: "not_in_allowlist" });
130
- continue;
131
- }
128
+ if (allowlist && !VTEX_COOKIE_PREFIXES.some((p) => name.startsWith(p))) {
129
+ dropped.push({ name, reason: "not_in_allowlist" });
130
+ continue;
131
+ }
132
132
 
133
- kept.push(`${name}=${value}`);
134
- }
133
+ kept.push(`${name}=${value}`);
134
+ }
135
135
 
136
- return { cookies: kept.join("; "), dropped };
136
+ return { cookies: kept.join("; "), dropped };
137
137
  }
138
138
 
139
139
  /**
@@ -142,7 +142,7 @@ export function sanitizeOutboundCookieHeader(
142
142
  * Equivalent to `sanitizeOutboundCookieHeader(raw, { allowlist: true }).cookies`.
143
143
  */
144
144
  export function extractVtexCookies(raw: string): string {
145
- return sanitizeOutboundCookieHeader(raw, { allowlist: true }).cookies;
145
+ return sanitizeOutboundCookieHeader(raw, { allowlist: true }).cookies;
146
146
  }
147
147
 
148
148
  /**
@@ -158,16 +158,16 @@ const _warned = new Set<string>();
158
158
  * arbitrary `host` label so we can correlate in logs.
159
159
  */
160
160
  export function warnDroppedCookies(dropped: DroppedCookie[], host: string): void {
161
- if (dropped.length === 0) return;
162
- for (const d of dropped) {
163
- const key = `${host}::${d.name}::${d.reason}`;
164
- if (_warned.has(key)) continue;
165
- _warned.add(key);
166
- console.warn(`[vtex.cookie.dropped] host=${host} name=${d.name} reason=${d.reason}`);
167
- }
161
+ if (dropped.length === 0) return;
162
+ for (const d of dropped) {
163
+ const key = `${host}::${d.name}::${d.reason}`;
164
+ if (_warned.has(key)) continue;
165
+ _warned.add(key);
166
+ console.warn(`[vtex.cookie.dropped] host=${host} name=${d.name} reason=${d.reason}`);
167
+ }
168
168
  }
169
169
 
170
170
  /** Reset the dedup set — exposed for tests. */
171
171
  export function _resetCookieWarnDedupForTests(): void {
172
- _warned.clear();
172
+ _warned.clear();
173
173
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * URL-derived operation name router for VTEX API calls.
3
3
  *
4
- * Plugged into `@decocms/start`'s `createInstrumentedFetch` via the
4
+ * Plugged into `createInstrumentedFetch` (`@decocms/blocks/fetch`) via the
5
5
  * `resolveOperation(url, method)` option. The resolved string becomes the
6
6
  * span suffix (`vtex.<operation>`) and the `fetch.operation` span +
7
7
  * histogram label, so it must be:
@@ -25,8 +25,8 @@
25
25
  type OperationResolver = string | ((match: RegExpMatchArray, method: string) => string);
26
26
 
27
27
  interface Matcher {
28
- pattern: RegExp;
29
- operation: OperationResolver;
28
+ pattern: RegExp;
29
+ operation: OperationResolver;
30
30
  }
31
31
 
32
32
  const m = (pattern: RegExp, operation: OperationResolver): Matcher => ({ pattern, operation });
@@ -42,66 +42,66 @@ const m = (pattern: RegExp, operation: OperationResolver): Matcher => ({ pattern
42
42
  * prefixes them with the integration name at span time.
43
43
  */
44
44
  const MATCHERS: ReadonlyArray<Matcher> = [
45
- m(/^\/api\/io\/_v\/api\/intelligent-search\/([a-z_-]+)/, (mm) => `intelligent-search.${mm[1]}`),
46
- m(/^\/_v\/private\/graphql\/v1/, "io.graphql"),
47
- m(/^\/_v\/segment\//, "io.segment"),
45
+ m(/^\/api\/io\/_v\/api\/intelligent-search\/([a-z_-]+)/, (mm) => `intelligent-search.${mm[1]}`),
46
+ m(/^\/_v\/private\/graphql\/v1/, "io.graphql"),
47
+ m(/^\/_v\/segment\//, "io.segment"),
48
48
 
49
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/items\/update/, (_mm, method) =>
50
- method === "POST" ? "checkout.orderform.items.update" : "checkout.orderform.items",
51
- ),
52
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/items/, (_mm, method) => {
53
- if (method === "DELETE") return "checkout.orderform.items.remove";
54
- if (method === "PATCH" || method === "PUT") return "checkout.orderform.items.update";
55
- return "checkout.orderform.items.add";
56
- }),
57
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/coupons/, "checkout.orderform.coupons"),
58
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/profile/, "checkout.orderform.profile"),
59
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/shippingData/, "checkout.orderform.shipping"),
60
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/paymentData/, "checkout.orderform.payment"),
61
- m(/^\/api\/checkout\/pub\/orderForm\/[^/]+/, (_mm, method) =>
62
- method === "GET" ? "checkout.orderform.get" : "checkout.orderform.update",
63
- ),
64
- m(/^\/api\/checkout\/pub\/orderForm(?:\/?$)/, (_mm, method) =>
65
- method === "POST" ? "checkout.orderform.create" : "checkout.orderform.get",
66
- ),
67
- m(/^\/api\/checkout\/pub\/orderForms\/simulation/, "checkout.simulation"),
68
- m(/^\/api\/checkout\/pub\/regions/, "checkout.regions"),
69
- m(/^\/api\/checkout\/pub\/postal-code/, "checkout.postal-code"),
49
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/items\/update/, (_mm, method) =>
50
+ method === "POST" ? "checkout.orderform.items.update" : "checkout.orderform.items",
51
+ ),
52
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/items/, (_mm, method) => {
53
+ if (method === "DELETE") return "checkout.orderform.items.remove";
54
+ if (method === "PATCH" || method === "PUT") return "checkout.orderform.items.update";
55
+ return "checkout.orderform.items.add";
56
+ }),
57
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/coupons/, "checkout.orderform.coupons"),
58
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/profile/, "checkout.orderform.profile"),
59
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/shippingData/, "checkout.orderform.shipping"),
60
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+\/paymentData/, "checkout.orderform.payment"),
61
+ m(/^\/api\/checkout\/pub\/orderForm\/[^/]+/, (_mm, method) =>
62
+ method === "GET" ? "checkout.orderform.get" : "checkout.orderform.update",
63
+ ),
64
+ m(/^\/api\/checkout\/pub\/orderForm(?:\/?$)/, (_mm, method) =>
65
+ method === "POST" ? "checkout.orderform.create" : "checkout.orderform.get",
66
+ ),
67
+ m(/^\/api\/checkout\/pub\/orderForms\/simulation/, "checkout.simulation"),
68
+ m(/^\/api\/checkout\/pub\/regions/, "checkout.regions"),
69
+ m(/^\/api\/checkout\/pub\/postal-code/, "checkout.postal-code"),
70
70
 
71
- m(/^\/api\/sessions/, (_mm, method) => (method === "POST" ? "sessions.update" : "sessions.get")),
72
- m(/^\/api\/segments\//, "segments.get"),
71
+ m(/^\/api\/sessions/, (_mm, method) => (method === "POST" ? "sessions.update" : "sessions.get")),
72
+ m(/^\/api\/segments\//, "segments.get"),
73
73
 
74
- m(/^\/api\/catalog_system\/pub\/portal\/pagetype\//, "catalog.pagetype"),
75
- m(
76
- /^\/api\/catalog_system\/pub\/products\/crossselling\/([^/]+)/,
77
- (mm) => `catalog.crossselling.${mm[1]}`,
78
- ),
79
- m(/^\/api\/catalog_system\/pub\/products\/variations\//, "catalog.products.variations"),
80
- m(/^\/api\/catalog_system\/pub\/products\/search/, "catalog.products.search"),
81
- m(/^\/api\/catalog_system\/pub\/facets\/search/, "catalog.facets.search"),
82
- m(/^\/api\/catalog_system\/pub\/category\/tree/, "catalog.category.tree"),
83
- m(/^\/api\/catalog_system\/(?:pub|pvt)\/specification/, "catalog.specification"),
84
- m(/^\/api\/catalog_system\/pub\/brand/, "catalog.brand"),
85
- m(/^\/api\/catalog_system\/pvt\/sku\//, "catalog.sku"),
86
- m(/^\/api\/catalog_system\//, "catalog.other"),
74
+ m(/^\/api\/catalog_system\/pub\/portal\/pagetype\//, "catalog.pagetype"),
75
+ m(
76
+ /^\/api\/catalog_system\/pub\/products\/crossselling\/([^/]+)/,
77
+ (mm) => `catalog.crossselling.${mm[1]}`,
78
+ ),
79
+ m(/^\/api\/catalog_system\/pub\/products\/variations\//, "catalog.products.variations"),
80
+ m(/^\/api\/catalog_system\/pub\/products\/search/, "catalog.products.search"),
81
+ m(/^\/api\/catalog_system\/pub\/facets\/search/, "catalog.facets.search"),
82
+ m(/^\/api\/catalog_system\/pub\/category\/tree/, "catalog.category.tree"),
83
+ m(/^\/api\/catalog_system\/(?:pub|pvt)\/specification/, "catalog.specification"),
84
+ m(/^\/api\/catalog_system\/pub\/brand/, "catalog.brand"),
85
+ m(/^\/api\/catalog_system\/pvt\/sku\//, "catalog.sku"),
86
+ m(/^\/api\/catalog_system\//, "catalog.other"),
87
87
 
88
- m(/^\/api\/wishlist\//, "wishlist"),
89
- m(/^\/api\/profile-system\/profile\//, "profile"),
90
- m(/^\/api\/dataentities\/([^/]+)/, (mm) => `masterdata.${mm[1]}`),
88
+ m(/^\/api\/wishlist\//, "wishlist"),
89
+ m(/^\/api\/profile-system\/profile\//, "profile"),
90
+ m(/^\/api\/dataentities\/([^/]+)/, (mm) => `masterdata.${mm[1]}`),
91
91
 
92
- m(/^\/api\/oms\/user\/orders\/[^/]+\/cancel/, "oms.orders.cancel"),
93
- m(/^\/api\/oms\/user\/orders/, "oms.orders"),
94
- m(/^\/api\/oms\/pvt\/orders/, "oms.orders.pvt"),
92
+ m(/^\/api\/oms\/user\/orders\/[^/]+\/cancel/, "oms.orders.cancel"),
93
+ m(/^\/api\/oms\/user\/orders/, "oms.orders"),
94
+ m(/^\/api\/oms\/pvt\/orders/, "oms.orders.pvt"),
95
95
 
96
- m(/^\/api\/vtexid\/pub\/logout/, "vtexid.logout"),
97
- m(/^\/api\/vtexid\/pub\/authentication\/start/, "vtexid.authentication.start"),
98
- m(/^\/api\/vtexid\/pub\/authentication\/[a-z]+\/validate/, "vtexid.authentication.validate"),
99
- m(/^\/api\/vtexid\/pub\/authenticated\/user/, "vtexid.user"),
100
- m(/^\/api\/vtexid\//, "vtexid.other"),
96
+ m(/^\/api\/vtexid\/pub\/logout/, "vtexid.logout"),
97
+ m(/^\/api\/vtexid\/pub\/authentication\/start/, "vtexid.authentication.start"),
98
+ m(/^\/api\/vtexid\/pub\/authentication\/[a-z]+\/validate/, "vtexid.authentication.validate"),
99
+ m(/^\/api\/vtexid\/pub\/authenticated\/user/, "vtexid.user"),
100
+ m(/^\/api\/vtexid\//, "vtexid.other"),
101
101
 
102
- m(/^\/api\/events\/v1\//, "events.send"),
103
- m(/^\/sitemap.*\.xml$/, "sitemap"),
104
- m(/^\/api\/license-manager/, "license-manager"),
102
+ m(/^\/api\/events\/v1\//, "events.send"),
103
+ m(/^\/sitemap.*\.xml$/, "sitemap"),
104
+ m(/^\/api\/license-manager/, "license-manager"),
105
105
  ];
106
106
 
107
107
  /**
@@ -118,22 +118,26 @@ const MATCHERS: ReadonlyArray<Matcher> = [
118
118
  * });
119
119
  * ```
120
120
  */
121
+ /** The URL's pathname; tolerates relative or malformed URLs by stripping `?`/`#`. */
122
+ function extractPathname(url: string): string {
123
+ try {
124
+ return new URL(url).pathname;
125
+ } catch {
126
+ const qs = url.indexOf("?");
127
+ const hash = url.indexOf("#");
128
+ const end = [qs, hash].filter((i) => i >= 0).sort((a, b) => a - b)[0];
129
+ return end === undefined ? url : url.slice(0, end);
130
+ }
131
+ }
132
+
121
133
  export function vtexOperationRouter(url: string, method: string): string | undefined {
122
- let pathname: string;
123
- try {
124
- pathname = new URL(url).pathname;
125
- } catch {
126
- const qs = url.indexOf("?");
127
- const hash = url.indexOf("#");
128
- const end = [qs, hash].filter((i) => i >= 0).sort((a, b) => a - b)[0];
129
- pathname = end === undefined ? url : url.slice(0, end);
130
- }
134
+ const pathname = extractPathname(url);
131
135
 
132
- const upperMethod = method.toUpperCase();
133
- for (const { pattern, operation } of MATCHERS) {
134
- const match = pathname.match(pattern);
135
- if (!match) continue;
136
- return typeof operation === "function" ? operation(match, upperMethod) : operation;
137
- }
138
- return undefined;
136
+ const upperMethod = method.toUpperCase();
137
+ for (const { pattern, operation } of MATCHERS) {
138
+ const match = pathname.match(pattern);
139
+ if (!match) continue;
140
+ return typeof operation === "function" ? operation(match, upperMethod) : operation;
141
+ }
142
+ return undefined;
139
143
  }