@swell/apps-sdk 1.0.160 → 1.0.162

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.mjs CHANGED
@@ -7291,12 +7291,7 @@ var ThemeCache = class extends Cache {
7291
7291
  }
7292
7292
  };
7293
7293
 
7294
- // src/cache/theme-file-storage.ts
7295
- import bluebird2 from "bluebird";
7296
-
7297
7294
  // src/cache/kv-variety.ts
7298
- import bluebird from "bluebird";
7299
- var { Promise: Promise2 } = bluebird;
7300
7295
  var CFKV = class {
7301
7296
  constructor(kv) {
7302
7297
  this.kv = kv;
@@ -7328,13 +7323,11 @@ var MiniflareKV = class {
7328
7323
  return /* @__PURE__ */ new Map();
7329
7324
  }
7330
7325
  const result = /* @__PURE__ */ new Map();
7331
- await Promise2.map(
7332
- keys,
7333
- async (key) => {
7326
+ await Promise.all(
7327
+ keys.map(async (key) => {
7334
7328
  const value = await this.kv.get(key, "text");
7335
7329
  result.set(key, value);
7336
- },
7337
- { concurrency: 50 }
7330
+ })
7338
7331
  );
7339
7332
  return result;
7340
7333
  }
@@ -7366,16 +7359,13 @@ function createClientKV(env, flavor = "cf") {
7366
7359
  return new MemoryKV();
7367
7360
  }
7368
7361
 
7369
- // src/cache/theme-file-storage.ts
7370
- var { Promise: Promise3 } = bluebird2;
7371
- var ThemeFileStorage = class {
7362
+ // src/cache/theme-file-cache.ts
7363
+ var ThemeFileCache = class {
7372
7364
  kv;
7373
- maxConcurrency;
7374
7365
  maxBatchSize = 20 * 1024 * 1024;
7375
7366
  // 20MB safety margin
7376
7367
  constructor(env, flavor = "cf") {
7377
7368
  this.kv = createClientKV(env, flavor);
7378
- this.maxConcurrency = flavor === "miniflare" ? 50 : 6;
7379
7369
  }
7380
7370
  /**
7381
7371
  * Build a KV storage key from a file hash
@@ -7468,10 +7458,8 @@ var ThemeFileStorage = class {
7468
7458
  totalSize,
7469
7459
  trace
7470
7460
  });
7471
- const results = await Promise3.map(
7472
- batches,
7473
- (batch) => this.loadBatch(batch),
7474
- { concurrency: Math.min(this.maxConcurrency, batches.length) }
7461
+ const results = await Promise.all(
7462
+ batches.map((batch) => this.loadBatch(batch))
7475
7463
  );
7476
7464
  const mergedConfigs = this.mergeResults(configs, results);
7477
7465
  const loadedCount = mergedConfigs.filter((c) => c.file_data).length;
@@ -7536,10 +7524,8 @@ var ThemeFileStorage = class {
7536
7524
  }
7537
7525
  const existing = /* @__PURE__ */ new Set();
7538
7526
  const batches = this.planGetBatches(configs);
7539
- const results = await Promise3.map(
7540
- batches,
7541
- (batch) => this.kv.get(batch.keys),
7542
- { concurrency: this.maxConcurrency }
7527
+ const results = await Promise.all(
7528
+ batches.map((batch) => this.kv.get(batch.keys))
7543
7529
  );
7544
7530
  for (const batchResult of results) {
7545
7531
  for (const [key, value] of batchResult.entries()) {
@@ -7603,15 +7589,13 @@ var ThemeFileStorage = class {
7603
7589
  skippedExisting: existing.size,
7604
7590
  trace
7605
7591
  });
7606
- await Promise3.map(
7607
- toWrite,
7608
- async (config) => {
7592
+ await Promise.all(
7593
+ toWrite.map(async (config) => {
7609
7594
  const key = this.buildKey(config.hash);
7610
7595
  const metadata = config.file?.content_type ? { content_type: config.file.content_type } : void 0;
7611
- await this.kv.put(key, config.file_data, metadata);
7596
+ await this.kv.put(key, config.file_data, { metadata });
7612
7597
  result.written++;
7613
- },
7614
- { concurrency: this.maxConcurrency }
7598
+ })
7615
7599
  );
7616
7600
  }
7617
7601
  logger.info("[ThemeFileStorage] Put files complete", {
@@ -7625,378 +7609,6 @@ var ThemeFileStorage = class {
7625
7609
  }
7626
7610
  };
7627
7611
 
7628
- // src/cache/constants.ts
7629
- var SECOND = 1e3;
7630
- var MINUTE = 60 * SECOND;
7631
- var HOUR = 60 * MINUTE;
7632
- var DAY = 24 * HOUR;
7633
- var YEAR = 365 * DAY;
7634
- var SHORT_TTL = 5 * SECOND;
7635
-
7636
- // src/cache/worker-html-cache.ts
7637
- var CACHE_NAME = "swell-html-v0";
7638
- var CACHE_KEY_ORIGIN = "https://cache.swell.store";
7639
- var TTL_CONFIG = {
7640
- LIVE: {
7641
- DEFAULT: 20,
7642
- HOME: 20,
7643
- PRODUCT: 20,
7644
- COLLECTION: 20,
7645
- PAGE: 20,
7646
- BLOG: 20,
7647
- SWR: 180
7648
- },
7649
- PREVIEW: {
7650
- DEFAULT: 20,
7651
- HOME: 20,
7652
- PRODUCT: 20,
7653
- COLLECTION: 20,
7654
- PAGE: 20,
7655
- BLOG: 20,
7656
- SWR: 180
7657
- }
7658
- };
7659
- var WorkerHtmlCache = class {
7660
- epoch;
7661
- constructor(epoch) {
7662
- this.epoch = epoch;
7663
- }
7664
- async get(request) {
7665
- const trace = createTraceId();
7666
- if (request.method !== "GET") {
7667
- logger.debug("[SDK Html-cache] non-cacheable", { trace });
7668
- return { found: false, cacheable: false };
7669
- }
7670
- if (!this.isCacheable(request)) {
7671
- logger.debug("[SDK Html-cache] non-cacheable", { trace });
7672
- return { found: false, cacheable: false };
7673
- }
7674
- try {
7675
- const cache = await caches.open(CACHE_NAME + this.epoch);
7676
- const cacheKey = this.buildCacheKey(request);
7677
- const cached = await cache.match(cacheKey);
7678
- if (!cached) {
7679
- logger.debug("[SDK Html-cache] cacheable, MISS", { trace });
7680
- return { found: false, cacheable: true };
7681
- }
7682
- const age = this.getResponseAge(cached);
7683
- const ttl = parseInt(cached.headers.get("X-Original-TTL") || "") || this.getTTLForRequest(request);
7684
- const swr = parseInt(cached.headers.get("X-Original-SWR") || "") || this.getSWRForRequest(request);
7685
- const isStale = age >= ttl;
7686
- const isExpired = age >= ttl + swr;
7687
- if (!isExpired) {
7688
- logger.debug("[SDK Html-cache] cacheable, HIT", {
7689
- stale: isStale,
7690
- age,
7691
- trace
7692
- });
7693
- const clientResponse = this.buildClientResponse(
7694
- cached,
7695
- ttl,
7696
- swr,
7697
- isStale,
7698
- age
7699
- );
7700
- return {
7701
- found: true,
7702
- stale: isStale,
7703
- response: clientResponse,
7704
- cacheable: true,
7705
- age: Math.floor(age)
7706
- };
7707
- }
7708
- logger.debug("[SDK Html-cache] cacheable, hit, expired", { trace });
7709
- return { found: false, cacheable: true };
7710
- } catch (_) {
7711
- logger.warn("[SDK Html-cache] no get support", { trace });
7712
- return null;
7713
- }
7714
- }
7715
- // 304 support
7716
- async getWithConditionals(request) {
7717
- const result = await this.get(request);
7718
- if (!result?.found || result.stale) {
7719
- return result;
7720
- }
7721
- const ifModifiedSince = request.headers.get("If-Modified-Since");
7722
- const ifNoneMatch = request.headers.get("If-None-Match");
7723
- if ((ifModifiedSince || ifNoneMatch) && result.response) {
7724
- const lastModified = result.response.headers.get("Last-Modified");
7725
- const etag = result.response.headers.get("ETag");
7726
- if (this.checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag)) {
7727
- result.notModified = true;
7728
- result.conditional304 = new Response(null, {
7729
- status: 304,
7730
- headers: {
7731
- "Last-Modified": lastModified || "",
7732
- ETag: etag || "",
7733
- "Cache-Control": result.response.headers.get("Cache-Control") || "",
7734
- "Cloudflare-CDN-Cache-Control": result.response.headers.get("Cloudflare-CDN-Cache-Control") || "",
7735
- "X-Cache-Status": "HIT-304"
7736
- }
7737
- });
7738
- }
7739
- }
7740
- return result;
7741
- }
7742
- checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag) {
7743
- if (ifNoneMatch && etag) {
7744
- return ifNoneMatch === etag;
7745
- }
7746
- if (ifModifiedSince && lastModified) {
7747
- const ifModDate = new Date(ifModifiedSince);
7748
- const lastModDate = new Date(lastModified);
7749
- return !isNaN(ifModDate.getTime()) && !isNaN(lastModDate.getTime()) && ifModDate >= lastModDate;
7750
- }
7751
- return false;
7752
- }
7753
- createRevalidationRequest(request) {
7754
- const headers = new Headers(request.headers);
7755
- headers.set("X-Cache-Bypass", "revalidation");
7756
- headers.delete("If-None-Match");
7757
- headers.delete("If-Modified-Since");
7758
- headers.delete("Cache-Control");
7759
- headers.delete("Pragma");
7760
- return new Request(request.url, {
7761
- method: "GET",
7762
- headers
7763
- });
7764
- }
7765
- async put(request, response) {
7766
- const trace = createTraceId();
7767
- if (request.method !== "GET" || !response.ok) {
7768
- logger.debug("[SDK Html-cache] put skipped", { trace });
7769
- return;
7770
- }
7771
- if (!this.isCacheable(request) || !this.isResponseCacheable(response)) {
7772
- logger.debug("[SDK Html-cache] put skipped, non-cacheable", {
7773
- trace
7774
- });
7775
- return;
7776
- }
7777
- try {
7778
- const cache = await caches.open(CACHE_NAME + this.epoch);
7779
- const cacheKey = this.buildCacheKey(request);
7780
- await cache.delete(cacheKey);
7781
- const ttl = this.getTTLForRequest(request);
7782
- const swr = this.getSWRForRequest(request);
7783
- const headers = new Headers(response.headers);
7784
- const existingCacheControl = response.headers.get("Cache-Control");
7785
- if (!existingCacheControl || existingCacheControl === "public") {
7786
- const internalMaxAge = ttl + swr;
7787
- headers.set("Cache-Control", `public, max-age=${internalMaxAge}`);
7788
- }
7789
- const cacheTime = (/* @__PURE__ */ new Date()).toISOString();
7790
- headers.set("X-Cache-Time", cacheTime);
7791
- headers.set("X-Original-TTL", ttl.toString());
7792
- headers.set("X-Original-SWR", swr.toString());
7793
- if (!headers.get("Last-Modified")) {
7794
- headers.set("Last-Modified", new Date(cacheTime).toUTCString());
7795
- }
7796
- const cacheableResponse = new Response(response.body, {
7797
- status: response.status,
7798
- statusText: response.statusText,
7799
- headers
7800
- });
7801
- await cache.put(cacheKey, cacheableResponse);
7802
- logger.debug("[SDK Html-cache] put done", { trace });
7803
- } catch (_) {
7804
- logger.warn("[SDK Html-cache] no put support", { trace });
7805
- }
7806
- }
7807
- buildClientResponse(cachedResponse, ttl, swr, isStale, age) {
7808
- const headers = new Headers(cachedResponse.headers);
7809
- headers.set(
7810
- "Cache-Control",
7811
- `public, max-age=${ttl}, stale-while-revalidate=${swr}`
7812
- );
7813
- headers.set(
7814
- "Cloudflare-CDN-Cache-Control",
7815
- `public, s-maxage=${ttl}, stale-while-revalidate=${swr}, stale-if-error=60`
7816
- );
7817
- const cacheTime = headers.get("X-Cache-Time");
7818
- if (cacheTime) {
7819
- const lastModified = new Date(cacheTime).toUTCString();
7820
- headers.set("Last-Modified", lastModified);
7821
- }
7822
- headers.set("X-Cache-Status", isStale ? "STALE" : "HIT");
7823
- headers.set("X-Cache-Age", Math.floor(age).toString());
7824
- headers.delete("X-Original-TTL");
7825
- headers.delete("X-Original-SWR");
7826
- headers.delete("X-Cache-Time");
7827
- return new Response(cachedResponse.body, {
7828
- status: cachedResponse.status,
7829
- statusText: cachedResponse.statusText,
7830
- headers
7831
- });
7832
- }
7833
- buildCacheKey(request) {
7834
- const url = new URL(request.url);
7835
- const versionHash = this.generateVersionHash(request.headers);
7836
- const normalizedQuery = this.normalizeSearchParams(url.searchParams);
7837
- const cacheKeyPath = `${versionHash}${url.pathname}`;
7838
- const keyUrl = new URL(`${CACHE_KEY_ORIGIN}${cacheKeyPath}`);
7839
- if (normalizedQuery) {
7840
- keyUrl.search = `?${normalizedQuery}`;
7841
- }
7842
- const sanitizedHeaders = this.sanitizeHeaders(request.headers);
7843
- return new Request(keyUrl.toString(), {
7844
- method: "GET",
7845
- headers: sanitizedHeaders
7846
- });
7847
- }
7848
- sanitizeHeaders(originalHeaders) {
7849
- const CACHE_RELEVANT_HEADERS = [
7850
- // Content negotiation (affects response format)
7851
- "accept",
7852
- "accept-language"
7853
- ];
7854
- const sanitized = new Headers();
7855
- CACHE_RELEVANT_HEADERS.forEach((header) => {
7856
- const value = originalHeaders.get(header);
7857
- if (value) {
7858
- sanitized.set(header, value);
7859
- }
7860
- });
7861
- return sanitized;
7862
- }
7863
- generateVersionHash(headers) {
7864
- const swellData = this.extractSwellData(headers);
7865
- const versionFactors = {
7866
- store: headers.get("swell-storefront-id") || "",
7867
- auth: headers.get("swell-access-token") || "",
7868
- theme: headers.get("swell-theme-version-hash") || "",
7869
- modified: headers.get("swell-cache-modified") || "",
7870
- currency: swellData["swell-currency"] || "USD",
7871
- locale: headers.get("x-locale") || headers.get("accept-language")?.split(",")[0] || "default",
7872
- context: headers.get("swell-storefront-context"),
7873
- epoch: this.epoch
7874
- };
7875
- return md5(JSON.stringify(versionFactors));
7876
- }
7877
- extractSwellData(headers) {
7878
- const cookie = headers.get("cookie");
7879
- if (!cookie) return {};
7880
- const swellDataMatch = cookie.match(/swell-data=([^;]+)/);
7881
- if (!swellDataMatch) return {};
7882
- try {
7883
- const parsed = JSON.parse(decodeURIComponent(swellDataMatch[1]));
7884
- if (typeof parsed === "object" && parsed !== null) {
7885
- return parsed;
7886
- }
7887
- return {};
7888
- } catch {
7889
- return {};
7890
- }
7891
- }
7892
- isCacheable(request) {
7893
- const url = new URL(request.url);
7894
- const headers = request.headers;
7895
- if (headers.get("swell-deployment-mode") === "editor") {
7896
- return false;
7897
- }
7898
- const skipPaths = ["/checkout"];
7899
- if (skipPaths.some((path) => url.pathname.startsWith(path))) {
7900
- return false;
7901
- }
7902
- if (headers.get("cache-control")?.includes("no-cache")) {
7903
- return false;
7904
- }
7905
- return true;
7906
- }
7907
- isResponseCacheable(response) {
7908
- const contentType = response.headers.get("content-type");
7909
- if (!contentType?.includes("text/html")) {
7910
- return false;
7911
- }
7912
- if (response.headers.get("set-cookie")) {
7913
- return false;
7914
- }
7915
- const cacheControl = response.headers.get("cache-control");
7916
- if (cacheControl?.includes("no-store") || cacheControl?.includes("private")) {
7917
- return false;
7918
- }
7919
- return true;
7920
- }
7921
- getDeploymentMode(headers) {
7922
- const mode = headers.get("swell-deployment-mode");
7923
- if (mode === "preview" || mode === "editor") {
7924
- return mode;
7925
- }
7926
- return "live";
7927
- }
7928
- getTTLForRequest(request) {
7929
- const url = new URL(request.url);
7930
- const path = url.pathname;
7931
- const mode = this.getDeploymentMode(request.headers);
7932
- if (mode === "editor") {
7933
- return 0;
7934
- }
7935
- const config = mode === "preview" ? TTL_CONFIG.PREVIEW : TTL_CONFIG.LIVE;
7936
- if (path === "/") {
7937
- return config.HOME;
7938
- }
7939
- if (path.startsWith("/products/")) {
7940
- return config.PRODUCT;
7941
- }
7942
- if (path.startsWith("/categories/")) {
7943
- return config.COLLECTION;
7944
- }
7945
- if (path.startsWith("/pages/")) {
7946
- return config.PAGE;
7947
- }
7948
- if (path.startsWith("/blogs/")) {
7949
- return config.BLOG;
7950
- }
7951
- return config.DEFAULT;
7952
- }
7953
- getSWRForRequest(request) {
7954
- const mode = this.getDeploymentMode(request.headers);
7955
- if (mode === "editor") {
7956
- return 0;
7957
- }
7958
- return mode === "preview" ? TTL_CONFIG.PREVIEW.SWR : TTL_CONFIG.LIVE.SWR;
7959
- }
7960
- getResponseAge(response) {
7961
- const cacheTime = response.headers.get("X-Cache-Time");
7962
- if (!cacheTime) {
7963
- return Infinity;
7964
- }
7965
- const cacheDate = new Date(cacheTime);
7966
- if (isNaN(cacheDate.getTime())) {
7967
- return Infinity;
7968
- }
7969
- const age = (Date.now() - cacheDate.getTime()) / 1e3;
7970
- return Math.max(0, age);
7971
- }
7972
- normalizeSearchParams(searchParams) {
7973
- const ignoredParams = [
7974
- "utm_source",
7975
- "utm_medium",
7976
- "utm_campaign",
7977
- "utm_content",
7978
- "utm_term",
7979
- "fbclid",
7980
- "gclid",
7981
- "gbraid",
7982
- "wbraid",
7983
- "ref",
7984
- "source",
7985
- "mc_cid",
7986
- "mc_eid"
7987
- ];
7988
- const relevantParams = [];
7989
- searchParams.forEach((value, key) => {
7990
- if (!ignoredParams.includes(key)) {
7991
- relevantParams.push(
7992
- `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
7993
- );
7994
- }
7995
- });
7996
- return relevantParams.sort().join("&");
7997
- }
7998
- };
7999
-
8000
7612
  // src/resources/addresses.ts
8001
7613
  var SwellAddresses = class extends SwellStorefrontCollection {
8002
7614
  constructor(swell, query) {
@@ -16262,7 +15874,7 @@ function ShopifyAddress(instance, address, account) {
16262
15874
  function joinAddressLines(...props) {
16263
15875
  return props.filter(Boolean).join("\n");
16264
15876
  }
16265
- function ShopifyCountry(_instance, countryCode) {
15877
+ function ShopifyCountry(_instance2, countryCode) {
16266
15878
  const currencyCode = getCurrencyByCountry(countryCode) || "USD";
16267
15879
  return new ShopifyResource(
16268
15880
  {
@@ -16595,7 +16207,7 @@ async function resolveLastOrder(instance, account) {
16595
16207
  }
16596
16208
 
16597
16209
  // src/compatibility/shopify-objects/font.ts
16598
- function ShopifyFont(_instance, font) {
16210
+ function ShopifyFont(_instance2, font) {
16599
16211
  if (font instanceof ShopifyResource) {
16600
16212
  return font.clone();
16601
16213
  }
@@ -16606,7 +16218,7 @@ function ShopifyFont(_instance, font) {
16606
16218
  family: font.family,
16607
16219
  style: font.style,
16608
16220
  "system?": font.system,
16609
- variants: font.variants.map((variant) => ShopifyFont(_instance, variant)),
16221
+ variants: font.variants.map((variant) => ShopifyFont(_instance2, variant)),
16610
16222
  weight: font.weight
16611
16223
  });
16612
16224
  }
@@ -16619,7 +16231,7 @@ var SHOPIFY_FORMS = {
16619
16231
  })
16620
16232
  }
16621
16233
  };
16622
- function ShopifyForm(_instance, form) {
16234
+ function ShopifyForm(_instance2, form) {
16623
16235
  if (form instanceof ShopifyResource) {
16624
16236
  return form.clone();
16625
16237
  }
@@ -16916,7 +16528,7 @@ function ShopifyRecommendations(instance, product) {
16916
16528
  }
16917
16529
 
16918
16530
  // src/compatibility/shopify-objects/page.ts
16919
- function ShopifyPage(_instance, page) {
16531
+ function ShopifyPage(_instance2, page) {
16920
16532
  if (page instanceof ShopifyResource) {
16921
16533
  return page.clone();
16922
16534
  }
@@ -18128,7 +17740,7 @@ ${injects.join("\n")}</script>`;
18128
17740
  };
18129
17741
 
18130
17742
  // src/compatibility/shopify-objects/template.ts
18131
- function ShopifyTemplate(_instance, template) {
17743
+ function ShopifyTemplate(_instance2, template) {
18132
17744
  return new ShopifyResource(
18133
17745
  {
18134
17746
  directory: template.path,
@@ -20448,7 +20060,7 @@ var ThemeLoader = class _ThemeLoader {
20448
20060
  flavor,
20449
20061
  trace
20450
20062
  });
20451
- const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20063
+ const storage = new ThemeFileCache(this.swell.workerEnv, flavor);
20452
20064
  const result = await storage.putFiles(configs);
20453
20065
  if (result.warnings.length > 0) {
20454
20066
  logger.warn("[ThemeLoader] Theme cache updated with warnings", {
@@ -20486,7 +20098,7 @@ var ThemeLoader = class _ThemeLoader {
20486
20098
  total: configMetadata.length
20487
20099
  });
20488
20100
  const flavor = getKVFlavor(this.swell.workerEnv);
20489
- const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20101
+ const storage = new ThemeFileCache(this.swell.workerEnv, flavor);
20490
20102
  const kvHydrated = await storage.getFiles(configMetadata);
20491
20103
  const completeConfigs = await this.ensureConfigsHaveData(kvHydrated);
20492
20104
  for (const config of completeConfigs) {
@@ -21068,12 +20680,7 @@ var SwellTheme3 = class {
21068
20680
  // Default value (always StorefrontResource)
21069
20681
  () => this.fetchCart()
21070
20682
  ),
21071
- this.fetchSingletonResourceCached(
21072
- "account",
21073
- () => this.fetchAccount(),
21074
- () => null,
21075
- false
21076
- )
20683
+ this.fetchAccount()
21077
20684
  ]);
21078
20685
  if (!cart) {
21079
20686
  throw new Error("Failed to fetch cart");
@@ -22708,6 +22315,551 @@ function getResourceQuery(slug, query) {
22708
22315
  ...query
22709
22316
  };
22710
22317
  }
22318
+
22319
+ // src/cache/html-cache/html-cache.ts
22320
+ var CACHE_KEY_ORIGIN = "https://cache.swell.store";
22321
+ var DEFAULT_CACHE_RULES = {
22322
+ defaults: {
22323
+ live: { ttl: 20, swr: 60 * 60 * 24 * 7 },
22324
+ // 20s TTL, 1 week SWR
22325
+ preview: { ttl: 10, swr: 60 * 60 * 24 * 7 }
22326
+ // 10s TTL, 1 week SWR
22327
+ },
22328
+ pathRules: [
22329
+ { path: "/checkout/*", skip: true }
22330
+ ]
22331
+ };
22332
+ var HtmlCache = class {
22333
+ epoch;
22334
+ backend;
22335
+ cacheRules;
22336
+ constructor(epoch, backend, cacheRules = DEFAULT_CACHE_RULES) {
22337
+ this.epoch = epoch;
22338
+ this.backend = backend;
22339
+ this.cacheRules = cacheRules;
22340
+ }
22341
+ async get(request) {
22342
+ const trace = createTraceId();
22343
+ if (!this.canReadFromCache(request)) {
22344
+ logger.debug("[SDK Html-cache] non-cacheable request", { trace });
22345
+ return { found: false, cacheable: false };
22346
+ }
22347
+ try {
22348
+ const cacheKey = this.buildCacheKey(request);
22349
+ const entry = await this.backend.read(cacheKey);
22350
+ if (!entry) {
22351
+ logger.debug("[SDK Html-cache] cacheable, MISS", { trace });
22352
+ return { found: false, cacheable: true };
22353
+ }
22354
+ const age = this.getEntryAge(entry);
22355
+ const { ttl, swr } = entry;
22356
+ const isStale = age >= ttl;
22357
+ const isExpired = age >= ttl + swr;
22358
+ if (!isExpired) {
22359
+ logger.debug("[SDK Html-cache] cacheable, HIT", {
22360
+ stale: isStale,
22361
+ age,
22362
+ trace
22363
+ });
22364
+ const clientResponse = this.buildClientResponse(entry, isStale, age);
22365
+ return {
22366
+ found: true,
22367
+ stale: isStale,
22368
+ response: clientResponse,
22369
+ cacheable: true,
22370
+ age: Math.floor(age)
22371
+ };
22372
+ }
22373
+ logger.debug("[SDK Html-cache] cacheable, hit, expired", { trace });
22374
+ return { found: false, cacheable: true };
22375
+ } catch (e) {
22376
+ logger.warn("[SDK Html-cache] get failed", {
22377
+ trace,
22378
+ error: e instanceof Error ? e.message : String(e)
22379
+ });
22380
+ return null;
22381
+ }
22382
+ }
22383
+ async getWithConditionals(request) {
22384
+ const result = await this.get(request);
22385
+ if (!result?.found || result.stale) {
22386
+ return result;
22387
+ }
22388
+ const ifModifiedSince = request.headers.get("If-Modified-Since");
22389
+ const ifNoneMatch = request.headers.get("If-None-Match");
22390
+ if ((ifModifiedSince || ifNoneMatch) && result.response) {
22391
+ const lastModified = result.response.headers.get("Last-Modified");
22392
+ const etag = result.response.headers.get("ETag");
22393
+ if (this.checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag)) {
22394
+ result.notModified = true;
22395
+ result.conditional304 = new Response(null, {
22396
+ status: 304,
22397
+ headers: {
22398
+ "Last-Modified": lastModified || "",
22399
+ ETag: etag || "",
22400
+ "Cache-Control": result.response.headers.get("Cache-Control") || "",
22401
+ "Cloudflare-CDN-Cache-Control": result.response.headers.get("Cloudflare-CDN-Cache-Control") || "",
22402
+ "X-Cache-Status": "HIT-304"
22403
+ }
22404
+ });
22405
+ }
22406
+ }
22407
+ return result;
22408
+ }
22409
+ async put(request, response) {
22410
+ const trace = createTraceId();
22411
+ if (!this.canWriteToCache(request, response)) {
22412
+ logger.debug("[SDK Html-cache] put skipped, non-cacheable", { trace });
22413
+ return;
22414
+ }
22415
+ try {
22416
+ const cacheKey = this.buildCacheKey(request);
22417
+ const ttl = this.getTTLForRequest(request);
22418
+ const swr = this.getSWRForRequest(request);
22419
+ const body = await response.text();
22420
+ const cacheTimeISO = (/* @__PURE__ */ new Date()).toISOString();
22421
+ const headers = this.normalizeHeaders(response.headers);
22422
+ const entry = {
22423
+ status: response.status,
22424
+ statusText: response.statusText,
22425
+ headers,
22426
+ body,
22427
+ cacheTimeISO,
22428
+ ttl,
22429
+ swr,
22430
+ etag: this.quoteETag(headers["etag"] || md5(body)),
22431
+ lastModifiedUTC: headers["last-modified"] || new Date(cacheTimeISO).toUTCString()
22432
+ };
22433
+ const hardExpireSeconds = ttl + swr;
22434
+ await this.backend.write(cacheKey, entry, hardExpireSeconds);
22435
+ logger.debug("[SDK Html-cache] put done", { trace });
22436
+ } catch (e) {
22437
+ logger.warn("[SDK Html-cache] put failed", {
22438
+ trace,
22439
+ error: e instanceof Error ? e.message : String(e)
22440
+ });
22441
+ }
22442
+ }
22443
+ async delete(requestOrKey) {
22444
+ try {
22445
+ const key = typeof requestOrKey === "string" ? requestOrKey : this.buildCacheKey(requestOrKey);
22446
+ if (this.backend.delete) {
22447
+ await this.backend.delete(key);
22448
+ }
22449
+ } catch (e) {
22450
+ logger.warn("[SDK Html-cache] delete failed", {
22451
+ error: e instanceof Error ? e.message : String(e)
22452
+ });
22453
+ }
22454
+ }
22455
+ canReadFromCache(request) {
22456
+ const method = request.method.toUpperCase();
22457
+ return (method === "GET" || method === "HEAD") && this.isRequestCacheable(request);
22458
+ }
22459
+ canWriteToCache(request, response) {
22460
+ const method = request.method.toUpperCase();
22461
+ return method === "GET" && response.ok && this.isRequestCacheable(request) && this.isResponseCacheable(response);
22462
+ }
22463
+ createRevalidationRequest(request) {
22464
+ const headers = new Headers(request.headers);
22465
+ headers.set("X-Cache-Bypass", "revalidation");
22466
+ headers.delete("If-None-Match");
22467
+ headers.delete("If-Modified-Since");
22468
+ headers.delete("Cache-Control");
22469
+ headers.delete("Pragma");
22470
+ return new Request(request.url, {
22471
+ method: "GET",
22472
+ headers
22473
+ });
22474
+ }
22475
+ buildClientResponse(entry, isStale, age) {
22476
+ const headers = new Headers(entry.headers);
22477
+ headers.set("Cache-Control", "public, max-age=0, must-revalidate");
22478
+ headers.set(
22479
+ "Cloudflare-CDN-Cache-Control",
22480
+ `public, s-maxage=${entry.ttl}, stale-while-revalidate=${entry.swr}, stale-if-error=60`
22481
+ );
22482
+ if (entry.lastModifiedUTC) {
22483
+ headers.set("Last-Modified", entry.lastModifiedUTC);
22484
+ }
22485
+ if (entry.etag) {
22486
+ headers.set("ETag", entry.etag);
22487
+ }
22488
+ headers.set("X-Cache-Status", isStale ? "STALE" : "HIT");
22489
+ headers.set("X-Cache-Age", Math.floor(age).toString());
22490
+ this.sanitizeClientHeaders(headers);
22491
+ return new Response(entry.body, {
22492
+ status: entry.status,
22493
+ statusText: entry.statusText,
22494
+ headers
22495
+ });
22496
+ }
22497
+ buildCacheKey(request) {
22498
+ const url = new URL(request.url);
22499
+ const versionHash = this.generateVersionHash(request.headers);
22500
+ const normalizedQuery = this.normalizeSearchParams(url.searchParams);
22501
+ const cacheKeyPath = `${versionHash}${url.pathname}`;
22502
+ const keyUrl = new URL(`${CACHE_KEY_ORIGIN}${cacheKeyPath}`);
22503
+ if (normalizedQuery) {
22504
+ keyUrl.search = `?${normalizedQuery}`;
22505
+ }
22506
+ return keyUrl.toString();
22507
+ }
22508
+ checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag) {
22509
+ if (this.ifNoneMatchMatches(ifNoneMatch, etag)) {
22510
+ return true;
22511
+ }
22512
+ if (ifModifiedSince && lastModified) {
22513
+ try {
22514
+ const ifModDate = new Date(ifModifiedSince);
22515
+ const lastModDate = new Date(lastModified);
22516
+ if (isNaN(ifModDate.getTime()) || isNaN(lastModDate.getTime())) {
22517
+ return false;
22518
+ }
22519
+ return ifModDate >= lastModDate;
22520
+ } catch {
22521
+ return false;
22522
+ }
22523
+ }
22524
+ return false;
22525
+ }
22526
+ ifNoneMatchMatches(ifNoneMatch, etag) {
22527
+ if (!ifNoneMatch || !etag) return false;
22528
+ const header = ifNoneMatch.trim();
22529
+ if (header === "*") return true;
22530
+ const tokens = header.split(",").map((t) => t.trim());
22531
+ const normalizedEtag = etag.replace(/^W\//, "");
22532
+ for (const token of tokens) {
22533
+ if (token === etag) return true;
22534
+ const normalizedToken = token.replace(/^W\//, "");
22535
+ if (normalizedToken === normalizedEtag) return true;
22536
+ }
22537
+ return false;
22538
+ }
22539
+ quoteETag(value) {
22540
+ if (!value) return value;
22541
+ if (value.startsWith('"') || value.startsWith('W/"')) return value;
22542
+ if (value.startsWith("W/")) return `W/"${value.slice(2)}"`;
22543
+ return `"${value}"`;
22544
+ }
22545
+ sanitizeClientHeaders(headers) {
22546
+ const HOP_BY_HOP = [
22547
+ "connection",
22548
+ "proxy-connection",
22549
+ "keep-alive",
22550
+ "transfer-encoding",
22551
+ "upgrade",
22552
+ "proxy-authenticate",
22553
+ "proxy-authorization",
22554
+ "te",
22555
+ "trailers",
22556
+ "via",
22557
+ "alt-svc",
22558
+ "content-length"
22559
+ ];
22560
+ for (const h of HOP_BY_HOP) headers.delete(h);
22561
+ headers.delete("content-encoding");
22562
+ headers.delete("x-original-ttl");
22563
+ headers.delete("x-original-swr");
22564
+ headers.delete("x-cache-time");
22565
+ }
22566
+ generateVersionHash(headers) {
22567
+ const swellData = this.extractSwellData(headers);
22568
+ const acceptLang = headers.get("accept-language") || "";
22569
+ const accept = headers.get("accept") || "";
22570
+ const versionFactors = {
22571
+ store: headers.get("swell-storefront-id") || "",
22572
+ app: (headers.get("swell-app-id") || "") + "@" + (swellData["swell-app-version"] || ""),
22573
+ auth: headers.get("swell-access-token") || "",
22574
+ theme: headers.get("swell-theme-version-hash") || "",
22575
+ modified: headers.get("swell-cache-modified") || "",
22576
+ currency: swellData["swell-currency"] || "USD",
22577
+ locale: headers.get("x-locale") || acceptLang.split(",")[0].trim().toLowerCase() || "default",
22578
+ context: headers.get("swell-storefront-context"),
22579
+ accept,
22580
+ epoch: this.epoch
22581
+ };
22582
+ return md5(JSON.stringify(versionFactors));
22583
+ }
22584
+ extractSwellData(headers) {
22585
+ const cookie = headers.get("cookie");
22586
+ if (!cookie) return {};
22587
+ const swellDataMatch = cookie.match(/swell-data=([^;]+)/);
22588
+ if (!swellDataMatch) return {};
22589
+ try {
22590
+ return JSON.parse(decodeURIComponent(swellDataMatch[1])) || {};
22591
+ } catch {
22592
+ return {};
22593
+ }
22594
+ }
22595
+ isRequestCacheable(request) {
22596
+ const url = new URL(request.url);
22597
+ if (request.headers.get("swell-deployment-mode") === "editor") return false;
22598
+ if (this.cacheRules.pathRules) {
22599
+ for (const rule of this.cacheRules.pathRules) {
22600
+ if (this.pathMatches(rule.path, url.pathname) && rule.skip) {
22601
+ return false;
22602
+ }
22603
+ }
22604
+ }
22605
+ if (request.headers.get("cache-control")?.includes("no-cache"))
22606
+ return false;
22607
+ return true;
22608
+ }
22609
+ isResponseCacheable(response) {
22610
+ if (!response.headers.get("content-type")?.includes("text/html"))
22611
+ return false;
22612
+ if (response.headers.get("set-cookie")) return false;
22613
+ const cacheControl = response.headers.get("cache-control");
22614
+ if (cacheControl?.includes("no-store") || cacheControl?.includes("private"))
22615
+ return false;
22616
+ return true;
22617
+ }
22618
+ getDeploymentMode(headers) {
22619
+ const mode = headers.get("swell-deployment-mode");
22620
+ return mode === "preview" ? "preview" : "live";
22621
+ }
22622
+ getTTLForRequest(request) {
22623
+ const url = new URL(request.url);
22624
+ const mode = this.getDeploymentMode(request.headers);
22625
+ if (this.cacheRules.pathRules) {
22626
+ for (const rule of this.cacheRules.pathRules) {
22627
+ if (this.pathMatches(rule.path, url.pathname) && rule.ttl !== void 0) {
22628
+ return rule.ttl;
22629
+ }
22630
+ }
22631
+ }
22632
+ const defaults = this.cacheRules.defaults?.[mode];
22633
+ return defaults?.ttl ?? DEFAULT_CACHE_RULES.defaults[mode].ttl;
22634
+ }
22635
+ getSWRForRequest(request) {
22636
+ const url = new URL(request.url);
22637
+ const mode = this.getDeploymentMode(request.headers);
22638
+ if (this.cacheRules.pathRules) {
22639
+ for (const rule of this.cacheRules.pathRules) {
22640
+ if (this.pathMatches(rule.path, url.pathname) && rule.swr !== void 0) {
22641
+ return rule.swr;
22642
+ }
22643
+ }
22644
+ }
22645
+ const defaults = this.cacheRules.defaults?.[mode];
22646
+ return defaults?.swr ?? DEFAULT_CACHE_RULES.defaults[mode].swr;
22647
+ }
22648
+ getEntryAge(entry) {
22649
+ const t = Date.parse(entry.cacheTimeISO);
22650
+ if (Number.isNaN(t)) return Infinity;
22651
+ const age = (Date.now() - t) / 1e3;
22652
+ return age < 0 ? 0 : age;
22653
+ }
22654
+ /**
22655
+ * Converts wildcard pattern to regex and tests against path.
22656
+ * - * matches any characters except /
22657
+ * - ** matches any characters including /
22658
+ */
22659
+ pathMatches(pattern, path) {
22660
+ const regex = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLE_STAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLE_STAR___/g, ".*");
22661
+ return new RegExp(`^${regex}$`).test(path);
22662
+ }
22663
+ normalizeHeaders(headers) {
22664
+ const normalized = {};
22665
+ headers.forEach((value, key) => {
22666
+ normalized[key.toLowerCase()] = value;
22667
+ });
22668
+ return normalized;
22669
+ }
22670
+ normalizeSearchParams(searchParams) {
22671
+ const ignoredParams = [
22672
+ "utm_source",
22673
+ "utm_medium",
22674
+ "utm_campaign",
22675
+ "utm_content",
22676
+ "utm_term",
22677
+ "fbclid",
22678
+ "gclid",
22679
+ "gbraid",
22680
+ "wbraid",
22681
+ "ref",
22682
+ "source",
22683
+ "mc_cid",
22684
+ "mc_eid"
22685
+ ];
22686
+ const relevantParams = [];
22687
+ searchParams.forEach((value, key) => {
22688
+ if (!ignoredParams.includes(key)) {
22689
+ relevantParams.push(
22690
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
22691
+ );
22692
+ }
22693
+ });
22694
+ return relevantParams.sort().join("&");
22695
+ }
22696
+ };
22697
+
22698
+ // src/cache/html-cache/html-cache-kv.ts
22699
+ var KVCacheBackend = class {
22700
+ kv;
22701
+ prefix;
22702
+ hashKeys;
22703
+ maxValueBytes;
22704
+ constructor(kv, opts = {}) {
22705
+ this.kv = kv;
22706
+ this.prefix = opts.prefix;
22707
+ this.hashKeys = opts.hashKeys !== false;
22708
+ this.maxValueBytes = opts.maxValueBytes ?? Math.floor(24.5 * 1024 * 1024);
22709
+ }
22710
+ async read(key) {
22711
+ const kvKey = this.makeKey(key);
22712
+ const value = await this.kv.get(kvKey, "json");
22713
+ if (!value) return null;
22714
+ const entry = {
22715
+ status: value.status,
22716
+ statusText: value.statusText,
22717
+ headers: value.headers,
22718
+ body: value.body,
22719
+ cacheTimeISO: value.cacheTimeISO,
22720
+ ttl: value.ttl,
22721
+ swr: value.swr,
22722
+ etag: value.etag,
22723
+ lastModifiedUTC: value.lastModifiedUTC
22724
+ };
22725
+ return entry;
22726
+ }
22727
+ async write(key, entry, hardExpireSeconds) {
22728
+ const kvKey = this.makeKey(key);
22729
+ const payload = {
22730
+ v: 1,
22731
+ status: entry.status,
22732
+ statusText: entry.statusText,
22733
+ headers: entry.headers,
22734
+ body: entry.body,
22735
+ cacheTimeISO: entry.cacheTimeISO,
22736
+ ttl: entry.ttl,
22737
+ swr: entry.swr,
22738
+ etag: entry.etag,
22739
+ lastModifiedUTC: entry.lastModifiedUTC
22740
+ };
22741
+ const json = JSON.stringify(payload);
22742
+ this.assertSize(json);
22743
+ const metadata = {
22744
+ v: 1,
22745
+ cacheTimeISO: entry.cacheTimeISO,
22746
+ ttl: entry.ttl,
22747
+ swr: entry.swr,
22748
+ etag: entry.etag,
22749
+ lastModifiedUTC: entry.lastModifiedUTC
22750
+ };
22751
+ await this.kv.put(kvKey, json, {
22752
+ expirationTtl: hardExpireSeconds + 60,
22753
+ // natural hard expiry after SWR + grace period
22754
+ metadata
22755
+ });
22756
+ }
22757
+ async delete(key) {
22758
+ const kvKey = this.makeKey(key);
22759
+ await this.kv.delete(kvKey);
22760
+ }
22761
+ // ---- private helpers ----
22762
+ makeKey(raw) {
22763
+ const core = this.hashKeys ? md5(raw) : raw;
22764
+ return this.prefix ? `${this.prefix}:${core}` : core;
22765
+ }
22766
+ assertSize(json) {
22767
+ const bytes = typeof TextEncoder !== "undefined" ? new TextEncoder().encode(json).length : this.approxUtf8Bytes(json);
22768
+ if (bytes > this.maxValueBytes) {
22769
+ throw new Error(
22770
+ `KV value too large: ${bytes} bytes exceeds limit ${this.maxValueBytes} bytes`
22771
+ );
22772
+ }
22773
+ }
22774
+ approxUtf8Bytes(str) {
22775
+ let count = 0;
22776
+ for (let i = 0; i < str.length; i++) {
22777
+ const code = str.charCodeAt(i);
22778
+ if (code <= 127) count += 1;
22779
+ else if (code <= 2047) count += 2;
22780
+ else count += 3;
22781
+ }
22782
+ return count;
22783
+ }
22784
+ };
22785
+
22786
+ // src/cache/html-cache/html-cache-worker.ts
22787
+ var CACHE_NAME_PREFIX = "swell-html-v0";
22788
+ var WorkerCacheBackend = class {
22789
+ cacheName;
22790
+ constructor(epoch) {
22791
+ this.cacheName = CACHE_NAME_PREFIX + epoch;
22792
+ }
22793
+ async read(key) {
22794
+ const cache = await caches.open(this.cacheName);
22795
+ const request = new Request(key);
22796
+ const response = await cache.match(request);
22797
+ if (!response) return null;
22798
+ const headers = {};
22799
+ response.headers.forEach((value, name) => {
22800
+ headers[name.toLowerCase()] = value;
22801
+ });
22802
+ return {
22803
+ status: response.status,
22804
+ statusText: response.statusText,
22805
+ headers,
22806
+ body: await response.text(),
22807
+ cacheTimeISO: response.headers.get("x-cache-time") || (/* @__PURE__ */ new Date(0)).toISOString(),
22808
+ ttl: parseInt(response.headers.get("x-original-ttl") || "0", 10),
22809
+ swr: parseInt(response.headers.get("x-original-swr") || "0", 10),
22810
+ etag: response.headers.get("etag") || void 0,
22811
+ lastModifiedUTC: response.headers.get("last-modified") || void 0
22812
+ };
22813
+ }
22814
+ async write(key, entry, _hardExpireSeconds) {
22815
+ const cache = await caches.open(this.cacheName);
22816
+ const request = new Request(key);
22817
+ const headers = new Headers(entry.headers);
22818
+ if (entry.lastModifiedUTC && !headers.get("Last-Modified")) {
22819
+ headers.set("Last-Modified", entry.lastModifiedUTC);
22820
+ }
22821
+ if (entry.etag && !headers.get("ETag")) {
22822
+ headers.set("ETag", entry.etag);
22823
+ }
22824
+ headers.set("X-Cache-Time", entry.cacheTimeISO);
22825
+ headers.set("X-Original-TTL", String(entry.ttl));
22826
+ headers.set("X-Original-SWR", String(entry.swr));
22827
+ headers.delete("content-encoding");
22828
+ headers.delete("content-length");
22829
+ const existing = headers.get("Cache-Control");
22830
+ if (!existing || existing.trim().toLowerCase() === "public") {
22831
+ headers.set("Cache-Control", `public, max-age=${entry.ttl + entry.swr}`);
22832
+ }
22833
+ const response = new Response(entry.body, {
22834
+ status: entry.status,
22835
+ statusText: entry.statusText,
22836
+ headers
22837
+ });
22838
+ await cache.delete(request);
22839
+ await cache.put(request, response);
22840
+ }
22841
+ async delete(key) {
22842
+ const cache = await caches.open(this.cacheName);
22843
+ const request = new Request(key);
22844
+ await cache.delete(request);
22845
+ }
22846
+ };
22847
+
22848
+ // src/cache/html-cache/html-cache-factory.ts
22849
+ var _instance = null;
22850
+ function getHtmlCache(env, cacheRules) {
22851
+ const epoch = env?.HTML_CACHE_EPOCH;
22852
+ if (typeof epoch !== "string" || !epoch) return null;
22853
+ if (_instance) return _instance;
22854
+ const kv = env?.NAMESPACE;
22855
+ const rules = cacheRules || env?.HTML_CACHE_RULES;
22856
+ if (env?.HTML_CACHE_BACKEND !== "worker" && kv) {
22857
+ _instance = new HtmlCache(epoch, new KVCacheBackend(kv), rules);
22858
+ return _instance;
22859
+ }
22860
+ _instance = new HtmlCache(epoch, new WorkerCacheBackend(epoch), rules);
22861
+ return _instance;
22862
+ }
22711
22863
  export {
22712
22864
  AccountAddressesResource,
22713
22865
  AccountOrderResource,
@@ -22721,10 +22873,12 @@ export {
22721
22873
  CartResource,
22722
22874
  CategoriesResource,
22723
22875
  CategoryResource,
22876
+ DEFAULT_CACHE_RULES,
22724
22877
  DEFAULT_QUERY_PAGE_LIMIT,
22725
22878
  DeferredShopifyResource,
22726
22879
  FILE_DATA_INCLUDE_QUERY,
22727
22880
  GEO_DATA,
22881
+ HtmlCache,
22728
22882
  LANG_TO_COUNTRY_CODES,
22729
22883
  LiquidSwell30 as LiquidSwell,
22730
22884
  MAX_QUERY_PAGE_LIMIT,
@@ -22772,7 +22926,6 @@ export {
22772
22926
  ThemeForm,
22773
22927
  ThemeFormErrors,
22774
22928
  VariantResource,
22775
- WorkerHtmlCache,
22776
22929
  adaptShopifyFontData,
22777
22930
  adaptShopifyFormData,
22778
22931
  adaptShopifyMenuData,
@@ -22800,6 +22953,7 @@ export {
22800
22953
  getEasyblocksComponentDefinitions,
22801
22954
  getEasyblocksPagePropsWithConfigs,
22802
22955
  getEasyblocksPageTemplate,
22956
+ getHtmlCache,
22803
22957
  getKVFlavor,
22804
22958
  getLayoutSectionGroups,
22805
22959
  getMenuItemStorefrontUrl,