@swell/apps-sdk 1.0.141 → 1.0.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -107,6 +107,97 @@
107
107
  // src/resources.ts
108
108
  var import_lodash_es2 = __require("lodash-es");
109
109
 
110
+ // src/utils/logger.ts
111
+ var logLevels = {
112
+ error: 0,
113
+ warn: 1,
114
+ info: 2,
115
+ debug: 3
116
+ };
117
+ var currentLogLevel = "warn";
118
+ var currentTimestampFormat = "off";
119
+ var isStructured = false;
120
+ function configureSdkLogger(config = {}) {
121
+ if (config.level && logLevels[config.level] !== void 0) {
122
+ currentLogLevel = config.level;
123
+ }
124
+ if (config.timestamp) {
125
+ currentTimestampFormat = config.timestamp;
126
+ }
127
+ if (typeof config.structured === "boolean") {
128
+ isStructured = config.structured;
129
+ }
130
+ }
131
+ function getTimestamp() {
132
+ if (currentTimestampFormat === "iso") {
133
+ return (/* @__PURE__ */ new Date()).toISOString();
134
+ }
135
+ if (currentTimestampFormat === "unix") {
136
+ return Date.now();
137
+ }
138
+ return void 0;
139
+ }
140
+ function formatArgs(args) {
141
+ const timestamp = getTimestamp();
142
+ if (!isStructured) {
143
+ return timestamp !== void 0 ? [...args, `(${timestamp})`] : args;
144
+ }
145
+ let message = "";
146
+ let context = {};
147
+ const messageParts = [];
148
+ for (const arg of args) {
149
+ if (arg instanceof Error) {
150
+ context.error = {
151
+ name: arg.name,
152
+ message: arg.message,
153
+ stack: arg.stack
154
+ };
155
+ } else if (typeof arg === "object" && arg !== null && !Array.isArray(arg) && Object.getPrototypeOf(arg) === Object.prototype) {
156
+ Object.assign(context, arg);
157
+ } else {
158
+ messageParts.push(arg);
159
+ }
160
+ }
161
+ message = messageParts.map((part) => typeof part === "object" ? JSON.stringify(part) : part).join(" ");
162
+ const finalLogObject = {
163
+ message,
164
+ ...context,
165
+ ...timestamp && { timestamp }
166
+ };
167
+ return [finalLogObject];
168
+ }
169
+ var logger = {
170
+ error: (...args) => {
171
+ console.error(...formatArgs(args));
172
+ },
173
+ warn: (...args) => {
174
+ if (logLevels[currentLogLevel] >= logLevels.warn) {
175
+ console.warn(...formatArgs(args));
176
+ }
177
+ },
178
+ info: (...args) => {
179
+ if (logLevels[currentLogLevel] >= logLevels.info) {
180
+ console.info(...formatArgs(args));
181
+ }
182
+ },
183
+ debug: (...args) => {
184
+ if (logLevels[currentLogLevel] >= logLevels.debug) {
185
+ console.debug(...formatArgs(args));
186
+ }
187
+ }
188
+ };
189
+ function createTraceId(data) {
190
+ if (data === void 0) {
191
+ return Math.random().toString(36).substring(2, 10);
192
+ }
193
+ let hash = 5381;
194
+ for (let i = 0; i < data.length; i++) {
195
+ const char = data.charCodeAt(i);
196
+ hash = (hash << 5) + hash + char;
197
+ }
198
+ return (hash >>> 0).toString(16);
199
+ }
200
+
110
201
  // src/liquid/utils.ts
111
202
  var import_liquidjs = __require("liquidjs");
112
203
 
@@ -502,7 +593,7 @@
502
593
  return instance[prop];
503
594
  }
504
595
  instance._result = instance._get().catch((err) => {
505
- console.log(err);
596
+ logger.error(err);
506
597
  return instance._getCollectionResultOrProp(instance, prop);
507
598
  });
508
599
  }
@@ -510,7 +601,7 @@
510
601
  return instance._result.then(() => {
511
602
  return instance._getCollectionResultOrProp(instance, prop);
512
603
  }).catch((err) => {
513
- console.log(err);
604
+ logger.error(err);
514
605
  return null;
515
606
  });
516
607
  }
@@ -569,7 +660,7 @@
569
660
  }
570
661
  return result;
571
662
  }).catch((err) => {
572
- console.log(err);
663
+ logger.error(err);
573
664
  return null;
574
665
  });
575
666
  }
@@ -747,7 +838,7 @@
747
838
  }
748
839
  return result;
749
840
  }).catch((err) => {
750
- console.log(err);
841
+ logger.error(err);
751
842
  return null;
752
843
  });
753
844
  }
@@ -861,7 +952,7 @@
861
952
  }
862
953
  return result;
863
954
  }).catch((err) => {
864
- console.log(err);
955
+ logger.error(err);
865
956
  return null;
866
957
  });
867
958
  }
@@ -895,15 +986,25 @@
895
986
  }
896
987
  async _get() {
897
988
  if (this._getter) {
989
+ const trace = createTraceId();
990
+ logger.debug("[SDK] Resource fetch start", {
991
+ resource: this.constructor.name,
992
+ hash: this._getterHash,
993
+ trace
994
+ });
898
995
  const getter = this._getter.bind(this);
899
996
  this._result = Promise.resolve().then(getter).then((result) => {
997
+ logger.debug("[SDK] Resource fetch end", {
998
+ hash: this._getterHash,
999
+ trace
1000
+ });
900
1001
  this._result = result;
901
1002
  if (result) {
902
1003
  Object.assign(this, result);
903
1004
  }
904
1005
  return result;
905
1006
  }).catch((err) => {
906
- console.log(err);
1007
+ logger.error(err, { trace });
907
1008
  return null;
908
1009
  });
909
1010
  }
@@ -6987,6 +7088,7 @@
6987
7088
  ttl: DEFAULT_TTL
6988
7089
  });
6989
7090
  var NULL_VALUE = "__NULL__";
7091
+ var SWR_PROMISE_MAP = /* @__PURE__ */ new Map();
6990
7092
  var Cache = class {
6991
7093
  client;
6992
7094
  workerCtx;
@@ -7008,19 +7110,31 @@
7008
7110
  * This will always return the cached value immediately if exists
7009
7111
  */
7010
7112
  async fetchSWR(key, fetchFn, ttl = DEFAULT_SWR_TTL) {
7113
+ const trace = createTraceId();
7114
+ logger.debug("[SDK] Cache fetch start", { key, trace });
7011
7115
  const cacheValue = await this.client.get(key);
7012
- const promiseValue = Promise.resolve().then(fetchFn).then(resolveAsyncResources).then(async (value) => {
7013
- const isNull = value === null || value === void 0;
7014
- await this.client.set(key, isNull ? NULL_VALUE : value, ttl);
7015
- return value;
7016
- });
7017
- if (this.workerCtx?.waitUntil) {
7018
- this.workerCtx.waitUntil(promiseValue);
7116
+ let promise = SWR_PROMISE_MAP.get(key);
7117
+ if (promise === void 0) {
7118
+ promise = Promise.resolve().then(fetchFn).then(resolveAsyncResources).then(async (value) => {
7119
+ const isNull = value === null || value === void 0;
7120
+ await this.client.set(key, isNull ? NULL_VALUE : value, ttl);
7121
+ logger.debug("[SDK] Cache update done", { key, trace });
7122
+ return value;
7123
+ }).finally(() => {
7124
+ SWR_PROMISE_MAP.delete(key);
7125
+ });
7126
+ SWR_PROMISE_MAP.set(key, promise);
7127
+ }
7128
+ if (typeof this.workerCtx?.waitUntil === "function") {
7129
+ this.workerCtx.waitUntil(promise);
7019
7130
  }
7020
7131
  if (cacheValue !== void 0) {
7132
+ logger.debug("[SDK] Cache check done", { status: "HIT", key, trace });
7021
7133
  return cacheValue === NULL_VALUE ? null : cacheValue;
7022
7134
  }
7023
- const result = await promiseValue;
7135
+ logger.debug("[SDK] Cache check done", { status: "MISS", key, trace });
7136
+ const result = await promise;
7137
+ logger.debug("[SDK] Cache fetch end", { key, trace });
7024
7138
  return result;
7025
7139
  }
7026
7140
  async get(key) {
@@ -7315,6 +7429,16 @@
7315
7429
  }
7316
7430
 
7317
7431
  // src/resources/product.ts
7432
+ var SORT_OPTIONS = [
7433
+ { value: "", name: "Featured" },
7434
+ { value: "popularity", name: "Popularity", query: "popularity desc" },
7435
+ { value: "price_asc", name: "Price, low to high", query: "price asc" },
7436
+ { value: "price_desc", name: "Price, high to low", query: "price desc" },
7437
+ { value: "date_asc", name: "Date, old to new", query: "date asc" },
7438
+ { value: "date_desc", name: "Date, new to old", query: "date desc" },
7439
+ { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
7440
+ { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
7441
+ ];
7318
7442
  function transformSwellProduct(params, product) {
7319
7443
  if (!product) {
7320
7444
  return product;
@@ -7351,6 +7475,28 @@
7351
7475
  return res;
7352
7476
  }
7353
7477
  };
7478
+ function productQueryWithFilters(swell, query = {}) {
7479
+ const sortBy = swell.queryParams.sort || "";
7480
+ const filters2 = Object.entries(swell.queryParams).reduce(
7481
+ (acc, [key, value]) => {
7482
+ if (key.startsWith("filter_")) {
7483
+ const qkey = key.replace("filter_", "");
7484
+ if (value?.gte !== void 0 || value?.lte !== void 0) {
7485
+ acc[qkey] = [value.gte || 0, value.lte || void 0];
7486
+ } else {
7487
+ acc[qkey] = value;
7488
+ }
7489
+ }
7490
+ return acc;
7491
+ },
7492
+ {}
7493
+ );
7494
+ return {
7495
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
7496
+ $filters: filters2,
7497
+ ...query
7498
+ };
7499
+ }
7354
7500
 
7355
7501
  // src/api.ts
7356
7502
  var DEFAULT_API_HOST = "https://api.schema.io";
@@ -7395,8 +7541,12 @@
7395
7541
  queryParams,
7396
7542
  workerEnv,
7397
7543
  workerCtx,
7544
+ logger: loggerConfig,
7398
7545
  ...clientProps
7399
7546
  } = params;
7547
+ if (loggerConfig) {
7548
+ configureSdkLogger(loggerConfig);
7549
+ }
7400
7550
  this.url = url instanceof URL ? url : new URL(url || "");
7401
7551
  this.config = config;
7402
7552
  this.shopifyCompatibilityConfig = shopifyCompatibilityConfig;
@@ -7406,7 +7556,9 @@
7406
7556
  this.workerCtx = workerCtx;
7407
7557
  this.workerEnv = workerEnv;
7408
7558
  this.resourceLoadingIndicator = params.resourceLoadingIndicator;
7409
- console.log(`KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}`);
7559
+ logger.info(
7560
+ `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}`
7561
+ );
7410
7562
  if (serverHeaders) {
7411
7563
  const { headers: headers2, swellHeaders: swellHeaders2 } = _Swell.formatHeaders(serverHeaders);
7412
7564
  this.headers = headers2;
@@ -7520,7 +7672,7 @@
7520
7672
  if (err instanceof Error) {
7521
7673
  err.message = `Swell: unable to load settings (${err.message})`;
7522
7674
  }
7523
- console.error(err);
7675
+ logger.error(err);
7524
7676
  }
7525
7677
  return this.storefront.settings.get();
7526
7678
  }
@@ -7603,7 +7755,9 @@
7603
7755
  decodeURIComponent(this.swellHeaders["storefront-context"])
7604
7756
  );
7605
7757
  } catch (error) {
7606
- console.error("Failed to parse swell-storefront-context. Ignoring...");
7758
+ logger.error(
7759
+ "[SDK] Failed to parse swell-storefront-context. Ignoring..."
7760
+ );
7607
7761
  }
7608
7762
  }
7609
7763
  return storefrontContext;
@@ -7633,7 +7787,7 @@
7633
7787
  return this.getRequestCache().fetchSWR(
7634
7788
  getCacheKey("request", [this.instanceId, method, url, id, data, opt]),
7635
7789
  () => {
7636
- console.log("Storefront request", { method, url, id, data });
7790
+ logger.info("[SDK] Storefront request", { method, url, id, data });
7637
7791
  return storefrontRequest(method, url, id, data, opt);
7638
7792
  }
7639
7793
  );
@@ -7657,7 +7811,7 @@
7657
7811
  */
7658
7812
  getResourceCache() {
7659
7813
  let cache = resourceCaches.get(this.instanceId);
7660
- if (!cache) {
7814
+ if (cache === void 0) {
7661
7815
  cache = new ResourceCache({
7662
7816
  kvStore: this.workerEnv?.THEME,
7663
7817
  workerCtx: this.workerCtx
@@ -7671,7 +7825,7 @@
7671
7825
  */
7672
7826
  getRequestCache() {
7673
7827
  let cache = requestCaches.get(this.instanceId);
7674
- if (!cache) {
7828
+ if (cache === void 0) {
7675
7829
  cache = new RequestCache({
7676
7830
  kvStore: this.workerEnv?.THEME,
7677
7831
  workerCtx: this.workerCtx
@@ -7724,6 +7878,11 @@
7724
7878
  }
7725
7879
  const endpointUrl = String(url).startsWith("/") ? url.substring(1) : url;
7726
7880
  const requestUrl = `${this.apiHost}/${endpointUrl}${query}`;
7881
+ const trace = createTraceId();
7882
+ logger.debug("[SDK] Backend request start", {
7883
+ query: `/${endpointUrl}${query}`,
7884
+ trace
7885
+ });
7727
7886
  const response = await fetch(requestUrl, requestOptions);
7728
7887
  const responseText = await response.text();
7729
7888
  let result;
@@ -7732,6 +7891,10 @@
7732
7891
  } catch {
7733
7892
  result = String(responseText || "").trim();
7734
7893
  }
7894
+ logger.debug("[SDK] Backend request end", {
7895
+ status: response.status,
7896
+ trace
7897
+ });
7735
7898
  if (response.status > 299) {
7736
7899
  throw new SwellError(result, {
7737
7900
  status: response.status,
@@ -14774,9 +14937,11 @@ ${formattedMessage}`;
14774
14937
  if (category instanceof StorefrontResource) {
14775
14938
  category = cloneStorefrontResource(category);
14776
14939
  }
14940
+ const productMapper = (product) => ShopifyProduct(instance, product);
14777
14941
  const resolveProducts = makeProductsCollectionResolve(
14942
+ instance,
14778
14943
  category,
14779
- (product) => ShopifyProduct(instance, product)
14944
+ productMapper
14780
14945
  );
14781
14946
  return new ShopifyResource({
14782
14947
  all_products_count: defer(
@@ -14828,8 +14993,9 @@ ${formattedMessage}`;
14828
14993
  category,
14829
14994
  (category2) => getFirstImage(instance, category2)
14830
14995
  ),
14831
- filters: defer(
14832
- async () => ((await resolveProducts())?.filter_options ?? []).map(
14996
+ filters: deferWith(
14997
+ category,
14998
+ (category2) => (category2?.filter_options ?? []).map(
14833
14999
  (filter) => ShopifyFilter(instance, filter)
14834
15000
  )
14835
15001
  ),
@@ -14839,9 +15005,7 @@ ${formattedMessage}`;
14839
15005
  metafields: {},
14840
15006
  next_product: void 0,
14841
15007
  previous_product: void 0,
14842
- products: defer(async () => {
14843
- return (await resolveProducts())?.results ?? [];
14844
- }),
15008
+ products: getProducts(instance, category, productMapper),
14845
15009
  products_count: defer(
14846
15010
  async () => (await resolveProducts())?.results?.length || 0
14847
15011
  ),
@@ -14880,28 +15044,35 @@ ${formattedMessage}`;
14880
15044
  return "manual";
14881
15045
  }
14882
15046
  }
14883
- function makeProductsCollectionResolve(object, mapper) {
14884
- const productResults = deferWith(object, (object2) => {
14885
- if (object2.products) {
14886
- if (object2.products instanceof SwellStorefrontCollection) {
14887
- return object2.products._cloneWithCompatibilityResult((products) => {
14888
- return {
14889
- ...products,
14890
- results: products.results.map(mapper)
14891
- };
14892
- });
15047
+ function getProducts(instance, object, mapper) {
15048
+ return deferWith(object, (object2) => {
15049
+ const { page, limit: limit2 } = instance.swell.queryParams;
15050
+ const categoryFilter = object2.id && object2.id !== "all" ? object2.id : void 0;
15051
+ const productQuery = categoryFilter ? { category: categoryFilter, $variants: true } : { $variants: true };
15052
+ const filterQuery = productQueryWithFilters(instance.swell, productQuery);
15053
+ const products = new SwellStorefrontCollection(
15054
+ instance.swell,
15055
+ "products",
15056
+ {
15057
+ page,
15058
+ limit: limit2,
15059
+ ...filterQuery
15060
+ },
15061
+ async function() {
15062
+ return this._defaultGetter().call(this);
14893
15063
  }
14894
- if (Array.isArray(object2.products?.results)) {
14895
- return {
14896
- ...object2.products,
14897
- results: object2.products.results.map(mapper)
14898
- };
15064
+ );
15065
+ return products._cloneWithCompatibilityResult(
15066
+ (products2) => {
15067
+ return { ...products2, results: products2.results.map(mapper) };
14899
15068
  }
14900
- }
14901
- return null;
15069
+ );
14902
15070
  });
15071
+ }
15072
+ function makeProductsCollectionResolve(instance, object, mapper) {
15073
+ const products = getProducts(instance, object, mapper);
14903
15074
  async function resolveProducts() {
14904
- const resolved = await productResults.resolve();
15075
+ const resolved = await products.resolve();
14905
15076
  if (resolved && "_resolve" in resolved) {
14906
15077
  return resolved._resolve();
14907
15078
  }
@@ -15665,11 +15836,15 @@ ${formattedMessage}`;
15665
15836
  if (search instanceof ShopifyResource) {
15666
15837
  return search.clone();
15667
15838
  }
15668
- const resolveProducts = makeProductsCollectionResolve(search, (product) => {
15669
- const shopifyProduct = ShopifyProduct(instance, product);
15670
- shopifyProduct.object_type = "product";
15671
- return shopifyProduct;
15672
- });
15839
+ const resolveProducts = makeProductsCollectionResolve(
15840
+ instance,
15841
+ search,
15842
+ (product) => {
15843
+ const shopifyProduct = ShopifyProduct(instance, product);
15844
+ shopifyProduct.object_type = "product";
15845
+ return shopifyProduct;
15846
+ }
15847
+ );
15673
15848
  return new ShopifyResource({
15674
15849
  default_sort_by: deferWith(
15675
15850
  search,
@@ -19148,9 +19323,14 @@ ${injects.join("\n")}<\/script>`;
19148
19323
  }
19149
19324
  async initGlobals(pageId, altTemplate) {
19150
19325
  this.pageId = pageId;
19326
+ const trace = createTraceId();
19327
+ logger.debug("[SDK] Theme init start", { page: pageId, trace });
19151
19328
  await this.themeLoader.init(this.themeConfigs || void 0);
19329
+ logger.debug("[SDK] ThemeLoader init done", { page: pageId, trace });
19152
19330
  const { store, session, menus, geo, configs } = await this.getSettingsAndConfigs();
19331
+ logger.debug("[SDK] Theme settings load done", { page: pageId, trace });
19153
19332
  const { settings, request, page, cart, account, customer } = await this.resolvePageData(store, configs, pageId, altTemplate);
19333
+ logger.debug("[SDK] Theme page data load done", { page: pageId, trace });
19154
19334
  this.page = page;
19155
19335
  const globals = {
19156
19336
  ...this.globalData,
@@ -19177,6 +19357,7 @@ ${injects.join("\n")}<\/script>`;
19177
19357
  if (this.shopifyCompatibility) {
19178
19358
  this.shopifyCompatibility.adaptQueryParams();
19179
19359
  }
19360
+ logger.debug("[SDK] Theme init end", { page: pageId, trace });
19180
19361
  }
19181
19362
  setGlobals(globals) {
19182
19363
  if (this.shopifyCompatibility) {
@@ -19209,7 +19390,7 @@ ${injects.join("\n")}<\/script>`;
19209
19390
  try {
19210
19391
  configValue = import_json56.default.parse(config.file_data);
19211
19392
  } catch (err) {
19212
- console.error(`Error parsing ${configName} config: ${err}`);
19393
+ logger.error(`Error parsing config`, err, { configName });
19213
19394
  configValue = {};
19214
19395
  }
19215
19396
  acc[configName] = configValue;
@@ -19296,7 +19477,7 @@ ${injects.join("\n")}<\/script>`;
19296
19477
  templateConfig?.file_data || "{}"
19297
19478
  );
19298
19479
  } catch (err) {
19299
- console.warn(err);
19480
+ logger.warn(err);
19300
19481
  }
19301
19482
  if (pageSchema?.page) {
19302
19483
  const {
@@ -19601,7 +19782,7 @@ ${injects.join("\n")}<\/script>`;
19601
19782
  try {
19602
19783
  return import_json56.default.parse(localeConfig?.file_data || "{}");
19603
19784
  } catch (err) {
19604
- console.warn(err);
19785
+ logger.warn(err);
19605
19786
  }
19606
19787
  }
19607
19788
  return {};
@@ -19770,10 +19951,20 @@ ${injects.join("\n")}<\/script>`;
19770
19951
  return "";
19771
19952
  }
19772
19953
  template = unescapeLiquidSyntax(template);
19954
+ const trace = createTraceId();
19773
19955
  try {
19774
- return await this.liquidSwell.parseAndRender(template, data);
19956
+ logger.debug("[SDK] Render template start", {
19957
+ config: config.name,
19958
+ trace
19959
+ });
19960
+ const result = await this.liquidSwell.parseAndRender(template, data);
19961
+ logger.debug("[SDK] Render template end", {
19962
+ config: config.name,
19963
+ trace
19964
+ });
19965
+ return result;
19775
19966
  } catch (err) {
19776
- console.error(err);
19967
+ logger.error(err);
19777
19968
  return `<!-- template render error: ${err.message} -->`;
19778
19969
  }
19779
19970
  }
@@ -19781,7 +19972,7 @@ ${injects.join("\n")}<\/script>`;
19781
19972
  try {
19782
19973
  return await this.liquidSwell.parseAndRender(templateString, data);
19783
19974
  } catch (err) {
19784
- console.error(err);
19975
+ logger.error(err);
19785
19976
  return "";
19786
19977
  }
19787
19978
  }
@@ -19805,7 +19996,7 @@ ${injects.join("\n")}<\/script>`;
19805
19996
  );
19806
19997
  }
19807
19998
  } catch (err) {
19808
- console.warn(err);
19999
+ logger.warn(err);
19809
20000
  return void 0;
19810
20001
  }
19811
20002
  } else if (config?.file_path?.endsWith(".liquid")) {
@@ -19858,11 +20049,10 @@ ${injects.join("\n")}<\/script>`;
19858
20049
  try {
19859
20050
  return import_json56.default.parse(content);
19860
20051
  } catch (err) {
19861
- console.log(
19862
- "Unable to render theme template",
19863
- config.file_path,
20052
+ logger.error("[SDK] Unable to render theme template", {
20053
+ file: config.file_path,
19864
20054
  content
19865
- );
20055
+ });
19866
20056
  throw new PageError(err);
19867
20057
  }
19868
20058
  }
@@ -20119,7 +20309,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20119
20309
  try {
20120
20310
  schema = import_json56.default.parse(resolvedConfig?.file_data) || void 0;
20121
20311
  } catch (err) {
20122
- console.warn(err);
20312
+ logger.warn(err);
20123
20313
  }
20124
20314
  }
20125
20315
  return schema;
@@ -20509,7 +20699,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20509
20699
  try {
20510
20700
  return import_json56.default.parse(config?.file_data || "{}");
20511
20701
  } catch (err) {
20512
- console.warn(err);
20702
+ logger.warn(err);
20513
20703
  return {};
20514
20704
  }
20515
20705
  }