@swell/apps-sdk 1.0.150 → 1.0.152

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 (43) hide show
  1. package/dist/index.cjs +767 -185
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +747 -185
  4. package/dist/index.js.map +4 -4
  5. package/dist/index.mjs +757 -185
  6. package/dist/index.mjs.map +4 -4
  7. package/dist/src/cache/cache-api-stub.d.ts +25 -0
  8. package/dist/src/cache/index.d.ts +2 -0
  9. package/dist/src/cache/worker-html-cache.d.ts +43 -0
  10. package/dist/src/compatibility/drops/all_products.d.ts +1 -1
  11. package/dist/src/compatibility/drops/articles.d.ts +1 -1
  12. package/dist/src/compatibility/drops/blogs.d.ts +1 -1
  13. package/dist/src/compatibility/drops/collections.d.ts +2 -3
  14. package/dist/src/compatibility/drops/images.d.ts +1 -1
  15. package/dist/src/compatibility/drops/pages.d.ts +2 -3
  16. package/dist/src/compatibility/shopify-objects/collections.d.ts +2 -2
  17. package/dist/src/content.d.ts +3 -3
  18. package/dist/src/index.d.ts +3 -0
  19. package/dist/src/liquid/filters/shopify/default_pagination.d.ts +1 -1
  20. package/dist/src/resources/account.d.ts +4 -4
  21. package/dist/src/resources/addresses.d.ts +7 -0
  22. package/dist/src/resources/blog.d.ts +5 -4
  23. package/dist/src/resources/blog_category.d.ts +5 -4
  24. package/dist/src/resources/cart.d.ts +4 -4
  25. package/dist/src/resources/categories.d.ts +7 -0
  26. package/dist/src/resources/category.d.ts +5 -4
  27. package/dist/src/resources/index.d.ts +18 -9
  28. package/dist/src/resources/order.d.ts +5 -4
  29. package/dist/src/resources/orders.d.ts +7 -0
  30. package/dist/src/resources/page.d.ts +5 -4
  31. package/dist/src/resources/predictive_search.d.ts +6 -0
  32. package/dist/src/resources/product.d.ts +5 -19
  33. package/dist/src/resources/product_helpers.d.ts +12 -2
  34. package/dist/src/resources/product_recommendations.d.ts +6 -0
  35. package/dist/src/resources/search.d.ts +6 -0
  36. package/dist/src/resources/subscription.d.ts +7 -0
  37. package/dist/src/resources/subscriptions.d.ts +7 -0
  38. package/dist/src/resources/swell_types.d.ts +66 -9
  39. package/dist/src/resources/variant.d.ts +8 -8
  40. package/dist/src/resources.d.ts +11 -12
  41. package/dist/types/shopify.d.ts +2 -2
  42. package/dist/types/swell.d.ts +3 -1
  43. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -41,72 +41,6 @@
41
41
  var import_keyv = __require("keyv");
42
42
  var import_cache_manager = __require("cache-manager");
43
43
 
44
- // src/cache/cf-worker-kv-keyv-adapter.ts
45
- var CFWorkerKVKeyvAdapter = class {
46
- store;
47
- namespace;
48
- // magically passed in from Keyv
49
- opts;
50
- constructor(store) {
51
- this.store = store;
52
- this.opts = null;
53
- this.namespace = "dummy";
54
- }
55
- async has(key) {
56
- const stream = await this.store.get(key, "stream");
57
- if (stream !== null) {
58
- await stream.cancel();
59
- return true;
60
- }
61
- return false;
62
- }
63
- async get(key) {
64
- const value = await this.store.get(key);
65
- return value !== null ? value : void 0;
66
- }
67
- set(key, value, ttl) {
68
- if (typeof ttl === "number") {
69
- ttl = Math.max(60, ttl / 1e3);
70
- }
71
- return this.store.put(key, value, { expirationTtl: ttl });
72
- }
73
- async delete(key) {
74
- await this.store.delete(key);
75
- return true;
76
- }
77
- async clear() {
78
- let cursor = "";
79
- let complete = false;
80
- const prefix = `${this.namespace}:`;
81
- do {
82
- const response = await this.store.list({
83
- prefix,
84
- cursor: cursor || void 0
85
- });
86
- cursor = response.cursor ?? "";
87
- complete = response.list_complete;
88
- if (response.keys.length > 0) {
89
- await Promise.all(
90
- response.keys.map((key) => {
91
- return this.store.delete(key.name);
92
- })
93
- );
94
- }
95
- } while (!complete);
96
- }
97
- on(_event, _listener) {
98
- return this;
99
- }
100
- };
101
-
102
- // src/utils/index.ts
103
- var import_qs = __toESM(__require("qs"), 1);
104
- var import_lodash_es3 = __require("lodash-es");
105
- var import_json52 = __toESM(__require("json5"), 1);
106
-
107
- // src/resources.ts
108
- var import_lodash_es2 = __require("lodash-es");
109
-
110
44
  // src/utils/logger.ts
111
45
  var logLevels = {
112
46
  error: 0,
@@ -198,6 +132,80 @@
198
132
  return (hash >>> 0).toString(16);
199
133
  }
200
134
 
135
+ // src/cache/cf-worker-kv-keyv-adapter.ts
136
+ var CFWorkerKVKeyvAdapter = class {
137
+ store;
138
+ namespace;
139
+ // magically passed in from Keyv
140
+ opts;
141
+ constructor(store) {
142
+ this.store = store;
143
+ this.opts = null;
144
+ this.namespace = "dummy";
145
+ }
146
+ async has(key) {
147
+ const stream = await this.store.get(key, "stream");
148
+ if (stream !== null) {
149
+ await stream.cancel();
150
+ return true;
151
+ }
152
+ return false;
153
+ }
154
+ async get(key) {
155
+ const trace = createTraceId();
156
+ logger.debug("[SDK] kv.get", { key, trace });
157
+ const value = await this.store.get(key);
158
+ const result = value !== null ? value : void 0;
159
+ logger.debug(`[SDK] kv.get ${value !== null ? "HIT" : "MISS"}`, {
160
+ key,
161
+ trace
162
+ });
163
+ return result;
164
+ }
165
+ set(key, value, ttl) {
166
+ if (typeof ttl === "number") {
167
+ ttl = Math.max(60, ttl / 1e3);
168
+ }
169
+ logger.debug("[SDK] kv.set", { key });
170
+ return this.store.put(key, value, { expirationTtl: ttl });
171
+ }
172
+ async delete(key) {
173
+ await this.store.delete(key);
174
+ return true;
175
+ }
176
+ async clear() {
177
+ let cursor = "";
178
+ let complete = false;
179
+ const prefix = `${this.namespace}:`;
180
+ do {
181
+ const response = await this.store.list({
182
+ prefix,
183
+ cursor: cursor || void 0
184
+ });
185
+ cursor = response.cursor ?? "";
186
+ complete = response.list_complete;
187
+ if (response.keys.length > 0) {
188
+ await Promise.all(
189
+ response.keys.map((key) => {
190
+ return this.store.delete(key.name);
191
+ })
192
+ );
193
+ }
194
+ } while (!complete);
195
+ }
196
+ on(_event, _listener) {
197
+ return this;
198
+ }
199
+ };
200
+
201
+ // src/utils/index.ts
202
+ var import_qs = __toESM(__require("qs"), 1);
203
+ var import_lodash_es3 = __require("lodash-es");
204
+ var import_json52 = __toESM(__require("json5"), 1);
205
+
206
+ // src/resources.ts
207
+ var import_lodash_es2 = __require("lodash-es");
208
+
201
209
  // src/liquid/utils.ts
202
210
  var import_liquidjs = __require("liquidjs");
203
211
 
@@ -653,18 +661,12 @@
653
661
  }
654
662
  return instance[prop];
655
663
  }
656
- // add additional properties to the loaded result
657
- _transformResult(result) {
658
- return result;
659
- }
660
664
  async _get(..._args) {
661
665
  if (this._getter) {
662
666
  const getter = this._getter.bind(
663
667
  this
664
668
  );
665
669
  return Promise.resolve().then(getter).then((result) => {
666
- return this._transformResult(result);
667
- }).then((result) => {
668
670
  this._result = result ?? null;
669
671
  if (result) {
670
672
  Object.assign(this, result);
@@ -747,7 +749,6 @@
747
749
  });
748
750
  const clone = new ClonedClass(input._getter);
749
751
  clone._params = input._params;
750
- clone._transformResult = input._transformResult.bind(clone);
751
752
  Object.defineProperty(clone, "_resourceName", {
752
753
  value: resourceName
753
754
  });
@@ -801,7 +802,11 @@
801
802
  limit = DEFAULT_QUERY_PAGE_LIMIT;
802
803
  name;
803
804
  constructor(swell, collection, query = {}, getter) {
804
- super(swell, collection, getter);
805
+ super(
806
+ swell,
807
+ collection,
808
+ getter
809
+ );
805
810
  this._query = this._initQuery(query);
806
811
  if (!getter) {
807
812
  this._setGetter(this._defaultGetter());
@@ -844,6 +849,7 @@
844
849
  this._collection,
845
850
  this._query,
846
851
  this._swell.queryParams,
852
+ // TODO: consider to exlcude
847
853
  this._getterHash
848
854
  ],
849
855
  getter,
@@ -972,13 +978,12 @@
972
978
  this._id,
973
979
  this._query,
974
980
  this._swell.queryParams,
981
+ // TODO: consider to exlcude
975
982
  this._getterHash
976
983
  ],
977
984
  getter,
978
985
  isResourceCacheble(this._collection)
979
986
  ).then((result) => {
980
- return this._transformResult(result);
981
- }).then((result) => {
982
987
  this._result = result;
983
988
  if (result) {
984
989
  Object.assign(this, result);
@@ -7202,8 +7207,6 @@
7202
7207
  * This will always return the cached value immediately if exists
7203
7208
  */
7204
7209
  async fetchSWR(key, fetchFn, ttl = DEFAULT_SWR_TTL, isCacheble = true) {
7205
- const trace = createTraceId();
7206
- logger.debug("[SDK] Cache fetch start", { key, trace });
7207
7210
  const cacheValue = isCacheble ? await this.client.get(key) : void 0;
7208
7211
  let promise = SWR_PROMISE_MAP.get(key);
7209
7212
  if (promise === void 0) {
@@ -7211,7 +7214,6 @@
7211
7214
  const isNull = value === null || value === void 0;
7212
7215
  if (isCacheble) {
7213
7216
  await this.client.set(key, isNull ? NULL_VALUE : value, ttl);
7214
- logger.debug("[SDK] Cache update done", { key, trace });
7215
7217
  }
7216
7218
  return value;
7217
7219
  }).finally(() => {
@@ -7223,12 +7225,9 @@
7223
7225
  this.workerCtx.waitUntil(promise);
7224
7226
  }
7225
7227
  if (cacheValue !== void 0) {
7226
- logger.debug("[SDK] Cache check done", { status: "HIT", key, trace });
7227
7228
  return cacheValue === NULL_VALUE ? null : cacheValue;
7228
7229
  }
7229
- logger.debug("[SDK] Cache check done", { status: "MISS", key, trace });
7230
7230
  const result = await promise;
7231
- logger.debug("[SDK] Cache fetch end", { key, trace });
7232
7231
  return result;
7233
7232
  }
7234
7233
  async get(key) {
@@ -7735,59 +7734,466 @@
7735
7734
  }
7736
7735
  };
7737
7736
 
7737
+ // src/cache/worker-html-cache.ts
7738
+ var CACHE_NAME2 = "swell-html-v1";
7739
+ var CACHE_KEY_ORIGIN2 = "https://cache.swell.store";
7740
+ var TTL_CONFIG = {
7741
+ DEFAULT: 300,
7742
+ // 5 minutes
7743
+ DEFAULT_SWR: 3600,
7744
+ // 1 hour stale-while-revalidate
7745
+ HOME: 300,
7746
+ // 5 minutes
7747
+ PRODUCT: 600,
7748
+ // 10 minutes
7749
+ COLLECTION: 900,
7750
+ // 15 minutes
7751
+ PAGE: 3600,
7752
+ // 1 hour
7753
+ BLOG: 1800
7754
+ // 30 minutes
7755
+ };
7756
+ var WorkerHtmlCache = class {
7757
+ epoch;
7758
+ constructor(epoch) {
7759
+ this.epoch = epoch;
7760
+ }
7761
+ async get(request) {
7762
+ const trace = createTraceId();
7763
+ if (request.method !== "GET") {
7764
+ logger.debug("[SDK Html-cache] non-cacheable", { trace });
7765
+ return { found: false, cacheable: false };
7766
+ }
7767
+ if (!this.isCacheable(request)) {
7768
+ logger.debug("[SDK Html-cache] non-cacheable", { trace });
7769
+ return { found: false, cacheable: false };
7770
+ }
7771
+ try {
7772
+ const cache = await caches.open(CACHE_NAME2 + this.epoch);
7773
+ const cacheKey = this.buildCacheKey(request);
7774
+ const cached = await cache.match(cacheKey);
7775
+ if (!cached) {
7776
+ logger.debug("[SDK Html-cache] cacheable, MISS", { trace });
7777
+ return { found: false, cacheable: true };
7778
+ }
7779
+ const age = this.getResponseAge(cached);
7780
+ const ttl = parseInt(cached.headers.get("X-Original-TTL") || "") || this.getTTLForRequest(request);
7781
+ const swr = parseInt(cached.headers.get("X-Original-SWR") || "") || TTL_CONFIG.DEFAULT_SWR;
7782
+ const isStale = age >= ttl;
7783
+ const isExpired = age >= ttl + swr;
7784
+ if (!isExpired) {
7785
+ logger.debug("[SDK Html-cache] cacheable, HIT", {
7786
+ stale: isStale,
7787
+ age,
7788
+ trace
7789
+ });
7790
+ const clientResponse = this.buildClientResponse(
7791
+ cached,
7792
+ ttl,
7793
+ swr,
7794
+ isStale,
7795
+ age
7796
+ );
7797
+ return {
7798
+ found: true,
7799
+ stale: isStale,
7800
+ response: clientResponse,
7801
+ cacheable: true,
7802
+ age: Math.floor(age)
7803
+ };
7804
+ }
7805
+ logger.debug("[SDK Html-cache] cacheable, hit, expired", { trace });
7806
+ return { found: false, cacheable: true };
7807
+ } catch (_) {
7808
+ logger.warn("[SDK Html-cache] no get support", { trace });
7809
+ return null;
7810
+ }
7811
+ }
7812
+ async put(request, response) {
7813
+ const trace = createTraceId();
7814
+ if (request.method !== "GET" || !response.ok) {
7815
+ logger.debug("[SDK Html-cache] put skipped", { trace });
7816
+ return;
7817
+ }
7818
+ if (!this.isCacheable(request) || !this.isResponseCacheable(response)) {
7819
+ logger.debug("[SDK Html-cache] put skipped, non-cacheable", {
7820
+ trace
7821
+ });
7822
+ return;
7823
+ }
7824
+ try {
7825
+ const cache = await caches.open(CACHE_NAME2 + this.epoch);
7826
+ const cacheKey = this.buildCacheKey(request);
7827
+ const ttl = this.getTTLForRequest(request);
7828
+ const swr = TTL_CONFIG.DEFAULT_SWR;
7829
+ const headers = new Headers(response.headers);
7830
+ const existingCacheControl = response.headers.get("Cache-Control");
7831
+ if (!existingCacheControl || existingCacheControl === "public") {
7832
+ const internalMaxAge = ttl + swr;
7833
+ headers.set("Cache-Control", `public, max-age=${internalMaxAge}`);
7834
+ }
7835
+ headers.set("X-Cache-Time", (/* @__PURE__ */ new Date()).toISOString());
7836
+ headers.set("X-Original-TTL", ttl.toString());
7837
+ headers.set("X-Original-SWR", swr.toString());
7838
+ const cacheableResponse = new Response(response.body, {
7839
+ status: response.status,
7840
+ statusText: response.statusText,
7841
+ headers
7842
+ });
7843
+ await cache.put(cacheKey, cacheableResponse);
7844
+ logger.debug("[SDK Html-cache] put done", { trace });
7845
+ } catch (_) {
7846
+ logger.warn("[SDK Html-cache] no put support", { trace });
7847
+ }
7848
+ }
7849
+ /**
7850
+ * Build client response with correct headers
7851
+ */
7852
+ buildClientResponse(cachedResponse, ttl, swr, isStale, age) {
7853
+ const headers = new Headers(cachedResponse.headers);
7854
+ headers.set(
7855
+ "Cache-Control",
7856
+ `public, max-age=${ttl}, stale-while-revalidate=${swr}`
7857
+ );
7858
+ headers.set("X-Cache-Status", isStale ? "STALE" : "HIT");
7859
+ headers.set("X-Cache-Age", Math.floor(age).toString());
7860
+ headers.delete("X-Original-TTL");
7861
+ headers.delete("X-Original-SWR");
7862
+ return new Response(cachedResponse.body, {
7863
+ status: cachedResponse.status,
7864
+ statusText: cachedResponse.statusText,
7865
+ headers
7866
+ });
7867
+ }
7868
+ /**
7869
+ * Build cache key from request using two-level structure
7870
+ */
7871
+ buildCacheKey(request) {
7872
+ const url = new URL(request.url);
7873
+ const versionHash = this.generateVersionHash(request.headers);
7874
+ const normalizedQuery = this.normalizeSearchParams(url.searchParams);
7875
+ const cacheKeyPath = `${versionHash}${url.pathname}`;
7876
+ const keyUrl = new URL(`${CACHE_KEY_ORIGIN2}${cacheKeyPath}`);
7877
+ if (normalizedQuery) {
7878
+ keyUrl.search = `?${normalizedQuery}`;
7879
+ }
7880
+ const sanitizedHeaders = this.sanitizeHeaders(request.headers);
7881
+ return new Request(keyUrl.toString(), {
7882
+ method: "GET",
7883
+ headers: sanitizedHeaders
7884
+ });
7885
+ }
7886
+ /**
7887
+ * Sanitize headers for cache operations - minimal whitelist approach
7888
+ */
7889
+ sanitizeHeaders(originalHeaders) {
7890
+ const CACHE_RELEVANT_HEADERS = [
7891
+ // Content negotiation (affects response format)
7892
+ "accept",
7893
+ "accept-encoding",
7894
+ "accept-language",
7895
+ // Cache control directives from client/proxy
7896
+ "cache-control",
7897
+ "pragma",
7898
+ // Legacy cache control
7899
+ // Conditional request headers (for 304 responses)
7900
+ "if-none-match",
7901
+ "if-modified-since"
7902
+ ];
7903
+ const sanitized = new Headers();
7904
+ CACHE_RELEVANT_HEADERS.forEach((header) => {
7905
+ const value = originalHeaders.get(header);
7906
+ if (value) {
7907
+ sanitized.set(header, value);
7908
+ }
7909
+ });
7910
+ return sanitized;
7911
+ }
7912
+ /**
7913
+ * Generate version hash from headers and cookies
7914
+ */
7915
+ generateVersionHash(headers) {
7916
+ const swellData = this.extractSwellData(headers);
7917
+ const versionFactors = {
7918
+ store: headers.get("swell-storefront-id") || "",
7919
+ auth: headers.get("swell-access-token") || "",
7920
+ theme: headers.get("swell-theme-version-hash") || "",
7921
+ currency: swellData["swell-currency"] || "USD",
7922
+ locale: headers.get("x-locale") || headers.get("accept-language")?.split(",")[0] || "default",
7923
+ context: headers.get("swell-storefront-context"),
7924
+ epoch: this.epoch
7925
+ };
7926
+ return md5(JSON.stringify(versionFactors));
7927
+ }
7928
+ /**
7929
+ * Extract swell-data from cookies
7930
+ */
7931
+ extractSwellData(headers) {
7932
+ const cookie = headers.get("cookie");
7933
+ if (!cookie) return {};
7934
+ const swellDataMatch = cookie.match(/swell-data=([^;]+)/);
7935
+ if (!swellDataMatch) return {};
7936
+ try {
7937
+ const parsed = JSON.parse(decodeURIComponent(swellDataMatch[1]));
7938
+ if (typeof parsed === "object" && parsed !== null) {
7939
+ return parsed;
7940
+ }
7941
+ return {};
7942
+ } catch {
7943
+ return {};
7944
+ }
7945
+ }
7946
+ isCacheable(request) {
7947
+ const url = new URL(request.url);
7948
+ const headers = request.headers;
7949
+ if (headers.get("swell-deployment-mode") === "editor") {
7950
+ return false;
7951
+ }
7952
+ const skipPaths = ["/checkout"];
7953
+ if (skipPaths.some((path) => url.pathname.startsWith(path))) {
7954
+ return false;
7955
+ }
7956
+ if (headers.get("cache-control")?.includes("no-cache")) {
7957
+ return false;
7958
+ }
7959
+ return true;
7960
+ }
7961
+ isResponseCacheable(response) {
7962
+ const contentType = response.headers.get("content-type");
7963
+ if (!contentType?.includes("text/html")) {
7964
+ return false;
7965
+ }
7966
+ const cacheControl = response.headers.get("cache-control");
7967
+ if (cacheControl?.includes("no-store") || cacheControl?.includes("private")) {
7968
+ return false;
7969
+ }
7970
+ return true;
7971
+ }
7972
+ getTTLForRequest(request) {
7973
+ const url = new URL(request.url);
7974
+ const path = url.pathname;
7975
+ if (path === "/") {
7976
+ return TTL_CONFIG.HOME;
7977
+ }
7978
+ if (path.startsWith("/products/")) {
7979
+ return TTL_CONFIG.PRODUCT;
7980
+ }
7981
+ if (path.startsWith("/categories/")) {
7982
+ return TTL_CONFIG.COLLECTION;
7983
+ }
7984
+ if (path.startsWith("/pages/")) {
7985
+ return TTL_CONFIG.PAGE;
7986
+ }
7987
+ if (path.startsWith("/blogs/")) {
7988
+ return TTL_CONFIG.BLOG;
7989
+ }
7990
+ return TTL_CONFIG.DEFAULT;
7991
+ }
7992
+ getResponseAge(response) {
7993
+ const cacheTime = response.headers.get("X-Cache-Time");
7994
+ if (!cacheTime) {
7995
+ return Infinity;
7996
+ }
7997
+ const cacheDate = new Date(cacheTime);
7998
+ if (isNaN(cacheDate.getTime())) {
7999
+ return Infinity;
8000
+ }
8001
+ const age = (Date.now() - cacheDate.getTime()) / 1e3;
8002
+ return Math.max(0, age);
8003
+ }
8004
+ getMaxAge(response) {
8005
+ const cacheControl = response.headers.get("Cache-Control");
8006
+ if (!cacheControl) {
8007
+ return TTL_CONFIG.DEFAULT;
8008
+ }
8009
+ const maxAgeMatch = cacheControl.match(/max-age=(\d+)/);
8010
+ if (maxAgeMatch) {
8011
+ return parseInt(maxAgeMatch[1], 10);
8012
+ }
8013
+ return TTL_CONFIG.DEFAULT;
8014
+ }
8015
+ getStaleWindow(response) {
8016
+ const cacheControl = response.headers.get("Cache-Control");
8017
+ if (!cacheControl) {
8018
+ return TTL_CONFIG.DEFAULT_SWR;
8019
+ }
8020
+ const swrMatch = cacheControl.match(/stale-while-revalidate=(\d+)/);
8021
+ if (swrMatch) {
8022
+ return parseInt(swrMatch[1], 10);
8023
+ }
8024
+ return TTL_CONFIG.DEFAULT_SWR;
8025
+ }
8026
+ /**
8027
+ * Normalize search params for cache key
8028
+ */
8029
+ normalizeSearchParams(searchParams) {
8030
+ const ignoredParams = [
8031
+ "utm_source",
8032
+ "utm_medium",
8033
+ "utm_campaign",
8034
+ "utm_content",
8035
+ "utm_term",
8036
+ "fbclid",
8037
+ "gclid",
8038
+ "gbraid",
8039
+ "wbraid",
8040
+ "ref",
8041
+ "source",
8042
+ "mc_cid",
8043
+ "mc_eid"
8044
+ ];
8045
+ const relevantParams = [];
8046
+ searchParams.forEach((value, key) => {
8047
+ if (!ignoredParams.includes(key)) {
8048
+ relevantParams.push(
8049
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`
8050
+ );
8051
+ }
8052
+ });
8053
+ return relevantParams.sort().join("&");
8054
+ }
8055
+ };
8056
+
8057
+ // src/resources/addresses.ts
8058
+ var SwellAddresses = class extends SwellStorefrontCollection {
8059
+ constructor(swell, query) {
8060
+ const { page, limit: limit2 } = swell.queryParams;
8061
+ super(swell, "accounts:addresses", { page, limit: limit2, ...query }, function() {
8062
+ return this._swell.storefront.account.listAddresses(
8063
+ this._query
8064
+ );
8065
+ });
8066
+ }
8067
+ };
8068
+
8069
+ // src/resources/orders.ts
8070
+ var SwellOrders = class extends SwellStorefrontCollection {
8071
+ constructor(swell, query) {
8072
+ const { page, limit: limit2 } = swell.queryParams;
8073
+ super(swell, "accounts:orders", { page, limit: limit2, ...query }, function() {
8074
+ return this._swell.storefront.account.listOrders(this._query);
8075
+ });
8076
+ }
8077
+ };
8078
+
8079
+ // src/resources/subscriptions.ts
8080
+ var SwellSubscriptions = class extends SwellStorefrontCollection {
8081
+ constructor(swell, query) {
8082
+ const { page, limit: limit2 } = swell.queryParams;
8083
+ super(
8084
+ swell,
8085
+ "accounts:subscriptions",
8086
+ { page, limit: limit2, ...query },
8087
+ function() {
8088
+ return this._swell.storefront.subscriptions.list(
8089
+ this._query
8090
+ );
8091
+ }
8092
+ );
8093
+ }
8094
+ };
8095
+
7738
8096
  // src/resources/account.ts
7739
8097
  var SwellAccount = class extends SwellStorefrontSingleton {
7740
- constructor(swell, getter) {
7741
- super(swell, "account", getter);
8098
+ constructor(swell) {
8099
+ super(swell, "account", async function() {
8100
+ const account = await this._defaultGetter().call(this);
8101
+ if (!account) {
8102
+ return null;
8103
+ }
8104
+ account.addresses = new SwellAddresses(
8105
+ this._swell
8106
+ );
8107
+ account.orders = new SwellOrders(
8108
+ this._swell
8109
+ );
8110
+ account.subscriptions = new SwellSubscriptions(
8111
+ this._swell
8112
+ );
8113
+ return account;
8114
+ });
7742
8115
  return this._getProxy();
7743
8116
  }
7744
8117
  };
7745
8118
 
7746
8119
  // src/resources/blog_category.ts
7747
8120
  var SwellBlogCategory = class extends SwellStorefrontRecord {
7748
- constructor(swell, id, query = {}, getter) {
7749
- super(swell, "content/blog-categories", id, query, getter);
8121
+ constructor(swell, id, query) {
8122
+ super(swell, "content/blog-categories", id, query, async function() {
8123
+ const category = await this._defaultGetter().call(this);
8124
+ if (!category) {
8125
+ return null;
8126
+ }
8127
+ category.blogs = new SwellStorefrontCollection(
8128
+ this._swell,
8129
+ "content/blogs",
8130
+ {
8131
+ category_id: category.id,
8132
+ expand: "author"
8133
+ }
8134
+ );
8135
+ return category;
8136
+ });
7750
8137
  return this._getProxy();
7751
8138
  }
7752
8139
  };
7753
8140
 
7754
8141
  // src/resources/blog.ts
7755
8142
  var SwellBlog = class extends SwellStorefrontRecord {
7756
- constructor(swell, id, query = {}, getter) {
7757
- super(swell, "content/blogs", id, query, getter);
8143
+ constructor(swell, blogId, categoryId, query) {
8144
+ super(swell, "content/blogs", blogId, query, async function() {
8145
+ this._query = { ...this._query, expand: "author" };
8146
+ const blog = await this._defaultGetter().call(this);
8147
+ if (!blog) {
8148
+ return null;
8149
+ }
8150
+ if (categoryId) {
8151
+ blog.category = new SwellStorefrontRecord(
8152
+ this._swell,
8153
+ "content/blog-categories",
8154
+ categoryId
8155
+ );
8156
+ }
8157
+ return blog;
8158
+ });
7758
8159
  return this._getProxy();
7759
8160
  }
7760
8161
  };
7761
8162
 
7762
8163
  // src/resources/cart.ts
7763
8164
  var SwellCart = class extends SwellStorefrontSingleton {
7764
- constructor(swell, getter) {
7765
- super(swell, "cart", getter);
7766
- return this._getProxy();
7767
- }
7768
- };
7769
-
7770
- // src/resources/category.ts
7771
- var SwellCategory = class extends SwellStorefrontRecord {
7772
- constructor(swell, id, query = {}, getter) {
7773
- super(swell, "categories", id, query, getter);
7774
- return this._getProxy();
7775
- }
7776
- };
7777
-
7778
- // src/resources/order.ts
7779
- var SwellOrder = class extends SwellStorefrontRecord {
7780
- constructor(swell, id, query = {}, getter) {
7781
- super(swell, "orders", id, query, getter);
8165
+ constructor(swell) {
8166
+ super(swell, "cart");
7782
8167
  return this._getProxy();
7783
8168
  }
7784
8169
  };
7785
8170
 
7786
- // src/resources/page.ts
7787
- var SwellPage = class extends SwellStorefrontRecord {
7788
- constructor(swell, id, query = {}, getter) {
7789
- super(swell, "content/pages", id, query, getter);
7790
- return this._getProxy();
8171
+ // src/resources/categories.ts
8172
+ var SwellCategories = class extends SwellStorefrontCollection {
8173
+ constructor(swell, query) {
8174
+ super(
8175
+ swell,
8176
+ "categories",
8177
+ {
8178
+ limit: 100,
8179
+ top_id: null,
8180
+ ...query
8181
+ },
8182
+ async function() {
8183
+ const categories = await this._defaultGetter().call(this);
8184
+ if (!categories) {
8185
+ return null;
8186
+ }
8187
+ for (const category of categories.results) {
8188
+ category.products = new SwellStorefrontCollection(
8189
+ this._swell,
8190
+ "products",
8191
+ { category: category.id }
8192
+ );
8193
+ }
8194
+ return categories;
8195
+ }
8196
+ );
7791
8197
  }
7792
8198
  };
7793
8199
 
@@ -7947,6 +8353,143 @@
7947
8353
  }
7948
8354
  return selectedPlan || subscriptionPurchaseOption.plans[0];
7949
8355
  }
8356
+ var SORT_OPTIONS = Object.freeze([
8357
+ { value: "", name: "Featured" },
8358
+ { value: "popularity", name: "Popularity", query: "popularity desc" },
8359
+ { value: "price_asc", name: "Price, low to high", query: "price asc" },
8360
+ { value: "price_desc", name: "Price, high to low", query: "price desc" },
8361
+ { value: "date_asc", name: "Date, old to new", query: "date asc" },
8362
+ { value: "date_desc", name: "Date, new to old", query: "date desc" },
8363
+ { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
8364
+ { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
8365
+ ]);
8366
+ async function getProductFilters(swell, productQuery) {
8367
+ const sortBy = swell.queryParams.sort || "";
8368
+ const filterQuery = productQueryWithFilters(swell, productQuery);
8369
+ return {
8370
+ filter_options: await getProductFiltersByQuery(swell, filterQuery),
8371
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.value,
8372
+ sort_options: [...SORT_OPTIONS]
8373
+ };
8374
+ }
8375
+ async function getProductFiltersByQuery(swell, query = {}) {
8376
+ const filters2 = await swell.get("/products/:filters", {
8377
+ ...query,
8378
+ sort: void 0
8379
+ }) || [];
8380
+ if (!Array.isArray(filters2)) {
8381
+ throw new Error("Product filters must be an array");
8382
+ }
8383
+ for (const filter of filters2) {
8384
+ filter.param_name = `filter_${filter.id}`;
8385
+ if (Array.isArray(filter.options)) {
8386
+ filter.active_options = [];
8387
+ filter.inactive_options = [];
8388
+ for (const option of filter.options) {
8389
+ const queryValue = swell.queryParams[filter.param_name];
8390
+ option.active = Array.isArray(queryValue) ? queryValue.includes(option.value) : queryValue === option.value;
8391
+ const list = option.active ? filter.active_options : filter.inactive_options;
8392
+ list.push(option);
8393
+ }
8394
+ }
8395
+ }
8396
+ return filters2;
8397
+ }
8398
+ function productQueryWithFilters(swell, query) {
8399
+ const filters2 = Object.keys(swell.queryParams).reduce(
8400
+ (acc, key) => {
8401
+ if (key.startsWith("filter_")) {
8402
+ const qkey = key.replace("filter_", "");
8403
+ const value = swell.queryParams[key];
8404
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && (value.gte !== void 0 || value.lte !== void 0)) {
8405
+ acc[qkey] = [value.gte || 0, value.lte || void 0];
8406
+ } else {
8407
+ acc[qkey] = value;
8408
+ }
8409
+ }
8410
+ return acc;
8411
+ },
8412
+ {}
8413
+ );
8414
+ const sortBy = swell.queryParams.sort || "";
8415
+ return {
8416
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
8417
+ $filters: filters2,
8418
+ ...query
8419
+ };
8420
+ }
8421
+
8422
+ // src/resources/category.ts
8423
+ var SwellCategory = class extends SwellStorefrontRecord {
8424
+ constructor(swell, id, query) {
8425
+ super(swell, "categories", id, query, async function() {
8426
+ let category = await this._defaultGetter().call(this);
8427
+ if (!category && this._id === "all") {
8428
+ category = {
8429
+ name: "Products",
8430
+ id: "all",
8431
+ slug: "all",
8432
+ filter_options: [],
8433
+ sort_options: []
8434
+ };
8435
+ }
8436
+ if (!category) {
8437
+ return null;
8438
+ }
8439
+ const productFilters = await getProductFilters(
8440
+ this._swell,
8441
+ category.id !== "all" ? { category: category.id, $variants: true } : { $variants: true }
8442
+ );
8443
+ Object.assign(category, productFilters);
8444
+ return category;
8445
+ });
8446
+ return this._getProxy();
8447
+ }
8448
+ };
8449
+
8450
+ // src/resources/order.ts
8451
+ var SwellOrder = class extends SwellStorefrontRecord {
8452
+ constructor(swell, id, query) {
8453
+ super(swell, "accounts:orders", id, query, function() {
8454
+ return this._swell.storefront.account.getOrder(this._id);
8455
+ });
8456
+ return this._getProxy();
8457
+ }
8458
+ };
8459
+
8460
+ // src/resources/page.ts
8461
+ var SwellPage = class extends SwellStorefrontRecord {
8462
+ constructor(swell, id, query) {
8463
+ super(swell, "content/pages", id, query);
8464
+ return this._getProxy();
8465
+ }
8466
+ };
8467
+
8468
+ // src/resources/predictive_search.ts
8469
+ var SwellPredictiveSearch = class extends StorefrontResource {
8470
+ constructor(swell, query) {
8471
+ super(async function() {
8472
+ const performed = String(query || "").length > 0;
8473
+ let products;
8474
+ if (performed) {
8475
+ products = new SwellStorefrontCollection(
8476
+ swell,
8477
+ "products",
8478
+ {
8479
+ search: query,
8480
+ limit: 10
8481
+ }
8482
+ );
8483
+ await products.resolve();
8484
+ }
8485
+ return {
8486
+ query,
8487
+ performed,
8488
+ products
8489
+ };
8490
+ });
8491
+ }
8492
+ };
7950
8493
 
7951
8494
  // src/resources/variant.ts
7952
8495
  function transformSwellVariant(params, product, variant) {
@@ -7966,27 +8509,30 @@
7966
8509
  )
7967
8510
  };
7968
8511
  }
8512
+ var SwellVariant = class extends SwellStorefrontRecord {
8513
+ product;
8514
+ constructor(swell, product, id, query) {
8515
+ super(swell, "products:variants", id, query, async function() {
8516
+ const variant = await this._swell.get(
8517
+ "/products:variants/{id}",
8518
+ { id: this._id }
8519
+ );
8520
+ return variant ?? null;
8521
+ });
8522
+ this.product = product;
8523
+ }
8524
+ };
7969
8525
 
7970
8526
  // src/resources/product.ts
7971
- var SORT_OPTIONS = [
7972
- { value: "", name: "Featured" },
7973
- { value: "popularity", name: "Popularity", query: "popularity desc" },
7974
- { value: "price_asc", name: "Price, low to high", query: "price asc" },
7975
- { value: "price_desc", name: "Price, high to low", query: "price desc" },
7976
- { value: "date_asc", name: "Date, old to new", query: "date asc" },
7977
- { value: "date_desc", name: "Date, new to old", query: "date desc" },
7978
- { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
7979
- { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
7980
- ];
7981
8527
  function transformSwellProduct(params, product) {
7982
8528
  if (!product) {
7983
- return product;
8529
+ return null;
7984
8530
  }
7985
8531
  const newProduct = {
7986
8532
  ...product,
7987
8533
  // add swell properties there
7988
8534
  selected_option_values: getSelectedVariantOptionValues(product, params),
7989
- purchase_options: getPurchaseOptions(product, params)
8535
+ purchase_options: getPurchaseOptions(product, params) ?? void 0
7990
8536
  };
7991
8537
  if (Array.isArray(newProduct.variants?.results)) {
7992
8538
  newProduct.variants = {
@@ -7999,43 +8545,49 @@
7999
8545
  return newProduct;
8000
8546
  }
8001
8547
  var SwellProduct = class extends SwellStorefrontRecord {
8002
- _params;
8003
- constructor(swell, id, query = {}, getter) {
8004
- super(swell, "products", id, query, getter);
8005
- this._params = swell.queryParams;
8548
+ constructor(swell, id, query) {
8549
+ const params = swell.queryParams;
8550
+ super(swell, "products", id, query, async function() {
8551
+ const result = await this._defaultGetter().call(this);
8552
+ return transformSwellProduct(params, result);
8553
+ });
8006
8554
  return this._getProxy();
8007
8555
  }
8008
- // add swell properties to the resolved object
8009
- _transformResult(result) {
8010
- const res = transformSwellProduct(
8011
- this._params,
8012
- result
8013
- );
8014
- return res;
8556
+ };
8557
+
8558
+ // src/resources/product_recommendations.ts
8559
+ var SwellProductRecommendations = class extends SwellProduct {
8560
+ constructor(swell, id, query) {
8561
+ super(swell, id, { ...query, $recommendations: true });
8562
+ }
8563
+ };
8564
+
8565
+ // src/resources/search.ts
8566
+ var SwellSearch = class extends StorefrontResource {
8567
+ constructor(swell, query) {
8568
+ super(async () => {
8569
+ const performed = String(query || "").length > 0;
8570
+ const productFilters = await getProductFilters(
8571
+ swell,
8572
+ performed ? { search: query } : void 0
8573
+ );
8574
+ return {
8575
+ query,
8576
+ performed,
8577
+ ...productFilters
8578
+ };
8579
+ });
8580
+ }
8581
+ };
8582
+
8583
+ // src/resources/subscription.ts
8584
+ var SwellSubscription = class extends SwellStorefrontRecord {
8585
+ constructor(swell, id, query) {
8586
+ super(swell, "accounts:subscriptions", id, query, function() {
8587
+ return this._swell.storefront.subscriptions.get(this._id, this._query);
8588
+ });
8015
8589
  }
8016
8590
  };
8017
- function productQueryWithFilters(swell, query = {}) {
8018
- const sortBy = swell.queryParams.sort || "";
8019
- const filters2 = Object.entries(swell.queryParams).reduce(
8020
- (acc, [key, value]) => {
8021
- if (key.startsWith("filter_")) {
8022
- const qkey = key.replace("filter_", "");
8023
- if (value?.gte !== void 0 || value?.lte !== void 0) {
8024
- acc[qkey] = [value.gte || 0, value.lte || void 0];
8025
- } else {
8026
- acc[qkey] = value;
8027
- }
8028
- }
8029
- return acc;
8030
- },
8031
- {}
8032
- );
8033
- return {
8034
- sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
8035
- $filters: filters2,
8036
- ...query
8037
- };
8038
- }
8039
8591
 
8040
8592
  // src/api.ts
8041
8593
  var DEFAULT_API_HOST = "https://api.schema.io";
@@ -8329,13 +8881,19 @@
8329
8881
  const storefrontRequest = storefront.request;
8330
8882
  return (method, url, id, data, opt) => {
8331
8883
  if (this.isStorefrontRequestCacheable(method, url, opt)) {
8332
- return this.getRequestCache().fetchSWR(
8333
- getCacheKey("request", [this.instanceId, method, url, id, data, opt]),
8334
- () => {
8335
- logger.info("[SDK] Storefront request", { method, url, id, data });
8336
- return storefrontRequest(method, url, id, data, opt);
8337
- }
8338
- );
8884
+ const key = getCacheKey("request", [
8885
+ this.instanceId,
8886
+ method,
8887
+ url,
8888
+ id,
8889
+ data,
8890
+ opt
8891
+ ]);
8892
+ return this.getRequestCache().fetchSWR(key, () => {
8893
+ const requestUrl = id ? `${url}/${id}` : url;
8894
+ logger.debug("[SDK] Cacheable API request", { url: requestUrl, key });
8895
+ return storefrontRequest(method, url, id, data, opt);
8896
+ });
8339
8897
  }
8340
8898
  switch (method) {
8341
8899
  case "delete":
@@ -8382,7 +8940,9 @@
8382
8940
  };
8383
8941
  function getCacheKey(key, args) {
8384
8942
  if (Array.isArray(args) && args.length > 0) {
8385
- return `${key}_${md5(JSON.stringify(args))}`;
8943
+ const fullKey = `${key}_${md5(JSON.stringify(args))}`;
8944
+ logger.debug(`[SDK] make cache key: ${fullKey}`);
8945
+ return fullKey;
8386
8946
  }
8387
8947
  return key;
8388
8948
  }
@@ -15606,11 +16166,9 @@ ${formattedMessage}`;
15606
16166
  return this._defaultGetter().call(this);
15607
16167
  }
15608
16168
  );
15609
- return products._cloneWithCompatibilityResult(
15610
- (products2) => {
15611
- return { ...products2, results: products2.results.map(mapper) };
15612
- }
15613
- );
16169
+ return products._cloneWithCompatibilityResult((products2) => {
16170
+ return { ...products2, results: products2.results.map(mapper) };
16171
+ });
15614
16172
  });
15615
16173
  }
15616
16174
  function makeProductsCollectionResolve(instance, object, mapper) {
@@ -15627,19 +16185,24 @@ ${formattedMessage}`;
15627
16185
 
15628
16186
  // src/compatibility/shopify-objects/collections.ts
15629
16187
  function ShopifyCollections(instance, categories) {
15630
- return new SwellStorefrontCollection(instance.swell, categories._collection, categories._query, async () => {
15631
- const results = (await categories.results)?.map((category) => {
15632
- return ShopifyCollection(instance, category);
15633
- });
15634
- return {
15635
- page: categories.page ?? 1,
15636
- count: categories.count ?? 0,
15637
- results: results ?? [],
15638
- page_count: categories.page_count ?? 0,
15639
- limit: categories.limit,
15640
- pages: categories.pages ?? {}
15641
- };
15642
- });
16188
+ return new SwellStorefrontCollection(
16189
+ instance.swell,
16190
+ categories._collection,
16191
+ categories._query,
16192
+ async () => {
16193
+ const results = (await categories.results)?.map((category) => {
16194
+ return ShopifyCollection(instance, category);
16195
+ });
16196
+ return {
16197
+ page: categories.page ?? 1,
16198
+ count: categories.count ?? 0,
16199
+ results: results ?? [],
16200
+ page_count: categories.page_count ?? 0,
16201
+ limit: categories.limit,
16202
+ pages: categories.pages ?? {}
16203
+ };
16204
+ }
16205
+ );
15643
16206
  }
15644
16207
 
15645
16208
  // src/compatibility/shopify-objects/address.ts
@@ -16826,13 +17389,12 @@ ${formattedMessage}`;
16826
17389
  var SwellImage = class extends StorefrontResource {
16827
17390
  constructor(swell, name) {
16828
17391
  super(async () => {
16829
- const files = await swell.get("/:files", {
17392
+ const file = await swell.get("/:files/:last", {
16830
17393
  private: { $ne: true },
16831
17394
  content_type: { $regex: "^image/" },
16832
17395
  filename: name
16833
17396
  });
16834
- const file = files?.results[0] ?? null;
16835
- if (file === null) {
17397
+ if (!file) {
16836
17398
  return null;
16837
17399
  }
16838
17400
  return { file };