@decocms/apps-vtex 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.
Files changed (163) hide show
  1. package/dist/index.d.ts +6 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +6 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/utils/constants.d.ts +28 -0
  6. package/dist/utils/constants.d.ts.map +1 -0
  7. package/dist/utils/constants.js +17 -0
  8. package/dist/utils/constants.js.map +1 -0
  9. package/dist/utils/cookieSanitizer.d.ts +87 -0
  10. package/dist/utils/cookieSanitizer.d.ts.map +1 -0
  11. package/dist/utils/cookieSanitizer.js +136 -0
  12. package/dist/utils/cookieSanitizer.js.map +1 -0
  13. package/dist/utils/operationRouter.d.ts +25 -0
  14. package/dist/utils/operationRouter.d.ts.map +1 -0
  15. package/dist/utils/operationRouter.js +120 -0
  16. package/dist/utils/operationRouter.js.map +1 -0
  17. package/dist/utils/types.d.ts +944 -0
  18. package/dist/utils/types.d.ts.map +1 -0
  19. package/dist/utils/types.js +25 -0
  20. package/dist/utils/types.js.map +1 -0
  21. package/dist/vtexClient.d.ts +173 -0
  22. package/dist/vtexClient.d.ts.map +1 -0
  23. package/dist/vtexClient.js +242 -0
  24. package/dist/vtexClient.js.map +1 -0
  25. package/package.json +16 -57
  26. package/src/index.ts +11 -29
  27. package/src/utils/constants.ts +2 -56
  28. package/src/utils/cookieSanitizer.ts +59 -59
  29. package/src/utils/operationRouter.ts +75 -65
  30. package/src/utils/types.ts +709 -1557
  31. package/src/vtexClient.ts +358 -347
  32. package/src/README.md +0 -6
  33. package/src/__conformance__/upstream.test.ts +0 -158
  34. package/src/__tests__/client-segment-cookie.test.ts +0 -255
  35. package/src/__tests__/client-set-cookie-forward.test.ts +0 -257
  36. package/src/__tests__/schemas.test.ts +0 -71
  37. package/src/actions/address.ts +0 -250
  38. package/src/actions/analytics/sendEvent.ts +0 -86
  39. package/src/actions/auth.ts +0 -333
  40. package/src/actions/checkout.ts +0 -659
  41. package/src/actions/index.ts +0 -11
  42. package/src/actions/masterData.ts +0 -168
  43. package/src/actions/misc.ts +0 -188
  44. package/src/actions/newsletter.ts +0 -105
  45. package/src/actions/orders.ts +0 -34
  46. package/src/actions/profile.ts +0 -201
  47. package/src/actions/session.ts +0 -85
  48. package/src/actions/trigger.ts +0 -43
  49. package/src/actions/wishlist.ts +0 -114
  50. package/src/client.ts +0 -724
  51. package/src/commerceLoaders.ts +0 -267
  52. package/src/hooks/__tests__/createCart.render.test.ts +0 -114
  53. package/src/hooks/__tests__/createCart.test.ts +0 -92
  54. package/src/hooks/__tests__/createUseCart.test.ts +0 -184
  55. package/src/hooks/__tests__/createUseUser.test.ts +0 -48
  56. package/src/hooks/__tests__/createUseWishlist.test.ts +0 -87
  57. package/src/hooks/cartQuery.ts +0 -204
  58. package/src/hooks/createCart.ts +0 -427
  59. package/src/hooks/createUseCart.ts +0 -360
  60. package/src/hooks/createUseUser.ts +0 -153
  61. package/src/hooks/createUseWishlist.ts +0 -242
  62. package/src/hooks/index.ts +0 -25
  63. package/src/hooks/useAutocomplete.ts +0 -83
  64. package/src/hooks/useCart.ts +0 -243
  65. package/src/hooks/useUser.ts +0 -78
  66. package/src/hooks/useWishlist.ts +0 -119
  67. package/src/invoke.ts +0 -235
  68. package/src/loaders/ProductDetailsPage.ts +0 -1
  69. package/src/loaders/ProductList.ts +0 -1
  70. package/src/loaders/ProductListingPage.ts +0 -1
  71. package/src/loaders/__tests__/legacyProductList.test.ts +0 -157
  72. package/src/loaders/address.ts +0 -115
  73. package/src/loaders/autocomplete.ts +0 -56
  74. package/src/loaders/brands.ts +0 -44
  75. package/src/loaders/cart/attachments.ts +0 -53
  76. package/src/loaders/cart/full.ts +0 -67
  77. package/src/loaders/cart/gifts.ts +0 -43
  78. package/src/loaders/cart/orderFormId.ts +0 -31
  79. package/src/loaders/cart/shipping.test.ts +0 -125
  80. package/src/loaders/cart/shipping.ts +0 -136
  81. package/src/loaders/cart/summary.ts +0 -36
  82. package/src/loaders/cart.ts +0 -52
  83. package/src/loaders/catalog.ts +0 -163
  84. package/src/loaders/collections.ts +0 -55
  85. package/src/loaders/index.ts +0 -20
  86. package/src/loaders/intelligentSearch/__tests__/productListingPage.test.ts +0 -51
  87. package/src/loaders/intelligentSearch/productDetailsPage.ts +0 -111
  88. package/src/loaders/intelligentSearch/productList.ts +0 -170
  89. package/src/loaders/intelligentSearch/productListingPage.ts +0 -525
  90. package/src/loaders/intelligentSearch/suggestions.ts +0 -44
  91. package/src/loaders/legacy/productDetailsPage.ts +0 -1
  92. package/src/loaders/legacy/productList.ts +0 -1
  93. package/src/loaders/legacy/relatedProductsLoader.ts +0 -79
  94. package/src/loaders/legacy.ts +0 -710
  95. package/src/loaders/logistics.ts +0 -111
  96. package/src/loaders/minicart.ts +0 -78
  97. package/src/loaders/navbar.ts +0 -26
  98. package/src/loaders/orders.ts +0 -92
  99. package/src/loaders/pageType.ts +0 -68
  100. package/src/loaders/paths/PDPDefaultPath.ts +0 -51
  101. package/src/loaders/payment.ts +0 -97
  102. package/src/loaders/productListFull.ts +0 -157
  103. package/src/loaders/profile.ts +0 -138
  104. package/src/loaders/promotion.ts +0 -30
  105. package/src/loaders/search.ts +0 -124
  106. package/src/loaders/session.ts +0 -83
  107. package/src/loaders/user.ts +0 -87
  108. package/src/loaders/wishlist.ts +0 -89
  109. package/src/loaders/wishlistProducts.ts +0 -69
  110. package/src/loaders/workflow/products.ts +0 -62
  111. package/src/loaders/workflow.ts +0 -316
  112. package/src/logo.png +0 -0
  113. package/src/manifest.gen.ts +0 -89
  114. package/src/middleware.cacheHeaders.test.ts +0 -44
  115. package/src/middleware.ts +0 -240
  116. package/src/mod.ts +0 -173
  117. package/src/registry.ts +0 -9
  118. package/src/schemas.gen.ts +0 -3191
  119. package/src/schemas.ts +0 -30
  120. package/src/types.ts +0 -248
  121. package/src/utils/__tests__/cartProjection.test.ts +0 -118
  122. package/src/utils/__tests__/cookieSanitizer.test.ts +0 -162
  123. package/src/utils/__tests__/fetch.test.ts +0 -80
  124. package/src/utils/__tests__/fetchCache.test.ts +0 -155
  125. package/src/utils/__tests__/instrumentedFetch.test.ts +0 -158
  126. package/src/utils/__tests__/intelligentSearch.test.ts +0 -88
  127. package/src/utils/__tests__/minicart.test.ts +0 -184
  128. package/src/utils/__tests__/operationRouter.test.ts +0 -227
  129. package/src/utils/__tests__/resilience.test.ts +0 -205
  130. package/src/utils/__tests__/resourceRange.test.ts +0 -32
  131. package/src/utils/__tests__/simulationCache.test.ts +0 -40
  132. package/src/utils/__tests__/sitemap.test.ts +0 -185
  133. package/src/utils/__tests__/slugify.test.ts +0 -31
  134. package/src/utils/__tests__/storefrontBaseUrl.test.ts +0 -61
  135. package/src/utils/__tests__/transform.test.ts +0 -1111
  136. package/src/utils/accountLoaders.ts +0 -203
  137. package/src/utils/authHelpers.ts +0 -85
  138. package/src/utils/batch.ts +0 -18
  139. package/src/utils/buildOfferShelf.test.ts +0 -59
  140. package/src/utils/cartProjection.ts +0 -96
  141. package/src/utils/cookies.ts +0 -167
  142. package/src/utils/enrichment.ts +0 -556
  143. package/src/utils/fetch.ts +0 -111
  144. package/src/utils/fetchCache.ts +0 -55
  145. package/src/utils/index.ts +0 -19
  146. package/src/utils/instrumentedFetch.ts +0 -98
  147. package/src/utils/intelligentSearch.ts +0 -96
  148. package/src/utils/legacy.ts +0 -139
  149. package/src/utils/minicart.ts +0 -139
  150. package/src/utils/pickAndOmit.ts +0 -25
  151. package/src/utils/proxy.ts +0 -495
  152. package/src/utils/resilience.ts +0 -352
  153. package/src/utils/resourceRange.ts +0 -10
  154. package/src/utils/segment.ts +0 -152
  155. package/src/utils/similars.ts +0 -35
  156. package/src/utils/simulationCache.ts +0 -73
  157. package/src/utils/sitemap.ts +0 -283
  158. package/src/utils/slugCache.ts +0 -28
  159. package/src/utils/slugify.ts +0 -13
  160. package/src/utils/transform.ts +0 -1691
  161. package/src/utils/vtexId.ts +0 -151
  162. package/src/vtexClient.test.ts +0 -204
  163. package/tsconfig.json +0 -7
@@ -1,352 +0,0 @@
1
- /**
2
- * VTEX fetch resilience — real request abort, circuit breaker, and a bounded
3
- * retry budget for the whole VTEX egress surface.
4
- *
5
- * Wraps a `typeof fetch` and is composed into `createVtexFetch` UNDER the
6
- * instrumentation layer, so it covers 100% of VTEX traffic — cached GETs,
7
- * Intelligent Search, and the checkout/cart/session POSTs — with zero
8
- * call-site changes.
9
- *
10
- * Motivation: on a VTEX upstream slowdown, the previous stack cascaded into
11
- * client-abort 499s (the SSR hangs on a socket that is never freed — the old
12
- * `fetchCache` timeout was a `Promise.race` that only frees the dedup slot, not
13
- * the connection) and, on the checkout POST path (which had no timeout/retry at
14
- * all), hard 500s — while a retry storm amplified load on an already-struggling
15
- * upstream.
16
- *
17
- * Three composed layers, per call:
18
- * 1. AbortController with a real per-attempt timeout, bounded by a total cap
19
- * across retries. Frees the TCP connection on VTEX slowness. The caller's
20
- * own `signal` (e.g. an SSR deadline) is unioned in, so a request-level
21
- * abort also cancels the in-flight VTEX call.
22
- * 2. Retry for IDEMPOTENT methods only (GET/HEAD, never a request with a
23
- * body — a checkout POST must never be retried) on network errors and
24
- * abort-by-timeout, with exponential backoff + jitter and a per-host retry
25
- * budget (token bucket) so a broad outage can't become a self-inflicted
26
- * DDoS. Retrying a 5xx *status* is intentionally left to the SWR
27
- * `fetchCache` layer to avoid double-retry; this layer still counts a 5xx
28
- * response as a breaker failure.
29
- * 3. A circuit breaker per host — opens on consecutive failures, fails fast
30
- * (no socket, no retry) with {@link VtexCircuitOpenError} while open, and
31
- * half-opens a bounded number of probes after a cooldown. State is
32
- * per-isolate: on Workers each isolate self-protects, no shared
33
- * coordination needed.
34
- *
35
- * Kill-switch: set `VTEX_RESILIENCE_DISABLED=true` to fall straight through to
36
- * the underlying fetch (emergency escape hatch — an env flip, no code deploy).
37
- */
38
-
39
- import { RequestContext } from "@decocms/blocks/sdk/requestContext";
40
- import { DEFAULT_RESILIENCE_CONFIG, type ResilienceConfig } from "./constants";
41
-
42
- // Re-exported so existing consumers (e.g. `instrumentedFetch.ts`'s
43
- // `import { type ResilienceConfig } from "./resilience"`) keep working
44
- // unchanged. The canonical definitions live in `constants.ts` alongside every
45
- // other resilience/cache tuning knob — see that file to tune or audit them.
46
- export { DEFAULT_RESILIENCE_CONFIG, type ResilienceConfig };
47
-
48
- // --- Errors ---------------------------------------------------------------
49
-
50
- export class VtexCircuitOpenError extends Error {
51
- readonly host: string;
52
- readonly isVtexResilience = true as const;
53
- constructor(host: string) {
54
- super(`VTEX circuit open for ${host} — failing fast`);
55
- this.name = "VtexCircuitOpenError";
56
- this.host = host;
57
- }
58
- }
59
-
60
- export class VtexTimeoutError extends Error {
61
- readonly host: string;
62
- readonly isVtexResilience = true as const;
63
- constructor(host: string, ms: number) {
64
- super(`VTEX request to ${host} aborted after ${ms}ms`);
65
- this.name = "VtexTimeoutError";
66
- this.host = host;
67
- }
68
- }
69
-
70
- /**
71
- * True for errors that the SWR `fetchCache` layer must NOT retry — the breaker
72
- * already decided to shed load, and a timed-out request will just time out
73
- * again. Retrying these would defeat the fast-fail / socket-freeing behavior.
74
- */
75
- export function isNonRetryableVtexError(error: unknown): boolean {
76
- return error instanceof VtexCircuitOpenError || error instanceof VtexTimeoutError;
77
- }
78
-
79
- // --- Kill switch ----------------------------------------------------------
80
-
81
- function isDisabled(): boolean {
82
- try {
83
- return globalThis.process?.env?.VTEX_RESILIENCE_DISABLED === "true";
84
- } catch {
85
- return false;
86
- }
87
- }
88
-
89
- // --- Circuit breaker (per host) ------------------------------------------
90
-
91
- type BreakerState = "closed" | "open" | "half-open";
92
-
93
- interface Breaker {
94
- state: BreakerState;
95
- consecutiveFailures: number;
96
- openedAt: number;
97
- halfOpenInFlight: number;
98
- }
99
-
100
- const breakers = new Map<string, Breaker>();
101
-
102
- function getBreaker(host: string): Breaker {
103
- let b = breakers.get(host);
104
- if (!b) {
105
- b = { state: "closed", consecutiveFailures: 0, openedAt: 0, halfOpenInFlight: 0 };
106
- breakers.set(host, b);
107
- }
108
- return b;
109
- }
110
-
111
- function breakerAllows(b: Breaker, cfg: ResilienceConfig, now: number): boolean {
112
- if (b.state === "closed") return true;
113
- if (b.state === "open") {
114
- if (now - b.openedAt >= cfg.breakerOpenCooldownMs) {
115
- b.state = "half-open";
116
- b.halfOpenInFlight = 0;
117
- } else {
118
- return false;
119
- }
120
- }
121
- if (b.halfOpenInFlight >= cfg.breakerHalfOpenProbes) return false;
122
- b.halfOpenInFlight++;
123
- return true;
124
- }
125
-
126
- function breakerOnSuccess(b: Breaker) {
127
- const wasOpen = b.state !== "closed";
128
- b.consecutiveFailures = 0;
129
- b.halfOpenInFlight = Math.max(0, b.halfOpenInFlight - 1);
130
- b.state = "closed";
131
- b.openedAt = 0;
132
- if (wasOpen) console.warn("[vtex-resilience] circuit CLOSED (recovered)");
133
- }
134
-
135
- function breakerOnFailure(b: Breaker, cfg: ResilienceConfig, host: string, now: number) {
136
- b.halfOpenInFlight = Math.max(0, b.halfOpenInFlight - 1);
137
- if (b.state === "half-open") {
138
- b.state = "open";
139
- b.openedAt = now;
140
- console.warn(`[vtex-resilience] circuit RE-OPENED for ${host} (probe failed)`);
141
- return;
142
- }
143
- b.consecutiveFailures++;
144
- if (b.state === "closed" && b.consecutiveFailures >= cfg.breakerConsecutiveFailures) {
145
- b.state = "open";
146
- b.openedAt = now;
147
- console.warn(
148
- `[vtex-resilience] circuit OPEN for ${host} after ${b.consecutiveFailures} consecutive failures`,
149
- );
150
- }
151
- }
152
-
153
- // --- Retry budget (token bucket per host) --------------------------------
154
-
155
- interface Bucket {
156
- tokens: number;
157
- lastRefill: number;
158
- }
159
-
160
- const buckets = new Map<string, Bucket>();
161
-
162
- function tryTakeRetryToken(host: string, cfg: ResilienceConfig, now: number): boolean {
163
- let bucket = buckets.get(host);
164
- if (!bucket) {
165
- bucket = { tokens: cfg.retryBudgetMax, lastRefill: now };
166
- buckets.set(host, bucket);
167
- }
168
- const elapsedSec = (now - bucket.lastRefill) / 1000;
169
- if (elapsedSec > 0) {
170
- bucket.tokens = Math.min(
171
- cfg.retryBudgetMax,
172
- bucket.tokens + elapsedSec * cfg.retryBudgetRefillPerSec,
173
- );
174
- bucket.lastRefill = now;
175
- }
176
- if (bucket.tokens >= 1) {
177
- bucket.tokens -= 1;
178
- return true;
179
- }
180
- return false;
181
- }
182
-
183
- /** Reset all breaker + budget state. Test-only. */
184
- export function resetResilienceState() {
185
- breakers.clear();
186
- buckets.clear();
187
- }
188
-
189
- // --- Helpers --------------------------------------------------------------
190
-
191
- function hostOf(input: RequestInfo | URL): string {
192
- try {
193
- if (typeof input === "string") return new URL(input).host;
194
- if (input instanceof URL) return input.host;
195
- if (input instanceof Request) return new URL(input.url).host;
196
- } catch {
197
- /* fall through */
198
- }
199
- return "unknown";
200
- }
201
-
202
- function methodOf(input: RequestInfo | URL, init?: RequestInit): string {
203
- const m = init?.method ?? (input instanceof Request ? input.method : "GET");
204
- return m.toUpperCase();
205
- }
206
-
207
- /** Only GET/HEAD without a body are safe to retry. Never retry a mutation. */
208
- function isIdempotent(input: RequestInfo | URL, init?: RequestInit): boolean {
209
- const method = methodOf(input, init);
210
- if (method !== "GET" && method !== "HEAD") return false;
211
- if (init?.body != null) return false;
212
- if (input instanceof Request && input.body != null) return false;
213
- return true;
214
- }
215
-
216
- function backoffDelay(cfg: ResilienceConfig, attempt: number): number {
217
- const base = Math.min(cfg.backoffCapMs, cfg.backoffBaseMs * 2 ** attempt);
218
- // Full jitter, derived from a coarse clock read so we don't depend on
219
- // Math.random (unavailable in some sandboxes).
220
- const jitter = (Date.now() % 100) / 100;
221
- return Math.round(base * (0.5 + 0.5 * jitter));
222
- }
223
-
224
- function sleep(ms: number): Promise<void> {
225
- return new Promise((resolve) => setTimeout(resolve, ms));
226
- }
227
-
228
- /** True for a fetch abort (DOMException / Error with name "AbortError"). */
229
- function isAbortError(err: unknown): boolean {
230
- return (
231
- typeof err === "object" && err !== null && (err as { name?: string }).name === "AbortError"
232
- );
233
- }
234
-
235
- /**
236
- * Union any number of upstream signals with our per-attempt timeout so a caller
237
- * abort, the ambient request abort (client disconnect / SSR deadline via
238
- * {@link RequestContext}), OR the timeout all cancel the underlying fetch and
239
- * free the socket.
240
- */
241
- function withTimeoutSignal(
242
- callerSignals: Array<AbortSignal | null | undefined>,
243
- timeoutMs: number,
244
- ) {
245
- const controller = new AbortController();
246
- let didTimeout = false;
247
- const onAbort = () => controller.abort();
248
- const timer = setTimeout(() => {
249
- didTimeout = true;
250
- controller.abort();
251
- }, timeoutMs);
252
- const bound: AbortSignal[] = [];
253
- for (const s of callerSignals) {
254
- if (!s) continue;
255
- if (s.aborted) controller.abort();
256
- else {
257
- s.addEventListener("abort", onAbort, { once: true });
258
- bound.push(s);
259
- }
260
- }
261
- return {
262
- signal: controller.signal,
263
- cleanup: () => {
264
- clearTimeout(timer);
265
- for (const s of bound) s.removeEventListener("abort", onAbort);
266
- },
267
- timedOut: () => didTimeout,
268
- };
269
- }
270
-
271
- // --- The resilient fetch --------------------------------------------------
272
-
273
- /**
274
- * Wrap a `typeof fetch` with abort/timeout + retry-budget + circuit breaker.
275
- * Pass the result as the `baseFetch` of {@link createVtexFetch}.
276
- */
277
- export function createResilientFetch(
278
- underlying: typeof fetch = globalThis.fetch,
279
- config: Partial<ResilienceConfig> = {},
280
- ): typeof fetch {
281
- const cfg: ResilienceConfig = { ...DEFAULT_RESILIENCE_CONFIG, ...config };
282
-
283
- return async function resilientFetch(
284
- input: RequestInfo | URL,
285
- init?: RequestInit,
286
- ): Promise<Response> {
287
- if (isDisabled()) return underlying(input, init);
288
-
289
- const host = hostOf(input);
290
- const breaker = getBreaker(host);
291
- const idempotent = isIdempotent(input, init);
292
- const startedAt = Date.now();
293
-
294
- if (!breakerAllows(breaker, cfg, startedAt)) {
295
- throw new VtexCircuitOpenError(host);
296
- }
297
-
298
- const attempts = idempotent ? cfg.maxRetries + 1 : 1;
299
- let lastError: unknown;
300
-
301
- for (let attempt = 0; attempt < attempts; attempt++) {
302
- const elapsed = Date.now() - startedAt;
303
- const remaining = cfg.totalTimeoutMs - elapsed;
304
- if (remaining <= 0) break;
305
- const perAttempt = Math.min(cfg.perAttemptTimeoutMs, remaining);
306
-
307
- // Union the caller's signal with the ambient request signal so a client
308
- // disconnect (or a request-level SSR deadline) also aborts the VTEX call.
309
- const { signal, cleanup, timedOut } = withTimeoutSignal(
310
- [init?.signal, RequestContext.current?.signal],
311
- perAttempt,
312
- );
313
- try {
314
- const response = await underlying(input, { ...init, signal });
315
- cleanup();
316
-
317
- // A 5xx counts as a breaker failure even though we return it (the
318
- // SWR/edge layers decide whether to serve stale). Status retries
319
- // are owned by the fetchCache layer to avoid double-retry.
320
- if (response.status >= 500) breakerOnFailure(breaker, cfg, host, Date.now());
321
- else breakerOnSuccess(breaker);
322
- return response;
323
- } catch (err) {
324
- cleanup();
325
-
326
- // An abort that did NOT come from our own timeout timer is a caller /
327
- // ambient abort — a client disconnect or a request-level cancel via
328
- // RequestContext.signal. That is NOT an upstream failure: it must not
329
- // count toward the breaker (a burst of users hitting "stop" would
330
- // otherwise trip the circuit against a perfectly healthy VTEX) and must
331
- // not be retried (the signal is still aborted). Surface it as-is.
332
- if (!timedOut() && isAbortError(err)) throw err;
333
-
334
- lastError = timedOut() ? new VtexTimeoutError(host, perAttempt) : err;
335
- breakerOnFailure(breaker, cfg, host, Date.now());
336
-
337
- const canRetry =
338
- idempotent &&
339
- attempt < attempts - 1 &&
340
- Date.now() - startedAt < cfg.totalTimeoutMs &&
341
- tryTakeRetryToken(host, cfg, Date.now());
342
- if (canRetry) {
343
- await sleep(backoffDelay(cfg, attempt));
344
- continue;
345
- }
346
- throw lastError;
347
- }
348
- }
349
-
350
- throw lastError ?? new VtexTimeoutError(host, cfg.totalTimeoutMs);
351
- };
352
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Build REST-Range header values for VTEX paginated APIs.
3
- * Ported from deco-cx/apps vtex/utils/resourceRange.ts
4
- */
5
- export function resourceRange(skip: number, take: number) {
6
- const from = Math.max(skip, 0);
7
- const to = from + Math.min(100, take);
8
-
9
- return { from, to };
10
- }
@@ -1,152 +0,0 @@
1
- import type { Segment } from "./types";
2
-
3
- const removeNonLatin1Chars = (str: string) => str.replace(/[^\x00-\xFF]/g, "");
4
-
5
- export const SEGMENT_COOKIE_NAME = "vtex_segment";
6
- export const SALES_CHANNEL_COOKIE = "VTEXSC";
7
-
8
- export interface WrappedSegment {
9
- payload: Partial<Segment>;
10
- token: string;
11
- }
12
-
13
- export const DEFAULT_SEGMENT: Partial<Segment> = {
14
- utmi_campaign: null,
15
- utmi_page: null,
16
- utmi_part: null,
17
- utm_campaign: null,
18
- utm_source: null,
19
- utm_medium: null,
20
- channel: "1",
21
- cultureInfo: "pt-BR",
22
- currencyCode: "BRL",
23
- currencySymbol: "R$",
24
- countryCode: "BRA",
25
- };
26
-
27
- /**
28
- * Stable serialization.
29
- *
30
- * Even if attributes are in a different order, the final segment
31
- * value will be the same. This improves cache hits.
32
- */
33
- export const serializeSegment = ({
34
- campaigns,
35
- channel,
36
- priceTables,
37
- regionId,
38
- utm_campaign,
39
- utm_source,
40
- utm_medium,
41
- utmi_campaign,
42
- utmi_page,
43
- utmi_part,
44
- currencyCode,
45
- currencySymbol,
46
- countryCode,
47
- cultureInfo,
48
- channelPrivacy,
49
- }: Partial<Segment>): string => {
50
- const seg = {
51
- campaigns,
52
- channel,
53
- priceTables,
54
- regionId,
55
- utm_campaign: utm_campaign && removeNonLatin1Chars(utm_campaign).replace(/[/[\]{}()<>.]/g, ""),
56
- utm_source: utm_source && removeNonLatin1Chars(utm_source).replace(/[/[\]{}()<>.]/g, ""),
57
- utm_medium: utm_medium && removeNonLatin1Chars(utm_medium).replace(/[/[\]{}()<>.]/g, ""),
58
- utmi_campaign: utmi_campaign && removeNonLatin1Chars(utmi_campaign),
59
- utmi_page: utmi_page && removeNonLatin1Chars(utmi_page),
60
- utmi_part: utmi_part && removeNonLatin1Chars(utmi_part),
61
- currencyCode,
62
- currencySymbol,
63
- countryCode,
64
- cultureInfo,
65
- channelPrivacy,
66
- };
67
- return btoa(JSON.stringify(seg));
68
- };
69
-
70
- export const parseSegment = (cookie: string): Partial<Segment> | null => {
71
- try {
72
- return JSON.parse(atob(cookie));
73
- } catch {
74
- return null;
75
- }
76
- };
77
-
78
- const SEGMENT_QUERY_PARAMS = [
79
- "utmi_campaign",
80
- "utmi_page",
81
- "utmi_part",
82
- "utm_campaign",
83
- "utm_source",
84
- "utm_medium",
85
- ] as const;
86
-
87
- export const buildSegmentFromParams = (searchParams: URLSearchParams): Partial<Segment> => {
88
- const partialSegment: Partial<Segment> = {};
89
- for (const qs of SEGMENT_QUERY_PARAMS) {
90
- const param = searchParams.get(qs);
91
- if (param) {
92
- partialSegment[qs] = param;
93
- }
94
- }
95
-
96
- const sc = searchParams.get("sc");
97
- if (sc) {
98
- partialSegment.channel = sc;
99
- }
100
-
101
- return partialSegment;
102
- };
103
-
104
- export const withSegmentCookie = (segment: WrappedSegment, headers?: Headers): Headers => {
105
- const h = new Headers(headers);
106
- if (!segment) return h;
107
-
108
- h.set("cookie", `${SEGMENT_COOKIE_NAME}=${segment.token}`);
109
- return h;
110
- };
111
-
112
- function getCookieValue(cookieHeader: string, name: string): string | null {
113
- const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]+)`));
114
- return match?.[1] ?? null;
115
- }
116
-
117
- /**
118
- * Build a complete segment from request cookies.
119
- * Reads both vtex_segment and VTEXSC cookies.
120
- * VTEXSC contains the sales channel and overrides the segment channel.
121
- */
122
- export const buildSegmentFromCookies = (cookieHeader: string): Partial<Segment> => {
123
- const segmentCookie = getCookieValue(cookieHeader, SEGMENT_COOKIE_NAME);
124
- const vtexsc = getCookieValue(cookieHeader, SALES_CHANNEL_COOKIE);
125
-
126
- const base = segmentCookie ? parseSegment(segmentCookie) : null;
127
- const segment: Partial<Segment> = { ...DEFAULT_SEGMENT, ...base };
128
-
129
- if (vtexsc) {
130
- segment.channel = vtexsc;
131
- }
132
-
133
- return segment;
134
- };
135
-
136
- /**
137
- * Check if the current segment represents an anonymous user
138
- * (no campaigns, no UTMs, no regionId, no custom priceTables).
139
- */
140
- export const isAnonymous = (segment: Partial<Segment>): boolean => {
141
- return (
142
- !segment.campaigns &&
143
- !segment.utm_campaign &&
144
- !segment.utm_source &&
145
- !segment.utm_medium &&
146
- !segment.utmi_campaign &&
147
- !segment.utmi_page &&
148
- !segment.utmi_part &&
149
- !segment.regionId &&
150
- !segment.priceTables
151
- );
152
- };
@@ -1,35 +0,0 @@
1
- import type { Product } from "@decocms/apps-commerce/types";
2
- import { getVtexConfig, storefrontBaseUrl, vtexFetch } from "../client";
3
- import { pickSku, toProduct } from "./transform";
4
- import type { LegacyProduct } from "./types";
5
-
6
- export const withIsSimilarTo = async (product: Product): Promise<Product> => {
7
- const id = product.isVariantOf?.productGroupID ?? product.inProductGroupWithID;
8
-
9
- if (!id) {
10
- return product;
11
- }
12
-
13
- try {
14
- const rawSimilars = await vtexFetch<LegacyProduct[]>(
15
- `/api/catalog_system/pub/products/crossselling/similars/${id}`,
16
- );
17
-
18
- if (!rawSimilars?.length) return product;
19
-
20
- const config = getVtexConfig();
21
- const baseUrl = storefrontBaseUrl(config);
22
-
23
- const similars = rawSimilars.map((p) => {
24
- const sku = pickSku(p);
25
- return toProduct(p, sku, 0, { baseUrl, priceCurrency: "BRL" });
26
- });
27
-
28
- return {
29
- ...product,
30
- isSimilarTo: similars,
31
- };
32
- } catch {
33
- return product;
34
- }
35
- };
@@ -1,73 +0,0 @@
1
- /**
2
- * Injectable cache for shipping simulation responses (#373).
3
- *
4
- * `simulateCart`'s POST to `/api/checkout/pub/orderForms/simulation` is not
5
- * user-personalized for a given `{items, postalCode, salesChannel}` tuple —
6
- * every shopper with the same cart composition and zip code gets the same
7
- * SLAs — but it goes through `vtexFetchWithCookies` (cookies can rotate on
8
- * this endpoint), so caching the raw response risks replaying a stale
9
- * session's cookies onto a different user.
10
- *
11
- * This cache stores ONLY the response body (SLAs/logistics info) — never
12
- * cookies or session state. The default is an in-process Map (works in both
13
- * `@decocms/tanstack` and `@decocms/nextjs`, no infra required, but is only
14
- * warm within a single Worker isolate's lifetime). Sites that want a shared
15
- * edge cache (Cloudflare Cache API, KV) inject their own implementation via
16
- * `setSimulationCache` — mirrors the `setFastDeployKVGetter` pattern in
17
- * `@decocms/blocks-admin/src/admin/decofile.ts`.
18
- */
19
-
20
- export interface SimulationCache {
21
- get(key: string): Promise<string | null> | string | null;
22
- put(key: string, value: string, ttlSeconds: number): Promise<void> | void;
23
- }
24
-
25
- interface InProcessEntry {
26
- value: string;
27
- expiresAt: number;
28
- }
29
-
30
- const DEFAULT_MAX_ENTRIES = 500;
31
-
32
- function createInProcessCache(): SimulationCache {
33
- const store = new Map<string, InProcessEntry>();
34
-
35
- function evictIfNeeded() {
36
- if (store.size <= DEFAULT_MAX_ENTRIES) return;
37
- const oldestKey = store.keys().next().value;
38
- if (oldestKey !== undefined) store.delete(oldestKey);
39
- }
40
-
41
- return {
42
- get(key) {
43
- const entry = store.get(key);
44
- if (!entry) return null;
45
- if (Date.now() > entry.expiresAt) {
46
- store.delete(key);
47
- return null;
48
- }
49
- return entry.value;
50
- },
51
- put(key, value, ttlSeconds) {
52
- store.delete(key); // re-insert for Map's insertion-order-based eviction
53
- store.set(key, { value, expiresAt: Date.now() + ttlSeconds * 1000 });
54
- evictIfNeeded();
55
- },
56
- };
57
- }
58
-
59
- let cache: SimulationCache = createInProcessCache();
60
-
61
- /** Inject a custom simulation cache (e.g. Cloudflare Cache API or KV-backed). */
62
- export function setSimulationCache(custom: SimulationCache): void {
63
- cache = custom;
64
- }
65
-
66
- export function getSimulationCache(): SimulationCache {
67
- return cache;
68
- }
69
-
70
- /** @internal exported for tests */
71
- export function __resetSimulationCacheForTests(): void {
72
- cache = createInProcessCache();
73
- }