@swell/apps-sdk 1.0.149 → 1.0.151

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 (49) hide show
  1. package/dist/index.cjs +1101 -446
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +1079 -446
  4. package/dist/index.js.map +4 -4
  5. package/dist/index.mjs +1090 -446
  6. package/dist/index.mjs.map +4 -4
  7. package/dist/src/cache/constants.d.ts +7 -0
  8. package/dist/src/cache/index.d.ts +4 -0
  9. package/dist/src/cache/kv-variety.d.ts +10 -0
  10. package/dist/src/cache/theme-file-storage.d.ts +88 -0
  11. package/dist/src/cache/worker-cache-proxy.d.ts +31 -0
  12. package/dist/src/compatibility/drops/all_products.d.ts +1 -1
  13. package/dist/src/compatibility/drops/articles.d.ts +1 -1
  14. package/dist/src/compatibility/drops/blogs.d.ts +1 -1
  15. package/dist/src/compatibility/drops/collections.d.ts +2 -3
  16. package/dist/src/compatibility/drops/images.d.ts +1 -1
  17. package/dist/src/compatibility/drops/pages.d.ts +2 -3
  18. package/dist/src/compatibility/shopify-objects/collections.d.ts +2 -2
  19. package/dist/src/content.d.ts +3 -3
  20. package/dist/src/liquid/filters/shopify/default_pagination.d.ts +1 -1
  21. package/dist/src/resources/account.d.ts +4 -4
  22. package/dist/src/resources/addresses.d.ts +7 -0
  23. package/dist/src/resources/blog.d.ts +5 -4
  24. package/dist/src/resources/blog_category.d.ts +5 -4
  25. package/dist/src/resources/cart.d.ts +4 -4
  26. package/dist/src/resources/categories.d.ts +7 -0
  27. package/dist/src/resources/category.d.ts +5 -4
  28. package/dist/src/resources/index.d.ts +18 -9
  29. package/dist/src/resources/order.d.ts +5 -4
  30. package/dist/src/resources/orders.d.ts +7 -0
  31. package/dist/src/resources/page.d.ts +5 -4
  32. package/dist/src/resources/predictive_search.d.ts +6 -0
  33. package/dist/src/resources/product.d.ts +5 -19
  34. package/dist/src/resources/product_helpers.d.ts +12 -2
  35. package/dist/src/resources/product_recommendations.d.ts +6 -0
  36. package/dist/src/resources/search.d.ts +6 -0
  37. package/dist/src/resources/subscription.d.ts +7 -0
  38. package/dist/src/resources/subscriptions.d.ts +7 -0
  39. package/dist/src/resources/swell_types.d.ts +66 -9
  40. package/dist/src/resources/variant.d.ts +8 -8
  41. package/dist/src/resources.d.ts +11 -12
  42. package/dist/src/theme/theme-loader.d.ts +36 -44
  43. package/dist/src/theme.d.ts +22 -7
  44. package/dist/src/utils/index.d.ts +1 -0
  45. package/dist/src/utils/kv-flavor.d.ts +7 -0
  46. package/dist/types/cloudflare.d.ts +14 -3
  47. package/dist/types/shopify.d.ts +2 -2
  48. package/dist/types/swell.d.ts +3 -1
  49. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -66,21 +66,30 @@ __export(index_exports, {
66
66
  StorefrontResource: () => StorefrontResource,
67
67
  Swell: () => Swell,
68
68
  SwellAccount: () => SwellAccount,
69
+ SwellAddresses: () => SwellAddresses,
69
70
  SwellBackendAPI: () => SwellBackendAPI,
70
71
  SwellBlog: () => SwellBlog,
71
72
  SwellBlogCategory: () => SwellBlogCategory,
72
73
  SwellCart: () => SwellCart,
74
+ SwellCategories: () => SwellCategories,
73
75
  SwellCategory: () => SwellCategory,
74
76
  SwellError: () => SwellError,
75
77
  SwellOrder: () => SwellOrder,
78
+ SwellOrders: () => SwellOrders,
76
79
  SwellPage: () => SwellPage,
80
+ SwellPredictiveSearch: () => SwellPredictiveSearch,
77
81
  SwellProduct: () => SwellProduct,
82
+ SwellProductRecommendations: () => SwellProductRecommendations,
83
+ SwellSearch: () => SwellSearch,
78
84
  SwellStorefrontCollection: () => SwellStorefrontCollection,
79
85
  SwellStorefrontPagination: () => SwellStorefrontPagination,
80
86
  SwellStorefrontRecord: () => SwellStorefrontRecord,
81
87
  SwellStorefrontResource: () => SwellStorefrontResource,
82
88
  SwellStorefrontSingleton: () => SwellStorefrontSingleton,
89
+ SwellSubscription: () => SwellSubscription,
90
+ SwellSubscriptions: () => SwellSubscriptions,
83
91
  SwellTheme: () => SwellTheme3,
92
+ SwellVariant: () => SwellVariant,
84
93
  ThemeColor: () => ThemeColor,
85
94
  ThemeFont: () => ThemeFont,
86
95
  ThemeForm: () => ThemeForm,
@@ -112,6 +121,7 @@ __export(index_exports, {
112
121
  getEasyblocksComponentDefinitions: () => getEasyblocksComponentDefinitions,
113
122
  getEasyblocksPagePropsWithConfigs: () => getEasyblocksPagePropsWithConfigs,
114
123
  getEasyblocksPageTemplate: () => getEasyblocksPageTemplate,
124
+ getKVFlavor: () => getKVFlavor,
115
125
  getLayoutSectionGroups: () => getLayoutSectionGroups,
116
126
  getMenuItemStorefrontUrl: () => getMenuItemStorefrontUrl,
117
127
  getMenuItemUrlAndResource: () => getMenuItemUrlAndResource,
@@ -127,6 +137,7 @@ __export(index_exports, {
127
137
  isObject: () => isObject2,
128
138
  md5: () => md5,
129
139
  removeCircularReferences: () => removeCircularReferences,
140
+ resetKVFlavorCache: () => resetKVFlavorCache,
130
141
  resolveAsyncResources: () => resolveAsyncResources,
131
142
  resolveLookupCollection: () => resolveLookupCollection,
132
143
  resolveMenuItemUrlAndResource: () => resolveMenuItemUrlAndResource,
@@ -761,18 +772,12 @@ var StorefrontResource = class {
761
772
  }
762
773
  return instance[prop];
763
774
  }
764
- // add additional properties to the loaded result
765
- _transformResult(result) {
766
- return result;
767
- }
768
775
  async _get(..._args) {
769
776
  if (this._getter) {
770
777
  const getter = this._getter.bind(
771
778
  this
772
779
  );
773
780
  return Promise.resolve().then(getter).then((result) => {
774
- return this._transformResult(result);
775
- }).then((result) => {
776
781
  this._result = result ?? null;
777
782
  if (result) {
778
783
  Object.assign(this, result);
@@ -855,7 +860,6 @@ function cloneStorefrontResource(input) {
855
860
  });
856
861
  const clone = new ClonedClass(input._getter);
857
862
  clone._params = input._params;
858
- clone._transformResult = input._transformResult.bind(clone);
859
863
  Object.defineProperty(clone, "_resourceName", {
860
864
  value: resourceName
861
865
  });
@@ -909,7 +913,11 @@ var SwellStorefrontCollection = class _SwellStorefrontCollection extends SwellSt
909
913
  limit = DEFAULT_QUERY_PAGE_LIMIT;
910
914
  name;
911
915
  constructor(swell, collection, query = {}, getter) {
912
- super(swell, collection, getter);
916
+ super(
917
+ swell,
918
+ collection,
919
+ getter
920
+ );
913
921
  this._query = this._initQuery(query);
914
922
  if (!getter) {
915
923
  this._setGetter(this._defaultGetter());
@@ -1085,8 +1093,6 @@ var SwellStorefrontRecord = class extends SwellStorefrontResource {
1085
1093
  getter,
1086
1094
  isResourceCacheble(this._collection)
1087
1095
  ).then((result) => {
1088
- return this._transformResult(result);
1089
- }).then((result) => {
1090
1096
  this._result = result;
1091
1097
  if (result) {
1092
1098
  Object.assign(this, result);
@@ -6832,6 +6838,50 @@ function md5(inputString) {
6832
6838
  return rh(a) + rh(b) + rh(c) + rh(d);
6833
6839
  }
6834
6840
 
6841
+ // src/utils/kv-flavor.ts
6842
+ var cachedKVFlavor;
6843
+ function getKVFlavor(workerEnv) {
6844
+ if (cachedKVFlavor) {
6845
+ return cachedKVFlavor;
6846
+ }
6847
+ if (!workerEnv?.THEME) {
6848
+ cachedKVFlavor = "memory";
6849
+ return cachedKVFlavor;
6850
+ }
6851
+ const kvFlavor = workerEnv.KV_FLAVOR;
6852
+ if (kvFlavor) {
6853
+ const flavorLower = String(kvFlavor).toLowerCase();
6854
+ switch (flavorLower) {
6855
+ case "cloudflare":
6856
+ case "cf":
6857
+ cachedKVFlavor = "cf";
6858
+ break;
6859
+ case "miniflare":
6860
+ cachedKVFlavor = "miniflare";
6861
+ break;
6862
+ case "memory":
6863
+ cachedKVFlavor = "memory";
6864
+ break;
6865
+ default:
6866
+ logger.warn(`[KV] Unknown KV_FLAVOR: ${kvFlavor}, using miniflare`);
6867
+ cachedKVFlavor = "miniflare";
6868
+ }
6869
+ return cachedKVFlavor;
6870
+ }
6871
+ try {
6872
+ if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
6873
+ cachedKVFlavor = "cf";
6874
+ return cachedKVFlavor;
6875
+ }
6876
+ } catch {
6877
+ }
6878
+ cachedKVFlavor = "miniflare";
6879
+ return cachedKVFlavor;
6880
+ }
6881
+ function resetKVFlavorCache() {
6882
+ cachedKVFlavor = void 0;
6883
+ }
6884
+
6835
6885
  // src/utils/index.ts
6836
6886
  function isSectionConfig(config, themeConfigs) {
6837
6887
  if (!config.file_path.startsWith("theme/sections/")) {
@@ -7361,68 +7411,584 @@ function buildStores2() {
7361
7411
 
7362
7412
  // src/cache/theme-cache.ts
7363
7413
  var TTL = 90 * 24 * 60 * 60 * 1e3;
7364
- var ThemeCache = class extends Cache {
7365
- constructor(options) {
7366
- super({
7367
- ttl: TTL,
7368
- ...options
7414
+
7415
+ // src/cache/theme-file-storage.ts
7416
+ var import_bluebird2 = __toESM(require("bluebird"), 1);
7417
+
7418
+ // src/cache/kv-variety.ts
7419
+ var import_bluebird = __toESM(require("bluebird"), 1);
7420
+ var { Promise: Promise2 } = import_bluebird.default;
7421
+ var CFKV = class {
7422
+ constructor(kv) {
7423
+ this.kv = kv;
7424
+ }
7425
+ async get(keys) {
7426
+ if (keys.length === 0) {
7427
+ return /* @__PURE__ */ new Map();
7428
+ }
7429
+ const result = await this.kv.get(keys, "text");
7430
+ if (!(result instanceof Map)) {
7431
+ const map = /* @__PURE__ */ new Map();
7432
+ for (const key of keys) {
7433
+ map.set(key, null);
7434
+ }
7435
+ return map;
7436
+ }
7437
+ return result;
7438
+ }
7439
+ async put(key, value, metadata) {
7440
+ await this.kv.put(key, value, { metadata });
7441
+ }
7442
+ };
7443
+ var MiniflareKV = class {
7444
+ constructor(kv) {
7445
+ this.kv = kv;
7446
+ }
7447
+ async get(keys) {
7448
+ if (keys.length === 0) {
7449
+ return /* @__PURE__ */ new Map();
7450
+ }
7451
+ const result = /* @__PURE__ */ new Map();
7452
+ await Promise2.map(
7453
+ keys,
7454
+ async (key) => {
7455
+ const value = await this.kv.get(key, "text");
7456
+ result.set(key, value);
7457
+ },
7458
+ { concurrency: 50 }
7459
+ );
7460
+ return result;
7461
+ }
7462
+ async put(key, value, metadata) {
7463
+ await this.kv.put(key, value, { metadata });
7464
+ }
7465
+ };
7466
+ var MemoryKV = class {
7467
+ store = /* @__PURE__ */ new Map();
7468
+ async get(keys) {
7469
+ const result = /* @__PURE__ */ new Map();
7470
+ for (const key of keys) {
7471
+ const entry = this.store.get(key);
7472
+ result.set(key, entry?.value ?? null);
7473
+ }
7474
+ return result;
7475
+ }
7476
+ async put(key, value, metadata) {
7477
+ this.store.set(key, { value, metadata });
7478
+ }
7479
+ };
7480
+ function createClientKV(env, flavor = "cf") {
7481
+ if (env?.THEME) {
7482
+ if (flavor === "miniflare") {
7483
+ return new MiniflareKV(env.THEME);
7484
+ }
7485
+ return new CFKV(env.THEME);
7486
+ }
7487
+ return new MemoryKV();
7488
+ }
7489
+
7490
+ // src/cache/theme-file-storage.ts
7491
+ var { Promise: Promise3 } = import_bluebird2.default;
7492
+ var ThemeFileStorage = class {
7493
+ kv;
7494
+ maxConcurrency;
7495
+ maxBatchSize = 20 * 1024 * 1024;
7496
+ // 20MB safety margin
7497
+ constructor(env, flavor = "cf") {
7498
+ this.kv = createClientKV(env, flavor);
7499
+ this.maxConcurrency = flavor === "miniflare" ? 50 : 6;
7500
+ }
7501
+ /**
7502
+ * Build a KV storage key from a file hash
7503
+ */
7504
+ buildKey(hash) {
7505
+ return `file_data:${hash}`;
7506
+ }
7507
+ /**
7508
+ * Extract hash from a KV storage key
7509
+ */
7510
+ extractHashFromKey(key) {
7511
+ return key.replace("file_data:", "");
7512
+ }
7513
+ /**
7514
+ * Plan GET batches based on file sizes to avoid 413 errors
7515
+ * Uses round-robin distribution for even batch sizes
7516
+ */
7517
+ planGetBatches(configs) {
7518
+ if (configs.length === 0) {
7519
+ return [];
7520
+ }
7521
+ const sorted = [...configs].sort((a, b) => {
7522
+ const sizeA = a.file?.length || 0;
7523
+ const sizeB = b.file?.length || 0;
7524
+ return sizeB - sizeA;
7525
+ });
7526
+ const totalSize = sorted.reduce((sum, config) => {
7527
+ return sum + (config.file?.length || 0);
7528
+ }, 0);
7529
+ const sizeBatches = Math.ceil(totalSize / this.maxBatchSize);
7530
+ const keyBatches = Math.ceil(sorted.length / 100);
7531
+ const targetBatches = Math.max(sizeBatches, keyBatches);
7532
+ const batches = Array.from(
7533
+ { length: targetBatches },
7534
+ () => ({
7535
+ configs: [],
7536
+ keys: [],
7537
+ estimatedSize: 0
7538
+ })
7539
+ );
7540
+ sorted.forEach((config, index) => {
7541
+ const batchIndex = index % targetBatches;
7542
+ const batch = batches[batchIndex];
7543
+ batch.configs.push(config);
7544
+ batch.keys.push(this.buildKey(config.hash));
7545
+ batch.estimatedSize += config.file?.length || 0;
7546
+ });
7547
+ return batches.filter((batch) => batch.configs.length > 0);
7548
+ }
7549
+ /**
7550
+ * Load a single batch from KV storage
7551
+ */
7552
+ async loadBatch(batch) {
7553
+ return this.kv.get(batch.keys);
7554
+ }
7555
+ /**
7556
+ * Merge batch results with original configs
7557
+ */
7558
+ mergeResults(configs, batchResults) {
7559
+ const allData = /* @__PURE__ */ new Map();
7560
+ for (const batchResult of batchResults) {
7561
+ for (const [key, value] of batchResult.entries()) {
7562
+ allData.set(key, value);
7563
+ }
7564
+ }
7565
+ return configs.map((config) => {
7566
+ const key = this.buildKey(config.hash);
7567
+ const fileData = allData.get(key);
7568
+ if (fileData) {
7569
+ return {
7570
+ ...config,
7571
+ file_data: fileData
7572
+ };
7573
+ }
7574
+ return config;
7575
+ });
7576
+ }
7577
+ async getFiles(configs) {
7578
+ if (configs.length === 0) {
7579
+ return [];
7580
+ }
7581
+ const trace = createTraceId();
7582
+ const batches = this.planGetBatches(configs);
7583
+ const totalSize = batches.reduce((sum, b) => sum + b.estimatedSize, 0);
7584
+ const maxBatchSize = Math.max(...batches.map((b) => b.estimatedSize));
7585
+ logger.debug("[ThemeFileStorage] Loading files start", {
7586
+ totalConfigs: configs.length,
7587
+ batchCount: batches.length,
7588
+ maxBatchSize,
7589
+ totalSize,
7590
+ trace
7591
+ });
7592
+ const results = await Promise3.map(
7593
+ batches,
7594
+ (batch) => this.loadBatch(batch),
7595
+ { concurrency: Math.min(this.maxConcurrency, batches.length) }
7596
+ );
7597
+ const mergedConfigs = this.mergeResults(configs, results);
7598
+ const loadedCount = mergedConfigs.filter((c) => c.file_data).length;
7599
+ logger.debug("[ThemeFileStorage] Loading files end", {
7600
+ requested: configs.length,
7601
+ loaded: loadedCount,
7602
+ missing: configs.length - loadedCount,
7603
+ batches: batches.length,
7604
+ trace
7605
+ });
7606
+ return mergedConfigs;
7607
+ }
7608
+ /**
7609
+ * Validate file sizes and categorize by threshold
7610
+ */
7611
+ validateFiles(configs) {
7612
+ const valid = [];
7613
+ const warnings = [];
7614
+ for (const config of configs) {
7615
+ if (!config.file_data) {
7616
+ continue;
7617
+ }
7618
+ const size = config.file?.length || 0;
7619
+ if (size >= 25 * 1024 * 1024) {
7620
+ warnings.push({
7621
+ hash: config.hash,
7622
+ filePath: config.file_path,
7623
+ size,
7624
+ reason: "exceeded_25mb",
7625
+ action: "rejected"
7626
+ });
7627
+ } else if (size >= 5 * 1024 * 1024) {
7628
+ warnings.push({
7629
+ hash: config.hash,
7630
+ filePath: config.file_path,
7631
+ size,
7632
+ reason: "rejected_5mb",
7633
+ action: "rejected"
7634
+ });
7635
+ } else {
7636
+ if (size >= 1024 * 1024) {
7637
+ warnings.push({
7638
+ hash: config.hash,
7639
+ filePath: config.file_path,
7640
+ size,
7641
+ reason: "warning_1mb",
7642
+ action: "stored"
7643
+ });
7644
+ }
7645
+ valid.push(config);
7646
+ }
7647
+ }
7648
+ return { valid, warnings };
7649
+ }
7650
+ /**
7651
+ * Check which files already exist in KV storage
7652
+ * Uses batch planning to avoid 413 errors when checking existence
7653
+ */
7654
+ async checkExistence(configs) {
7655
+ if (configs.length === 0) {
7656
+ return /* @__PURE__ */ new Set();
7657
+ }
7658
+ const existing = /* @__PURE__ */ new Set();
7659
+ const batches = this.planGetBatches(configs);
7660
+ const results = await Promise3.map(
7661
+ batches,
7662
+ (batch) => this.kv.get(batch.keys),
7663
+ { concurrency: this.maxConcurrency }
7664
+ );
7665
+ for (const batchResult of results) {
7666
+ for (const [key, value] of batchResult.entries()) {
7667
+ if (value !== null) {
7668
+ const hash = this.extractHashFromKey(key);
7669
+ existing.add(hash);
7670
+ }
7671
+ }
7672
+ }
7673
+ return existing;
7674
+ }
7675
+ async putFiles(configs) {
7676
+ const result = {
7677
+ written: 0,
7678
+ skipped: 0,
7679
+ skippedExisting: 0,
7680
+ warnings: []
7681
+ };
7682
+ if (configs.length === 0) {
7683
+ return result;
7684
+ }
7685
+ const trace = createTraceId();
7686
+ logger.debug("[ThemeFileStorage] Put files start", {
7687
+ totalConfigs: configs.length,
7688
+ trace
7689
+ });
7690
+ const { valid, warnings } = this.validateFiles(configs);
7691
+ result.warnings = warnings;
7692
+ if (warnings.length > 0) {
7693
+ const rejectedCount = warnings.filter(
7694
+ (w) => w.action === "rejected"
7695
+ ).length;
7696
+ const warnedCount = warnings.filter((w) => w.action === "stored").length;
7697
+ logger.warn("[ThemeFileStorage] File size validation issues", {
7698
+ totalWarnings: warnings.length,
7699
+ rejected: rejectedCount,
7700
+ warned: warnedCount,
7701
+ trace
7702
+ });
7703
+ warnings.filter((w) => w.action === "rejected").forEach((w) => {
7704
+ logger.error("[ThemeFileStorage] File rejected due to size", {
7705
+ filePath: w.filePath,
7706
+ size: w.size,
7707
+ reason: w.reason,
7708
+ trace
7709
+ });
7710
+ });
7711
+ }
7712
+ const rejected = warnings.filter((w) => w.action === "rejected").length;
7713
+ result.skipped = rejected + (configs.length - configs.filter((c) => c.file_data).length);
7714
+ logger.debug("[ThemeFileStorage] Checking existence", {
7715
+ validFiles: valid.length,
7716
+ trace
7717
+ });
7718
+ const existing = await this.checkExistence(valid);
7719
+ result.skippedExisting = existing.size;
7720
+ const toWrite = valid.filter((config) => !existing.has(config.hash));
7721
+ if (toWrite.length > 0) {
7722
+ logger.debug("[ThemeFileStorage] Writing new files", {
7723
+ toWrite: toWrite.length,
7724
+ skippedExisting: existing.size,
7725
+ trace
7726
+ });
7727
+ await Promise3.map(
7728
+ toWrite,
7729
+ async (config) => {
7730
+ const key = this.buildKey(config.hash);
7731
+ const metadata = config.file?.content_type ? { content_type: config.file.content_type } : void 0;
7732
+ await this.kv.put(key, config.file_data, metadata);
7733
+ result.written++;
7734
+ },
7735
+ { concurrency: this.maxConcurrency }
7736
+ );
7737
+ }
7738
+ logger.info("[ThemeFileStorage] Put files complete", {
7739
+ written: result.written,
7740
+ skipped: result.skipped,
7741
+ skippedExisting: result.skippedExisting,
7742
+ warnings: result.warnings.length,
7743
+ trace
7369
7744
  });
7745
+ return result;
7746
+ }
7747
+ };
7748
+
7749
+ // src/cache/constants.ts
7750
+ var SECOND = 1e3;
7751
+ var MINUTE = 60 * SECOND;
7752
+ var HOUR = 60 * MINUTE;
7753
+ var DAY = 24 * HOUR;
7754
+ var YEAR = 365 * DAY;
7755
+ var MAX_TTL = YEAR;
7756
+ var SHORT_TTL = 5 * SECOND;
7757
+
7758
+ // src/cache/worker-cache-proxy.ts
7759
+ var CACHE_NAME = "swell-cache-v1";
7760
+ var CACHE_KEY_ORIGIN = "https://cache.swell.store";
7761
+ var WorkerCacheProxy = class {
7762
+ swell;
7763
+ constructor(swell) {
7764
+ this.swell = swell;
7765
+ }
7766
+ /**
7767
+ * Reads a JSON value from Worker Cache using a key built from path+query.
7768
+ * Returns null on miss or if running outside of a Worker environment.
7769
+ */
7770
+ async get(path, query, opts) {
7771
+ if (typeof caches === "undefined") {
7772
+ return null;
7773
+ }
7774
+ const { keyUrl } = await this.buildKeyUrl(path, query, opts?.version);
7775
+ try {
7776
+ const cache = await caches.open(CACHE_NAME);
7777
+ const match = await cache.match(keyUrl);
7778
+ if (!match) return null;
7779
+ const data = await match.json();
7780
+ return data;
7781
+ } catch {
7782
+ return null;
7783
+ }
7784
+ }
7785
+ /**
7786
+ * Stores a JSON value in Worker Cache under key built from path+query.
7787
+ * No-ops outside of a Worker environment.
7788
+ */
7789
+ async put(path, query, value, opts) {
7790
+ if (typeof caches === "undefined") {
7791
+ return;
7792
+ }
7793
+ const { keyUrl, hasVersion } = await this.buildKeyUrl(
7794
+ path,
7795
+ query,
7796
+ opts?.version
7797
+ );
7798
+ const ttlMs = hasVersion ? MAX_TTL : SHORT_TTL;
7799
+ try {
7800
+ const cache = await caches.open(CACHE_NAME);
7801
+ const response = new Response(JSON.stringify(value), {
7802
+ headers: {
7803
+ "Content-Type": "application/json",
7804
+ "Cache-Control": `public, max-age=${Math.floor(ttlMs / 1e3)}`
7805
+ }
7806
+ });
7807
+ await cache.put(keyUrl, response);
7808
+ } catch {
7809
+ }
7810
+ }
7811
+ /**
7812
+ * Builds a deterministic key URL for Worker Cache from the backend API URL
7813
+ * composed using path and query. Includes tenant and auth isolation and an
7814
+ * optional version segment.
7815
+ */
7816
+ async buildKeyUrl(path, query, explicitVersion) {
7817
+ const apiHost = this.swell.backend?.apiHost;
7818
+ const endpointPath = String(path).startsWith("/") ? String(path).substring(1) : String(path);
7819
+ let queryString = "";
7820
+ if (query && this.swell.backend) {
7821
+ queryString = this.swell.backend.stringifyQuery(query);
7822
+ }
7823
+ const fullUrl = `${apiHost}/${endpointPath}${queryString ? `?${queryString}` : ""}`;
7824
+ const instanceId = this.swell.instanceId || "";
7825
+ const authKey = String(this.swell.swellHeaders?.["swell-auth-key"] || "");
7826
+ const tenantHash = await this.sha256Hex(`${instanceId}|${authKey}`);
7827
+ const version = explicitVersion !== void 0 ? explicitVersion : this.swell.swellHeaders?.["theme-version-hash"] || null;
7828
+ const hasVersion = Boolean(version);
7829
+ const versionHash = hasVersion ? await this.sha256Hex(String(version)) : null;
7830
+ const urlHash = await this.sha256Hex(fullUrl);
7831
+ const keyUrl = versionHash ? `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${versionHash}/${urlHash}` : `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${urlHash}`;
7832
+ return { keyUrl, hasVersion };
7833
+ }
7834
+ /**
7835
+ * SHA-256 digest with hex encoding. Requires Worker crypto; callers
7836
+ * should avoid invoking this outside of Worker code paths.
7837
+ */
7838
+ async sha256Hex(input) {
7839
+ if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
7840
+ const encoder = new TextEncoder();
7841
+ const digest = await crypto.subtle.digest(
7842
+ "SHA-256",
7843
+ encoder.encode(input)
7844
+ );
7845
+ const bytes = new Uint8Array(digest);
7846
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
7847
+ }
7848
+ return md5(input);
7849
+ }
7850
+ };
7851
+
7852
+ // src/resources/addresses.ts
7853
+ var SwellAddresses = class extends SwellStorefrontCollection {
7854
+ constructor(swell, query) {
7855
+ const { page, limit: limit2 } = swell.queryParams;
7856
+ super(swell, "accounts:addresses", { page, limit: limit2, ...query }, function() {
7857
+ return this._swell.storefront.account.listAddresses(
7858
+ this._query
7859
+ );
7860
+ });
7861
+ }
7862
+ };
7863
+
7864
+ // src/resources/orders.ts
7865
+ var SwellOrders = class extends SwellStorefrontCollection {
7866
+ constructor(swell, query) {
7867
+ const { page, limit: limit2 } = swell.queryParams;
7868
+ super(swell, "accounts:orders", { page, limit: limit2, ...query }, function() {
7869
+ return this._swell.storefront.account.listOrders(this._query);
7870
+ });
7871
+ }
7872
+ };
7873
+
7874
+ // src/resources/subscriptions.ts
7875
+ var SwellSubscriptions = class extends SwellStorefrontCollection {
7876
+ constructor(swell, query) {
7877
+ const { page, limit: limit2 } = swell.queryParams;
7878
+ super(
7879
+ swell,
7880
+ "accounts:subscriptions",
7881
+ { page, limit: limit2, ...query },
7882
+ function() {
7883
+ return this._swell.storefront.subscriptions.list(
7884
+ this._query
7885
+ );
7886
+ }
7887
+ );
7370
7888
  }
7371
7889
  };
7372
7890
 
7373
7891
  // src/resources/account.ts
7374
7892
  var SwellAccount = class extends SwellStorefrontSingleton {
7375
- constructor(swell, getter) {
7376
- super(swell, "account", getter);
7893
+ constructor(swell) {
7894
+ super(swell, "account", async function() {
7895
+ const account = await this._defaultGetter().call(this);
7896
+ if (!account) {
7897
+ return null;
7898
+ }
7899
+ account.addresses = new SwellAddresses(
7900
+ this._swell
7901
+ );
7902
+ account.orders = new SwellOrders(
7903
+ this._swell
7904
+ );
7905
+ account.subscriptions = new SwellSubscriptions(
7906
+ this._swell
7907
+ );
7908
+ return account;
7909
+ });
7377
7910
  return this._getProxy();
7378
7911
  }
7379
7912
  };
7380
7913
 
7381
7914
  // src/resources/blog_category.ts
7382
7915
  var SwellBlogCategory = class extends SwellStorefrontRecord {
7383
- constructor(swell, id, query = {}, getter) {
7384
- super(swell, "content/blog-categories", id, query, getter);
7916
+ constructor(swell, id, query) {
7917
+ super(swell, "content/blog-categories", id, query, async function() {
7918
+ const category = await this._defaultGetter().call(this);
7919
+ if (!category) {
7920
+ return null;
7921
+ }
7922
+ category.blogs = new SwellStorefrontCollection(
7923
+ this._swell,
7924
+ "content/blogs",
7925
+ {
7926
+ category_id: category.id,
7927
+ expand: "author"
7928
+ }
7929
+ );
7930
+ return category;
7931
+ });
7385
7932
  return this._getProxy();
7386
7933
  }
7387
7934
  };
7388
7935
 
7389
7936
  // src/resources/blog.ts
7390
7937
  var SwellBlog = class extends SwellStorefrontRecord {
7391
- constructor(swell, id, query = {}, getter) {
7392
- super(swell, "content/blogs", id, query, getter);
7938
+ constructor(swell, blogId, categoryId, query) {
7939
+ super(swell, "content/blogs", blogId, query, async function() {
7940
+ this._query = { ...this._query, expand: "author" };
7941
+ const blog = await this._defaultGetter().call(this);
7942
+ if (!blog) {
7943
+ return null;
7944
+ }
7945
+ if (categoryId) {
7946
+ blog.category = new SwellStorefrontRecord(
7947
+ this._swell,
7948
+ "content/blog-categories",
7949
+ categoryId
7950
+ );
7951
+ }
7952
+ return blog;
7953
+ });
7393
7954
  return this._getProxy();
7394
7955
  }
7395
7956
  };
7396
7957
 
7397
7958
  // src/resources/cart.ts
7398
7959
  var SwellCart = class extends SwellStorefrontSingleton {
7399
- constructor(swell, getter) {
7400
- super(swell, "cart", getter);
7401
- return this._getProxy();
7402
- }
7403
- };
7404
-
7405
- // src/resources/category.ts
7406
- var SwellCategory = class extends SwellStorefrontRecord {
7407
- constructor(swell, id, query = {}, getter) {
7408
- super(swell, "categories", id, query, getter);
7409
- return this._getProxy();
7410
- }
7411
- };
7412
-
7413
- // src/resources/order.ts
7414
- var SwellOrder = class extends SwellStorefrontRecord {
7415
- constructor(swell, id, query = {}, getter) {
7416
- super(swell, "orders", id, query, getter);
7960
+ constructor(swell) {
7961
+ super(swell, "cart");
7417
7962
  return this._getProxy();
7418
7963
  }
7419
7964
  };
7420
7965
 
7421
- // src/resources/page.ts
7422
- var SwellPage = class extends SwellStorefrontRecord {
7423
- constructor(swell, id, query = {}, getter) {
7424
- super(swell, "content/pages", id, query, getter);
7425
- return this._getProxy();
7966
+ // src/resources/categories.ts
7967
+ var SwellCategories = class extends SwellStorefrontCollection {
7968
+ constructor(swell, query) {
7969
+ super(
7970
+ swell,
7971
+ "categories",
7972
+ {
7973
+ limit: 100,
7974
+ top_id: null,
7975
+ ...query
7976
+ },
7977
+ async function() {
7978
+ const categories = await this._defaultGetter().call(this);
7979
+ if (!categories) {
7980
+ return null;
7981
+ }
7982
+ for (const category of categories.results) {
7983
+ category.products = new SwellStorefrontCollection(
7984
+ this._swell,
7985
+ "products",
7986
+ { category: category.id }
7987
+ );
7988
+ }
7989
+ return categories;
7990
+ }
7991
+ );
7426
7992
  }
7427
7993
  };
7428
7994
 
@@ -7573,15 +8139,152 @@ function getSelectedSubscriptionPurchaseOptionPlan(selectedPurchaseOptionType, s
7573
8139
  if (selectedPurchaseOptionType !== "subscription") {
7574
8140
  return null;
7575
8141
  }
7576
- const { purchase_option: purchaseOption } = queryParams;
7577
- let selectedPlan = null;
7578
- if (purchaseOption?.plan_id) {
7579
- selectedPlan = subscriptionPurchaseOption.plans.find(
7580
- (plan) => plan.id === purchaseOption.plan_id
7581
- );
8142
+ const { purchase_option: purchaseOption } = queryParams;
8143
+ let selectedPlan = null;
8144
+ if (purchaseOption?.plan_id) {
8145
+ selectedPlan = subscriptionPurchaseOption.plans.find(
8146
+ (plan) => plan.id === purchaseOption.plan_id
8147
+ );
8148
+ }
8149
+ return selectedPlan || subscriptionPurchaseOption.plans[0];
8150
+ }
8151
+ var SORT_OPTIONS = Object.freeze([
8152
+ { value: "", name: "Featured" },
8153
+ { value: "popularity", name: "Popularity", query: "popularity desc" },
8154
+ { value: "price_asc", name: "Price, low to high", query: "price asc" },
8155
+ { value: "price_desc", name: "Price, high to low", query: "price desc" },
8156
+ { value: "date_asc", name: "Date, old to new", query: "date asc" },
8157
+ { value: "date_desc", name: "Date, new to old", query: "date desc" },
8158
+ { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
8159
+ { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
8160
+ ]);
8161
+ async function getProductFilters(swell, productQuery) {
8162
+ const sortBy = swell.queryParams.sort || "";
8163
+ const filterQuery = productQueryWithFilters(swell, productQuery);
8164
+ return {
8165
+ filter_options: await getProductFiltersByQuery(swell, filterQuery),
8166
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.value,
8167
+ sort_options: [...SORT_OPTIONS]
8168
+ };
8169
+ }
8170
+ async function getProductFiltersByQuery(swell, query = {}) {
8171
+ const filters2 = await swell.get("/products/:filters", {
8172
+ ...query,
8173
+ sort: void 0
8174
+ }) || [];
8175
+ if (!Array.isArray(filters2)) {
8176
+ throw new Error("Product filters must be an array");
8177
+ }
8178
+ for (const filter of filters2) {
8179
+ filter.param_name = `filter_${filter.id}`;
8180
+ if (Array.isArray(filter.options)) {
8181
+ filter.active_options = [];
8182
+ filter.inactive_options = [];
8183
+ for (const option of filter.options) {
8184
+ const queryValue = swell.queryParams[filter.param_name];
8185
+ option.active = Array.isArray(queryValue) ? queryValue.includes(option.value) : queryValue === option.value;
8186
+ const list = option.active ? filter.active_options : filter.inactive_options;
8187
+ list.push(option);
8188
+ }
8189
+ }
8190
+ }
8191
+ return filters2;
8192
+ }
8193
+ function productQueryWithFilters(swell, query) {
8194
+ const filters2 = Object.keys(swell.queryParams).reduce(
8195
+ (acc, key) => {
8196
+ if (key.startsWith("filter_")) {
8197
+ const qkey = key.replace("filter_", "");
8198
+ const value = swell.queryParams[key];
8199
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && (value.gte !== void 0 || value.lte !== void 0)) {
8200
+ acc[qkey] = [value.gte || 0, value.lte || void 0];
8201
+ } else {
8202
+ acc[qkey] = value;
8203
+ }
8204
+ }
8205
+ return acc;
8206
+ },
8207
+ {}
8208
+ );
8209
+ const sortBy = swell.queryParams.sort || "";
8210
+ return {
8211
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
8212
+ $filters: filters2,
8213
+ ...query
8214
+ };
8215
+ }
8216
+
8217
+ // src/resources/category.ts
8218
+ var SwellCategory = class extends SwellStorefrontRecord {
8219
+ constructor(swell, id, query) {
8220
+ super(swell, "categories", id, query, async function() {
8221
+ let category = await this._defaultGetter().call(this);
8222
+ if (!category && this._id === "all") {
8223
+ category = {
8224
+ name: "Products",
8225
+ id: "all",
8226
+ slug: "all",
8227
+ filter_options: [],
8228
+ sort_options: []
8229
+ };
8230
+ }
8231
+ if (!category) {
8232
+ return null;
8233
+ }
8234
+ const productFilters = await getProductFilters(
8235
+ this._swell,
8236
+ category.id !== "all" ? { category: category.id, $variants: true } : { $variants: true }
8237
+ );
8238
+ Object.assign(category, productFilters);
8239
+ return category;
8240
+ });
8241
+ return this._getProxy();
8242
+ }
8243
+ };
8244
+
8245
+ // src/resources/order.ts
8246
+ var SwellOrder = class extends SwellStorefrontRecord {
8247
+ constructor(swell, id, query) {
8248
+ super(swell, "accounts:orders", id, query, function() {
8249
+ return this._swell.storefront.account.getOrder(this._id);
8250
+ });
8251
+ return this._getProxy();
8252
+ }
8253
+ };
8254
+
8255
+ // src/resources/page.ts
8256
+ var SwellPage = class extends SwellStorefrontRecord {
8257
+ constructor(swell, id, query) {
8258
+ super(swell, "content/pages", id, query);
8259
+ return this._getProxy();
8260
+ }
8261
+ };
8262
+
8263
+ // src/resources/predictive_search.ts
8264
+ var SwellPredictiveSearch = class extends StorefrontResource {
8265
+ constructor(swell, query) {
8266
+ super(async function() {
8267
+ const performed = String(query || "").length > 0;
8268
+ let products;
8269
+ if (performed) {
8270
+ products = new SwellStorefrontCollection(
8271
+ swell,
8272
+ "products",
8273
+ {
8274
+ search: query,
8275
+ limit: 10
8276
+ }
8277
+ );
8278
+ await products.resolve();
8279
+ }
8280
+ return {
8281
+ query,
8282
+ performed,
8283
+ products
8284
+ };
8285
+ });
7582
8286
  }
7583
- return selectedPlan || subscriptionPurchaseOption.plans[0];
7584
- }
8287
+ };
7585
8288
 
7586
8289
  // src/resources/variant.ts
7587
8290
  function transformSwellVariant(params, product, variant) {
@@ -7601,27 +8304,30 @@ function transformSwellVariant(params, product, variant) {
7601
8304
  )
7602
8305
  };
7603
8306
  }
8307
+ var SwellVariant = class extends SwellStorefrontRecord {
8308
+ product;
8309
+ constructor(swell, product, id, query) {
8310
+ super(swell, "products:variants", id, query, async function() {
8311
+ const variant = await this._swell.get(
8312
+ "/products:variants/{id}",
8313
+ { id: this._id }
8314
+ );
8315
+ return variant ?? null;
8316
+ });
8317
+ this.product = product;
8318
+ }
8319
+ };
7604
8320
 
7605
8321
  // src/resources/product.ts
7606
- var SORT_OPTIONS = [
7607
- { value: "", name: "Featured" },
7608
- { value: "popularity", name: "Popularity", query: "popularity desc" },
7609
- { value: "price_asc", name: "Price, low to high", query: "price asc" },
7610
- { value: "price_desc", name: "Price, high to low", query: "price desc" },
7611
- { value: "date_asc", name: "Date, old to new", query: "date asc" },
7612
- { value: "date_desc", name: "Date, new to old", query: "date desc" },
7613
- { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
7614
- { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
7615
- ];
7616
8322
  function transformSwellProduct(params, product) {
7617
8323
  if (!product) {
7618
- return product;
8324
+ return null;
7619
8325
  }
7620
8326
  const newProduct = {
7621
8327
  ...product,
7622
8328
  // add swell properties there
7623
8329
  selected_option_values: getSelectedVariantOptionValues(product, params),
7624
- purchase_options: getPurchaseOptions(product, params)
8330
+ purchase_options: getPurchaseOptions(product, params) ?? void 0
7625
8331
  };
7626
8332
  if (Array.isArray(newProduct.variants?.results)) {
7627
8333
  newProduct.variants = {
@@ -7634,43 +8340,49 @@ function transformSwellProduct(params, product) {
7634
8340
  return newProduct;
7635
8341
  }
7636
8342
  var SwellProduct = class extends SwellStorefrontRecord {
7637
- _params;
7638
- constructor(swell, id, query = {}, getter) {
7639
- super(swell, "products", id, query, getter);
7640
- this._params = swell.queryParams;
8343
+ constructor(swell, id, query) {
8344
+ const params = swell.queryParams;
8345
+ super(swell, "products", id, query, async function() {
8346
+ const result = await this._defaultGetter().call(this);
8347
+ return transformSwellProduct(params, result);
8348
+ });
7641
8349
  return this._getProxy();
7642
8350
  }
7643
- // add swell properties to the resolved object
7644
- _transformResult(result) {
7645
- const res = transformSwellProduct(
7646
- this._params,
7647
- result
7648
- );
7649
- return res;
8351
+ };
8352
+
8353
+ // src/resources/product_recommendations.ts
8354
+ var SwellProductRecommendations = class extends SwellProduct {
8355
+ constructor(swell, id, query) {
8356
+ super(swell, id, { ...query, $recommendations: true });
8357
+ }
8358
+ };
8359
+
8360
+ // src/resources/search.ts
8361
+ var SwellSearch = class extends StorefrontResource {
8362
+ constructor(swell, query) {
8363
+ super(async () => {
8364
+ const performed = String(query || "").length > 0;
8365
+ const productFilters = await getProductFilters(
8366
+ swell,
8367
+ performed ? { search: query } : void 0
8368
+ );
8369
+ return {
8370
+ query,
8371
+ performed,
8372
+ ...productFilters
8373
+ };
8374
+ });
8375
+ }
8376
+ };
8377
+
8378
+ // src/resources/subscription.ts
8379
+ var SwellSubscription = class extends SwellStorefrontRecord {
8380
+ constructor(swell, id, query) {
8381
+ super(swell, "accounts:subscriptions", id, query, function() {
8382
+ return this._swell.storefront.subscriptions.get(this._id, this._query);
8383
+ });
7650
8384
  }
7651
8385
  };
7652
- function productQueryWithFilters(swell, query = {}) {
7653
- const sortBy = swell.queryParams.sort || "";
7654
- const filters2 = Object.entries(swell.queryParams).reduce(
7655
- (acc, [key, value]) => {
7656
- if (key.startsWith("filter_")) {
7657
- const qkey = key.replace("filter_", "");
7658
- if (value?.gte !== void 0 || value?.lte !== void 0) {
7659
- acc[qkey] = [value.gte || 0, value.lte || void 0];
7660
- } else {
7661
- acc[qkey] = value;
7662
- }
7663
- }
7664
- return acc;
7665
- },
7666
- {}
7667
- );
7668
- return {
7669
- sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
7670
- $filters: filters2,
7671
- ...query
7672
- };
7673
- }
7674
8386
 
7675
8387
  // src/api.ts
7676
8388
  var DEFAULT_API_HOST = "https://api.schema.io";
@@ -7731,7 +8443,7 @@ var Swell = class _Swell {
7731
8443
  this.workerEnv = workerEnv;
7732
8444
  this.resourceLoadingIndicator = params.resourceLoadingIndicator;
7733
8445
  logger.info(
7734
- `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}`
8446
+ `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}, flavor: ${getKVFlavor(this.workerEnv)}`
7735
8447
  );
7736
8448
  if (serverHeaders) {
7737
8449
  const { headers: headers2, swellHeaders: swellHeaders2 } = _Swell.formatHeaders(serverHeaders);
@@ -15241,11 +15953,9 @@ function getProducts(instance, object, mapper) {
15241
15953
  return this._defaultGetter().call(this);
15242
15954
  }
15243
15955
  );
15244
- return products._cloneWithCompatibilityResult(
15245
- (products2) => {
15246
- return { ...products2, results: products2.results.map(mapper) };
15247
- }
15248
- );
15956
+ return products._cloneWithCompatibilityResult((products2) => {
15957
+ return { ...products2, results: products2.results.map(mapper) };
15958
+ });
15249
15959
  });
15250
15960
  }
15251
15961
  function makeProductsCollectionResolve(instance, object, mapper) {
@@ -15262,19 +15972,24 @@ function makeProductsCollectionResolve(instance, object, mapper) {
15262
15972
 
15263
15973
  // src/compatibility/shopify-objects/collections.ts
15264
15974
  function ShopifyCollections(instance, categories) {
15265
- return new SwellStorefrontCollection(instance.swell, categories._collection, categories._query, async () => {
15266
- const results = (await categories.results)?.map((category) => {
15267
- return ShopifyCollection(instance, category);
15268
- });
15269
- return {
15270
- page: categories.page ?? 1,
15271
- count: categories.count ?? 0,
15272
- results: results ?? [],
15273
- page_count: categories.page_count ?? 0,
15274
- limit: categories.limit,
15275
- pages: categories.pages ?? {}
15276
- };
15277
- });
15975
+ return new SwellStorefrontCollection(
15976
+ instance.swell,
15977
+ categories._collection,
15978
+ categories._query,
15979
+ async () => {
15980
+ const results = (await categories.results)?.map((category) => {
15981
+ return ShopifyCollection(instance, category);
15982
+ });
15983
+ return {
15984
+ page: categories.page ?? 1,
15985
+ count: categories.count ?? 0,
15986
+ results: results ?? [],
15987
+ page_count: categories.page_count ?? 0,
15988
+ limit: categories.limit,
15989
+ pages: categories.pages ?? {}
15990
+ };
15991
+ }
15992
+ );
15278
15993
  }
15279
15994
 
15280
15995
  // src/compatibility/shopify-objects/address.ts
@@ -16461,13 +17176,12 @@ var ImagesDrop = class extends import_liquidjs7.Drop {
16461
17176
  var SwellImage = class extends StorefrontResource {
16462
17177
  constructor(swell, name) {
16463
17178
  super(async () => {
16464
- const files = await swell.get("/:files", {
17179
+ const file = await swell.get("/:files/:last", {
16465
17180
  private: { $ne: true },
16466
17181
  content_type: { $regex: "^image/" },
16467
17182
  filename: name
16468
17183
  });
16469
- const file = files?.results[0] ?? null;
16470
- if (file === null) {
17184
+ if (!file) {
16471
17185
  return null;
16472
17186
  }
16473
17187
  return { file };
@@ -19382,296 +20096,255 @@ function getLiquidFS(getThemeConfig, extName) {
19382
20096
  }
19383
20097
 
19384
20098
  // src/theme/theme-loader.ts
19385
- var import_bluebird = __toESM(require("bluebird"), 1);
19386
- var { Promise: Promise2 } = import_bluebird.default;
19387
20099
  var MAX_INDIVIDUAL_CONFIGS_TO_FETCH = 50;
19388
- var ThemeLoader = class _ThemeLoader {
19389
- static cache = null;
20100
+ var ThemeLoader = class {
19390
20101
  swell;
19391
- manifest;
19392
20102
  configs;
19393
- configPaths;
19394
20103
  constructor(swell) {
19395
20104
  this.swell = swell;
19396
- this.manifest = null;
19397
20105
  this.configs = /* @__PURE__ */ new Map();
19398
- this.configPaths = [];
19399
20106
  }
20107
+ /**
20108
+ * Initialize the theme loader with all configurations.
20109
+ * Either uses provided configs (editor mode) or loads from storage.
20110
+ */
19400
20111
  async init(themeConfigs) {
19401
20112
  if (themeConfigs) {
19402
20113
  this.setConfigs(themeConfigs);
19403
20114
  return;
19404
20115
  }
19405
20116
  if (!this.getThemeId()) {
20117
+ logger.debug("[ThemeLoader] No theme ID, skipping init");
19406
20118
  return;
19407
20119
  }
19408
- await this.fetchManifest();
19409
- if (this.manifest === null) {
19410
- console.log("ThemeLoader.init - version manifest not found");
19411
- await this.loadTheme();
19412
- }
20120
+ await this.loadAllConfigs();
20121
+ logger.info("[ThemeLoader] Initialization complete", {
20122
+ configCount: this.configs.size,
20123
+ themeId: this.getThemeId()
20124
+ });
19413
20125
  }
19414
20126
  /**
19415
- * Loads theme configs for this version.
20127
+ * Get a single config by file path (synchronous).
20128
+ * Returns null if config not found.
19416
20129
  */
19417
- async loadTheme() {
19418
- const { swellHeaders } = this.swell;
19419
- console.log("ThemeLoader.loadTheme", swellHeaders["theme-version-hash"]);
19420
- if (swellHeaders["theme-version-hash"]) {
19421
- const configs = await this.loadThemeFromManifest();
19422
- if (configs) {
19423
- return configs;
19424
- }
19425
- }
19426
- return this.loadThemeAllConfigs();
20130
+ getConfig(filePath) {
20131
+ return this.configs.get(filePath) ?? null;
19427
20132
  }
19428
20133
  /**
19429
- * Returns the cache instance for this theme loader.
20134
+ * Get all loaded configs.
20135
+ * Used by theme getter to expose configs to editor/tests.
19430
20136
  */
19431
- getCache() {
19432
- if (_ThemeLoader.cache === null) {
19433
- _ThemeLoader.cache = new ThemeCache({
19434
- kvStore: this.swell.workerEnv?.THEME
19435
- });
19436
- }
19437
- return _ThemeLoader.cache;
20137
+ getConfigs() {
20138
+ return this.configs;
19438
20139
  }
19439
20140
  /**
19440
- * Load theme configs from internal data, typically in the editor.
20141
+ * Get multiple configs by path pattern (synchronous).
20142
+ * Filters configs by prefix and optional suffix.
19441
20143
  */
19442
- setConfigs(themeConfigs) {
19443
- this.configs = new Map(themeConfigs);
19444
- this.manifest = /* @__PURE__ */ new Map();
19445
- for (const { file_path, hash } of this.configs.values()) {
19446
- this.manifest.set(file_path, hash);
20144
+ getConfigsByPath(pathPrefix, pathSuffix) {
20145
+ const results = [];
20146
+ for (const [path, config] of this.configs) {
20147
+ if (path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))) {
20148
+ results.push(config);
20149
+ }
19447
20150
  }
19448
- this.configPaths = Array.from(this.configs.keys());
20151
+ return results;
19449
20152
  }
19450
20153
  /**
19451
- * Preloads a theme version and configs. This is used to optimize initial theme load.
20154
+ * Load theme configs from internal data, typically in the editor.
20155
+ * Used when configs are provided externally (e.g., from editor).
19452
20156
  */
19453
- async preloadTheme(payload) {
19454
- const { version, configs } = payload;
19455
- console.log(
19456
- `ThemeLoader.preloadTheme${version?.hash ? ` - manifest: ${version.hash}` : ""}${configs?.length ? ` - configs: ${configs.length}` : ""}`
19457
- );
19458
- const promises = [];
19459
- if (version) {
19460
- promises.push(this.cacheManifest(version));
19461
- }
19462
- if (configs) {
19463
- const themeId = this.getThemeId();
19464
- promises.push(
19465
- Promise2.map(
19466
- configs,
19467
- async (config) => {
19468
- const promises2 = [
19469
- this.cacheThemeConfig(config)
19470
- ];
19471
- if (themeId && config.file?.url) {
19472
- promises2.push(
19473
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19474
- );
19475
- }
19476
- await Promise2.all(promises2);
19477
- },
19478
- { concurrency: 10 }
19479
- )
19480
- );
19481
- }
19482
- await Promise2.all(promises);
20157
+ setConfigs(themeConfigs) {
20158
+ this.configs = new Map(themeConfigs);
19483
20159
  }
19484
20160
  /**
19485
- * Fetches a theme config by file path.
20161
+ * Updates KV with file_data for provided theme configs (warmup path).
20162
+ * Uses the new ThemeFileStorage abstraction for optimized operations.
19486
20163
  */
19487
- async fetchThemeConfig(filePath) {
19488
- const config = this.configs.get(filePath);
19489
- if (config !== void 0) {
19490
- return config;
19491
- }
19492
- const hash = this.manifest?.get(filePath);
19493
- if (!hash) {
19494
- return null;
20164
+ async updateThemeCache(payload) {
20165
+ const configs = payload?.configs || [];
20166
+ if (configs.length === 0) {
20167
+ logger.debug("[ThemeLoader] No configs to cache");
20168
+ return {
20169
+ written: 0,
20170
+ skipped: 0,
20171
+ skippedExisting: 0,
20172
+ warnings: []
20173
+ };
19495
20174
  }
19496
- const cache = this.getCache();
19497
- const themeId = this.getThemeId();
19498
- const [themeConfig, fileUrl] = await Promise2.all([
19499
- cache.get(`config:${hash}`),
19500
- themeId ? cache.get(`file:${themeId}:${hash}`) : void 0
19501
- ]);
19502
- if (themeConfig) {
19503
- let config2 = themeConfig;
19504
- if (fileUrl && themeConfig.file?.url) {
19505
- config2 = {
19506
- ...themeConfig,
19507
- file: { ...themeConfig.file, url: fileUrl }
19508
- };
19509
- }
19510
- this.configs.set(filePath, config2);
19511
- return config2;
20175
+ const flavor = getKVFlavor(this.swell.workerEnv);
20176
+ const trace = createTraceId();
20177
+ logger.info("[ThemeLoader] Starting theme cache update", {
20178
+ totalConfigs: configs.length,
20179
+ flavor,
20180
+ trace
20181
+ });
20182
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20183
+ const result = await storage.putFiles(configs);
20184
+ if (result.warnings.length > 0) {
20185
+ logger.warn("[ThemeLoader] Theme cache updated with warnings", {
20186
+ total: configs.length,
20187
+ written: result.written,
20188
+ skipped: result.skipped,
20189
+ skippedExisting: result.skippedExisting,
20190
+ warnings: result.warnings.length,
20191
+ trace
20192
+ });
20193
+ } else {
20194
+ logger.info("[ThemeLoader] Theme cache updated successfully", {
20195
+ total: configs.length,
20196
+ written: result.written,
20197
+ skipped: result.skipped,
20198
+ skippedExisting: result.skippedExisting,
20199
+ trace
20200
+ });
19512
20201
  }
19513
- return this.fetchThemeConfigsFromSourceByPath(filePath, hash);
19514
- }
19515
- async fetchThemeConfigsByPath(pathPrefix, pathSuffix) {
19516
- const paths = this.configPaths.filter(
19517
- (path) => path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))
19518
- );
19519
- const configs = await Promise2.map(
19520
- paths,
19521
- (path) => this.fetchThemeConfig(path),
19522
- { concurrency: 10 }
19523
- );
19524
- return configs.filter((config) => config !== null);
20202
+ return result;
19525
20203
  }
19526
20204
  /**
19527
- * Load all theme configs.
20205
+ * Main loading logic - loads all configs at once.
20206
+ * 1. Fetches lightweight metadata (cached when possible)
20207
+ * 2. Batch hydrates file_data from KV
20208
+ * 3. Fetches missing file_data from API if needed
19528
20209
  */
19529
- async loadThemeAllConfigs() {
19530
- const { swellHeaders } = this.swell;
19531
- const configVersion = String(swellHeaders["theme-config-version"]);
19532
- if (!configVersion) {
19533
- throw new Error("Theme version is required");
20210
+ async loadAllConfigs() {
20211
+ const configMetadata = await this.fetchConfigMetadata();
20212
+ if (configMetadata.length === 0) {
20213
+ logger.warn("[ThemeLoader] No configs found");
20214
+ return;
19534
20215
  }
19535
- const configs = await this.getCache().fetch(
19536
- `configs-all:${this.swell.instanceId}:v@${configVersion}2`,
19537
- () => this.fetchThemeConfigsFromSource()
19538
- );
19539
- return configs?.results ?? [];
20216
+ logger.debug("[ThemeLoader] Loading configs", {
20217
+ total: configMetadata.length
20218
+ });
20219
+ const flavor = getKVFlavor(this.swell.workerEnv);
20220
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20221
+ const kvHydrated = await storage.getFiles(configMetadata);
20222
+ const completeConfigs = await this.ensureConfigsHaveData(kvHydrated);
20223
+ for (const config of completeConfigs) {
20224
+ this.configs.set(config.file_path, config);
20225
+ }
20226
+ logger.info("[ThemeLoader] All configs loaded", {
20227
+ total: completeConfigs.length,
20228
+ withData: completeConfigs.filter((c) => c.file_data).length
20229
+ });
19540
20230
  }
19541
20231
  /**
19542
- * Load theme configs via manifest.
19543
- *
19544
- * This approach has the following optimizations:
19545
- * - cached manifests and configs can be shared by other clients
19546
- * - when fetching from source, only fetch the missing records
20232
+ * Fetch lightweight config metadata from API or cache.
20233
+ * Does NOT include file_data to minimize payload size.
19547
20234
  */
19548
- async loadThemeFromManifest() {
19549
- const manifest = await this.fetchManifest();
19550
- if (!manifest) {
19551
- return null;
19552
- }
19553
- const configHashesUnresolved = [];
19554
- const configsByHash = /* @__PURE__ */ new Map();
19555
- const themeId = this.getThemeId();
19556
- const cache = this.getCache();
19557
- await Promise2.map(
19558
- manifest.values(),
19559
- async (configHash) => {
19560
- const [themeConfig, fileUrl] = await Promise2.all([
19561
- cache.get(`config:${configHash}`),
19562
- themeId ? cache.get(`file:${themeId}:${configHash}`) : void 0
19563
- ]);
19564
- if (!themeConfig) {
19565
- configHashesUnresolved.push(configHash);
19566
- return;
19567
- }
19568
- let config = themeConfig;
19569
- if (fileUrl && themeConfig.file?.url) {
19570
- config = {
19571
- ...themeConfig,
19572
- file: { ...themeConfig.file, url: fileUrl }
19573
- };
20235
+ async fetchConfigMetadata() {
20236
+ const query = {
20237
+ ...this.themeVersionQueryFilter(),
20238
+ limit: 1e3,
20239
+ type: "theme",
20240
+ fields: "id, name, type, file, file_path, hash"
20241
+ // NO file_data
20242
+ };
20243
+ try {
20244
+ const cache = new WorkerCacheProxy(this.swell);
20245
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20246
+ const cached = await cache.get(
20247
+ "/:themes:configs",
20248
+ query,
20249
+ {
20250
+ version: versionHash || null
19574
20251
  }
19575
- configsByHash.set(config.hash, config);
19576
- this.configs.set(config.file_path, config);
19577
- },
19578
- { concurrency: 10 }
19579
- );
19580
- if (configHashesUnresolved.length > 0) {
19581
- const configs = await this.fetchThemeConfigsFromSource(
19582
- // If no configs were resolved, then fetch them all. otherwise fetch
19583
- // the specific subset of configs.
19584
- configsByHash.size === 0 ? void 0 : configHashesUnresolved
19585
20252
  );
19586
- const newConfigs = configs?.results ?? [];
19587
- for (const config of newConfigs) {
19588
- configsByHash.set(config.hash, config);
19589
- this.configs.set(config.file_path, config);
20253
+ if (cached) {
20254
+ logger.debug("[ThemeLoader] Config metadata cache hit");
20255
+ return cached;
19590
20256
  }
19591
- await Promise2.map(
19592
- newConfigs,
19593
- async (config) => {
19594
- const promises = [this.cacheThemeConfig(config)];
19595
- if (themeId && config.file?.url) {
19596
- promises.push(
19597
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19598
- );
19599
- }
19600
- await Promise2.all(promises);
19601
- },
19602
- { concurrency: 10 }
19603
- );
20257
+ } catch (err) {
20258
+ logger.warn("[ThemeLoader] Cache read failed, fetching from API", err);
19604
20259
  }
19605
- return Array.from(configsByHash.values());
19606
- }
19607
- /**
19608
- * Caches a theme version manifest by hash.
19609
- */
19610
- async cacheManifest(version) {
19611
- if (version?.hash) {
19612
- await this.getCache().set(`manifest:${version.hash}`, version.manifest);
20260
+ logger.debug("[ThemeLoader] Fetching config metadata from API");
20261
+ const response = await this.swell.get(
20262
+ "/:themes:configs",
20263
+ query
20264
+ );
20265
+ const configs = response?.results || [];
20266
+ try {
20267
+ const cache = new WorkerCacheProxy(this.swell);
20268
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20269
+ await cache.put("/:themes:configs", query, configs, {
20270
+ version: versionHash || null
20271
+ });
20272
+ } catch (err) {
20273
+ logger.warn("[ThemeLoader] Cache write failed", err);
19613
20274
  }
20275
+ return configs;
19614
20276
  }
19615
20277
  /**
19616
- * Caches a theme config by hash.
20278
+ * Helper to ensure all configs have file_data.
20279
+ * Fetches missing data from API and updates KV cache.
19617
20280
  */
19618
- async cacheThemeConfig(config) {
19619
- if (config?.hash) {
19620
- await this.getCache().set(`config:${config.hash}`, config);
20281
+ async ensureConfigsHaveData(configs) {
20282
+ const missingData = configs.filter((c) => !c.file_data);
20283
+ if (missingData.length === 0) {
20284
+ logger.debug("[ThemeLoader] All configs have file_data from KV");
20285
+ return configs;
19621
20286
  }
19622
- }
19623
- /**
19624
- * Caches a CDN file url by config hash.
19625
- */
19626
- async cacheThemeFileUrl(themeId, configHash, fileUrl) {
19627
- await this.getCache().set(`file:${themeId}:${configHash}`, fileUrl);
19628
- }
19629
- /**
19630
- * Fetches the manifest (set of config hashes) for a theme version.
19631
- */
19632
- async fetchManifest() {
19633
- const { swellHeaders } = this.swell;
19634
- const versionHash = swellHeaders["theme-version-hash"];
19635
- console.log("ThemeLoader.fetchManifest", versionHash);
19636
- let manifest = await this.getCache().get(
19637
- `manifest:${versionHash}`
20287
+ const trace = createTraceId();
20288
+ logger.info(
20289
+ `[ThemeLoader] Loading ${missingData.length} missing file_data from API`,
20290
+ {
20291
+ trace
20292
+ }
19638
20293
  );
19639
- if (!manifest) {
19640
- const themeVersion = await this.swell.get(
19641
- "/:themes:versions/:last",
19642
- {
19643
- ...this.themeVersionQueryFilter(),
19644
- fields: "hash, manifest"
19645
- }
19646
- );
19647
- if (themeVersion) {
19648
- await this.cacheManifest(themeVersion);
19649
- manifest = themeVersion.manifest;
20294
+ const hashes = missingData.map((c) => c.hash);
20295
+ const apiResponse = await this.fetchThemeConfigsFromSource(hashes);
20296
+ const fetched = apiResponse.results || [];
20297
+ logger.info(`[ThemeLoader] Fetched ${fetched.length} configs from API`, {
20298
+ trace
20299
+ });
20300
+ if (fetched.length > 0) {
20301
+ const cacheResult = await this.updateThemeCache({
20302
+ api: 1,
20303
+ // Required by SwellThemePreload type
20304
+ configs: fetched
20305
+ });
20306
+ if (cacheResult.warnings.length > 0) {
20307
+ logger.warn("[ThemeLoader] Some files had size issues", {
20308
+ warnings: cacheResult.warnings.length
20309
+ });
19650
20310
  }
19651
20311
  }
19652
- if (manifest) {
19653
- this.manifest = new Map(Object.entries(manifest));
19654
- this.configPaths = [...this.manifest.keys()];
20312
+ const fetchedMap = new Map(fetched.map((c) => [c.hash, c]));
20313
+ const mergedConfigs = configs.map((config) => {
20314
+ if (!config.file_data) {
20315
+ const withData = fetchedMap.get(config.hash);
20316
+ if (withData?.file_data) {
20317
+ return { ...config, file_data: withData.file_data };
20318
+ }
20319
+ }
20320
+ return config;
20321
+ });
20322
+ const stillMissing = mergedConfigs.filter((c) => !c.file_data).length;
20323
+ if (stillMissing > 0) {
20324
+ logger.warn(
20325
+ `[ThemeLoader] ${stillMissing} configs still missing file_data after fetch`
20326
+ );
19655
20327
  }
19656
- return this.manifest;
20328
+ return mergedConfigs;
19657
20329
  }
19658
20330
  /**
19659
- * Fetches many theme configs via Swell Backend API.
20331
+ * Fetches theme configs with file_data from Swell Backend API.
20332
+ * Used to retrieve missing file_data for configs.
19660
20333
  */
19661
20334
  async fetchThemeConfigsFromSource(configHashes = void 0) {
19662
20335
  configHashes = configHashes || [];
19663
20336
  const { swellHeaders } = this.swell;
19664
20337
  const version = String(swellHeaders["theme-config-version"]);
19665
20338
  const fetchAll = configHashes.length === 0 || configHashes.length > MAX_INDIVIDUAL_CONFIGS_TO_FETCH;
19666
- console.log(
19667
- `Retrieving ${fetchAll ? "all" : "some"} theme configurations - version: ${version}`
20339
+ logger.debug(
20340
+ `[ThemeLoader] Fetching ${fetchAll ? "all" : configHashes.length} configs with file_data`,
20341
+ { version }
19668
20342
  );
19669
20343
  const configs = await this.swell.get(
19670
20344
  "/:themes:configs",
19671
20345
  {
19672
20346
  ...this.themeVersionQueryFilter(),
19673
20347
  ...fetchAll ? void 0 : { hash: { $in: configHashes } },
19674
- // TODO: paginate to support more than 1000 configs
19675
20348
  limit: 1e3,
19676
20349
  type: "theme",
19677
20350
  fields: "name, file, file_path, hash",
@@ -19683,45 +20356,13 @@ var ThemeLoader = class _ThemeLoader {
19683
20356
  return configs;
19684
20357
  }
19685
20358
  /**
19686
- * Fetches one theme config via Swell Backend API.
19687
- * This is used when a hash entry cannot be found.
19688
- * We may override the cached hash in order to ensure it is found on reload,
19689
- * but we probably need to find why that happens in the first place (TODO).
20359
+ * Get the current theme ID from headers.
19690
20360
  */
19691
- async fetchThemeConfigsFromSourceByPath(filePath, hash) {
19692
- console.log(`Retrieving theme config - ${filePath}`);
19693
- const config = await this.swell.get(
19694
- "/:themes:configs/:last",
19695
- {
19696
- ...this.themeVersionQueryFilter(),
19697
- file_path: filePath,
19698
- fields: "name, file, file_path, hash",
19699
- include: {
19700
- file_data: FILE_DATA_INCLUDE_QUERY
19701
- }
19702
- }
19703
- );
19704
- if (config) {
19705
- this.configs.set(filePath, config);
19706
- if (hash) {
19707
- config.hash = hash;
19708
- }
19709
- const themeId = this.getThemeId();
19710
- const promises = [this.cacheThemeConfig(config)];
19711
- if (themeId && config.file?.url) {
19712
- promises.push(
19713
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19714
- );
19715
- }
19716
- await Promise2.all(promises);
19717
- }
19718
- return config ?? null;
19719
- }
19720
20361
  getThemeId() {
19721
20362
  return this.swell.swellHeaders["theme-id"];
19722
20363
  }
19723
20364
  /**
19724
- * Generates a Swell API query filter for this theme version.
20365
+ * Generate a Swell API query filter for this theme version.
19725
20366
  */
19726
20367
  themeVersionQueryFilter() {
19727
20368
  const { swellHeaders } = this.swell;
@@ -19901,7 +20542,6 @@ var SwellTheme3 = class {
19901
20542
  resources;
19902
20543
  liquidSwell;
19903
20544
  themeLoader;
19904
- themeConfigs = null;
19905
20545
  page;
19906
20546
  pageId;
19907
20547
  shopifyCompatibility = null;
@@ -19935,6 +20575,23 @@ var SwellTheme3 = class {
19935
20575
  });
19936
20576
  this.themeLoader = new ThemeLoader(swell);
19937
20577
  }
20578
+ /**
20579
+ * Getter for theme configs - returns the configs from the loader.
20580
+ * Used by editor and tests to access loaded configs.
20581
+ */
20582
+ get themeConfigs() {
20583
+ const configs = this.themeLoader.getConfigs();
20584
+ return configs.size > 0 ? configs : null;
20585
+ }
20586
+ /**
20587
+ * Setter for theme configs - directly sets configs in the loader.
20588
+ * Used by editor and tests to inject configs without API/KV loading.
20589
+ */
20590
+ set themeConfigs(configs) {
20591
+ if (configs) {
20592
+ this.themeLoader.setConfigs(configs);
20593
+ }
20594
+ }
19938
20595
  getSwellAppThemeProps(swellConfig) {
19939
20596
  return swellConfig?.storefront?.theme || {};
19940
20597
  }
@@ -20008,10 +20665,8 @@ var SwellTheme3 = class {
20008
20665
  }
20009
20666
  async getSettingsAndConfigs() {
20010
20667
  const geo = GEO_DATA;
20011
- const [storefrontSettings, settingConfigs] = await Promise.all([
20012
- this.swell.getStorefrontSettings(),
20013
- this.getThemeConfigsByPath("theme/config/", ".json")
20014
- ]);
20668
+ const storefrontSettings = await this.swell.getStorefrontSettings();
20669
+ const settingConfigs = this.getThemeConfigsByPath("theme/config/", ".json");
20015
20670
  const configs = {
20016
20671
  theme: {},
20017
20672
  editor: {},
@@ -20106,7 +20761,7 @@ var SwellTheme3 = class {
20106
20761
  $locale: void 0
20107
20762
  };
20108
20763
  if (pageId) {
20109
- const templateConfig = await this.getThemeTemplateConfigByType(
20764
+ const templateConfig = this._getTemplateConfigByType(
20110
20765
  "templates",
20111
20766
  pageId,
20112
20767
  altTemplate
@@ -20401,7 +21056,7 @@ var SwellTheme3 = class {
20401
21056
  this.shopifyCompatibility.adaptPageData(pageData);
20402
21057
  }
20403
21058
  async getLocaleConfig(localeCode = "en", suffix = ".json") {
20404
- const allLocaleConfigs = await this.getThemeConfigsByPath(
21059
+ const allLocaleConfigs = this.getThemeConfigsByPath(
20405
21060
  "theme/locales/",
20406
21061
  suffix
20407
21062
  );
@@ -20505,22 +21160,24 @@ var SwellTheme3 = class {
20505
21160
  }
20506
21161
  return resolvedUrl;
20507
21162
  }
20508
- async getAllThemeConfigs() {
20509
- if (this.themeConfigs === null) {
20510
- const configs = await this.themeLoader.loadTheme();
20511
- const configsByPath = /* @__PURE__ */ new Map();
20512
- for (const config of configs) {
20513
- configsByPath.set(config.file_path, config);
20514
- }
20515
- this.themeConfigs = configsByPath;
20516
- }
20517
- return this.themeConfigs;
20518
- }
20519
- /**
20520
- * Preloads updated theme configs. Used to optimize initial theme load.
20521
- */
20522
21163
  async preloadThemeConfigs(payload) {
20523
- await this.themeLoader.preloadTheme(payload);
21164
+ const result = await this.themeLoader.updateThemeCache(payload);
21165
+ if (result.warnings && result.warnings.length > 0) {
21166
+ const rejected = result.warnings.filter(
21167
+ (w) => w.reason === "rejected_5mb" || w.reason === "exceeded_25mb"
21168
+ ).length;
21169
+ const warned = result.warnings.filter(
21170
+ (w) => w.reason === "warning_1mb"
21171
+ ).length;
21172
+ logger.warn("[Theme] File size issues detected during cache update", {
21173
+ totalWarnings: result.warnings.length,
21174
+ rejected,
21175
+ warned,
21176
+ details: result.warnings.slice(0, 5)
21177
+ // Log first 5 warnings for debugging
21178
+ });
21179
+ }
21180
+ return result;
20524
21181
  }
20525
21182
  getPageConfigPath(pageId, altTemplate) {
20526
21183
  if (this.shopifyCompatibility) {
@@ -20533,16 +21190,10 @@ var SwellTheme3 = class {
20533
21190
  return `${withSuffix(`theme/templates/${pageId}`, altTemplate)}.json`;
20534
21191
  }
20535
21192
  async getThemeConfig(filePath) {
20536
- if (this.themeConfigs !== null) {
20537
- return this.themeConfigs.get(filePath) ?? null;
20538
- }
20539
- return this.themeLoader.fetchThemeConfig(filePath);
21193
+ return this.themeLoader.getConfig(filePath);
20540
21194
  }
20541
- async getThemeConfigsByPath(pathPrefix, pathSuffix) {
20542
- const configs = await this.themeLoader.fetchThemeConfigsByPath(
20543
- pathPrefix,
20544
- pathSuffix
20545
- );
21195
+ getThemeConfigsByPath(pathPrefix, pathSuffix) {
21196
+ const configs = this.themeLoader.getConfigsByPath(pathPrefix, pathSuffix);
20546
21197
  const configsByPath = /* @__PURE__ */ new Map();
20547
21198
  for (const config of configs) {
20548
21199
  configsByPath.set(config.file_path, config);
@@ -20550,31 +21201,42 @@ var SwellTheme3 = class {
20550
21201
  return configsByPath;
20551
21202
  }
20552
21203
  async getThemeTemplateConfig(filePath) {
20553
- if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
20554
- return this.getThemeConfig(filePath);
20555
- }
20556
- const jsonTemplate = await this.getThemeConfig(`${filePath}.json`);
20557
- if (jsonTemplate) {
20558
- return jsonTemplate;
20559
- }
20560
- return this.getThemeConfig(`${filePath}.liquid`);
21204
+ return this._getTemplateConfig(filePath);
20561
21205
  }
20562
- async getThemeTemplateConfigByType(type, name, suffix) {
21206
+ /**
21207
+ * Internal synchronous helper for getting template configs by type.
21208
+ * Used internally within theme.ts to avoid async overhead.
21209
+ */
21210
+ _getTemplateConfigByType(type, name, suffix) {
20563
21211
  const templatesByPriority = [withSuffix(`${type}/${name}`, suffix)];
20564
21212
  if (this.shopifyCompatibility) {
20565
21213
  const path = this.shopifyCompatibility.getThemeFilePath(type, name);
20566
21214
  templatesByPriority.push(withSuffix(path, suffix));
20567
21215
  }
20568
21216
  for (const filePath of templatesByPriority) {
20569
- const templateConfig = await this.getThemeTemplateConfig(
20570
- `theme/${filePath}`
20571
- );
21217
+ const templateConfig = this._getTemplateConfig(`theme/${filePath}`);
20572
21218
  if (templateConfig) {
20573
21219
  return templateConfig;
20574
21220
  }
20575
21221
  }
20576
21222
  return null;
20577
21223
  }
21224
+ /**
21225
+ * Internal synchronous helper for getting template configs.
21226
+ */
21227
+ _getTemplateConfig(filePath) {
21228
+ if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
21229
+ return this.themeLoader.getConfig(filePath);
21230
+ }
21231
+ const jsonTemplate = this.themeLoader.getConfig(`${filePath}.json`);
21232
+ if (jsonTemplate) {
21233
+ return jsonTemplate;
21234
+ }
21235
+ return this.themeLoader.getConfig(`${filePath}.liquid`);
21236
+ }
21237
+ async getThemeTemplateConfigByType(type, name, suffix) {
21238
+ return this._getTemplateConfigByType(type, name, suffix);
21239
+ }
20578
21240
  async getAssetConfig(assetName) {
20579
21241
  return await this.getThemeConfig(`theme/assets/${assetName}`) ?? await this.getThemeConfig(`assets/${assetName}`) ?? null;
20580
21242
  }
@@ -20596,17 +21258,8 @@ var SwellTheme3 = class {
20596
21258
  return "";
20597
21259
  }
20598
21260
  template = unescapeLiquidSyntax(template);
20599
- const trace = createTraceId();
20600
21261
  try {
20601
- logger.debug("[SDK] Render template start", {
20602
- config: config.name,
20603
- trace
20604
- });
20605
21262
  const result = await this.liquidSwell.parseAndRender(template, data);
20606
- logger.debug("[SDK] Render template end", {
20607
- config: config.name,
20608
- trace
20609
- });
20610
21263
  return result;
20611
21264
  } catch (err) {
20612
21265
  logger.error(err);
@@ -20623,10 +21276,7 @@ var SwellTheme3 = class {
20623
21276
  }
20624
21277
  async getSectionSchema(sectionName) {
20625
21278
  let result;
20626
- const config = await this.getThemeTemplateConfigByType(
20627
- "sections",
20628
- sectionName
20629
- );
21279
+ const config = this._getTemplateConfigByType("sections", sectionName);
20630
21280
  if (config?.file_path?.endsWith(".json")) {
20631
21281
  try {
20632
21282
  result = import_json56.default.parse(config.file_data) || void 0;
@@ -20704,10 +21354,7 @@ var SwellTheme3 = class {
20704
21354
  return content;
20705
21355
  }
20706
21356
  async renderLayoutTemplate(name, data) {
20707
- const templateConfig = await this.getThemeTemplateConfigByType(
20708
- "layouts",
20709
- name
20710
- );
21357
+ const templateConfig = this._getTemplateConfigByType("layouts", name);
20711
21358
  if (!templateConfig) {
20712
21359
  throw new Error(`Layout template not found: ${name}`);
20713
21360
  }
@@ -20742,17 +21389,14 @@ ${content.slice(pos)}`;
20742
21389
  async renderPageTemplate(name, data, altTemplateId) {
20743
21390
  let templateConfig = null;
20744
21391
  if (altTemplateId) {
20745
- templateConfig = await this.getThemeTemplateConfigByType(
21392
+ templateConfig = this._getTemplateConfigByType(
20746
21393
  "templates",
20747
21394
  name,
20748
21395
  altTemplateId
20749
21396
  );
20750
21397
  }
20751
21398
  if (!templateConfig) {
20752
- templateConfig = await this.getThemeTemplateConfigByType(
20753
- "templates",
20754
- name
20755
- );
21399
+ templateConfig = this._getTemplateConfigByType("templates", name);
20756
21400
  }
20757
21401
  if (templateConfig) {
20758
21402
  const templatePath = name.split("/").splice(1).join("/") || null;
@@ -20853,7 +21497,7 @@ ${content.slice(pos)}`;
20853
21497
  }
20854
21498
  const [sectionKey, originalPageId] = sectionId.split(/__/).reverse();
20855
21499
  const pageId = (originalPageId || "").replaceAll("_", "/");
20856
- const templateConfig = await this.getThemeTemplateConfigByType(
21500
+ const templateConfig = this._getTemplateConfigByType(
20857
21501
  pageId ? "templates" : "sections",
20858
21502
  pageId ? pageId : sectionKey
20859
21503
  );
@@ -21028,7 +21672,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
21028
21672
  return defaults;
21029
21673
  }
21030
21674
  async getAllSections() {
21031
- const configs = await this.getThemeConfigsByPath("theme/sections/");
21675
+ const configs = this.getThemeConfigsByPath("theme/sections/");
21032
21676
  return getAllSections(configs, this.getTemplateSchema.bind(this));
21033
21677
  }
21034
21678
  async getPageSections(sectionGroup, resolveSettings = true) {
@@ -21055,7 +21699,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
21055
21699
  const sectionName = sectionSchema?.label || sectionFileName;
21056
21700
  let sourcePath = "";
21057
21701
  if (group) {
21058
- const sectionConfig = await this.getThemeTemplateConfigByType(
21702
+ const sectionConfig = this._getTemplateConfigByType(
21059
21703
  "sections",
21060
21704
  `${sectionFileName}.json`
21061
21705
  );
@@ -21072,7 +21716,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
21072
21716
  * Get a list of sections and section groups in a page layout.
21073
21717
  */
21074
21718
  async getPageSectionGroups(pageId, altTemplate) {
21075
- const pageConfig = await this.getThemeTemplateConfigByType(
21719
+ const pageConfig = this._getTemplateConfigByType(
21076
21720
  "templates",
21077
21721
  pageId,
21078
21722
  altTemplate
@@ -21139,7 +21783,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
21139
21783
  sectionConfigs.map(async (sectionConfig, index) => {
21140
21784
  const { section, schema } = sectionConfig;
21141
21785
  const settings = schema?.fields && this.globals ? resolveSectionSettings(this, sectionConfig, index) : { ...sectionConfig.settings };
21142
- const templateConfig = await this.getThemeTemplateConfigByType(
21786
+ const templateConfig = this._getTemplateConfigByType(
21143
21787
  "sections",
21144
21788
  `${section.type}.liquid`
21145
21789
  );
@@ -21832,21 +22476,30 @@ function getResourceQuery(slug, query) {
21832
22476
  StorefrontResource,
21833
22477
  Swell,
21834
22478
  SwellAccount,
22479
+ SwellAddresses,
21835
22480
  SwellBackendAPI,
21836
22481
  SwellBlog,
21837
22482
  SwellBlogCategory,
21838
22483
  SwellCart,
22484
+ SwellCategories,
21839
22485
  SwellCategory,
21840
22486
  SwellError,
21841
22487
  SwellOrder,
22488
+ SwellOrders,
21842
22489
  SwellPage,
22490
+ SwellPredictiveSearch,
21843
22491
  SwellProduct,
22492
+ SwellProductRecommendations,
22493
+ SwellSearch,
21844
22494
  SwellStorefrontCollection,
21845
22495
  SwellStorefrontPagination,
21846
22496
  SwellStorefrontRecord,
21847
22497
  SwellStorefrontResource,
21848
22498
  SwellStorefrontSingleton,
22499
+ SwellSubscription,
22500
+ SwellSubscriptions,
21849
22501
  SwellTheme,
22502
+ SwellVariant,
21850
22503
  ThemeColor,
21851
22504
  ThemeFont,
21852
22505
  ThemeForm,
@@ -21878,6 +22531,7 @@ function getResourceQuery(slug, query) {
21878
22531
  getEasyblocksComponentDefinitions,
21879
22532
  getEasyblocksPagePropsWithConfigs,
21880
22533
  getEasyblocksPageTemplate,
22534
+ getKVFlavor,
21881
22535
  getLayoutSectionGroups,
21882
22536
  getMenuItemStorefrontUrl,
21883
22537
  getMenuItemUrlAndResource,
@@ -21893,6 +22547,7 @@ function getResourceQuery(slug, query) {
21893
22547
  isObject,
21894
22548
  md5,
21895
22549
  removeCircularReferences,
22550
+ resetKVFlavorCache,
21896
22551
  resolveAsyncResources,
21897
22552
  resolveLookupCollection,
21898
22553
  resolveMenuItemUrlAndResource,