@swell/apps-sdk 1.0.161 → 1.0.163

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.js CHANGED
@@ -7199,6 +7199,7 @@
7199
7199
  });
7200
7200
  var NULL_VALUE = "__NULL__";
7201
7201
  var SWR_PROMISE_MAP = /* @__PURE__ */ new Map();
7202
+ var FETCH_PROMISE_MAP = /* @__PURE__ */ new Map();
7202
7203
  var Cache = class {
7203
7204
  client;
7204
7205
  workerCtx;
@@ -7211,8 +7212,30 @@
7211
7212
  ...options
7212
7213
  });
7213
7214
  }
7214
- async fetch(key, fetchFn, ttl) {
7215
- return this.client.wrap(key, fetchFn, ttl);
7215
+ /**
7216
+ * Always fetches fresh data and updates cache
7217
+ * Deduplicates concurrent requests with the same key
7218
+ *
7219
+ * @param key Cache key
7220
+ * @param fetchFn Function to fetch fresh data
7221
+ * @param ttl Time to live in milliseconds (default: DEFAULT_SWR_TTL)
7222
+ * @param isCacheable Whether to store result in cache (default: true)
7223
+ */
7224
+ async fetch(key, fetchFn, ttl = DEFAULT_SWR_TTL, isCacheable = true) {
7225
+ let promise = FETCH_PROMISE_MAP.get(key);
7226
+ if (!promise) {
7227
+ promise = Promise.resolve().then(fetchFn).then(resolveAsyncResources).then(async (value) => {
7228
+ const isNull = value === null || value === void 0;
7229
+ if (isCacheable) {
7230
+ await this.client.set(key, isNull ? NULL_VALUE : value, ttl);
7231
+ }
7232
+ return value;
7233
+ }).finally(() => {
7234
+ FETCH_PROMISE_MAP.delete(key);
7235
+ });
7236
+ FETCH_PROMISE_MAP.set(key, promise);
7237
+ }
7238
+ return await promise;
7216
7239
  }
7217
7240
  /**
7218
7241
  * Fetch cache using SWR (stale-while-revalidate)
@@ -8320,18 +8343,24 @@
8320
8343
  }
8321
8344
  return params;
8322
8345
  }
8346
+ /**
8347
+ * Checks if cache bypass is requested via X-Cache-Bypass header
8348
+ */
8349
+ shouldBypassCache() {
8350
+ return this.headers?.["x-cache-bypass"] === "revalidation";
8351
+ }
8323
8352
  /**
8324
8353
  * Fetches a resource.
8325
8354
  * First attempts to fetch from cache.
8326
8355
  */
8327
8356
  async getCachedResource(key, args, handler, isCacheble = true) {
8328
8357
  const cacheKey = getCacheKey(key, [this.instanceId, args]);
8329
- return this.getResourceCache().fetchSWR(
8330
- cacheKey,
8331
- handler,
8332
- void 0,
8333
- isCacheble
8334
- );
8358
+ const cache = this.getResourceCache();
8359
+ if (this.shouldBypassCache()) {
8360
+ return cache.fetch(cacheKey, handler, void 0, isCacheble);
8361
+ } else {
8362
+ return cache.fetchSWR(cacheKey, handler, void 0, isCacheble);
8363
+ }
8335
8364
  }
8336
8365
  async getAppSettings() {
8337
8366
  const settings = await this.get(
@@ -8498,11 +8527,17 @@
8498
8527
  data,
8499
8528
  opt
8500
8529
  ]);
8501
- return this.getRequestCache().fetchSWR(key, () => {
8530
+ const cache = this.getRequestCache();
8531
+ const fetchFn = () => {
8502
8532
  const requestUrl = id ? `${url}/${id}` : url;
8503
8533
  logger.debug("[SDK] Cacheable API request", { url: requestUrl, key });
8504
8534
  return storefrontRequest(method, url, id, data, opt);
8505
- });
8535
+ };
8536
+ if (this.shouldBypassCache()) {
8537
+ return cache.fetch(key, fetchFn);
8538
+ } else {
8539
+ return cache.fetchSWR(key, fetchFn);
8540
+ }
8506
8541
  }
8507
8542
  switch (method) {
8508
8543
  case "delete":
@@ -15901,7 +15936,7 @@ ${formattedMessage}`;
15901
15936
  function joinAddressLines(...props) {
15902
15937
  return props.filter(Boolean).join("\n");
15903
15938
  }
15904
- function ShopifyCountry(_instance2, countryCode) {
15939
+ function ShopifyCountry(_instance, countryCode) {
15905
15940
  const currencyCode = getCurrencyByCountry(countryCode) || "USD";
15906
15941
  return new ShopifyResource(
15907
15942
  {
@@ -16234,7 +16269,7 @@ ${formattedMessage}`;
16234
16269
  }
16235
16270
 
16236
16271
  // src/compatibility/shopify-objects/font.ts
16237
- function ShopifyFont(_instance2, font) {
16272
+ function ShopifyFont(_instance, font) {
16238
16273
  if (font instanceof ShopifyResource) {
16239
16274
  return font.clone();
16240
16275
  }
@@ -16245,7 +16280,7 @@ ${formattedMessage}`;
16245
16280
  family: font.family,
16246
16281
  style: font.style,
16247
16282
  "system?": font.system,
16248
- variants: font.variants.map((variant) => ShopifyFont(_instance2, variant)),
16283
+ variants: font.variants.map((variant) => ShopifyFont(_instance, variant)),
16249
16284
  weight: font.weight
16250
16285
  });
16251
16286
  }
@@ -16258,7 +16293,7 @@ ${formattedMessage}`;
16258
16293
  })
16259
16294
  }
16260
16295
  };
16261
- function ShopifyForm(_instance2, form) {
16296
+ function ShopifyForm(_instance, form) {
16262
16297
  if (form instanceof ShopifyResource) {
16263
16298
  return form.clone();
16264
16299
  }
@@ -16555,7 +16590,7 @@ ${formattedMessage}`;
16555
16590
  }
16556
16591
 
16557
16592
  // src/compatibility/shopify-objects/page.ts
16558
- function ShopifyPage(_instance2, page) {
16593
+ function ShopifyPage(_instance, page) {
16559
16594
  if (page instanceof ShopifyResource) {
16560
16595
  return page.clone();
16561
16596
  }
@@ -17767,7 +17802,7 @@ ${injects.join("\n")}<\/script>`;
17767
17802
  };
17768
17803
 
17769
17804
  // src/compatibility/shopify-objects/template.ts
17770
- function ShopifyTemplate(_instance2, template) {
17805
+ function ShopifyTemplate(_instance, template) {
17771
17806
  return new ShopifyResource(
17772
17807
  {
17773
17808
  directory: template.path,
@@ -20699,12 +20734,7 @@ ${injects.join("\n")}<\/script>`;
20699
20734
  // Default value (always StorefrontResource)
20700
20735
  () => this.fetchCart()
20701
20736
  ),
20702
- this.fetchSingletonResourceCached(
20703
- "account",
20704
- () => this.fetchAccount(),
20705
- () => null,
20706
- false
20707
- )
20737
+ this.fetchAccount()
20708
20738
  ]);
20709
20739
  if (!cart) {
20710
20740
  throw new Error("Failed to fetch cart");
@@ -22342,42 +22372,27 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22342
22372
 
22343
22373
  // src/cache/html-cache/html-cache.ts
22344
22374
  var CACHE_KEY_ORIGIN = "https://cache.swell.store";
22345
- var TTL_CONFIG = {
22346
- LIVE: {
22347
- DEFAULT: 20,
22348
- HOME: 20,
22349
- PRODUCT: 20,
22350
- COLLECTION: 20,
22351
- PAGE: 20,
22352
- BLOG: 20,
22353
- SWR: 60 * 60 * 24 * 7
22354
- // 1 week
22355
- },
22356
- PREVIEW: {
22357
- DEFAULT: 10,
22358
- HOME: 10,
22359
- PRODUCT: 10,
22360
- COLLECTION: 10,
22361
- PAGE: 10,
22362
- BLOG: 10,
22363
- SWR: 60 * 60 * 24 * 7
22364
- // 1 week
22365
- }
22375
+ var DEFAULT_CACHE_RULES = {
22376
+ defaults: {
22377
+ live: { ttl: 20, swr: 60 * 60 * 24 * 7 },
22378
+ // 20s TTL, 1 week SWR
22379
+ preview: { ttl: 10, swr: 60 * 60 * 24 * 7 }
22380
+ // 10s TTL, 1 week SWR
22381
+ },
22382
+ pathRules: [{ path: "/checkout/*", skip: true }]
22366
22383
  };
22367
22384
  var HtmlCache = class {
22368
22385
  epoch;
22369
22386
  backend;
22370
- constructor(epoch, backend) {
22387
+ cacheRules;
22388
+ constructor(epoch, backend, cacheRules = DEFAULT_CACHE_RULES) {
22371
22389
  this.epoch = epoch;
22372
22390
  this.backend = backend;
22391
+ this.cacheRules = cacheRules;
22373
22392
  }
22374
22393
  async get(request) {
22375
22394
  const trace = createTraceId();
22376
- if (request.method !== "GET") {
22377
- logger.debug("[SDK Html-cache] non-cacheable method", { trace });
22378
- return { found: false, cacheable: false };
22379
- }
22380
- if (!this.isCacheable(request)) {
22395
+ if (!this.canReadFromCache(request)) {
22381
22396
  logger.debug("[SDK Html-cache] non-cacheable request", { trace });
22382
22397
  return { found: false, cacheable: false };
22383
22398
  }
@@ -22445,31 +22460,27 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22445
22460
  }
22446
22461
  async put(request, response) {
22447
22462
  const trace = createTraceId();
22448
- if (request.method !== "GET" || !response.ok) {
22449
- logger.debug("[SDK Html-cache] put skipped, invalid method or response", {
22450
- trace
22451
- });
22452
- return;
22453
- }
22454
- if (!this.isCacheable(request) || !this.isResponseCacheable(response)) {
22463
+ if (!this.canWriteToCache(request, response)) {
22455
22464
  logger.debug("[SDK Html-cache] put skipped, non-cacheable", { trace });
22456
22465
  return;
22457
22466
  }
22458
22467
  try {
22459
- let lowercaseHeaders2 = function(headers2) {
22460
- const out = {};
22461
- headers2.forEach((value, key) => {
22462
- out[key.toLowerCase()] = value;
22463
- });
22464
- return out;
22465
- };
22466
- var lowercaseHeaders = lowercaseHeaders2;
22467
22468
  const cacheKey = this.buildCacheKey(request);
22468
22469
  const ttl = this.getTTLForRequest(request);
22469
22470
  const swr = this.getSWRForRequest(request);
22470
22471
  const body = await response.text();
22472
+ if (!body || body.trim().length === 0) {
22473
+ logger.warn(
22474
+ "[SDK Html-cache] put skipped, empty or minimal response body",
22475
+ {
22476
+ trace,
22477
+ bodyLength: body.length
22478
+ }
22479
+ );
22480
+ return;
22481
+ }
22471
22482
  const cacheTimeISO = (/* @__PURE__ */ new Date()).toISOString();
22472
- const headers = lowercaseHeaders2(response.headers);
22483
+ const headers = this.normalizeHeaders(response.headers);
22473
22484
  const entry = {
22474
22485
  status: response.status,
22475
22486
  statusText: response.statusText,
@@ -22503,14 +22514,13 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22503
22514
  });
22504
22515
  }
22505
22516
  }
22506
- isReadCacheCandidate(request) {
22507
- const m = request.method.toUpperCase();
22508
- return (m === "GET" || m === "HEAD") && this.isCacheable(request);
22517
+ canReadFromCache(request) {
22518
+ const method = request.method.toUpperCase();
22519
+ return (method === "GET" || method === "HEAD") && this.isRequestCacheable(request);
22509
22520
  }
22510
- isWriteCacheCandidate(request, response) {
22511
- if (request.method.toUpperCase() !== "GET") return false;
22512
- if (!this.isCacheable(request)) return false;
22513
- return this.isResponseCacheable(response);
22521
+ canWriteToCache(request, response) {
22522
+ const method = request.method.toUpperCase();
22523
+ return method === "GET" && response.ok && this.isRequestCacheable(request) && this.isResponseCacheable(response);
22514
22524
  }
22515
22525
  createRevalidationRequest(request) {
22516
22526
  const headers = new Headers(request.headers);
@@ -22526,7 +22536,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22526
22536
  }
22527
22537
  buildClientResponse(entry, isStale, age) {
22528
22538
  const headers = new Headers(entry.headers);
22529
- headers.set("Cache-Control", "public, max-age=0, must-revalidate");
22539
+ headers.set("Cache-Control", "public, max-age=1, must-revalidate");
22530
22540
  headers.set(
22531
22541
  "Cloudflare-CDN-Cache-Control",
22532
22542
  `public, s-maxage=${entry.ttl}, stale-while-revalidate=${entry.swr}, stale-if-error=60`
@@ -22621,6 +22631,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22621
22631
  const accept = headers.get("accept") || "";
22622
22632
  const versionFactors = {
22623
22633
  store: headers.get("swell-storefront-id") || "",
22634
+ app: (headers.get("swell-app-id") || "") + "@" + (swellData["swell-app-version"] || ""),
22624
22635
  auth: headers.get("swell-access-token") || "",
22625
22636
  theme: headers.get("swell-theme-version-hash") || "",
22626
22637
  modified: headers.get("swell-cache-modified") || "",
@@ -22643,11 +22654,16 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22643
22654
  return {};
22644
22655
  }
22645
22656
  }
22646
- isCacheable(request) {
22657
+ isRequestCacheable(request) {
22647
22658
  const url = new URL(request.url);
22648
22659
  if (request.headers.get("swell-deployment-mode") === "editor") return false;
22649
- const skipPaths = ["/checkout"];
22650
- if (skipPaths.some((path) => url.pathname.startsWith(path))) return false;
22660
+ if (this.cacheRules.pathRules) {
22661
+ for (const rule of this.cacheRules.pathRules) {
22662
+ if (this.pathMatches(rule.path, url.pathname) && rule.skip) {
22663
+ return false;
22664
+ }
22665
+ }
22666
+ }
22651
22667
  if (request.headers.get("cache-control")?.includes("no-cache"))
22652
22668
  return false;
22653
22669
  return true;
@@ -22663,24 +22679,33 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22663
22679
  }
22664
22680
  getDeploymentMode(headers) {
22665
22681
  const mode = headers.get("swell-deployment-mode");
22666
- return mode === "preview" || mode === "editor" ? mode : "live";
22682
+ return mode === "preview" ? "preview" : "live";
22667
22683
  }
22668
22684
  getTTLForRequest(request) {
22669
22685
  const url = new URL(request.url);
22670
22686
  const mode = this.getDeploymentMode(request.headers);
22671
- if (mode === "editor") return 0;
22672
- const config = mode === "preview" ? TTL_CONFIG.PREVIEW : TTL_CONFIG.LIVE;
22673
- if (url.pathname === "/") return config.HOME;
22674
- if (url.pathname.startsWith("/products/")) return config.PRODUCT;
22675
- if (url.pathname.startsWith("/categories/")) return config.COLLECTION;
22676
- if (url.pathname.startsWith("/pages/")) return config.PAGE;
22677
- if (url.pathname.startsWith("/blogs/")) return config.BLOG;
22678
- return config.DEFAULT;
22687
+ if (this.cacheRules.pathRules) {
22688
+ for (const rule of this.cacheRules.pathRules) {
22689
+ if (this.pathMatches(rule.path, url.pathname) && rule.ttl !== void 0) {
22690
+ return rule.ttl;
22691
+ }
22692
+ }
22693
+ }
22694
+ const defaults = this.cacheRules.defaults?.[mode];
22695
+ return defaults?.ttl ?? DEFAULT_CACHE_RULES.defaults[mode].ttl;
22679
22696
  }
22680
22697
  getSWRForRequest(request) {
22698
+ const url = new URL(request.url);
22681
22699
  const mode = this.getDeploymentMode(request.headers);
22682
- if (mode === "editor") return 0;
22683
- return mode === "preview" ? TTL_CONFIG.PREVIEW.SWR : TTL_CONFIG.LIVE.SWR;
22700
+ if (this.cacheRules.pathRules) {
22701
+ for (const rule of this.cacheRules.pathRules) {
22702
+ if (this.pathMatches(rule.path, url.pathname) && rule.swr !== void 0) {
22703
+ return rule.swr;
22704
+ }
22705
+ }
22706
+ }
22707
+ const defaults = this.cacheRules.defaults?.[mode];
22708
+ return defaults?.swr ?? DEFAULT_CACHE_RULES.defaults[mode].swr;
22684
22709
  }
22685
22710
  getEntryAge(entry) {
22686
22711
  const t = Date.parse(entry.cacheTimeISO);
@@ -22688,6 +22713,22 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22688
22713
  const age = (Date.now() - t) / 1e3;
22689
22714
  return age < 0 ? 0 : age;
22690
22715
  }
22716
+ /**
22717
+ * Converts wildcard pattern to regex and tests against path.
22718
+ * - * matches any characters except /
22719
+ * - ** matches any characters including /
22720
+ */
22721
+ pathMatches(pattern, path) {
22722
+ const regex = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLE_STAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLE_STAR___/g, ".*");
22723
+ return new RegExp(`^${regex}$`).test(path);
22724
+ }
22725
+ normalizeHeaders(headers) {
22726
+ const normalized = {};
22727
+ headers.forEach((value, key) => {
22728
+ normalized[key.toLowerCase()] = value;
22729
+ });
22730
+ return normalized;
22731
+ }
22691
22732
  normalizeSearchParams(searchParams) {
22692
22733
  const ignoredParams = [
22693
22734
  "utm_source",
@@ -22856,7 +22897,6 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22856
22897
  statusText: entry.statusText,
22857
22898
  headers
22858
22899
  });
22859
- await cache.delete(request);
22860
22900
  await cache.put(request, response);
22861
22901
  }
22862
22902
  async delete(key) {
@@ -22867,18 +22907,15 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
22867
22907
  };
22868
22908
 
22869
22909
  // src/cache/html-cache/html-cache-factory.ts
22870
- var _instance = null;
22871
- function getHtmlCache(env) {
22910
+ function getHtmlCache(env, cacheRules) {
22872
22911
  const epoch = env?.HTML_CACHE_EPOCH;
22873
22912
  if (typeof epoch !== "string" || !epoch) return null;
22874
- if (_instance) return _instance;
22875
22913
  const kv = env?.NAMESPACE;
22914
+ const rules = cacheRules || env?.HTML_CACHE_RULES;
22876
22915
  if (env?.HTML_CACHE_BACKEND !== "worker" && kv) {
22877
- _instance = new HtmlCache(epoch, new KVCacheBackend(kv));
22878
- return _instance;
22916
+ return new HtmlCache(epoch, new KVCacheBackend(kv), rules);
22879
22917
  }
22880
- _instance = new HtmlCache(epoch, new WorkerCacheBackend(epoch));
22881
- return _instance;
22918
+ return new HtmlCache(epoch, new WorkerCacheBackend(epoch), rules);
22882
22919
  }
22883
22920
  })();
22884
22921
  //# sourceMappingURL=index.js.map