@swell/apps-sdk 1.0.152 → 1.0.154

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
@@ -713,18 +713,31 @@ var StorefrontResource = class {
713
713
  return this._compatibilityProps[prop];
714
714
  }
715
715
  };
716
+ var RESOURCE_CLONE_PROPS = Object.freeze([
717
+ "_defaultGetter",
718
+ "_getResourceObject",
719
+ "_collection",
720
+ "_swell",
721
+ "_query",
722
+ "_params",
723
+ "_id",
724
+ "_resourceName"
725
+ ]);
716
726
  function cloneStorefrontResource(input) {
717
- const resourceName = input._resourceName;
727
+ const resourceName = input._resourceName || input.constructor?.name;
718
728
  const ClonedClass = class extends StorefrontResource {
719
729
  };
720
730
  Object.defineProperty(ClonedClass, "name", {
721
731
  value: resourceName
722
732
  });
723
733
  const clone = new ClonedClass(input._getter);
724
- clone._params = input._params;
725
- Object.defineProperty(clone, "_resourceName", {
726
- value: resourceName
727
- });
734
+ for (const key of RESOURCE_CLONE_PROPS) {
735
+ if (input[key] !== void 0) {
736
+ Object.defineProperty(clone, key, {
737
+ value: input[key]
738
+ });
739
+ }
740
+ }
728
741
  return clone;
729
742
  }
730
743
  var SwellStorefrontResource = class extends StorefrontResource {
@@ -743,7 +756,7 @@ var SwellStorefrontResource = class extends StorefrontResource {
743
756
  _getProxy() {
744
757
  return super._getProxy();
745
758
  }
746
- getResourceObject() {
759
+ _getResourceObject() {
747
760
  const { _swell, _collection } = this;
748
761
  this._resource = (_swell?.storefront)[_collection];
749
762
  if (_swell && _collection.startsWith("content/")) {
@@ -800,7 +813,7 @@ var SwellStorefrontCollection = class _SwellStorefrontCollection extends SwellSt
800
813
  return properQuery;
801
814
  }
802
815
  _defaultGetter() {
803
- const resource = this.getResourceObject();
816
+ const resource = this._getResourceObject();
804
817
  async function defaultGetter() {
805
818
  return resource.list(this._query);
806
819
  }
@@ -927,7 +940,7 @@ var SwellStorefrontRecord = class extends SwellStorefrontResource {
927
940
  return super._getProxy();
928
941
  }
929
942
  _defaultGetter() {
930
- const resource = this.getResourceObject();
943
+ const resource = this._getResourceObject();
931
944
  async function defaultGetter() {
932
945
  return resource.get(this._id, this._query);
933
946
  }
@@ -986,7 +999,7 @@ var SwellStorefrontSingleton = class extends SwellStorefrontResource {
986
999
  _collection,
987
1000
  _swell: { storefrontContext }
988
1001
  } = this;
989
- const resource = this.getResourceObject();
1002
+ const resource = this._getResourceObject();
990
1003
  async function defaultGetter() {
991
1004
  if (storefrontContext[_collection] !== void 0) {
992
1005
  return storefrontContext[_collection];
@@ -7711,20 +7724,38 @@ var WorkerCacheProxy = class {
7711
7724
  var CACHE_NAME2 = "swell-html-v1";
7712
7725
  var CACHE_KEY_ORIGIN2 = "https://cache.swell.store";
7713
7726
  var TTL_CONFIG = {
7714
- DEFAULT: 300,
7715
- // 5 minutes
7716
- DEFAULT_SWR: 3600,
7717
- // 1 hour stale-while-revalidate
7718
- HOME: 300,
7719
- // 5 minutes
7720
- PRODUCT: 600,
7721
- // 10 minutes
7722
- COLLECTION: 900,
7723
- // 15 minutes
7724
- PAGE: 3600,
7725
- // 1 hour
7726
- BLOG: 1800
7727
- // 30 minutes
7727
+ LIVE: {
7728
+ DEFAULT: 300,
7729
+ // 5 minutes
7730
+ HOME: 300,
7731
+ // 5 minutes
7732
+ PRODUCT: 600,
7733
+ // 10 minutes
7734
+ COLLECTION: 900,
7735
+ // 15 minutes
7736
+ PAGE: 3600,
7737
+ // 1 hour
7738
+ BLOG: 1800,
7739
+ // 30 minutes
7740
+ SWR: 3600
7741
+ // 1 hour stale-while-revalidate
7742
+ },
7743
+ PREVIEW: {
7744
+ DEFAULT: 5,
7745
+ // 1 minute - faster updates in preview
7746
+ HOME: 5,
7747
+ // 1 minute
7748
+ PRODUCT: 5,
7749
+ // 2 minutes
7750
+ COLLECTION: 5,
7751
+ // 3 minutes
7752
+ PAGE: 5,
7753
+ // 5 minutes
7754
+ BLOG: 5,
7755
+ // 5 minutes
7756
+ SWR: 600
7757
+ // 10 minutes stale-while-revalidate
7758
+ }
7728
7759
  };
7729
7760
  var WorkerHtmlCache = class {
7730
7761
  epoch;
@@ -7751,7 +7782,7 @@ var WorkerHtmlCache = class {
7751
7782
  }
7752
7783
  const age = this.getResponseAge(cached);
7753
7784
  const ttl = parseInt(cached.headers.get("X-Original-TTL") || "") || this.getTTLForRequest(request);
7754
- const swr = parseInt(cached.headers.get("X-Original-SWR") || "") || TTL_CONFIG.DEFAULT_SWR;
7785
+ const swr = parseInt(cached.headers.get("X-Original-SWR") || "") || this.getSWRForRequest(request);
7755
7786
  const isStale = age >= ttl;
7756
7787
  const isExpired = age >= ttl + swr;
7757
7788
  if (!isExpired) {
@@ -7782,6 +7813,56 @@ var WorkerHtmlCache = class {
7782
7813
  return null;
7783
7814
  }
7784
7815
  }
7816
+ // 304 support
7817
+ async getWithConditionals(request) {
7818
+ const result = await this.get(request);
7819
+ if (!result?.found || result.stale) {
7820
+ return result;
7821
+ }
7822
+ const ifModifiedSince = request.headers.get("If-Modified-Since");
7823
+ const ifNoneMatch = request.headers.get("If-None-Match");
7824
+ if ((ifModifiedSince || ifNoneMatch) && result.response) {
7825
+ const lastModified = result.response.headers.get("Last-Modified");
7826
+ const etag = result.response.headers.get("ETag");
7827
+ if (this.checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag)) {
7828
+ result.notModified = true;
7829
+ result.conditional304 = new Response(null, {
7830
+ status: 304,
7831
+ headers: {
7832
+ "Last-Modified": lastModified || "",
7833
+ ETag: etag || "",
7834
+ "Cache-Control": result.response.headers.get("Cache-Control") || "",
7835
+ "Cloudflare-CDN-Cache-Control": result.response.headers.get("Cloudflare-CDN-Cache-Control") || "",
7836
+ "X-Cache-Status": "HIT-304"
7837
+ }
7838
+ });
7839
+ }
7840
+ }
7841
+ return result;
7842
+ }
7843
+ checkNotModified(ifModifiedSince, ifNoneMatch, lastModified, etag) {
7844
+ if (ifNoneMatch && etag) {
7845
+ return ifNoneMatch === etag;
7846
+ }
7847
+ if (ifModifiedSince && lastModified) {
7848
+ const ifModDate = new Date(ifModifiedSince);
7849
+ const lastModDate = new Date(lastModified);
7850
+ return !isNaN(ifModDate.getTime()) && !isNaN(lastModDate.getTime()) && ifModDate >= lastModDate;
7851
+ }
7852
+ return false;
7853
+ }
7854
+ createRevalidationRequest(request) {
7855
+ const headers = new Headers(request.headers);
7856
+ headers.set("X-Cache-Bypass", "revalidation");
7857
+ headers.delete("If-None-Match");
7858
+ headers.delete("If-Modified-Since");
7859
+ headers.delete("Cache-Control");
7860
+ headers.delete("Pragma");
7861
+ return new Request(request.url, {
7862
+ method: "GET",
7863
+ headers
7864
+ });
7865
+ }
7785
7866
  async put(request, response) {
7786
7867
  const trace = createTraceId();
7787
7868
  if (request.method !== "GET" || !response.ok) {
@@ -7797,17 +7878,22 @@ var WorkerHtmlCache = class {
7797
7878
  try {
7798
7879
  const cache = await caches.open(CACHE_NAME2 + this.epoch);
7799
7880
  const cacheKey = this.buildCacheKey(request);
7881
+ await cache.delete(cacheKey);
7800
7882
  const ttl = this.getTTLForRequest(request);
7801
- const swr = TTL_CONFIG.DEFAULT_SWR;
7883
+ const swr = this.getSWRForRequest(request);
7802
7884
  const headers = new Headers(response.headers);
7803
7885
  const existingCacheControl = response.headers.get("Cache-Control");
7804
7886
  if (!existingCacheControl || existingCacheControl === "public") {
7805
7887
  const internalMaxAge = ttl + swr;
7806
7888
  headers.set("Cache-Control", `public, max-age=${internalMaxAge}`);
7807
7889
  }
7808
- headers.set("X-Cache-Time", (/* @__PURE__ */ new Date()).toISOString());
7890
+ const cacheTime = (/* @__PURE__ */ new Date()).toISOString();
7891
+ headers.set("X-Cache-Time", cacheTime);
7809
7892
  headers.set("X-Original-TTL", ttl.toString());
7810
7893
  headers.set("X-Original-SWR", swr.toString());
7894
+ if (!headers.get("Last-Modified")) {
7895
+ headers.set("Last-Modified", new Date(cacheTime).toUTCString());
7896
+ }
7811
7897
  const cacheableResponse = new Response(response.body, {
7812
7898
  status: response.status,
7813
7899
  statusText: response.statusText,
@@ -7819,28 +7905,32 @@ var WorkerHtmlCache = class {
7819
7905
  logger.warn("[SDK Html-cache] no put support", { trace });
7820
7906
  }
7821
7907
  }
7822
- /**
7823
- * Build client response with correct headers
7824
- */
7825
7908
  buildClientResponse(cachedResponse, ttl, swr, isStale, age) {
7826
7909
  const headers = new Headers(cachedResponse.headers);
7827
7910
  headers.set(
7828
7911
  "Cache-Control",
7829
7912
  `public, max-age=${ttl}, stale-while-revalidate=${swr}`
7830
7913
  );
7914
+ headers.set(
7915
+ "Cloudflare-CDN-Cache-Control",
7916
+ `public, s-maxage=${ttl}, stale-while-revalidate=${swr}, stale-if-error=60`
7917
+ );
7918
+ const cacheTime = headers.get("X-Cache-Time");
7919
+ if (cacheTime) {
7920
+ const lastModified = new Date(cacheTime).toUTCString();
7921
+ headers.set("Last-Modified", lastModified);
7922
+ }
7831
7923
  headers.set("X-Cache-Status", isStale ? "STALE" : "HIT");
7832
7924
  headers.set("X-Cache-Age", Math.floor(age).toString());
7833
7925
  headers.delete("X-Original-TTL");
7834
7926
  headers.delete("X-Original-SWR");
7927
+ headers.delete("X-Cache-Time");
7835
7928
  return new Response(cachedResponse.body, {
7836
7929
  status: cachedResponse.status,
7837
7930
  statusText: cachedResponse.statusText,
7838
7931
  headers
7839
7932
  });
7840
7933
  }
7841
- /**
7842
- * Build cache key from request using two-level structure
7843
- */
7844
7934
  buildCacheKey(request) {
7845
7935
  const url = new URL(request.url);
7846
7936
  const versionHash = this.generateVersionHash(request.headers);
@@ -7856,22 +7946,11 @@ var WorkerHtmlCache = class {
7856
7946
  headers: sanitizedHeaders
7857
7947
  });
7858
7948
  }
7859
- /**
7860
- * Sanitize headers for cache operations - minimal whitelist approach
7861
- */
7862
7949
  sanitizeHeaders(originalHeaders) {
7863
7950
  const CACHE_RELEVANT_HEADERS = [
7864
7951
  // Content negotiation (affects response format)
7865
7952
  "accept",
7866
- "accept-encoding",
7867
- "accept-language",
7868
- // Cache control directives from client/proxy
7869
- "cache-control",
7870
- "pragma",
7871
- // Legacy cache control
7872
- // Conditional request headers (for 304 responses)
7873
- "if-none-match",
7874
- "if-modified-since"
7953
+ "accept-language"
7875
7954
  ];
7876
7955
  const sanitized = new Headers();
7877
7956
  CACHE_RELEVANT_HEADERS.forEach((header) => {
@@ -7882,15 +7961,13 @@ var WorkerHtmlCache = class {
7882
7961
  });
7883
7962
  return sanitized;
7884
7963
  }
7885
- /**
7886
- * Generate version hash from headers and cookies
7887
- */
7888
7964
  generateVersionHash(headers) {
7889
7965
  const swellData = this.extractSwellData(headers);
7890
7966
  const versionFactors = {
7891
7967
  store: headers.get("swell-storefront-id") || "",
7892
7968
  auth: headers.get("swell-access-token") || "",
7893
7969
  theme: headers.get("swell-theme-version-hash") || "",
7970
+ modified: headers.get("swell-cache-modified") || "",
7894
7971
  currency: swellData["swell-currency"] || "USD",
7895
7972
  locale: headers.get("x-locale") || headers.get("accept-language")?.split(",")[0] || "default",
7896
7973
  context: headers.get("swell-storefront-context"),
@@ -7898,9 +7975,6 @@ var WorkerHtmlCache = class {
7898
7975
  };
7899
7976
  return md5(JSON.stringify(versionFactors));
7900
7977
  }
7901
- /**
7902
- * Extract swell-data from cookies
7903
- */
7904
7978
  extractSwellData(headers) {
7905
7979
  const cookie = headers.get("cookie");
7906
7980
  if (!cookie) return {};
@@ -7936,31 +8010,53 @@ var WorkerHtmlCache = class {
7936
8010
  if (!contentType?.includes("text/html")) {
7937
8011
  return false;
7938
8012
  }
8013
+ if (response.headers.get("set-cookie")) {
8014
+ return false;
8015
+ }
7939
8016
  const cacheControl = response.headers.get("cache-control");
7940
8017
  if (cacheControl?.includes("no-store") || cacheControl?.includes("private")) {
7941
8018
  return false;
7942
8019
  }
7943
8020
  return true;
7944
8021
  }
8022
+ getDeploymentMode(headers) {
8023
+ const mode = headers.get("swell-deployment-mode");
8024
+ if (mode === "preview" || mode === "editor") {
8025
+ return mode;
8026
+ }
8027
+ return "live";
8028
+ }
7945
8029
  getTTLForRequest(request) {
7946
8030
  const url = new URL(request.url);
7947
8031
  const path = url.pathname;
8032
+ const mode = this.getDeploymentMode(request.headers);
8033
+ if (mode === "editor") {
8034
+ return 0;
8035
+ }
8036
+ const config = mode === "preview" ? TTL_CONFIG.PREVIEW : TTL_CONFIG.LIVE;
7948
8037
  if (path === "/") {
7949
- return TTL_CONFIG.HOME;
8038
+ return config.HOME;
7950
8039
  }
7951
8040
  if (path.startsWith("/products/")) {
7952
- return TTL_CONFIG.PRODUCT;
8041
+ return config.PRODUCT;
7953
8042
  }
7954
8043
  if (path.startsWith("/categories/")) {
7955
- return TTL_CONFIG.COLLECTION;
8044
+ return config.COLLECTION;
7956
8045
  }
7957
8046
  if (path.startsWith("/pages/")) {
7958
- return TTL_CONFIG.PAGE;
8047
+ return config.PAGE;
7959
8048
  }
7960
8049
  if (path.startsWith("/blogs/")) {
7961
- return TTL_CONFIG.BLOG;
8050
+ return config.BLOG;
7962
8051
  }
7963
- return TTL_CONFIG.DEFAULT;
8052
+ return config.DEFAULT;
8053
+ }
8054
+ getSWRForRequest(request) {
8055
+ const mode = this.getDeploymentMode(request.headers);
8056
+ if (mode === "editor") {
8057
+ return 0;
8058
+ }
8059
+ return mode === "preview" ? TTL_CONFIG.PREVIEW.SWR : TTL_CONFIG.LIVE.SWR;
7964
8060
  }
7965
8061
  getResponseAge(response) {
7966
8062
  const cacheTime = response.headers.get("X-Cache-Time");
@@ -7974,31 +8070,6 @@ var WorkerHtmlCache = class {
7974
8070
  const age = (Date.now() - cacheDate.getTime()) / 1e3;
7975
8071
  return Math.max(0, age);
7976
8072
  }
7977
- getMaxAge(response) {
7978
- const cacheControl = response.headers.get("Cache-Control");
7979
- if (!cacheControl) {
7980
- return TTL_CONFIG.DEFAULT;
7981
- }
7982
- const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
7983
- if (maxAgeMatch) {
7984
- return parseInt(maxAgeMatch[1], 10);
7985
- }
7986
- return TTL_CONFIG.DEFAULT;
7987
- }
7988
- getStaleWindow(response) {
7989
- const cacheControl = response.headers.get("Cache-Control");
7990
- if (!cacheControl) {
7991
- return TTL_CONFIG.DEFAULT_SWR;
7992
- }
7993
- const swrMatch = cacheControl.match(/stale-while-revalidate=(\d+)/);
7994
- if (swrMatch) {
7995
- return parseInt(swrMatch[1], 10);
7996
- }
7997
- return TTL_CONFIG.DEFAULT_SWR;
7998
- }
7999
- /**
8000
- * Normalize search params for cache key
8001
- */
8002
8073
  normalizeSearchParams(searchParams) {
8003
8074
  const ignoredParams = [
8004
8075
  "utm_source",