@swell/apps-sdk 1.0.148 → 1.0.150

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 (34) hide show
  1. package/dist/index.cjs +1505 -598
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +1498 -597
  4. package/dist/index.js.map +4 -4
  5. package/dist/index.mjs +1416 -512
  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 +10 -0
  13. package/dist/src/compatibility/drops/articles.d.ts +10 -0
  14. package/dist/src/compatibility/drops/blogs.d.ts +10 -0
  15. package/dist/src/compatibility/drops/collections.d.ts +9 -4
  16. package/dist/src/compatibility/drops/images.d.ts +10 -0
  17. package/dist/src/compatibility/drops/pages.d.ts +18 -0
  18. package/dist/src/compatibility/drops/robots-rule.d.ts +9 -0
  19. package/dist/src/compatibility/shopify-objects/image.d.ts +1 -2
  20. package/dist/src/compatibility/shopify-objects/media.d.ts +1 -2
  21. package/dist/src/compatibility/shopify.d.ts +1 -2
  22. package/dist/src/content.d.ts +8 -8
  23. package/dist/src/globals.d.ts +7 -0
  24. package/dist/src/liquid/drops/render.d.ts +10 -0
  25. package/dist/src/liquid/utils.d.ts +2 -1
  26. package/dist/src/theme/theme-loader.d.ts +36 -44
  27. package/dist/src/theme.d.ts +28 -11
  28. package/dist/src/utils/index.d.ts +2 -0
  29. package/dist/src/utils/kv-flavor.d.ts +7 -0
  30. package/dist/types/cloudflare.d.ts +14 -3
  31. package/dist/types/shopify.d.ts +12 -0
  32. package/dist/types/swell.d.ts +16 -1
  33. package/package.json +1 -1
  34. package/dist/src/compatibility/drops/render.d.ts +0 -6
package/dist/index.cjs CHANGED
@@ -48,7 +48,7 @@ __export(index_exports, {
48
48
  ShopifyCart: () => ShopifyCart,
49
49
  ShopifyCollection: () => ShopifyCollection,
50
50
  ShopifyCollections: () => ShopifyCollections,
51
- ShopifyCompatibility: () => ShopifyCompatibility3,
51
+ ShopifyCompatibility: () => ShopifyCompatibility2,
52
52
  ShopifyCustomer: () => ShopifyCustomer,
53
53
  ShopifyFont: () => ShopifyFont,
54
54
  ShopifyForm: () => ShopifyForm,
@@ -112,6 +112,7 @@ __export(index_exports, {
112
112
  getEasyblocksComponentDefinitions: () => getEasyblocksComponentDefinitions,
113
113
  getEasyblocksPagePropsWithConfigs: () => getEasyblocksPagePropsWithConfigs,
114
114
  getEasyblocksPageTemplate: () => getEasyblocksPageTemplate,
115
+ getKVFlavor: () => getKVFlavor,
115
116
  getLayoutSectionGroups: () => getLayoutSectionGroups,
116
117
  getMenuItemStorefrontUrl: () => getMenuItemStorefrontUrl,
117
118
  getMenuItemUrlAndResource: () => getMenuItemUrlAndResource,
@@ -119,6 +120,7 @@ __export(index_exports, {
119
120
  getPage: () => getPage,
120
121
  getPageSections: () => getPageSections,
121
122
  getSectionGroupProp: () => getSectionGroupProp,
123
+ getSectionLocation: () => getSectionLocation,
122
124
  getSectionSettingsFromProps: () => getSectionSettingsFromProps,
123
125
  getThemeSettingsFromProps: () => getThemeSettingsFromProps,
124
126
  isArray: () => isArray2,
@@ -126,6 +128,7 @@ __export(index_exports, {
126
128
  isObject: () => isObject2,
127
129
  md5: () => md5,
128
130
  removeCircularReferences: () => removeCircularReferences,
131
+ resetKVFlavorCache: () => resetKVFlavorCache,
129
132
  resolveAsyncResources: () => resolveAsyncResources,
130
133
  resolveLookupCollection: () => resolveLookupCollection,
131
134
  resolveMenuItemUrlAndResource: () => resolveMenuItemUrlAndResource,
@@ -459,7 +462,8 @@ var ForloopDrop = class extends import_liquidjs.Drop {
459
462
  last;
460
463
  rindex;
461
464
  rindex0;
462
- constructor(length, collection, variable) {
465
+ parentloop;
466
+ constructor(length, collection, variable, parent) {
463
467
  super();
464
468
  this.length = length;
465
469
  this.name = `${variable}-${collection}`;
@@ -467,9 +471,10 @@ var ForloopDrop = class extends import_liquidjs.Drop {
467
471
  this.index = 1;
468
472
  this.index0 = 0;
469
473
  this.first = true;
470
- this.last = false;
474
+ this.last = length <= 0;
471
475
  this.rindex = length;
472
476
  this.rindex0 = length - 1;
477
+ this.parentloop = parent ?? null;
473
478
  }
474
479
  next() {
475
480
  this.i += 1;
@@ -676,6 +681,8 @@ var StorefrontResource = class {
676
681
  switch (prop) {
677
682
  // Ignore liquid prop checks
678
683
  case "toLiquid":
684
+ return typeof instance.toLiquid === "function" ? instance.toLiquid : void 0;
685
+ // Ignore liquid prop checks
679
686
  case "next":
680
687
  return;
681
688
  // Indicate props are thenable
@@ -6668,6 +6675,7 @@ function getSectionSettingsFromProps(props, sectionSchema) {
6668
6675
  }
6669
6676
  ),
6670
6677
  id: sectionSchema.id,
6678
+ type: sectionSchema.id,
6671
6679
  blocks: props.Blocks?.filter(
6672
6680
  (propBlock) => Boolean(propBlock.props.compiled?._component)
6673
6681
  ).map((propBlock) => {
@@ -6688,7 +6696,8 @@ function getSectionSettingsFromProps(props, sectionSchema) {
6688
6696
  { $locale: blockProps.$locale }
6689
6697
  ) || {}
6690
6698
  };
6691
- })
6699
+ }),
6700
+ location: getSectionLocation(sectionSchema.id)
6692
6701
  };
6693
6702
  }
6694
6703
  function toEasyblocksFieldId(fieldId) {
@@ -6825,6 +6834,50 @@ function md5(inputString) {
6825
6834
  return rh(a) + rh(b) + rh(c) + rh(d);
6826
6835
  }
6827
6836
 
6837
+ // src/utils/kv-flavor.ts
6838
+ var cachedKVFlavor;
6839
+ function getKVFlavor(workerEnv) {
6840
+ if (cachedKVFlavor) {
6841
+ return cachedKVFlavor;
6842
+ }
6843
+ if (!workerEnv?.THEME) {
6844
+ cachedKVFlavor = "memory";
6845
+ return cachedKVFlavor;
6846
+ }
6847
+ const kvFlavor = workerEnv.KV_FLAVOR;
6848
+ if (kvFlavor) {
6849
+ const flavorLower = String(kvFlavor).toLowerCase();
6850
+ switch (flavorLower) {
6851
+ case "cloudflare":
6852
+ case "cf":
6853
+ cachedKVFlavor = "cf";
6854
+ break;
6855
+ case "miniflare":
6856
+ cachedKVFlavor = "miniflare";
6857
+ break;
6858
+ case "memory":
6859
+ cachedKVFlavor = "memory";
6860
+ break;
6861
+ default:
6862
+ logger.warn(`[KV] Unknown KV_FLAVOR: ${kvFlavor}, using miniflare`);
6863
+ cachedKVFlavor = "miniflare";
6864
+ }
6865
+ return cachedKVFlavor;
6866
+ }
6867
+ try {
6868
+ if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
6869
+ cachedKVFlavor = "cf";
6870
+ return cachedKVFlavor;
6871
+ }
6872
+ } catch {
6873
+ }
6874
+ cachedKVFlavor = "miniflare";
6875
+ return cachedKVFlavor;
6876
+ }
6877
+ function resetKVFlavorCache() {
6878
+ cachedKVFlavor = void 0;
6879
+ }
6880
+
6828
6881
  // src/utils/index.ts
6829
6882
  function isSectionConfig(config, themeConfigs) {
6830
6883
  if (!config.file_path.startsWith("theme/sections/")) {
@@ -6945,7 +6998,8 @@ async function getLayoutSectionGroups(allSections, renderTemplateSchema) {
6945
6998
  async function getPageSections(sectionGroup, getSchema) {
6946
6999
  const order = Array.isArray(sectionGroup?.order) && sectionGroup.order.length > 0 ? sectionGroup.order : Object.keys(sectionGroup?.sections || {});
6947
7000
  const pageSections = [];
6948
- for (const key of order) {
7001
+ for (let i = 0; i < order.length; ++i) {
7002
+ const key = order[i];
6949
7003
  const section = sectionGroup.sections?.[key];
6950
7004
  if (!section) {
6951
7005
  continue;
@@ -6967,7 +7021,10 @@ async function getPageSections(sectionGroup, getSchema) {
6967
7021
  section: {
6968
7022
  id,
6969
7023
  ...section,
6970
- blocks
7024
+ blocks,
7025
+ index0: i,
7026
+ index: i + 1,
7027
+ location: getSectionLocation(section.type)
6971
7028
  }
6972
7029
  };
6973
7030
  pageSections.push({
@@ -7211,6 +7268,15 @@ function extractSettingsFromForm(form, currentSettings) {
7211
7268
  (0, import_lodash_es3.cloneDeep)(currentSettings)
7212
7269
  );
7213
7270
  }
7271
+ function getSectionLocation(sectionType) {
7272
+ switch (sectionType) {
7273
+ case "header":
7274
+ case "footer":
7275
+ return sectionType;
7276
+ default:
7277
+ return sectionType ? `custom.${sectionType}` : "template";
7278
+ }
7279
+ }
7214
7280
  var SECTION_GROUP_CONTENT = "ContentSections";
7215
7281
  function getSectionGroupProp(sectionId) {
7216
7282
  return `SectionGroup_${sectionId}`;
@@ -7341,12 +7407,441 @@ function buildStores2() {
7341
7407
 
7342
7408
  // src/cache/theme-cache.ts
7343
7409
  var TTL = 90 * 24 * 60 * 60 * 1e3;
7344
- var ThemeCache = class extends Cache {
7345
- constructor(options) {
7346
- super({
7347
- ttl: TTL,
7348
- ...options
7410
+
7411
+ // src/cache/theme-file-storage.ts
7412
+ var import_bluebird2 = __toESM(require("bluebird"), 1);
7413
+
7414
+ // src/cache/kv-variety.ts
7415
+ var import_bluebird = __toESM(require("bluebird"), 1);
7416
+ var { Promise: Promise2 } = import_bluebird.default;
7417
+ var CFKV = class {
7418
+ constructor(kv) {
7419
+ this.kv = kv;
7420
+ }
7421
+ async get(keys) {
7422
+ if (keys.length === 0) {
7423
+ return /* @__PURE__ */ new Map();
7424
+ }
7425
+ const result = await this.kv.get(keys, "text");
7426
+ if (!(result instanceof Map)) {
7427
+ const map = /* @__PURE__ */ new Map();
7428
+ for (const key of keys) {
7429
+ map.set(key, null);
7430
+ }
7431
+ return map;
7432
+ }
7433
+ return result;
7434
+ }
7435
+ async put(key, value, metadata) {
7436
+ await this.kv.put(key, value, { metadata });
7437
+ }
7438
+ };
7439
+ var MiniflareKV = class {
7440
+ constructor(kv) {
7441
+ this.kv = kv;
7442
+ }
7443
+ async get(keys) {
7444
+ if (keys.length === 0) {
7445
+ return /* @__PURE__ */ new Map();
7446
+ }
7447
+ const result = /* @__PURE__ */ new Map();
7448
+ await Promise2.map(
7449
+ keys,
7450
+ async (key) => {
7451
+ const value = await this.kv.get(key, "text");
7452
+ result.set(key, value);
7453
+ },
7454
+ { concurrency: 50 }
7455
+ );
7456
+ return result;
7457
+ }
7458
+ async put(key, value, metadata) {
7459
+ await this.kv.put(key, value, { metadata });
7460
+ }
7461
+ };
7462
+ var MemoryKV = class {
7463
+ store = /* @__PURE__ */ new Map();
7464
+ async get(keys) {
7465
+ const result = /* @__PURE__ */ new Map();
7466
+ for (const key of keys) {
7467
+ const entry = this.store.get(key);
7468
+ result.set(key, entry?.value ?? null);
7469
+ }
7470
+ return result;
7471
+ }
7472
+ async put(key, value, metadata) {
7473
+ this.store.set(key, { value, metadata });
7474
+ }
7475
+ };
7476
+ function createClientKV(env, flavor = "cf") {
7477
+ if (env?.THEME) {
7478
+ if (flavor === "miniflare") {
7479
+ return new MiniflareKV(env.THEME);
7480
+ }
7481
+ return new CFKV(env.THEME);
7482
+ }
7483
+ return new MemoryKV();
7484
+ }
7485
+
7486
+ // src/cache/theme-file-storage.ts
7487
+ var { Promise: Promise3 } = import_bluebird2.default;
7488
+ var ThemeFileStorage = class {
7489
+ kv;
7490
+ maxConcurrency;
7491
+ maxBatchSize = 20 * 1024 * 1024;
7492
+ // 20MB safety margin
7493
+ constructor(env, flavor = "cf") {
7494
+ this.kv = createClientKV(env, flavor);
7495
+ this.maxConcurrency = flavor === "miniflare" ? 50 : 6;
7496
+ }
7497
+ /**
7498
+ * Build a KV storage key from a file hash
7499
+ */
7500
+ buildKey(hash) {
7501
+ return `file_data:${hash}`;
7502
+ }
7503
+ /**
7504
+ * Extract hash from a KV storage key
7505
+ */
7506
+ extractHashFromKey(key) {
7507
+ return key.replace("file_data:", "");
7508
+ }
7509
+ /**
7510
+ * Plan GET batches based on file sizes to avoid 413 errors
7511
+ * Uses round-robin distribution for even batch sizes
7512
+ */
7513
+ planGetBatches(configs) {
7514
+ if (configs.length === 0) {
7515
+ return [];
7516
+ }
7517
+ const sorted = [...configs].sort((a, b) => {
7518
+ const sizeA = a.file?.length || 0;
7519
+ const sizeB = b.file?.length || 0;
7520
+ return sizeB - sizeA;
7521
+ });
7522
+ const totalSize = sorted.reduce((sum, config) => {
7523
+ return sum + (config.file?.length || 0);
7524
+ }, 0);
7525
+ const sizeBatches = Math.ceil(totalSize / this.maxBatchSize);
7526
+ const keyBatches = Math.ceil(sorted.length / 100);
7527
+ const targetBatches = Math.max(sizeBatches, keyBatches);
7528
+ const batches = Array.from(
7529
+ { length: targetBatches },
7530
+ () => ({
7531
+ configs: [],
7532
+ keys: [],
7533
+ estimatedSize: 0
7534
+ })
7535
+ );
7536
+ sorted.forEach((config, index) => {
7537
+ const batchIndex = index % targetBatches;
7538
+ const batch = batches[batchIndex];
7539
+ batch.configs.push(config);
7540
+ batch.keys.push(this.buildKey(config.hash));
7541
+ batch.estimatedSize += config.file?.length || 0;
7542
+ });
7543
+ return batches.filter((batch) => batch.configs.length > 0);
7544
+ }
7545
+ /**
7546
+ * Load a single batch from KV storage
7547
+ */
7548
+ async loadBatch(batch) {
7549
+ return this.kv.get(batch.keys);
7550
+ }
7551
+ /**
7552
+ * Merge batch results with original configs
7553
+ */
7554
+ mergeResults(configs, batchResults) {
7555
+ const allData = /* @__PURE__ */ new Map();
7556
+ for (const batchResult of batchResults) {
7557
+ for (const [key, value] of batchResult.entries()) {
7558
+ allData.set(key, value);
7559
+ }
7560
+ }
7561
+ return configs.map((config) => {
7562
+ const key = this.buildKey(config.hash);
7563
+ const fileData = allData.get(key);
7564
+ if (fileData) {
7565
+ return {
7566
+ ...config,
7567
+ file_data: fileData
7568
+ };
7569
+ }
7570
+ return config;
7571
+ });
7572
+ }
7573
+ async getFiles(configs) {
7574
+ if (configs.length === 0) {
7575
+ return [];
7576
+ }
7577
+ const trace = createTraceId();
7578
+ const batches = this.planGetBatches(configs);
7579
+ const totalSize = batches.reduce((sum, b) => sum + b.estimatedSize, 0);
7580
+ const maxBatchSize = Math.max(...batches.map((b) => b.estimatedSize));
7581
+ logger.debug("[ThemeFileStorage] Loading files start", {
7582
+ totalConfigs: configs.length,
7583
+ batchCount: batches.length,
7584
+ maxBatchSize,
7585
+ totalSize,
7586
+ trace
7587
+ });
7588
+ const results = await Promise3.map(
7589
+ batches,
7590
+ (batch) => this.loadBatch(batch),
7591
+ { concurrency: Math.min(this.maxConcurrency, batches.length) }
7592
+ );
7593
+ const mergedConfigs = this.mergeResults(configs, results);
7594
+ const loadedCount = mergedConfigs.filter((c) => c.file_data).length;
7595
+ logger.debug("[ThemeFileStorage] Loading files end", {
7596
+ requested: configs.length,
7597
+ loaded: loadedCount,
7598
+ missing: configs.length - loadedCount,
7599
+ batches: batches.length,
7600
+ trace
7601
+ });
7602
+ return mergedConfigs;
7603
+ }
7604
+ /**
7605
+ * Validate file sizes and categorize by threshold
7606
+ */
7607
+ validateFiles(configs) {
7608
+ const valid = [];
7609
+ const warnings = [];
7610
+ for (const config of configs) {
7611
+ if (!config.file_data) {
7612
+ continue;
7613
+ }
7614
+ const size = config.file?.length || 0;
7615
+ if (size >= 25 * 1024 * 1024) {
7616
+ warnings.push({
7617
+ hash: config.hash,
7618
+ filePath: config.file_path,
7619
+ size,
7620
+ reason: "exceeded_25mb",
7621
+ action: "rejected"
7622
+ });
7623
+ } else if (size >= 5 * 1024 * 1024) {
7624
+ warnings.push({
7625
+ hash: config.hash,
7626
+ filePath: config.file_path,
7627
+ size,
7628
+ reason: "rejected_5mb",
7629
+ action: "rejected"
7630
+ });
7631
+ } else {
7632
+ if (size >= 1024 * 1024) {
7633
+ warnings.push({
7634
+ hash: config.hash,
7635
+ filePath: config.file_path,
7636
+ size,
7637
+ reason: "warning_1mb",
7638
+ action: "stored"
7639
+ });
7640
+ }
7641
+ valid.push(config);
7642
+ }
7643
+ }
7644
+ return { valid, warnings };
7645
+ }
7646
+ /**
7647
+ * Check which files already exist in KV storage
7648
+ * Uses batch planning to avoid 413 errors when checking existence
7649
+ */
7650
+ async checkExistence(configs) {
7651
+ if (configs.length === 0) {
7652
+ return /* @__PURE__ */ new Set();
7653
+ }
7654
+ const existing = /* @__PURE__ */ new Set();
7655
+ const batches = this.planGetBatches(configs);
7656
+ const results = await Promise3.map(
7657
+ batches,
7658
+ (batch) => this.kv.get(batch.keys),
7659
+ { concurrency: this.maxConcurrency }
7660
+ );
7661
+ for (const batchResult of results) {
7662
+ for (const [key, value] of batchResult.entries()) {
7663
+ if (value !== null) {
7664
+ const hash = this.extractHashFromKey(key);
7665
+ existing.add(hash);
7666
+ }
7667
+ }
7668
+ }
7669
+ return existing;
7670
+ }
7671
+ async putFiles(configs) {
7672
+ const result = {
7673
+ written: 0,
7674
+ skipped: 0,
7675
+ skippedExisting: 0,
7676
+ warnings: []
7677
+ };
7678
+ if (configs.length === 0) {
7679
+ return result;
7680
+ }
7681
+ const trace = createTraceId();
7682
+ logger.debug("[ThemeFileStorage] Put files start", {
7683
+ totalConfigs: configs.length,
7684
+ trace
7685
+ });
7686
+ const { valid, warnings } = this.validateFiles(configs);
7687
+ result.warnings = warnings;
7688
+ if (warnings.length > 0) {
7689
+ const rejectedCount = warnings.filter(
7690
+ (w) => w.action === "rejected"
7691
+ ).length;
7692
+ const warnedCount = warnings.filter((w) => w.action === "stored").length;
7693
+ logger.warn("[ThemeFileStorage] File size validation issues", {
7694
+ totalWarnings: warnings.length,
7695
+ rejected: rejectedCount,
7696
+ warned: warnedCount,
7697
+ trace
7698
+ });
7699
+ warnings.filter((w) => w.action === "rejected").forEach((w) => {
7700
+ logger.error("[ThemeFileStorage] File rejected due to size", {
7701
+ filePath: w.filePath,
7702
+ size: w.size,
7703
+ reason: w.reason,
7704
+ trace
7705
+ });
7706
+ });
7707
+ }
7708
+ const rejected = warnings.filter((w) => w.action === "rejected").length;
7709
+ result.skipped = rejected + (configs.length - configs.filter((c) => c.file_data).length);
7710
+ logger.debug("[ThemeFileStorage] Checking existence", {
7711
+ validFiles: valid.length,
7712
+ trace
7713
+ });
7714
+ const existing = await this.checkExistence(valid);
7715
+ result.skippedExisting = existing.size;
7716
+ const toWrite = valid.filter((config) => !existing.has(config.hash));
7717
+ if (toWrite.length > 0) {
7718
+ logger.debug("[ThemeFileStorage] Writing new files", {
7719
+ toWrite: toWrite.length,
7720
+ skippedExisting: existing.size,
7721
+ trace
7722
+ });
7723
+ await Promise3.map(
7724
+ toWrite,
7725
+ async (config) => {
7726
+ const key = this.buildKey(config.hash);
7727
+ const metadata = config.file?.content_type ? { content_type: config.file.content_type } : void 0;
7728
+ await this.kv.put(key, config.file_data, metadata);
7729
+ result.written++;
7730
+ },
7731
+ { concurrency: this.maxConcurrency }
7732
+ );
7733
+ }
7734
+ logger.info("[ThemeFileStorage] Put files complete", {
7735
+ written: result.written,
7736
+ skipped: result.skipped,
7737
+ skippedExisting: result.skippedExisting,
7738
+ warnings: result.warnings.length,
7739
+ trace
7349
7740
  });
7741
+ return result;
7742
+ }
7743
+ };
7744
+
7745
+ // src/cache/constants.ts
7746
+ var SECOND = 1e3;
7747
+ var MINUTE = 60 * SECOND;
7748
+ var HOUR = 60 * MINUTE;
7749
+ var DAY = 24 * HOUR;
7750
+ var YEAR = 365 * DAY;
7751
+ var MAX_TTL = YEAR;
7752
+ var SHORT_TTL = 5 * SECOND;
7753
+
7754
+ // src/cache/worker-cache-proxy.ts
7755
+ var CACHE_NAME = "swell-cache-v1";
7756
+ var CACHE_KEY_ORIGIN = "https://cache.swell.store";
7757
+ var WorkerCacheProxy = class {
7758
+ swell;
7759
+ constructor(swell) {
7760
+ this.swell = swell;
7761
+ }
7762
+ /**
7763
+ * Reads a JSON value from Worker Cache using a key built from path+query.
7764
+ * Returns null on miss or if running outside of a Worker environment.
7765
+ */
7766
+ async get(path, query, opts) {
7767
+ if (typeof caches === "undefined") {
7768
+ return null;
7769
+ }
7770
+ const { keyUrl } = await this.buildKeyUrl(path, query, opts?.version);
7771
+ try {
7772
+ const cache = await caches.open(CACHE_NAME);
7773
+ const match = await cache.match(keyUrl);
7774
+ if (!match) return null;
7775
+ const data = await match.json();
7776
+ return data;
7777
+ } catch {
7778
+ return null;
7779
+ }
7780
+ }
7781
+ /**
7782
+ * Stores a JSON value in Worker Cache under key built from path+query.
7783
+ * No-ops outside of a Worker environment.
7784
+ */
7785
+ async put(path, query, value, opts) {
7786
+ if (typeof caches === "undefined") {
7787
+ return;
7788
+ }
7789
+ const { keyUrl, hasVersion } = await this.buildKeyUrl(
7790
+ path,
7791
+ query,
7792
+ opts?.version
7793
+ );
7794
+ const ttlMs = hasVersion ? MAX_TTL : SHORT_TTL;
7795
+ try {
7796
+ const cache = await caches.open(CACHE_NAME);
7797
+ const response = new Response(JSON.stringify(value), {
7798
+ headers: {
7799
+ "Content-Type": "application/json",
7800
+ "Cache-Control": `public, max-age=${Math.floor(ttlMs / 1e3)}`
7801
+ }
7802
+ });
7803
+ await cache.put(keyUrl, response);
7804
+ } catch {
7805
+ }
7806
+ }
7807
+ /**
7808
+ * Builds a deterministic key URL for Worker Cache from the backend API URL
7809
+ * composed using path and query. Includes tenant and auth isolation and an
7810
+ * optional version segment.
7811
+ */
7812
+ async buildKeyUrl(path, query, explicitVersion) {
7813
+ const apiHost = this.swell.backend?.apiHost;
7814
+ const endpointPath = String(path).startsWith("/") ? String(path).substring(1) : String(path);
7815
+ let queryString = "";
7816
+ if (query && this.swell.backend) {
7817
+ queryString = this.swell.backend.stringifyQuery(query);
7818
+ }
7819
+ const fullUrl = `${apiHost}/${endpointPath}${queryString ? `?${queryString}` : ""}`;
7820
+ const instanceId = this.swell.instanceId || "";
7821
+ const authKey = String(this.swell.swellHeaders?.["swell-auth-key"] || "");
7822
+ const tenantHash = await this.sha256Hex(`${instanceId}|${authKey}`);
7823
+ const version = explicitVersion !== void 0 ? explicitVersion : this.swell.swellHeaders?.["theme-version-hash"] || null;
7824
+ const hasVersion = Boolean(version);
7825
+ const versionHash = hasVersion ? await this.sha256Hex(String(version)) : null;
7826
+ const urlHash = await this.sha256Hex(fullUrl);
7827
+ const keyUrl = versionHash ? `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${versionHash}/${urlHash}` : `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${urlHash}`;
7828
+ return { keyUrl, hasVersion };
7829
+ }
7830
+ /**
7831
+ * SHA-256 digest with hex encoding. Requires Worker crypto; callers
7832
+ * should avoid invoking this outside of Worker code paths.
7833
+ */
7834
+ async sha256Hex(input) {
7835
+ if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
7836
+ const encoder = new TextEncoder();
7837
+ const digest = await crypto.subtle.digest(
7838
+ "SHA-256",
7839
+ encoder.encode(input)
7840
+ );
7841
+ const bytes = new Uint8Array(digest);
7842
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
7843
+ }
7844
+ return md5(input);
7350
7845
  }
7351
7846
  };
7352
7847
 
@@ -7711,7 +8206,7 @@ var Swell = class _Swell {
7711
8206
  this.workerEnv = workerEnv;
7712
8207
  this.resourceLoadingIndicator = params.resourceLoadingIndicator;
7713
8208
  logger.info(
7714
- `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}`
8209
+ `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}, flavor: ${getKVFlavor(this.workerEnv)}`
7715
8210
  );
7716
8211
  if (serverHeaders) {
7717
8212
  const { headers: headers2, swellHeaders: swellHeaders2 } = _Swell.formatHeaders(serverHeaders);
@@ -8120,19 +8615,19 @@ function getContentModel(swell, name) {
8120
8615
  })
8121
8616
  );
8122
8617
  }
8123
- async function getContentList(swell, type, query) {
8618
+ function getContentList(swell, type, query) {
8124
8619
  return new SwellStorefrontCollection(swell, `content/${type}`, query);
8125
8620
  }
8126
- async function getContentEntry(swell, type, id, query) {
8621
+ function getContentEntry(swell, type, id, query) {
8127
8622
  return new SwellStorefrontRecord(swell, `content/${type}`, id, query);
8128
8623
  }
8129
- async function getPage(swell, id, query) {
8624
+ function getPage(swell, id, query) {
8130
8625
  return getContentEntry(swell, "pages", id, query);
8131
8626
  }
8132
- async function getBlogs(swell, query) {
8627
+ function getBlogs(swell, query) {
8133
8628
  return getContentList(swell, "blogs", query);
8134
8629
  }
8135
- async function getBlog(swell, id, query) {
8630
+ function getBlog(swell, id, query) {
8136
8631
  return getContentEntry(swell, "blogs", id, query);
8137
8632
  }
8138
8633
 
@@ -8942,11 +9437,11 @@ function getRandomId() {
8942
9437
  }
8943
9438
 
8944
9439
  // src/menus.ts
8945
- var import_lodash_es12 = require("lodash-es");
9440
+ var import_lodash_es14 = require("lodash-es");
8946
9441
 
8947
9442
  // src/theme.ts
8948
9443
  var import_json56 = __toESM(require("json5"), 1);
8949
- var import_lodash_es11 = require("lodash-es");
9444
+ var import_lodash_es13 = require("lodash-es");
8950
9445
 
8951
9446
  // src/compatibility/shopify.ts
8952
9447
  var import_lodash_es7 = require("lodash-es");
@@ -14130,7 +14625,7 @@ __export(shopify_objects_exports, {
14130
14625
  });
14131
14626
 
14132
14627
  // src/compatibility/shopify-objects/image.ts
14133
- function ShopifyImage(_instance, image, options = {}, product, variant) {
14628
+ function ShopifyImage(image, options = {}, product, variant) {
14134
14629
  if (image instanceof ShopifyResource) {
14135
14630
  return image.clone();
14136
14631
  }
@@ -14192,7 +14687,7 @@ function ShopifyArticle(instance, blog, blogCategory) {
14192
14687
  id: defer(() => blog.id),
14193
14688
  image: deferWith(
14194
14689
  blog,
14195
- (blog2) => blog2.image ? ShopifyImage(instance, blog2.image) : void 0
14690
+ (blog2) => blog2.image ? ShopifyImage(blog2.image) : void 0
14196
14691
  ),
14197
14692
  metafields: {},
14198
14693
  published_at: deferWith(
@@ -14282,7 +14777,7 @@ function ShopifyBlog(instance, blogCategory) {
14282
14777
  }
14283
14778
 
14284
14779
  // src/compatibility/shopify-objects/media.ts
14285
- function ShopifyMedia(instance, image, options) {
14780
+ function ShopifyMedia(image, options) {
14286
14781
  if (image instanceof ShopifyResource) {
14287
14782
  return image.clone();
14288
14783
  }
@@ -14291,7 +14786,7 @@ function ShopifyMedia(instance, image, options) {
14291
14786
  id: image.id || image.file?.id,
14292
14787
  media_type: options?.media_type ?? "image",
14293
14788
  position: options?.position,
14294
- preview_image: ShopifyImage(instance, image)
14789
+ preview_image: ShopifyImage(image)
14295
14790
  });
14296
14791
  }
14297
14792
 
@@ -14319,19 +14814,19 @@ function ShopifyVariant(instance, variant, productIn, depth = 0) {
14319
14814
  [product, variant],
14320
14815
  (product2, variant2) => {
14321
14816
  const image = variant2.images?.[0] || product2.images?.[0];
14322
- return image ? ShopifyImage(instance, image, {}, product2, variant2) : void 0;
14817
+ return image ? ShopifyImage(image, {}, product2, variant2) : void 0;
14323
14818
  }
14324
14819
  ),
14325
14820
  featured_media: deferWith([product, variant], (product2, variant2) => {
14326
14821
  const image = variant2.images?.[0] || product2.images?.[0];
14327
- return image ? ShopifyMedia(instance, image, { media_type: "image" }) : void 0;
14822
+ return image ? ShopifyMedia(image, { media_type: "image" }) : void 0;
14328
14823
  }),
14329
14824
  id: defer(() => variant.id),
14330
14825
  image: deferWith(
14331
14826
  [product, variant],
14332
14827
  (product2, variant2) => {
14333
14828
  const image = variant2.images?.[0] || product2.images?.[0];
14334
- return image ? ShopifyImage(instance, image, {}, product2, variant2) : void 0;
14829
+ return image ? ShopifyImage(image, {}, product2, variant2) : void 0;
14335
14830
  }
14336
14831
  ),
14337
14832
  incoming: false,
@@ -14483,11 +14978,11 @@ function ShopifyProduct(instance, product, depth = 0) {
14483
14978
  description: defer(() => product.description),
14484
14979
  featured_image: deferWith(product, (product2) => {
14485
14980
  const image = product2.images?.[0];
14486
- return image ? ShopifyImage(instance, image, {}, product2) : void 0;
14981
+ return image ? ShopifyImage(image, {}, product2) : void 0;
14487
14982
  }),
14488
14983
  featured_media: deferWith(product, (product2) => {
14489
14984
  const image = product2.images?.[0];
14490
- return image ? ShopifyMedia(instance, image) : void 0;
14985
+ return image ? ShopifyMedia(image) : void 0;
14491
14986
  }),
14492
14987
  // not used
14493
14988
  first_available_variant: deferWith(product, (product2) => {
@@ -14507,7 +15002,7 @@ function ShopifyProduct(instance, product, depth = 0) {
14507
15002
  return [];
14508
15003
  }
14509
15004
  return product2.images.map(
14510
- (image, index) => ShopifyImage(instance, image, { position: index + 1 }, product2)
15005
+ (image, index) => ShopifyImage(image, { position: index + 1 }, product2)
14511
15006
  );
14512
15007
  }),
14513
15008
  media: deferWith(product, (product2) => {
@@ -14515,7 +15010,7 @@ function ShopifyProduct(instance, product, depth = 0) {
14515
15010
  return [];
14516
15011
  }
14517
15012
  return product2.images.map(
14518
- (image, index) => ShopifyMedia(instance, image, {
15013
+ (image, index) => ShopifyMedia(image, {
14519
15014
  media_type: "image",
14520
15015
  position: index + 1
14521
15016
  })
@@ -14745,7 +15240,7 @@ function ShopifyLineItem(instance, item, cart, options = {}) {
14745
15240
  [item.product, item.variant],
14746
15241
  (product, variant) => {
14747
15242
  const image = product?.images?.[0];
14748
- return image ? ShopifyImage(instance, image, {}, product, variant) : void 0;
15243
+ return image ? ShopifyImage(image, {}, product, variant) : void 0;
14749
15244
  }
14750
15245
  ),
14751
15246
  item_components: (item.bundle_items ?? []).map(
@@ -15151,10 +15646,7 @@ function ShopifyCollection(instance, category) {
15151
15646
  (category2) => convertToShopifySorting(category2.sort_options?.[0].value ?? "")
15152
15647
  ),
15153
15648
  description: defer(() => category.description),
15154
- featured_image: deferWith(
15155
- category,
15156
- (category2) => getFirstImage(instance, category2)
15157
- ),
15649
+ featured_image: deferWith(category, (category2) => getFirstImage(category2)),
15158
15650
  filters: deferWith(
15159
15651
  category,
15160
15652
  (category2) => (category2?.filter_options ?? []).map(
@@ -15163,7 +15655,7 @@ function ShopifyCollection(instance, category) {
15163
15655
  ),
15164
15656
  handle: defer(() => category.slug),
15165
15657
  id: defer(() => category.id),
15166
- image: deferWith(category, (category2) => getFirstImage(instance, category2)),
15658
+ image: deferWith(category, (category2) => getFirstImage(category2)),
15167
15659
  metafields: {},
15168
15660
  next_product: void 0,
15169
15661
  previous_product: void 0,
@@ -15183,9 +15675,9 @@ function ShopifyCollection(instance, category) {
15183
15675
  url: deferWith(category, (category2) => `/collections/${category2.slug}`)
15184
15676
  });
15185
15677
  }
15186
- function getFirstImage(instance, category) {
15678
+ function getFirstImage(category) {
15187
15679
  const image = category.images?.[0];
15188
- return image ? ShopifyImage(instance, image) : void 0;
15680
+ return image ? ShopifyImage(image) : void 0;
15189
15681
  }
15190
15682
  function convertToShopifySorting(value) {
15191
15683
  switch (value) {
@@ -16138,38 +16630,29 @@ function ShopifyShop(instance, store) {
16138
16630
  });
16139
16631
  }
16140
16632
 
16141
- // src/compatibility/drops/object-handles.ts
16633
+ // src/compatibility/drops/all_products.ts
16142
16634
  var import_liquidjs3 = require("liquidjs");
16143
- var ObjectHandlesDrop = class extends import_liquidjs3.Drop {
16635
+ var AllProductsDrop = class extends import_liquidjs3.Drop {
16636
+ #instance;
16144
16637
  #map;
16145
- constructor(map) {
16638
+ constructor(instance) {
16146
16639
  super();
16147
- switch (typeof map) {
16148
- case "object": {
16149
- if (map === null) {
16150
- this.#map = /* @__PURE__ */ new Map();
16151
- break;
16152
- }
16153
- this.#map = new Map(
16154
- map instanceof Map ? map : Object.entries(map)
16155
- );
16156
- break;
16157
- }
16158
- default:
16159
- this.#map = /* @__PURE__ */ new Map();
16160
- break;
16161
- }
16640
+ this.#instance = instance;
16641
+ this.#map = /* @__PURE__ */ new Map();
16162
16642
  }
16163
16643
  liquidMethodMissing(key) {
16164
16644
  switch (typeof key) {
16165
- case "string":
16166
- return this.#map.get(key);
16645
+ case "string": {
16646
+ return this.getProduct(key);
16647
+ }
16167
16648
  case "object": {
16168
- if (key !== null && "handle" in key) {
16169
- const { handle } = key;
16170
- if (typeof handle === "string") {
16171
- return this.#map.get(handle);
16649
+ if (key !== null) {
16650
+ const obj = key;
16651
+ const id = obj.handle || obj.id || obj._id;
16652
+ if (isLikePromise(id)) {
16653
+ return id.then((id2) => this.getProduct(id2));
16172
16654
  }
16655
+ return this.getProduct(id);
16173
16656
  }
16174
16657
  break;
16175
16658
  }
@@ -16177,58 +16660,42 @@ var ObjectHandlesDrop = class extends import_liquidjs3.Drop {
16177
16660
  break;
16178
16661
  }
16179
16662
  }
16663
+ getProduct(slug) {
16664
+ let resource = this.#map.get(slug);
16665
+ if (resource === void 0 && this.#map.size < 20) {
16666
+ resource = ShopifyProduct(
16667
+ this.#instance,
16668
+ new SwellStorefrontRecord(this.#instance.swell, "products", slug)
16669
+ );
16670
+ this.#map.set(slug, resource);
16671
+ }
16672
+ return resource ?? null;
16673
+ }
16180
16674
  };
16181
16675
 
16182
- // src/compatibility/drops/collections.ts
16676
+ // src/compatibility/drops/articles.ts
16183
16677
  var import_liquidjs4 = require("liquidjs");
16184
- var AllCategoryResource = class extends SwellStorefrontRecord {
16185
- constructor(instance) {
16186
- super(instance.swell, "categories", "all", {}, () => {
16187
- const category = {
16188
- id: "all",
16189
- slug: "all",
16190
- name: "Products",
16191
- products: new SwellStorefrontProducts(instance, { $variants: true })
16192
- };
16193
- return category;
16194
- });
16195
- }
16196
- };
16197
- var CollectionsDrop = class extends import_liquidjs4.Drop {
16678
+ var ArticlesDrop = class extends import_liquidjs4.Drop {
16198
16679
  #instance;
16199
- #categories;
16200
- #size;
16201
16680
  #map;
16202
16681
  constructor(instance) {
16203
16682
  super();
16204
16683
  this.#instance = instance;
16205
- this.#size = Number.NaN;
16206
16684
  this.#map = /* @__PURE__ */ new Map();
16207
16685
  }
16208
16686
  liquidMethodMissing(key) {
16209
16687
  switch (typeof key) {
16210
16688
  case "string": {
16211
- if (key === "all") {
16212
- let resource = this.#map.get(key);
16213
- if (resource === void 0) {
16214
- resource = ShopifyCollection(
16215
- this.#instance,
16216
- new AllCategoryResource(this.#instance)
16217
- );
16218
- this.#map.set(key, resource);
16219
- }
16220
- return resource;
16221
- }
16222
- return this.getCollection(key);
16689
+ return this.getArticle(key);
16223
16690
  }
16224
16691
  case "object": {
16225
16692
  if (key !== null) {
16226
16693
  const obj = key;
16227
16694
  const id = obj.handle || obj.id || obj._id;
16228
16695
  if (isLikePromise(id)) {
16229
- return id.then((id2) => this.getCollection(id2));
16696
+ return id.then((id2) => this.getArticle(id2));
16230
16697
  }
16231
- return this.getCollection(id);
16698
+ return this.getArticle(id);
16232
16699
  }
16233
16700
  break;
16234
16701
  }
@@ -16236,48 +16703,158 @@ var CollectionsDrop = class extends import_liquidjs4.Drop {
16236
16703
  break;
16237
16704
  }
16238
16705
  }
16239
- getCollection(slug) {
16706
+ getArticle(slug) {
16240
16707
  let resource = this.#map.get(slug);
16241
16708
  if (resource === void 0) {
16242
- resource = ShopifyCollection(
16709
+ resource = ShopifyArticle(
16243
16710
  this.#instance,
16244
- new SwellStorefrontCategory(this.#instance, slug)
16711
+ new SwellStorefrontRecord(this.#instance.swell, "content/blogs", slug)
16245
16712
  );
16246
16713
  this.#map.set(slug, resource);
16247
16714
  }
16248
16715
  return resource;
16249
16716
  }
16250
- get size() {
16251
- if (!Number.isFinite(this.#size)) {
16252
- return this.#instance.swell.storefront.get("/categories/:count").then((count) => {
16253
- const size = Number(count ?? 0);
16254
- this.#size = size;
16255
- return size;
16256
- });
16257
- }
16258
- return this.#size;
16259
- }
16260
- [Symbol.iterator]() {
16261
- return this.iterator();
16262
- }
16263
- async iterator() {
16264
- if (!this.#categories) {
16265
- this.#categories = await this.#instance.swell.storefront.categories.list().then((res) => {
16266
- return res.results.map(
16267
- (category) => ShopifyCollection(
16268
- this.#instance,
16269
- new SwellStorefrontCategory(this.#instance, category.slug)
16270
- )
16271
- );
16272
- });
16273
- }
16274
- return this.#categories.values();
16275
- }
16276
16717
  };
16277
- var SwellStorefrontCategory = class extends SwellStorefrontRecord {
16278
- constructor(instance, id, query) {
16279
- super(instance.swell, "categories", id, query, async () => {
16280
- const category = new SwellStorefrontRecord(
16718
+
16719
+ // src/compatibility/drops/blogs.ts
16720
+ var import_liquidjs5 = require("liquidjs");
16721
+ var BlogsDrop = class extends import_liquidjs5.Drop {
16722
+ #instance;
16723
+ #map;
16724
+ constructor(instance) {
16725
+ super();
16726
+ this.#instance = instance;
16727
+ this.#map = /* @__PURE__ */ new Map();
16728
+ }
16729
+ liquidMethodMissing(key) {
16730
+ switch (typeof key) {
16731
+ case "string": {
16732
+ return this.getBlog(key);
16733
+ }
16734
+ case "object": {
16735
+ if (key !== null) {
16736
+ const obj = key;
16737
+ const id = obj.handle || obj.id || obj._id;
16738
+ if (isLikePromise(id)) {
16739
+ return id.then((id2) => this.getBlog(id2));
16740
+ }
16741
+ return this.getBlog(id);
16742
+ }
16743
+ break;
16744
+ }
16745
+ default:
16746
+ break;
16747
+ }
16748
+ }
16749
+ getBlog(slug) {
16750
+ let resource = this.#map.get(slug);
16751
+ if (resource === void 0) {
16752
+ resource = ShopifyBlog(
16753
+ this.#instance,
16754
+ new SwellStorefrontRecord(
16755
+ this.#instance.swell,
16756
+ "content/blog-categories",
16757
+ slug
16758
+ )
16759
+ );
16760
+ this.#map.set(slug, resource);
16761
+ }
16762
+ return resource;
16763
+ }
16764
+ };
16765
+
16766
+ // src/compatibility/drops/collections.ts
16767
+ var import_liquidjs6 = require("liquidjs");
16768
+ var AllCategoryResource = class extends StorefrontResource {
16769
+ constructor(instance) {
16770
+ super(() => {
16771
+ const category = {
16772
+ id: "all",
16773
+ slug: "all",
16774
+ name: "Products",
16775
+ products: new SwellStorefrontProducts(instance, { $variants: true })
16776
+ };
16777
+ return category;
16778
+ });
16779
+ }
16780
+ };
16781
+ var CollectionsDrop = class extends import_liquidjs6.Drop {
16782
+ #instance;
16783
+ #map;
16784
+ constructor(instance) {
16785
+ super();
16786
+ this.#instance = instance;
16787
+ this.#map = /* @__PURE__ */ new Map();
16788
+ }
16789
+ liquidMethodMissing(key) {
16790
+ switch (typeof key) {
16791
+ case "string": {
16792
+ if (key === "all") {
16793
+ let resource = this.#map.get(key);
16794
+ if (resource === void 0) {
16795
+ resource = ShopifyCollection(
16796
+ this.#instance,
16797
+ new AllCategoryResource(this.#instance)
16798
+ );
16799
+ this.#map.set(key, resource);
16800
+ }
16801
+ return resource;
16802
+ }
16803
+ return this.getCollection(key);
16804
+ }
16805
+ case "object": {
16806
+ if (key !== null) {
16807
+ const obj = key;
16808
+ const id = obj.handle || obj.id || obj._id;
16809
+ if (isLikePromise(id)) {
16810
+ return id.then((id2) => this.getCollection(id2));
16811
+ }
16812
+ return this.getCollection(id);
16813
+ }
16814
+ break;
16815
+ }
16816
+ default:
16817
+ break;
16818
+ }
16819
+ }
16820
+ getCollection(slug) {
16821
+ let resource = this.#map.get(slug);
16822
+ if (resource === void 0) {
16823
+ resource = ShopifyCollection(
16824
+ this.#instance,
16825
+ new SwellStorefrontCategory(this.#instance, slug)
16826
+ );
16827
+ this.#map.set(slug, resource);
16828
+ }
16829
+ return resource;
16830
+ }
16831
+ };
16832
+ var Collections = class extends SwellStorefrontCollection {
16833
+ #drop;
16834
+ constructor(instance) {
16835
+ super(instance.swell, "categories", {}, async () => {
16836
+ const response = await this._defaultGetter().call(this);
16837
+ if (!response) {
16838
+ return null;
16839
+ }
16840
+ return {
16841
+ ...response,
16842
+ page_count: response.page_count || 0,
16843
+ results: response.results.map(
16844
+ (item) => ShopifyCollection(instance, item)
16845
+ )
16846
+ };
16847
+ });
16848
+ this.#drop = new CollectionsDrop(instance);
16849
+ }
16850
+ toLiquid() {
16851
+ return this.#drop;
16852
+ }
16853
+ };
16854
+ var SwellStorefrontCategory = class extends StorefrontResource {
16855
+ constructor(instance, id, query) {
16856
+ super(async () => {
16857
+ const category = new SwellStorefrontRecord(
16281
16858
  instance.swell,
16282
16859
  "categories",
16283
16860
  id,
@@ -16317,8 +16894,168 @@ var SwellStorefrontProducts = class extends SwellStorefrontCollection {
16317
16894
  }
16318
16895
  };
16319
16896
 
16897
+ // src/compatibility/drops/images.ts
16898
+ var import_liquidjs7 = require("liquidjs");
16899
+ var ImagesDrop = class extends import_liquidjs7.Drop {
16900
+ #instance;
16901
+ #map;
16902
+ constructor(instance) {
16903
+ super();
16904
+ this.#instance = instance;
16905
+ this.#map = /* @__PURE__ */ new Map();
16906
+ }
16907
+ liquidMethodMissing(key) {
16908
+ switch (typeof key) {
16909
+ case "string": {
16910
+ return this.getImage(key);
16911
+ }
16912
+ case "object": {
16913
+ if (key !== null) {
16914
+ const obj = key;
16915
+ const id = obj.handle || obj.id || obj._id;
16916
+ if (isLikePromise(id)) {
16917
+ return id.then((id2) => this.getImage(id2));
16918
+ }
16919
+ return this.getImage(id);
16920
+ }
16921
+ break;
16922
+ }
16923
+ default:
16924
+ break;
16925
+ }
16926
+ }
16927
+ getImage(name) {
16928
+ let resource = this.#map.get(name);
16929
+ if (resource === void 0) {
16930
+ resource = ShopifyImage(new SwellImage(this.#instance.swell, name));
16931
+ this.#map.set(name, resource);
16932
+ }
16933
+ return resource;
16934
+ }
16935
+ };
16936
+ var SwellImage = class extends StorefrontResource {
16937
+ constructor(swell, name) {
16938
+ super(async () => {
16939
+ const files = await swell.get("/:files", {
16940
+ private: { $ne: true },
16941
+ content_type: { $regex: "^image/" },
16942
+ filename: name
16943
+ });
16944
+ const file = files?.results[0] ?? null;
16945
+ if (file === null) {
16946
+ return null;
16947
+ }
16948
+ return { file };
16949
+ });
16950
+ }
16951
+ };
16952
+
16953
+ // src/compatibility/drops/object-handles.ts
16954
+ var import_liquidjs8 = require("liquidjs");
16955
+ var ObjectHandlesDrop = class extends import_liquidjs8.Drop {
16956
+ #map;
16957
+ constructor(map) {
16958
+ super();
16959
+ switch (typeof map) {
16960
+ case "object": {
16961
+ if (map === null) {
16962
+ this.#map = /* @__PURE__ */ new Map();
16963
+ break;
16964
+ }
16965
+ this.#map = new Map(
16966
+ map instanceof Map ? map : Object.entries(map)
16967
+ );
16968
+ break;
16969
+ }
16970
+ default:
16971
+ this.#map = /* @__PURE__ */ new Map();
16972
+ break;
16973
+ }
16974
+ }
16975
+ liquidMethodMissing(key) {
16976
+ switch (typeof key) {
16977
+ case "string":
16978
+ return this.#map.get(key);
16979
+ case "object": {
16980
+ if (key !== null && "handle" in key) {
16981
+ const { handle } = key;
16982
+ if (typeof handle === "string") {
16983
+ return this.#map.get(handle);
16984
+ }
16985
+ }
16986
+ break;
16987
+ }
16988
+ default:
16989
+ break;
16990
+ }
16991
+ }
16992
+ };
16993
+
16994
+ // src/compatibility/drops/pages.ts
16995
+ var import_liquidjs9 = require("liquidjs");
16996
+ var PagesDrop = class extends import_liquidjs9.Drop {
16997
+ #instance;
16998
+ #map;
16999
+ constructor(instance) {
17000
+ super();
17001
+ this.#instance = instance;
17002
+ this.#map = /* @__PURE__ */ new Map();
17003
+ }
17004
+ liquidMethodMissing(key) {
17005
+ switch (typeof key) {
17006
+ case "string": {
17007
+ return this.getPage(key);
17008
+ }
17009
+ case "object": {
17010
+ if (key !== null) {
17011
+ const obj = key;
17012
+ const id = obj.handle || obj.id || obj._id;
17013
+ if (isLikePromise(id)) {
17014
+ return id.then((id2) => this.getPage(id2));
17015
+ }
17016
+ return this.getPage(id);
17017
+ }
17018
+ break;
17019
+ }
17020
+ default:
17021
+ break;
17022
+ }
17023
+ }
17024
+ getPage(slug) {
17025
+ let resource = this.#map.get(slug);
17026
+ if (resource === void 0) {
17027
+ resource = ShopifyPage(
17028
+ this.#instance,
17029
+ new SwellStorefrontRecord(this.#instance.swell, "content/pages", slug)
17030
+ );
17031
+ this.#map.set(slug, resource);
17032
+ }
17033
+ return resource;
17034
+ }
17035
+ };
17036
+ var Pages = class extends SwellStorefrontCollection {
17037
+ #drop;
17038
+ constructor(instance) {
17039
+ super(instance.swell, "content/pages", {}, async () => {
17040
+ const response = await this._defaultGetter().call(this);
17041
+ if (!response) {
17042
+ return null;
17043
+ }
17044
+ return {
17045
+ ...response,
17046
+ page_count: response.page_count || 0,
17047
+ results: response.results.map((page) => ShopifyPage(instance, page))
17048
+ };
17049
+ });
17050
+ this.#drop = new PagesDrop(instance);
17051
+ }
17052
+ toLiquid() {
17053
+ return this.#drop;
17054
+ }
17055
+ };
17056
+
16320
17057
  // src/compatibility/shopify.ts
16321
- var ShopifyCompatibility3 = class {
17058
+ var ShopifyCompatibility2 = class {
16322
17059
  theme;
16323
17060
  swell;
16324
17061
  pageId;
@@ -16339,9 +17076,25 @@ var ShopifyCompatibility3 = class {
16339
17076
  }
16340
17077
  initGlobals(globals) {
16341
17078
  const { request, page } = globals;
17079
+ globals.additional_checkout_buttons = false;
17080
+ globals.all_products = new AllProductsDrop(this);
17081
+ globals.articles = new ArticlesDrop(this);
17082
+ globals.blogs = new BlogsDrop(this);
17083
+ globals.closest = {};
17084
+ globals.collections = new Collections(this);
17085
+ globals.content_for_additional_checkout_buttons = "";
17086
+ globals.content_for_header = "";
17087
+ globals.content_for_index = "";
17088
+ globals.content_for_layout = "";
17089
+ globals.current_page = this.swell.queryParams.page || 1;
17090
+ globals.images = new ImagesDrop(this);
17091
+ globals.linklists = null;
17092
+ globals.localization = null;
17093
+ globals.metaobjects = {};
16342
17094
  globals.page = {
16343
17095
  ...page || void 0
16344
17096
  };
17097
+ globals.pages = new Pages(this);
16345
17098
  globals.request = {
16346
17099
  ...request || void 0,
16347
17100
  design_mode: this.swell.isEditor,
@@ -16349,9 +17102,11 @@ var ShopifyCompatibility3 = class {
16349
17102
  // TODO: Add support for visual section preview
16350
17103
  page_type: page?.id
16351
17104
  };
16352
- globals.collections = new CollectionsDrop(this);
16353
- globals.current_page = this.swell.queryParams.page || 1;
16354
17105
  globals.routes = this.getPageRoutes();
17106
+ globals.scripts = {};
17107
+ globals.shop = null;
17108
+ globals.template = {};
17109
+ globals.theme = {};
16355
17110
  }
16356
17111
  adaptGlobals(globals, prevGlobals) {
16357
17112
  if (globals.page) {
@@ -16372,11 +17127,6 @@ var ShopifyCompatibility3 = class {
16372
17127
  if (globals.menus) {
16373
17128
  globals.linklists = new ObjectHandlesDrop(globals.menus);
16374
17129
  }
16375
- if (globals.geo) {
16376
- const countryOptions = this.getAllCountryOptionTags(globals.geo);
16377
- globals.all_country_option_tags = countryOptions;
16378
- globals.country_option_tags = countryOptions;
16379
- }
16380
17130
  if (globals.store) {
16381
17131
  globals.shop = this.getShopData(globals.store);
16382
17132
  const request = globals.request || prevGlobals.request;
@@ -16888,20 +17638,6 @@ ${injects.join("\n")}</script>`;
16888
17638
  }
16889
17639
  ];
16890
17640
  }
16891
- getAllCountryOptionTags(geoSettings) {
16892
- if (!geoSettings) {
16893
- return "";
16894
- }
16895
- return geoSettings.countries?.map((country) => {
16896
- if (!country) return "";
16897
- const provinces = (geoSettings.states || []).filter((state) => state.country === country.id).map((state) => [state.id, state.name]);
16898
- const provincesEncoded = JSON.stringify(provinces).replace(
16899
- /"/g,
16900
- "&quot;"
16901
- );
16902
- return `<option value="${country.id}" data-provinces="${provincesEncoded}">${country.name}</option>`;
16903
- }).filter(Boolean).join("\n");
16904
- }
16905
17641
  // returns true if this URL is used for script actions
16906
17642
  isScriptFormActionUrl(url) {
16907
17643
  if (!url) {
@@ -16957,25 +17693,41 @@ function ShopifyTemplate(_instance, template) {
16957
17693
  );
16958
17694
  }
16959
17695
 
16960
- // src/compatibility/drops/render.ts
16961
- var import_liquidjs5 = require("liquidjs");
16962
- var RenderDrop = class extends import_liquidjs5.Drop {
17696
+ // src/liquid/drops/render.ts
17697
+ var import_liquidjs10 = require("liquidjs");
17698
+ var import_lodash_es8 = require("lodash-es");
17699
+ var RenderDrop = class extends import_liquidjs10.Drop {
17700
+ #result;
17701
+ #handler;
16963
17702
  constructor(handler) {
16964
17703
  super();
16965
- this.handler = handler;
17704
+ this.#result = void 0;
17705
+ this.#handler = handler;
16966
17706
  }
16967
- valueOf() {
16968
- return this.handler();
17707
+ /**
17708
+ * For `Drop` we usually use `valueOf` to convert the `object` to a `string`.
17709
+ * Use `then` instead of `valueOf` since `valueOf` doesn't work for `Promise`.
17710
+ */
17711
+ then(onfulfilled, onrejected) {
17712
+ if (this.#handler !== import_lodash_es8.noop) {
17713
+ this.#result = Promise.resolve().then(this.#handler).then((result) => {
17714
+ this.#result = result;
17715
+ return this.#result;
17716
+ }).then(onfulfilled, onrejected);
17717
+ this.#handler = import_lodash_es8.noop;
17718
+ return;
17719
+ }
17720
+ onfulfilled(this.#result);
16969
17721
  }
16970
17722
  };
16971
17723
 
16972
17724
  // src/liquid/index.ts
16973
- var import_liquidjs25 = require("liquidjs");
17725
+ var import_liquidjs30 = require("liquidjs");
16974
17726
 
16975
17727
  // src/liquid/tags/assign.ts
16976
- var import_liquidjs6 = require("liquidjs");
17728
+ var import_liquidjs11 = require("liquidjs");
16977
17729
  function bind(_liquidSwell) {
16978
- return class AssignTag extends import_liquidjs6.Tag {
17730
+ return class AssignTag extends import_liquidjs11.Tag {
16979
17731
  key;
16980
17732
  value;
16981
17733
  identifier;
@@ -16987,7 +17739,7 @@ function bind(_liquidSwell) {
16987
17739
  this.tokenizer.skipBlank();
16988
17740
  this.tokenizer.advance();
16989
17741
  try {
16990
- this.value = new import_liquidjs6.Value(this.tokenizer.readFilteredValue(), this.liquid);
17742
+ this.value = new import_liquidjs11.Value(this.tokenizer.readFilteredValue(), this.liquid);
16991
17743
  } catch (e) {
16992
17744
  console.warn(
16993
17745
  `Liquid "assign" tag: ${e instanceof Error ? e.stack : String(e)}`
@@ -17014,9 +17766,9 @@ function bind(_liquidSwell) {
17014
17766
  }
17015
17767
 
17016
17768
  // src/liquid/tags/case.ts
17017
- var import_liquidjs7 = require("liquidjs");
17769
+ var import_liquidjs12 = require("liquidjs");
17018
17770
  function bind2(liquidSwell) {
17019
- return class CaseTag extends import_liquidjs7.Tag {
17771
+ return class CaseTag extends import_liquidjs12.Tag {
17020
17772
  value;
17021
17773
  branches;
17022
17774
  elseTemplates;
@@ -17027,7 +17779,7 @@ function bind2(liquidSwell) {
17027
17779
  const caseVar = this.tokenizer.readValue()?.getText();
17028
17780
  this.isBlock = Boolean(caseVar?.startsWith("block."));
17029
17781
  this.tokenizer.p = begin;
17030
- this.value = new import_liquidjs7.Value(this.tokenizer.readFilteredValue(), this.liquid);
17782
+ this.value = new import_liquidjs12.Value(this.tokenizer.readFilteredValue(), this.liquid);
17031
17783
  this.branches = [];
17032
17784
  this.elseTemplates = [];
17033
17785
  let p = [];
@@ -17065,12 +17817,12 @@ function bind2(liquidSwell) {
17065
17817
  }
17066
17818
  *render(ctx, emitter) {
17067
17819
  const r = this.liquid.renderer;
17068
- const target = (0, import_liquidjs7.toValue)(yield this.value.value(ctx, ctx.opts.lenientIf));
17820
+ const target = (0, import_liquidjs12.toValue)(yield this.value.value(ctx, ctx.opts.lenientIf));
17069
17821
  let branchHit = false;
17070
17822
  let output = "";
17071
17823
  for (const branch of this.branches) {
17072
17824
  for (const valueToken of branch.values) {
17073
- const value = yield (0, import_liquidjs7.evalToken)(valueToken, ctx, ctx.opts.lenientIf);
17825
+ const value = yield (0, import_liquidjs12.evalToken)(valueToken, ctx, ctx.opts.lenientIf);
17074
17826
  if (target === value) {
17075
17827
  const blockOutput = yield r.renderTemplates(branch.templates, ctx);
17076
17828
  output += this.isBlock && liquidSwell.isEditor ? `<span class="swell-block">${blockOutput}</span>` : blockOutput;
@@ -17103,15 +17855,15 @@ function bind2(liquidSwell) {
17103
17855
  }
17104
17856
 
17105
17857
  // src/liquid/tags/comment.ts
17106
- var import_liquidjs8 = require("liquidjs");
17858
+ var import_liquidjs13 = require("liquidjs");
17107
17859
  function bind3(_liquidSwell) {
17108
- return class CommentTag extends import_liquidjs8.Tag {
17860
+ return class CommentTag extends import_liquidjs13.Tag {
17109
17861
  constructor(tagToken, remainTokens, liquid) {
17110
17862
  super(tagToken, remainTokens, liquid);
17111
17863
  let nested = 1;
17112
17864
  while (remainTokens.length > 0) {
17113
17865
  const token = remainTokens.shift();
17114
- if (import_liquidjs8.TypeGuards.isTagToken(token)) {
17866
+ if (import_liquidjs13.TypeGuards.isTagToken(token)) {
17115
17867
  switch (token.name) {
17116
17868
  case "comment":
17117
17869
  nested += 1;
@@ -17136,20 +17888,22 @@ function bind3(_liquidSwell) {
17136
17888
  }
17137
17889
 
17138
17890
  // src/liquid/tags/for.ts
17139
- var import_liquidjs9 = require("liquidjs");
17891
+ var import_liquidjs14 = require("liquidjs");
17140
17892
  var MODIFIERS = Object.freeze(["offset", "limit", "reversed"]);
17141
17893
  function bind4(_liquidSwell) {
17142
- return class ForTag extends import_liquidjs9.ForTag {
17894
+ return class ForTag extends import_liquidjs14.ForTag {
17143
17895
  *render(ctx, emitter) {
17144
17896
  const r = this.liquid.renderer;
17145
- let collection = yield (0, import_liquidjs9.evalToken)(this.collection, ctx);
17897
+ let collection = yield (0, import_liquidjs14.evalToken)(this.collection, ctx);
17146
17898
  collection = yield resolveEnumerable(collection);
17147
17899
  if (!collection.length) {
17148
17900
  yield r.renderTemplates(this.elseTemplates, ctx, emitter);
17149
17901
  return;
17150
17902
  }
17151
17903
  const continueKey = "continue-" + this.variable + "-" + this.collection.getText();
17152
- ctx.push({ continue: ctx.getRegister(continueKey) });
17904
+ ctx.push({
17905
+ continue: ctx.getRegister(continueKey)
17906
+ });
17153
17907
  const hash = yield this.hash.render(ctx);
17154
17908
  ctx.pop();
17155
17909
  const modifiers = this.liquid.options.orderedFilterParameters ? Object.keys(hash).filter((x) => MODIFIERS.includes(x)) : MODIFIERS.filter((x) => hash[x] !== void 0);
@@ -17165,23 +17919,29 @@ function bind4(_liquidSwell) {
17165
17919
  return collection2;
17166
17920
  }
17167
17921
  }, collection);
17168
- ctx.setRegister(continueKey, (hash["offset"] || 0) + collection.length);
17922
+ const length = Math.min(collection.length, 50);
17923
+ const parent = ctx.getRegister("parentloop");
17924
+ ctx.setRegister(continueKey, (hash["offset"] || 0) + length);
17925
+ const forloop = new ForloopDrop(
17926
+ length,
17927
+ this.collection.getText(),
17928
+ this.variable,
17929
+ parent
17930
+ );
17169
17931
  const scope = {
17170
- forloop: new ForloopDrop(
17171
- collection.length,
17172
- this.collection.getText(),
17173
- this.variable
17174
- )
17932
+ forloop
17175
17933
  };
17176
17934
  ctx.push(scope);
17177
- for (const item of collection) {
17178
- scope[this.variable] = item;
17935
+ ctx.setRegister("parentloop", forloop);
17936
+ for (let i = 0; i < length; ++i) {
17937
+ scope[this.variable] = collection[i];
17179
17938
  ctx.continueCalled = ctx.breakCalled = false;
17180
17939
  yield r.renderTemplates(this.templates, ctx, emitter);
17181
17940
  if (ctx.breakCalled) break;
17182
17941
  scope.forloop.next();
17183
17942
  }
17184
17943
  ctx.continueCalled = ctx.breakCalled = false;
17944
+ ctx.setRegister("parentloop", parent);
17185
17945
  ctx.pop();
17186
17946
  }
17187
17947
  };
@@ -17197,10 +17957,10 @@ function limit(arr, count) {
17197
17957
  }
17198
17958
 
17199
17959
  // src/liquid/tags/form.ts
17200
- var import_liquidjs10 = require("liquidjs");
17960
+ var import_liquidjs15 = require("liquidjs");
17201
17961
  var IGNORED_SHOPIFY_FORMS = Object.freeze(["new_comment", "guest_login"]);
17202
17962
  function bind5(liquidSwell) {
17203
- return class FormTag extends import_liquidjs10.Tag {
17963
+ return class FormTag extends import_liquidjs15.Tag {
17204
17964
  formType;
17205
17965
  formConfig;
17206
17966
  templates;
@@ -17214,10 +17974,10 @@ function bind5(liquidSwell) {
17214
17974
  tokenizer.advance();
17215
17975
  this.arg = tokenizer.readValue();
17216
17976
  this.templates = [];
17217
- this.hash = new import_liquidjs10.Hash(this.tokenizer.remaining());
17977
+ this.hash = new import_liquidjs15.Hash(this.tokenizer.remaining());
17218
17978
  while (remainTokens.length > 0) {
17219
17979
  const token2 = remainTokens.shift();
17220
- if (import_liquidjs10.TypeGuards.isTagToken(token2) && token2.name === "endform") {
17980
+ if (import_liquidjs15.TypeGuards.isTagToken(token2) && token2.name === "endform") {
17221
17981
  return;
17222
17982
  }
17223
17983
  this.templates.push(parser.parseToken(token2, remainTokens));
@@ -17235,7 +17995,7 @@ function bind5(liquidSwell) {
17235
17995
  return;
17236
17996
  }
17237
17997
  const r = this.liquid.renderer;
17238
- const arg = yield (0, import_liquidjs10.evalToken)(this.arg, ctx);
17998
+ const arg = yield (0, import_liquidjs15.evalToken)(this.arg, ctx);
17239
17999
  const hash = yield this.hash.render(ctx);
17240
18000
  const scope = ctx.getAll();
17241
18001
  const attrs = " " + Object.entries(hash).reduce((acc, [key, value]) => {
@@ -17290,14 +18050,15 @@ function bind5(liquidSwell) {
17290
18050
  ${html}
17291
18051
  </form>
17292
18052
  `);
18053
+ ctx.pop();
17293
18054
  }
17294
18055
  };
17295
18056
  }
17296
18057
 
17297
18058
  // src/liquid/tags/if.ts
17298
- var import_liquidjs11 = require("liquidjs");
18059
+ var import_liquidjs16 = require("liquidjs");
17299
18060
  function bind6(_liquidSwell) {
17300
- return class IfTag extends import_liquidjs11.Tag {
18061
+ return class IfTag extends import_liquidjs16.Tag {
17301
18062
  branches = [];
17302
18063
  elseTemplates;
17303
18064
  constructor(tagToken, remainTokens, liquid, parser) {
@@ -17306,22 +18067,22 @@ function bind6(_liquidSwell) {
17306
18067
  parser.parseStream(remainTokens).on(
17307
18068
  "start",
17308
18069
  () => this.branches.push({
17309
- value: new import_liquidjs11.Value(
18070
+ value: new import_liquidjs16.Value(
17310
18071
  tagToken.tokenizer.readFilteredValue(),
17311
18072
  this.liquid
17312
18073
  ),
17313
18074
  templates: p = []
17314
18075
  })
17315
18076
  ).on("tag:elsif", (token) => {
17316
- (0, import_liquidjs11.assert)(!this.elseTemplates, "unexpected elsif after else");
18077
+ (0, import_liquidjs16.assert)(!this.elseTemplates, "unexpected elsif after else");
17317
18078
  this.branches.push({
17318
- value: new import_liquidjs11.Value(token.tokenizer.readFilteredValue(), this.liquid),
18079
+ value: new import_liquidjs16.Value(token.tokenizer.readFilteredValue(), this.liquid),
17319
18080
  templates: p = []
17320
18081
  });
17321
18082
  }).on("tag:else", (tag) => {
17322
18083
  if (tag.args.length > 0) {
17323
18084
  this.branches.push({
17324
- value: new import_liquidjs11.Value(tag.tokenizer.readFilteredValue(), this.liquid),
18085
+ value: new import_liquidjs16.Value(tag.tokenizer.readFilteredValue(), this.liquid),
17325
18086
  templates: p = []
17326
18087
  });
17327
18088
  } else {
@@ -17339,7 +18100,7 @@ function bind6(_liquidSwell) {
17339
18100
  const r = this.liquid.renderer;
17340
18101
  for (const { value, templates } of this.branches) {
17341
18102
  const v = yield value.value(ctx, ctx.opts.lenientIf);
17342
- if ((0, import_liquidjs11.isTruthy)(v, ctx)) {
18103
+ if ((0, import_liquidjs16.isTruthy)(v, ctx)) {
17343
18104
  yield r.renderTemplates(templates, ctx, emitter);
17344
18105
  return;
17345
18106
  }
@@ -17360,20 +18121,20 @@ function bind6(_liquidSwell) {
17360
18121
  };
17361
18122
  }
17362
18123
  function assertEmpty(predicate, message = `unexpected ${JSON.stringify(predicate)}`) {
17363
- (0, import_liquidjs11.assert)(!predicate, message);
18124
+ (0, import_liquidjs16.assert)(!predicate, message);
17364
18125
  }
17365
18126
 
17366
18127
  // src/liquid/tags/javascript.ts
17367
- var import_liquidjs12 = require("liquidjs");
18128
+ var import_liquidjs17 = require("liquidjs");
17368
18129
  function bind7(_liquidSwell) {
17369
- return class JavascriptTag extends import_liquidjs12.Tag {
18130
+ return class JavascriptTag extends import_liquidjs17.Tag {
17370
18131
  templates;
17371
18132
  constructor(token, remainTokens, liquid, parser) {
17372
18133
  super(token, remainTokens, liquid);
17373
18134
  this.templates = [];
17374
18135
  while (remainTokens.length > 0) {
17375
18136
  const token2 = remainTokens.shift();
17376
- if (import_liquidjs12.TypeGuards.isTagToken(token2) && token2.name === "endjavascript") {
18137
+ if (import_liquidjs17.TypeGuards.isTagToken(token2) && token2.name === "endjavascript") {
17377
18138
  return;
17378
18139
  }
17379
18140
  this.templates.push(parser.parseToken(token2, remainTokens));
@@ -17391,9 +18152,9 @@ function bind7(_liquidSwell) {
17391
18152
  }
17392
18153
 
17393
18154
  // src/liquid/tags/layout.ts
17394
- var import_liquidjs13 = require("liquidjs");
18155
+ var import_liquidjs18 = require("liquidjs");
17395
18156
  function bind8(liquidSwell) {
17396
- return class LayoutTag extends import_liquidjs13.Tag {
18157
+ return class LayoutTag extends import_liquidjs18.Tag {
17397
18158
  fileName;
17398
18159
  constructor(token, remainTokens, liquid, _parser) {
17399
18160
  super(token, remainTokens, liquid);
@@ -17409,9 +18170,9 @@ function bind8(liquidSwell) {
17409
18170
  }
17410
18171
 
17411
18172
  // src/liquid/tags/paginate.ts
17412
- var import_liquidjs14 = require("liquidjs");
18173
+ var import_liquidjs19 = require("liquidjs");
17413
18174
  function bind9(liquidSwell) {
17414
- return class PaginateTag extends import_liquidjs14.Tag {
18175
+ return class PaginateTag extends import_liquidjs19.Tag {
17415
18176
  collection;
17416
18177
  pageSize;
17417
18178
  templates;
@@ -17426,7 +18187,7 @@ function bind9(liquidSwell) {
17426
18187
  }
17427
18188
  this.templates = [];
17428
18189
  this.collection = collection;
17429
- this.hash = new import_liquidjs14.Hash(this.tokenizer.remaining());
18190
+ this.hash = new import_liquidjs19.Hash(this.tokenizer.remaining());
17430
18191
  const stream = parser.parseStream(remainTokens).on("tag:endpaginate", () => stream.stop()).on("template", (tpl) => {
17431
18192
  this.templates.push(tpl);
17432
18193
  }).on("end", () => {
@@ -17436,10 +18197,10 @@ function bind9(liquidSwell) {
17436
18197
  }
17437
18198
  *render(ctx, emitter) {
17438
18199
  const r = this.liquid.renderer;
17439
- const collection = yield (0, import_liquidjs14.evalToken)(this.collection, ctx);
17440
- const pageSize = Number(yield (0, import_liquidjs14.evalToken)(this.pageSize, ctx));
18200
+ const collection = yield (0, import_liquidjs19.evalToken)(this.collection, ctx);
18201
+ const pageSize = Number(yield (0, import_liquidjs19.evalToken)(this.pageSize, ctx));
17441
18202
  const hash = yield this.hash.render(ctx);
17442
- if (!Number.isNaN(pageSize) && collection instanceof SwellStorefrontCollection && collection.limit != pageSize) {
18203
+ if (!Number.isNaN(pageSize) && collection instanceof SwellStorefrontCollection && collection.limit !== pageSize) {
17443
18204
  yield collection._get({
17444
18205
  limit: pageSize,
17445
18206
  window: hash.window_size || void 0
@@ -17455,15 +18216,18 @@ function bind9(liquidSwell) {
17455
18216
  ctx.push({ paginate });
17456
18217
  }
17457
18218
  yield r.renderTemplates(this.templates, ctx, emitter);
18219
+ if (collection) {
18220
+ ctx.pop();
18221
+ }
17458
18222
  }
17459
18223
  };
17460
18224
  }
17461
18225
 
17462
18226
  // src/liquid/tags/render.ts
17463
- var import_lodash_es8 = require("lodash-es");
17464
- var import_liquidjs15 = require("liquidjs");
18227
+ var import_lodash_es9 = require("lodash-es");
18228
+ var import_liquidjs20 = require("liquidjs");
17465
18229
  function bind10(liquidSwell) {
17466
- return class RenderTag extends import_liquidjs15.RenderTag {
18230
+ return class RenderTag extends import_liquidjs20.RenderTag {
17467
18231
  *render(ctx, emitter) {
17468
18232
  const { liquid, hash } = this;
17469
18233
  const filepath = yield renderFilePath(
@@ -17471,23 +18235,23 @@ function bind10(liquidSwell) {
17471
18235
  ctx,
17472
18236
  liquid
17473
18237
  );
17474
- (0, import_liquidjs15.assert)(filepath, () => `illegal file path "${filepath}"`);
18238
+ (0, import_liquidjs20.assert)(filepath, () => `illegal file path "${filepath}"`);
17475
18239
  const themeConfig = yield liquidSwell.getComponentPath(filepath).then(
17476
18240
  (fileName) => liquidSwell.getThemeConfig(fileName)
17477
18241
  );
17478
18242
  const childCtx = ctx.spawn();
17479
18243
  const scope = childCtx.bottom();
17480
- (0, import_lodash_es8.assign)(scope, yield hash.render(ctx));
18244
+ (0, import_lodash_es9.assign)(scope, yield hash.render(ctx));
17481
18245
  const parentSection = yield ctx._get(["section"]);
17482
- if (parentSection) (0, import_lodash_es8.assign)(scope, { section: parentSection });
18246
+ if (parentSection) (0, import_lodash_es9.assign)(scope, { section: parentSection });
17483
18247
  if (this["with"]) {
17484
18248
  const { value, alias } = this["with"];
17485
18249
  const aliasName = alias || filepath;
17486
- scope[aliasName] = yield (0, import_liquidjs15.evalToken)(value, ctx);
18250
+ scope[aliasName] = yield (0, import_liquidjs20.evalToken)(value, ctx);
17487
18251
  }
17488
18252
  if (this["for"]) {
17489
18253
  const { value, alias } = this["for"];
17490
- let collection = yield (0, import_liquidjs15.evalToken)(value, ctx);
18254
+ let collection = yield (0, import_liquidjs20.evalToken)(value, ctx);
17491
18255
  collection = yield resolveEnumerable(collection);
17492
18256
  scope["forloop"] = new ForloopDrop(
17493
18257
  collection.length,
@@ -17520,13 +18284,13 @@ function* renderFilePath(file, ctx, liquid) {
17520
18284
  default:
17521
18285
  break;
17522
18286
  }
17523
- return yield (0, import_liquidjs15.evalToken)(file, ctx);
18287
+ return yield (0, import_liquidjs20.evalToken)(file, ctx);
17524
18288
  }
17525
18289
 
17526
18290
  // src/liquid/tags/section.ts
17527
- var import_liquidjs16 = require("liquidjs");
18291
+ var import_liquidjs21 = require("liquidjs");
17528
18292
  function bind11(liquidSwell) {
17529
- return class SectionTag extends import_liquidjs16.Tag {
18293
+ return class SectionTag extends import_liquidjs21.Tag {
17530
18294
  fileName;
17531
18295
  constructor(token, remainTokens, liquid, _parser) {
17532
18296
  super(token, remainTokens, liquid);
@@ -17558,7 +18322,8 @@ function bind11(liquidSwell) {
17558
18322
  section: {
17559
18323
  id: this.fileName,
17560
18324
  settings: { ...defaultSettings, blocks: void 0 },
17561
- blocks: defaultSettings.blocks
18325
+ blocks: defaultSettings.blocks,
18326
+ location: "static"
17562
18327
  }
17563
18328
  });
17564
18329
  });
@@ -17576,10 +18341,10 @@ function bind11(liquidSwell) {
17576
18341
  }
17577
18342
 
17578
18343
  // src/liquid/tags/sections.ts
17579
- var import_liquidjs17 = require("liquidjs");
18344
+ var import_liquidjs22 = require("liquidjs");
17580
18345
  var import_json54 = __toESM(require("json5"), 1);
17581
18346
  function bind12(liquidSwell) {
17582
- return class SectionsTag extends import_liquidjs17.Tag {
18347
+ return class SectionsTag extends import_liquidjs22.Tag {
17583
18348
  fileName;
17584
18349
  constructor(token, remainTokens, liquid, _parser) {
17585
18350
  super(token, remainTokens, liquid);
@@ -17618,9 +18383,9 @@ function bind12(liquidSwell) {
17618
18383
  }
17619
18384
 
17620
18385
  // src/liquid/tags/style.ts
17621
- var import_liquidjs18 = require("liquidjs");
18386
+ var import_liquidjs23 = require("liquidjs");
17622
18387
  function bind13(_liquidSwell) {
17623
- return class StyleTag extends import_liquidjs18.Tag {
18388
+ return class StyleTag extends import_liquidjs23.Tag {
17624
18389
  templates;
17625
18390
  hash;
17626
18391
  constructor(token, remainTokens, liquid, parser) {
@@ -17629,7 +18394,7 @@ function bind13(_liquidSwell) {
17629
18394
  const tagBegin = token.begin;
17630
18395
  while (remainTokens.length > 0) {
17631
18396
  const token2 = remainTokens.shift();
17632
- if (import_liquidjs18.TypeGuards.isTagToken(token2) && token2.name === "endstyle") {
18397
+ if (import_liquidjs23.TypeGuards.isTagToken(token2) && token2.name === "endstyle") {
17633
18398
  this.hash = md5(token2.input.slice(tagBegin, token2.end));
17634
18399
  return;
17635
18400
  }
@@ -17652,16 +18417,16 @@ function bind13(_liquidSwell) {
17652
18417
  }
17653
18418
 
17654
18419
  // src/liquid/tags/stylesheet.ts
17655
- var import_liquidjs19 = require("liquidjs");
18420
+ var import_liquidjs24 = require("liquidjs");
17656
18421
  function bind14(_liquidSwell) {
17657
- return class StyleSheetTag extends import_liquidjs19.Tag {
18422
+ return class StyleSheetTag extends import_liquidjs24.Tag {
17658
18423
  templates;
17659
18424
  constructor(token, remainTokens, liquid, parser) {
17660
18425
  super(token, remainTokens, liquid);
17661
18426
  this.templates = [];
17662
18427
  while (remainTokens.length > 0) {
17663
18428
  const token2 = remainTokens.shift();
17664
- if (import_liquidjs19.TypeGuards.isTagToken(token2) && token2.name === "endstylesheet") {
18429
+ if (import_liquidjs24.TypeGuards.isTagToken(token2) && token2.name === "endstylesheet") {
17665
18430
  return;
17666
18431
  }
17667
18432
  this.templates.push(parser.parseToken(token2, remainTokens));
@@ -17677,9 +18442,9 @@ function bind14(_liquidSwell) {
17677
18442
  }
17678
18443
 
17679
18444
  // src/liquid/tags/shopify/include.ts
17680
- var import_liquidjs20 = require("liquidjs");
18445
+ var import_liquidjs25 = require("liquidjs");
17681
18446
  function bind15(liquidSwell) {
17682
- return class IncludeTag extends import_liquidjs20.IncludeTag {
18447
+ return class IncludeTag extends import_liquidjs25.IncludeTag {
17683
18448
  *render(ctx, emitter) {
17684
18449
  const { hash } = this;
17685
18450
  const filepath = yield renderFilePath(
@@ -17687,13 +18452,13 @@ function bind15(liquidSwell) {
17687
18452
  ctx,
17688
18453
  this.liquid
17689
18454
  );
17690
- (0, import_liquidjs20.assert)(filepath, () => `illegal file path "${filepath}"`);
18455
+ (0, import_liquidjs25.assert)(filepath, () => `illegal file path "${filepath}"`);
17691
18456
  const saved = ctx.saveRegister("blocks", "blockMode");
17692
18457
  ctx.setRegister("blocks", {});
17693
18458
  ctx.setRegister("blockMode", 0);
17694
18459
  const scope = yield hash.render(ctx);
17695
18460
  if (this.withVar) {
17696
- scope[filepath] = yield (0, import_liquidjs20.evalToken)(this.withVar, ctx);
18461
+ scope[filepath] = yield (0, import_liquidjs25.evalToken)(this.withVar, ctx);
17697
18462
  }
17698
18463
  ctx.push(ctx.opts.jekyllInclude ? { include: scope } : scope);
17699
18464
  const output = yield liquidSwell.getComponentPath(filepath).then((path) => liquidSwell.getThemeConfig(path)).then((themeConfig) => liquidSwell.renderTemplate(themeConfig, scope));
@@ -17705,10 +18470,10 @@ function bind15(liquidSwell) {
17705
18470
  }
17706
18471
 
17707
18472
  // src/liquid/tags/shopify/schema.ts
17708
- var import_liquidjs21 = require("liquidjs");
18473
+ var import_liquidjs26 = require("liquidjs");
17709
18474
  var import_json55 = __toESM(require("json5"), 1);
17710
18475
  function bind16(liquidSwell) {
17711
- return class SchemaTag extends import_liquidjs21.Tag {
18476
+ return class SchemaTag extends import_liquidjs26.Tag {
17712
18477
  templates;
17713
18478
  constructor(token, remainTokens, liquid, parser) {
17714
18479
  super(token, remainTokens, liquid);
@@ -17741,9 +18506,9 @@ function bind16(liquidSwell) {
17741
18506
  }
17742
18507
 
17743
18508
  // src/liquid/tags/inline_editable.ts
17744
- var import_liquidjs22 = require("liquidjs");
18509
+ var import_liquidjs27 = require("liquidjs");
17745
18510
  function bind17(_liquidSwell) {
17746
- return class InlineEditableTag extends import_liquidjs22.Tag {
18511
+ return class InlineEditableTag extends import_liquidjs27.Tag {
17747
18512
  key;
17748
18513
  value;
17749
18514
  constructor(token, remainTokens, liquid, _parser) {
@@ -17995,12 +18760,12 @@ function bind35(_liquidSwell) {
17995
18760
  }
17996
18761
 
17997
18762
  // src/liquid/filters/embedded_content.ts
17998
- var import_lodash_es9 = require("lodash-es");
18763
+ var import_lodash_es10 = require("lodash-es");
17999
18764
  function bind36(_liquidSwell) {
18000
18765
  return (value, tag = "iframe") => {
18001
18766
  const escapeIframes = value.replaceAll(`<${tag}`, `&lt;${tag}`).replaceAll(`</${tag}`, `&lt;/${tag}`);
18002
18767
  const removeTags = escapeIframes.replaceAll(/<(.*?)>/gi, "");
18003
- const unescaped = (0, import_lodash_es9.unescape)(removeTags);
18768
+ const unescaped = (0, import_lodash_es10.unescape)(removeTags);
18004
18769
  const replaceSpaces = unescaped.replaceAll("&nbsp;", " ");
18005
18770
  return replaceSpaces;
18006
18771
  };
@@ -18075,10 +18840,10 @@ var format_address_default = {
18075
18840
  };
18076
18841
 
18077
18842
  // src/liquid/filters/handleize.ts
18078
- var import_lodash_es10 = require("lodash-es");
18843
+ var import_lodash_es11 = require("lodash-es");
18079
18844
  function bind40(_liquidSwell) {
18080
18845
  return function filterHandleize(handle) {
18081
- return (0, import_lodash_es10.kebabCase)(handle);
18846
+ return (0, import_lodash_es11.kebabCase)(handle);
18082
18847
  };
18083
18848
  }
18084
18849
 
@@ -18803,9 +19568,9 @@ async function resolveAsyncProps(propArg, resolveProps) {
18803
19568
  }
18804
19569
 
18805
19570
  // src/liquid/operators.ts
18806
- var import_liquidjs23 = require("liquidjs");
19571
+ var import_liquidjs28 = require("liquidjs");
18807
19572
  var swellOperators = {
18808
- ...import_liquidjs23.defaultOperators,
19573
+ ...import_liquidjs28.defaultOperators,
18809
19574
  "==": equal,
18810
19575
  "!=": (l, r) => !equal(l, r),
18811
19576
  contains: (l, r) => {
@@ -18832,8 +19597,8 @@ function arrayEqual(lhs, rhs) {
18832
19597
 
18833
19598
  // src/liquid/color.ts
18834
19599
  var import_color = __toESM(require("color"), 1);
18835
- var import_liquidjs24 = require("liquidjs");
18836
- var ThemeColor = class _ThemeColor extends import_liquidjs24.Drop {
19600
+ var import_liquidjs29 = require("liquidjs");
19601
+ var ThemeColor = class _ThemeColor extends import_liquidjs29.Drop {
18837
19602
  color;
18838
19603
  colorValues;
18839
19604
  red;
@@ -18968,7 +19733,7 @@ function isThemeColorLike(value) {
18968
19733
  }
18969
19734
 
18970
19735
  // src/liquid/index.ts
18971
- var LiquidSwell29 = class extends import_liquidjs25.Liquid {
19736
+ var LiquidSwell29 = class extends import_liquidjs30.Liquid {
18972
19737
  theme;
18973
19738
  getThemeConfig;
18974
19739
  getThemeTemplateConfigByType;
@@ -19092,296 +19857,255 @@ function getLiquidFS(getThemeConfig, extName) {
19092
19857
  }
19093
19858
 
19094
19859
  // src/theme/theme-loader.ts
19095
- var import_bluebird = __toESM(require("bluebird"), 1);
19096
- var { Promise: Promise2 } = import_bluebird.default;
19097
19860
  var MAX_INDIVIDUAL_CONFIGS_TO_FETCH = 50;
19098
- var ThemeLoader = class _ThemeLoader {
19099
- static cache = null;
19861
+ var ThemeLoader = class {
19100
19862
  swell;
19101
- manifest;
19102
19863
  configs;
19103
- configPaths;
19104
19864
  constructor(swell) {
19105
19865
  this.swell = swell;
19106
- this.manifest = null;
19107
19866
  this.configs = /* @__PURE__ */ new Map();
19108
- this.configPaths = [];
19109
19867
  }
19868
+ /**
19869
+ * Initialize the theme loader with all configurations.
19870
+ * Either uses provided configs (editor mode) or loads from storage.
19871
+ */
19110
19872
  async init(themeConfigs) {
19111
19873
  if (themeConfigs) {
19112
19874
  this.setConfigs(themeConfigs);
19113
19875
  return;
19114
19876
  }
19115
19877
  if (!this.getThemeId()) {
19878
+ logger.debug("[ThemeLoader] No theme ID, skipping init");
19116
19879
  return;
19117
19880
  }
19118
- await this.fetchManifest();
19119
- if (this.manifest === null) {
19120
- console.log("ThemeLoader.init - version manifest not found");
19121
- await this.loadTheme();
19122
- }
19881
+ await this.loadAllConfigs();
19882
+ logger.info("[ThemeLoader] Initialization complete", {
19883
+ configCount: this.configs.size,
19884
+ themeId: this.getThemeId()
19885
+ });
19123
19886
  }
19124
19887
  /**
19125
- * Loads theme configs for this version.
19888
+ * Get a single config by file path (synchronous).
19889
+ * Returns null if config not found.
19126
19890
  */
19127
- async loadTheme() {
19128
- const { swellHeaders } = this.swell;
19129
- console.log("ThemeLoader.loadTheme", swellHeaders["theme-version-hash"]);
19130
- if (swellHeaders["theme-version-hash"]) {
19131
- const configs = await this.loadThemeFromManifest();
19132
- if (configs) {
19133
- return configs;
19134
- }
19135
- }
19136
- return this.loadThemeAllConfigs();
19891
+ getConfig(filePath) {
19892
+ return this.configs.get(filePath) ?? null;
19137
19893
  }
19138
19894
  /**
19139
- * Returns the cache instance for this theme loader.
19895
+ * Get all loaded configs.
19896
+ * Used by theme getter to expose configs to editor/tests.
19140
19897
  */
19141
- getCache() {
19142
- if (_ThemeLoader.cache === null) {
19143
- _ThemeLoader.cache = new ThemeCache({
19144
- kvStore: this.swell.workerEnv?.THEME
19145
- });
19146
- }
19147
- return _ThemeLoader.cache;
19898
+ getConfigs() {
19899
+ return this.configs;
19148
19900
  }
19149
19901
  /**
19150
- * Load theme configs from internal data, typically in the editor.
19902
+ * Get multiple configs by path pattern (synchronous).
19903
+ * Filters configs by prefix and optional suffix.
19151
19904
  */
19152
- setConfigs(themeConfigs) {
19153
- this.configs = new Map(themeConfigs);
19154
- this.manifest = /* @__PURE__ */ new Map();
19155
- for (const { file_path, hash } of this.configs.values()) {
19156
- this.manifest.set(file_path, hash);
19905
+ getConfigsByPath(pathPrefix, pathSuffix) {
19906
+ const results = [];
19907
+ for (const [path, config] of this.configs) {
19908
+ if (path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))) {
19909
+ results.push(config);
19910
+ }
19157
19911
  }
19158
- this.configPaths = Array.from(this.configs.keys());
19912
+ return results;
19159
19913
  }
19160
19914
  /**
19161
- * Preloads a theme version and configs. This is used to optimize initial theme load.
19915
+ * Load theme configs from internal data, typically in the editor.
19916
+ * Used when configs are provided externally (e.g., from editor).
19162
19917
  */
19163
- async preloadTheme(payload) {
19164
- const { version, configs } = payload;
19165
- console.log(
19166
- `ThemeLoader.preloadTheme${version?.hash ? ` - manifest: ${version.hash}` : ""}${configs?.length ? ` - configs: ${configs.length}` : ""}`
19167
- );
19168
- const promises = [];
19169
- if (version) {
19170
- promises.push(this.cacheManifest(version));
19171
- }
19172
- if (configs) {
19173
- const themeId = this.getThemeId();
19174
- promises.push(
19175
- Promise2.map(
19176
- configs,
19177
- async (config) => {
19178
- const promises2 = [
19179
- this.cacheThemeConfig(config)
19180
- ];
19181
- if (themeId && config.file?.url) {
19182
- promises2.push(
19183
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19184
- );
19185
- }
19186
- await Promise2.all(promises2);
19187
- },
19188
- { concurrency: 10 }
19189
- )
19190
- );
19191
- }
19192
- await Promise2.all(promises);
19918
+ setConfigs(themeConfigs) {
19919
+ this.configs = new Map(themeConfigs);
19193
19920
  }
19194
19921
  /**
19195
- * Fetches a theme config by file path.
19922
+ * Updates KV with file_data for provided theme configs (warmup path).
19923
+ * Uses the new ThemeFileStorage abstraction for optimized operations.
19196
19924
  */
19197
- async fetchThemeConfig(filePath) {
19198
- const config = this.configs.get(filePath);
19199
- if (config !== void 0) {
19200
- return config;
19201
- }
19202
- const hash = this.manifest?.get(filePath);
19203
- if (!hash) {
19204
- return null;
19925
+ async updateThemeCache(payload) {
19926
+ const configs = payload?.configs || [];
19927
+ if (configs.length === 0) {
19928
+ logger.debug("[ThemeLoader] No configs to cache");
19929
+ return {
19930
+ written: 0,
19931
+ skipped: 0,
19932
+ skippedExisting: 0,
19933
+ warnings: []
19934
+ };
19205
19935
  }
19206
- const cache = this.getCache();
19207
- const themeId = this.getThemeId();
19208
- const [themeConfig, fileUrl] = await Promise2.all([
19209
- cache.get(`config:${hash}`),
19210
- themeId ? cache.get(`file:${themeId}:${hash}`) : void 0
19211
- ]);
19212
- if (themeConfig) {
19213
- let config2 = themeConfig;
19214
- if (fileUrl && themeConfig.file?.url) {
19215
- config2 = {
19216
- ...themeConfig,
19217
- file: { ...themeConfig.file, url: fileUrl }
19218
- };
19219
- }
19220
- this.configs.set(filePath, config2);
19221
- return config2;
19936
+ const flavor = getKVFlavor(this.swell.workerEnv);
19937
+ const trace = createTraceId();
19938
+ logger.info("[ThemeLoader] Starting theme cache update", {
19939
+ totalConfigs: configs.length,
19940
+ flavor,
19941
+ trace
19942
+ });
19943
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
19944
+ const result = await storage.putFiles(configs);
19945
+ if (result.warnings.length > 0) {
19946
+ logger.warn("[ThemeLoader] Theme cache updated with warnings", {
19947
+ total: configs.length,
19948
+ written: result.written,
19949
+ skipped: result.skipped,
19950
+ skippedExisting: result.skippedExisting,
19951
+ warnings: result.warnings.length,
19952
+ trace
19953
+ });
19954
+ } else {
19955
+ logger.info("[ThemeLoader] Theme cache updated successfully", {
19956
+ total: configs.length,
19957
+ written: result.written,
19958
+ skipped: result.skipped,
19959
+ skippedExisting: result.skippedExisting,
19960
+ trace
19961
+ });
19222
19962
  }
19223
- return this.fetchThemeConfigsFromSourceByPath(filePath, hash);
19224
- }
19225
- async fetchThemeConfigsByPath(pathPrefix, pathSuffix) {
19226
- const paths = this.configPaths.filter(
19227
- (path) => path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))
19228
- );
19229
- const configs = await Promise2.map(
19230
- paths,
19231
- (path) => this.fetchThemeConfig(path),
19232
- { concurrency: 10 }
19233
- );
19234
- return configs.filter((config) => config !== null);
19963
+ return result;
19235
19964
  }
19236
19965
  /**
19237
- * Load all theme configs.
19966
+ * Main loading logic - loads all configs at once.
19967
+ * 1. Fetches lightweight metadata (cached when possible)
19968
+ * 2. Batch hydrates file_data from KV
19969
+ * 3. Fetches missing file_data from API if needed
19238
19970
  */
19239
- async loadThemeAllConfigs() {
19240
- const { swellHeaders } = this.swell;
19241
- const configVersion = String(swellHeaders["theme-config-version"]);
19242
- if (!configVersion) {
19243
- throw new Error("Theme version is required");
19971
+ async loadAllConfigs() {
19972
+ const configMetadata = await this.fetchConfigMetadata();
19973
+ if (configMetadata.length === 0) {
19974
+ logger.warn("[ThemeLoader] No configs found");
19975
+ return;
19244
19976
  }
19245
- const configs = await this.getCache().fetch(
19246
- `configs-all:${this.swell.instanceId}:v@${configVersion}2`,
19247
- () => this.fetchThemeConfigsFromSource()
19248
- );
19249
- return configs?.results ?? [];
19977
+ logger.debug("[ThemeLoader] Loading configs", {
19978
+ total: configMetadata.length
19979
+ });
19980
+ const flavor = getKVFlavor(this.swell.workerEnv);
19981
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
19982
+ const kvHydrated = await storage.getFiles(configMetadata);
19983
+ const completeConfigs = await this.ensureConfigsHaveData(kvHydrated);
19984
+ for (const config of completeConfigs) {
19985
+ this.configs.set(config.file_path, config);
19986
+ }
19987
+ logger.info("[ThemeLoader] All configs loaded", {
19988
+ total: completeConfigs.length,
19989
+ withData: completeConfigs.filter((c) => c.file_data).length
19990
+ });
19250
19991
  }
19251
19992
  /**
19252
- * Load theme configs via manifest.
19253
- *
19254
- * This approach has the following optimizations:
19255
- * - cached manifests and configs can be shared by other clients
19256
- * - when fetching from source, only fetch the missing records
19993
+ * Fetch lightweight config metadata from API or cache.
19994
+ * Does NOT include file_data to minimize payload size.
19257
19995
  */
19258
- async loadThemeFromManifest() {
19259
- const manifest = await this.fetchManifest();
19260
- if (!manifest) {
19261
- return null;
19262
- }
19263
- const configHashesUnresolved = [];
19264
- const configsByHash = /* @__PURE__ */ new Map();
19265
- const themeId = this.getThemeId();
19266
- const cache = this.getCache();
19267
- await Promise2.map(
19268
- manifest.values(),
19269
- async (configHash) => {
19270
- const [themeConfig, fileUrl] = await Promise2.all([
19271
- cache.get(`config:${configHash}`),
19272
- themeId ? cache.get(`file:${themeId}:${configHash}`) : void 0
19273
- ]);
19274
- if (!themeConfig) {
19275
- configHashesUnresolved.push(configHash);
19276
- return;
19277
- }
19278
- let config = themeConfig;
19279
- if (fileUrl && themeConfig.file?.url) {
19280
- config = {
19281
- ...themeConfig,
19282
- file: { ...themeConfig.file, url: fileUrl }
19283
- };
19996
+ async fetchConfigMetadata() {
19997
+ const query = {
19998
+ ...this.themeVersionQueryFilter(),
19999
+ limit: 1e3,
20000
+ type: "theme",
20001
+ fields: "id, name, type, file, file_path, hash"
20002
+ // NO file_data
20003
+ };
20004
+ try {
20005
+ const cache = new WorkerCacheProxy(this.swell);
20006
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20007
+ const cached = await cache.get(
20008
+ "/:themes:configs",
20009
+ query,
20010
+ {
20011
+ version: versionHash || null
19284
20012
  }
19285
- configsByHash.set(config.hash, config);
19286
- this.configs.set(config.file_path, config);
19287
- },
19288
- { concurrency: 10 }
19289
- );
19290
- if (configHashesUnresolved.length > 0) {
19291
- const configs = await this.fetchThemeConfigsFromSource(
19292
- // If no configs were resolved, then fetch them all. otherwise fetch
19293
- // the specific subset of configs.
19294
- configsByHash.size === 0 ? void 0 : configHashesUnresolved
19295
20013
  );
19296
- const newConfigs = configs?.results ?? [];
19297
- for (const config of newConfigs) {
19298
- configsByHash.set(config.hash, config);
19299
- this.configs.set(config.file_path, config);
20014
+ if (cached) {
20015
+ logger.debug("[ThemeLoader] Config metadata cache hit");
20016
+ return cached;
19300
20017
  }
19301
- await Promise2.map(
19302
- newConfigs,
19303
- async (config) => {
19304
- const promises = [this.cacheThemeConfig(config)];
19305
- if (themeId && config.file?.url) {
19306
- promises.push(
19307
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19308
- );
19309
- }
19310
- await Promise2.all(promises);
19311
- },
19312
- { concurrency: 10 }
19313
- );
20018
+ } catch (err) {
20019
+ logger.warn("[ThemeLoader] Cache read failed, fetching from API", err);
19314
20020
  }
19315
- return Array.from(configsByHash.values());
19316
- }
19317
- /**
19318
- * Caches a theme version manifest by hash.
19319
- */
19320
- async cacheManifest(version) {
19321
- if (version?.hash) {
19322
- await this.getCache().set(`manifest:${version.hash}`, version.manifest);
20021
+ logger.debug("[ThemeLoader] Fetching config metadata from API");
20022
+ const response = await this.swell.get(
20023
+ "/:themes:configs",
20024
+ query
20025
+ );
20026
+ const configs = response?.results || [];
20027
+ try {
20028
+ const cache = new WorkerCacheProxy(this.swell);
20029
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20030
+ await cache.put("/:themes:configs", query, configs, {
20031
+ version: versionHash || null
20032
+ });
20033
+ } catch (err) {
20034
+ logger.warn("[ThemeLoader] Cache write failed", err);
19323
20035
  }
20036
+ return configs;
19324
20037
  }
19325
20038
  /**
19326
- * Caches a theme config by hash.
20039
+ * Helper to ensure all configs have file_data.
20040
+ * Fetches missing data from API and updates KV cache.
19327
20041
  */
19328
- async cacheThemeConfig(config) {
19329
- if (config?.hash) {
19330
- await this.getCache().set(`config:${config.hash}`, config);
20042
+ async ensureConfigsHaveData(configs) {
20043
+ const missingData = configs.filter((c) => !c.file_data);
20044
+ if (missingData.length === 0) {
20045
+ logger.debug("[ThemeLoader] All configs have file_data from KV");
20046
+ return configs;
19331
20047
  }
19332
- }
19333
- /**
19334
- * Caches a CDN file url by config hash.
19335
- */
19336
- async cacheThemeFileUrl(themeId, configHash, fileUrl) {
19337
- await this.getCache().set(`file:${themeId}:${configHash}`, fileUrl);
19338
- }
19339
- /**
19340
- * Fetches the manifest (set of config hashes) for a theme version.
19341
- */
19342
- async fetchManifest() {
19343
- const { swellHeaders } = this.swell;
19344
- const versionHash = swellHeaders["theme-version-hash"];
19345
- console.log("ThemeLoader.fetchManifest", versionHash);
19346
- let manifest = await this.getCache().get(
19347
- `manifest:${versionHash}`
20048
+ const trace = createTraceId();
20049
+ logger.info(
20050
+ `[ThemeLoader] Loading ${missingData.length} missing file_data from API`,
20051
+ {
20052
+ trace
20053
+ }
19348
20054
  );
19349
- if (!manifest) {
19350
- const themeVersion = await this.swell.get(
19351
- "/:themes:versions/:last",
19352
- {
19353
- ...this.themeVersionQueryFilter(),
19354
- fields: "hash, manifest"
19355
- }
19356
- );
19357
- if (themeVersion) {
19358
- await this.cacheManifest(themeVersion);
19359
- manifest = themeVersion.manifest;
20055
+ const hashes = missingData.map((c) => c.hash);
20056
+ const apiResponse = await this.fetchThemeConfigsFromSource(hashes);
20057
+ const fetched = apiResponse.results || [];
20058
+ logger.info(`[ThemeLoader] Fetched ${fetched.length} configs from API`, {
20059
+ trace
20060
+ });
20061
+ if (fetched.length > 0) {
20062
+ const cacheResult = await this.updateThemeCache({
20063
+ api: 1,
20064
+ // Required by SwellThemePreload type
20065
+ configs: fetched
20066
+ });
20067
+ if (cacheResult.warnings.length > 0) {
20068
+ logger.warn("[ThemeLoader] Some files had size issues", {
20069
+ warnings: cacheResult.warnings.length
20070
+ });
19360
20071
  }
19361
20072
  }
19362
- if (manifest) {
19363
- this.manifest = new Map(Object.entries(manifest));
19364
- this.configPaths = [...this.manifest.keys()];
20073
+ const fetchedMap = new Map(fetched.map((c) => [c.hash, c]));
20074
+ const mergedConfigs = configs.map((config) => {
20075
+ if (!config.file_data) {
20076
+ const withData = fetchedMap.get(config.hash);
20077
+ if (withData?.file_data) {
20078
+ return { ...config, file_data: withData.file_data };
20079
+ }
20080
+ }
20081
+ return config;
20082
+ });
20083
+ const stillMissing = mergedConfigs.filter((c) => !c.file_data).length;
20084
+ if (stillMissing > 0) {
20085
+ logger.warn(
20086
+ `[ThemeLoader] ${stillMissing} configs still missing file_data after fetch`
20087
+ );
19365
20088
  }
19366
- return this.manifest;
20089
+ return mergedConfigs;
19367
20090
  }
19368
20091
  /**
19369
- * Fetches many theme configs via Swell Backend API.
20092
+ * Fetches theme configs with file_data from Swell Backend API.
20093
+ * Used to retrieve missing file_data for configs.
19370
20094
  */
19371
20095
  async fetchThemeConfigsFromSource(configHashes = void 0) {
19372
20096
  configHashes = configHashes || [];
19373
20097
  const { swellHeaders } = this.swell;
19374
20098
  const version = String(swellHeaders["theme-config-version"]);
19375
20099
  const fetchAll = configHashes.length === 0 || configHashes.length > MAX_INDIVIDUAL_CONFIGS_TO_FETCH;
19376
- console.log(
19377
- `Retrieving ${fetchAll ? "all" : "some"} theme configurations - version: ${version}`
20100
+ logger.debug(
20101
+ `[ThemeLoader] Fetching ${fetchAll ? "all" : configHashes.length} configs with file_data`,
20102
+ { version }
19378
20103
  );
19379
20104
  const configs = await this.swell.get(
19380
20105
  "/:themes:configs",
19381
20106
  {
19382
20107
  ...this.themeVersionQueryFilter(),
19383
20108
  ...fetchAll ? void 0 : { hash: { $in: configHashes } },
19384
- // TODO: paginate to support more than 1000 configs
19385
20109
  limit: 1e3,
19386
20110
  type: "theme",
19387
20111
  fields: "name, file, file_path, hash",
@@ -19393,45 +20117,13 @@ var ThemeLoader = class _ThemeLoader {
19393
20117
  return configs;
19394
20118
  }
19395
20119
  /**
19396
- * Fetches one theme config via Swell Backend API.
19397
- * This is used when a hash entry cannot be found.
19398
- * We may override the cached hash in order to ensure it is found on reload,
19399
- * but we probably need to find why that happens in the first place (TODO).
20120
+ * Get the current theme ID from headers.
19400
20121
  */
19401
- async fetchThemeConfigsFromSourceByPath(filePath, hash) {
19402
- console.log(`Retrieving theme config - ${filePath}`);
19403
- const config = await this.swell.get(
19404
- "/:themes:configs/:last",
19405
- {
19406
- ...this.themeVersionQueryFilter(),
19407
- file_path: filePath,
19408
- fields: "name, file, file_path, hash",
19409
- include: {
19410
- file_data: FILE_DATA_INCLUDE_QUERY
19411
- }
19412
- }
19413
- );
19414
- if (config) {
19415
- this.configs.set(filePath, config);
19416
- if (hash) {
19417
- config.hash = hash;
19418
- }
19419
- const themeId = this.getThemeId();
19420
- const promises = [this.cacheThemeConfig(config)];
19421
- if (themeId && config.file?.url) {
19422
- promises.push(
19423
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19424
- );
19425
- }
19426
- await Promise2.all(promises);
19427
- }
19428
- return config ?? null;
19429
- }
19430
20122
  getThemeId() {
19431
20123
  return this.swell.swellHeaders["theme-id"];
19432
20124
  }
19433
20125
  /**
19434
- * Generates a Swell API query filter for this theme version.
20126
+ * Generate a Swell API query filter for this theme version.
19435
20127
  */
19436
20128
  themeVersionQueryFilter() {
19437
20129
  const { swellHeaders } = this.swell;
@@ -19447,6 +20139,161 @@ var ThemeLoader = class _ThemeLoader {
19447
20139
  }
19448
20140
  };
19449
20141
 
20142
+ // src/globals.ts
20143
+ var import_lodash_es12 = require("lodash-es");
20144
+
20145
+ // src/compatibility/drops/robots-rule.ts
20146
+ var import_liquidjs31 = require("liquidjs");
20147
+ var RobotsRule = class _RobotsRule extends import_liquidjs31.Drop {
20148
+ constructor(directive, value) {
20149
+ super();
20150
+ this.directive = directive;
20151
+ this.value = value;
20152
+ }
20153
+ static from(directive, value) {
20154
+ return new _RobotsRule(directive, value);
20155
+ }
20156
+ valueOf() {
20157
+ return `${this.directive}: ${this.value}
20158
+ `;
20159
+ }
20160
+ };
20161
+
20162
+ // src/globals.ts
20163
+ async function getFirstFilledValue(values) {
20164
+ for (const promise of values) {
20165
+ const value = await promise;
20166
+ if (value) {
20167
+ return value;
20168
+ }
20169
+ }
20170
+ }
20171
+ function getRecordGlobals(theme, record) {
20172
+ const globals = {};
20173
+ globals.handle = new RenderDrop(() => record.slug);
20174
+ globals.page_title = new RenderDrop(
20175
+ () => getFirstFilledValue([
20176
+ record.meta_title,
20177
+ // filled SEO
20178
+ record.name,
20179
+ // all records
20180
+ record.title,
20181
+ // content/pages, content/blogs, content/blog-categories
20182
+ theme.page?.title,
20183
+ // default page title
20184
+ theme.globals.store?.name
20185
+ // fallback to store name
20186
+ ])
20187
+ );
20188
+ globals.page_description = new RenderDrop(
20189
+ () => getFirstFilledValue([
20190
+ record.meta_description,
20191
+ // filled SEO
20192
+ record.description,
20193
+ // product and category
20194
+ record.summary,
20195
+ // content/blogs
20196
+ theme.page?.description
20197
+ ])
20198
+ );
20199
+ globals.page_image = new RenderDrop(
20200
+ () => getFirstFilledValue([
20201
+ record.images,
20202
+ // product and category
20203
+ record.image
20204
+ // article
20205
+ ]).then((images) => {
20206
+ if (typeof images !== "object" || images === null) {
20207
+ return;
20208
+ }
20209
+ const image = Array.isArray(images) ? images[0] : images;
20210
+ return image ? ShopifyImage(image) : void 0;
20211
+ })
20212
+ );
20213
+ return globals;
20214
+ }
20215
+ var POWERED_BY_LINK = '<a target="_blank" rel="nofollow" href="https://www.swell.is/?utm_campaign=poweredby&amp;utm_medium=swell&amp;utm_source=onlinestore">Powered by Swell</a>';
20216
+ function getAllCountryOptionTags(geoSettings) {
20217
+ if (!geoSettings) {
20218
+ return "";
20219
+ }
20220
+ const stateMap = (geoSettings.states || []).reduce((map, state) => {
20221
+ let list = map.get(state.country);
20222
+ if (list === void 0) {
20223
+ list = [];
20224
+ map.set(state.country, list);
20225
+ }
20226
+ list.push(state);
20227
+ return map;
20228
+ }, /* @__PURE__ */ new Map());
20229
+ return (geoSettings.countries || []).map((country) => {
20230
+ if (!country) return "";
20231
+ const provinces = (stateMap.get(country.id) || []).map((state) => [
20232
+ state.id,
20233
+ state.name
20234
+ ]);
20235
+ const provincesEncoded = JSON.stringify(provinces).replace(
20236
+ /"/g,
20237
+ "&quot;"
20238
+ );
20239
+ return `<option value="${country.id}" data-provinces="${provincesEncoded}">${country.name}</option>`;
20240
+ }).filter(Boolean).join("\n");
20241
+ }
20242
+ function getRobotsGlobals(canonicalUrl) {
20243
+ const sitemapUrl = `${(0, import_lodash_es12.trimEnd)(canonicalUrl, "/")}/sitemap.xml`;
20244
+ const defaultRules = [
20245
+ { directive: "Disallow", value: "/admin" },
20246
+ { directive: "Disallow", value: "/cart" },
20247
+ { directive: "Disallow", value: "/account" },
20248
+ { directive: "Disallow", value: "/search" },
20249
+ { directive: "Disallow", value: "/categories/*sort_by*" },
20250
+ { directive: "Disallow", value: "/*/categories/*sort_by*" }
20251
+ ];
20252
+ return {
20253
+ default_groups: [
20254
+ {
20255
+ user_agent: RobotsRule.from("User-agent", "*"),
20256
+ // sitemap: RobotsRule.from('Sitemap', sitemapUrl),
20257
+ rules: defaultRules.map(
20258
+ (rule) => RobotsRule.from(rule.directive, rule.value)
20259
+ )
20260
+ },
20261
+ {
20262
+ user_agent: RobotsRule.from("User-agent", "AhrefsBot"),
20263
+ // sitemap: RobotsRule.from('Sitemap', sitemapUrl),
20264
+ rules: [
20265
+ RobotsRule.from("Crawl-delay", "10"),
20266
+ ...defaultRules.map(
20267
+ (rule) => RobotsRule.from(rule.directive, rule.value)
20268
+ )
20269
+ ]
20270
+ },
20271
+ {
20272
+ user_agent: RobotsRule.from("User-agent", "AhrefsSiteAudit"),
20273
+ // sitemap: RobotsRule.from('Sitemap', sitemapUrl),
20274
+ rules: [
20275
+ RobotsRule.from("Crawl-delay", "10"),
20276
+ ...defaultRules.map(
20277
+ (rule) => RobotsRule.from(rule.directive, rule.value)
20278
+ )
20279
+ ]
20280
+ },
20281
+ {
20282
+ user_agent: RobotsRule.from("User-agent", "Nutch"),
20283
+ rules: [RobotsRule.from("Crawl-delay", "10")]
20284
+ },
20285
+ {
20286
+ user_agent: RobotsRule.from("User-agent", "MJ12bot"),
20287
+ rules: [RobotsRule.from("Crawl-delay", "10")]
20288
+ },
20289
+ {
20290
+ user_agent: RobotsRule.from("User-agent", "Pinterest"),
20291
+ rules: [RobotsRule.from("Crawl-delay", "1")]
20292
+ }
20293
+ ]
20294
+ };
20295
+ }
20296
+
19450
20297
  // src/theme.ts
19451
20298
  var SwellTheme3 = class {
19452
20299
  swell;
@@ -19456,11 +20303,10 @@ var SwellTheme3 = class {
19456
20303
  resources;
19457
20304
  liquidSwell;
19458
20305
  themeLoader;
19459
- themeConfigs = null;
19460
20306
  page;
19461
20307
  pageId;
19462
20308
  shopifyCompatibility = null;
19463
- shopifyCompatibilityClass = ShopifyCompatibility3;
20309
+ shopifyCompatibilityClass = ShopifyCompatibility2;
19464
20310
  shopifyCompatibilityConfig = null;
19465
20311
  formData = {};
19466
20312
  globalData = {};
@@ -19475,7 +20321,7 @@ var SwellTheme3 = class {
19475
20321
  this.globals = globals || {};
19476
20322
  this.forms = forms;
19477
20323
  this.resources = resources;
19478
- this.shopifyCompatibilityClass = shopifyCompatibilityClass || ShopifyCompatibility3;
20324
+ this.shopifyCompatibilityClass = shopifyCompatibilityClass || ShopifyCompatibility2;
19479
20325
  this.liquidSwell = new LiquidSwell29({
19480
20326
  theme: this,
19481
20327
  getThemeConfig: this.getThemeConfig.bind(this),
@@ -19490,11 +20336,30 @@ var SwellTheme3 = class {
19490
20336
  });
19491
20337
  this.themeLoader = new ThemeLoader(swell);
19492
20338
  }
20339
+ /**
20340
+ * Getter for theme configs - returns the configs from the loader.
20341
+ * Used by editor and tests to access loaded configs.
20342
+ */
20343
+ get themeConfigs() {
20344
+ const configs = this.themeLoader.getConfigs();
20345
+ return configs.size > 0 ? configs : null;
20346
+ }
20347
+ /**
20348
+ * Setter for theme configs - directly sets configs in the loader.
20349
+ * Used by editor and tests to inject configs without API/KV loading.
20350
+ */
20351
+ set themeConfigs(configs) {
20352
+ if (configs) {
20353
+ this.themeLoader.setConfigs(configs);
20354
+ }
20355
+ }
19493
20356
  getSwellAppThemeProps(swellConfig) {
19494
20357
  return swellConfig?.storefront?.theme || {};
19495
20358
  }
19496
- async initGlobals(pageId, altTemplate) {
20359
+ async initGlobals(pageId, options) {
19497
20360
  this.pageId = pageId;
20361
+ const pageRecord = options?.pageRecord;
20362
+ const altTemplate = options?.altTemplate;
19498
20363
  const trace = createTraceId();
19499
20364
  logger.debug("[SDK] Theme init start", { page: pageId, trace });
19500
20365
  await this.themeLoader.init(this.themeConfigs || void 0);
@@ -19504,6 +20369,7 @@ var SwellTheme3 = class {
19504
20369
  const { settings, request, page, cart, account, customer } = await this.resolvePageData(store, configs, pageId, altTemplate);
19505
20370
  logger.debug("[SDK] Theme page data load done", { page: pageId, trace });
19506
20371
  this.page = page;
20372
+ const countryOptions = getAllCountryOptionTags(geo);
19507
20373
  const globals = {
19508
20374
  ...this.globalData,
19509
20375
  // return all storefront settings in the store
@@ -19519,10 +20385,24 @@ var SwellTheme3 = class {
19519
20385
  geo,
19520
20386
  configs,
19521
20387
  language: configs?.language,
20388
+ ...pageRecord ? getRecordGlobals(this, pageRecord) : {
20389
+ page_title: page.title,
20390
+ page_description: page.description
20391
+ },
20392
+ all_country_option_tags: countryOptions,
20393
+ country_option_tags: countryOptions,
19522
20394
  canonical_url: `${store.url}${this.swell.url?.pathname || ""}`,
20395
+ powered_by_link: POWERED_BY_LINK,
19523
20396
  // Flag to enable Shopify compatibility in sections and tags/filters
19524
20397
  shopify_compatibility: Boolean(settings.shopify_compatibility)
19525
20398
  };
20399
+ switch (pageId) {
20400
+ case "robots.txt":
20401
+ globals.robots = getRobotsGlobals(globals.canonical_url);
20402
+ break;
20403
+ default:
20404
+ break;
20405
+ }
19526
20406
  if (this.shopifyCompatibility) {
19527
20407
  this.shopifyCompatibility.initGlobals(globals);
19528
20408
  }
@@ -19546,10 +20426,8 @@ var SwellTheme3 = class {
19546
20426
  }
19547
20427
  async getSettingsAndConfigs() {
19548
20428
  const geo = GEO_DATA;
19549
- const [storefrontSettings, settingConfigs] = await Promise.all([
19550
- this.swell.getStorefrontSettings(),
19551
- this.getThemeConfigsByPath("theme/config/", ".json")
19552
- ]);
20429
+ const storefrontSettings = await this.swell.getStorefrontSettings();
20430
+ const settingConfigs = this.getThemeConfigsByPath("theme/config/", ".json");
19553
20431
  const configs = {
19554
20432
  theme: {},
19555
20433
  editor: {},
@@ -19644,7 +20522,7 @@ var SwellTheme3 = class {
19644
20522
  $locale: void 0
19645
20523
  };
19646
20524
  if (pageId) {
19647
- const templateConfig = await this.getThemeTemplateConfigByType(
20525
+ const templateConfig = this._getTemplateConfigByType(
19648
20526
  "templates",
19649
20527
  pageId,
19650
20528
  altTemplate
@@ -19857,7 +20735,7 @@ var SwellTheme3 = class {
19857
20735
  return languageConfig;
19858
20736
  }
19859
20737
  const localeShortCode = locale.split("-")[0];
19860
- return (0, import_lodash_es11.reduce)(
20738
+ return (0, import_lodash_es13.reduce)(
19861
20739
  languageConfig,
19862
20740
  (acc, value, key) => {
19863
20741
  if (isObject2(value)) {
@@ -19881,15 +20759,15 @@ var SwellTheme3 = class {
19881
20759
  const translationEnd = translationParts.pop();
19882
20760
  const translationPath = translationParts.join(".");
19883
20761
  const translationConfigGlobal = this.globals.language;
19884
- acc[key] = (0, import_lodash_es11.get)(
20762
+ acc[key] = (0, import_lodash_es13.get)(
19885
20763
  translationConfigGlobal,
19886
20764
  `${translationPath}.$locale.${locale}.${translationEnd}`
19887
- ) || (0, import_lodash_es11.get)(
20765
+ ) || (0, import_lodash_es13.get)(
19888
20766
  translationConfigGlobal,
19889
20767
  `${translationPath}.$locale.${localeShortCode}.${translationEnd}`
19890
- ) || (0, import_lodash_es11.get)(translationConfigGlobal, translationKey) || value;
20768
+ ) || (0, import_lodash_es13.get)(translationConfigGlobal, translationKey) || value;
19891
20769
  } else {
19892
- acc[key] = (0, import_lodash_es11.get)(languageConfig, `$locale.${locale}.${key}`) || (0, import_lodash_es11.get)(languageConfig, `$locale.${localeShortCode}.${key}`) || value;
20770
+ acc[key] = (0, import_lodash_es13.get)(languageConfig, `$locale.${locale}.${key}`) || (0, import_lodash_es13.get)(languageConfig, `$locale.${localeShortCode}.${key}`) || value;
19893
20771
  }
19894
20772
  }
19895
20773
  return acc;
@@ -19904,7 +20782,7 @@ var SwellTheme3 = class {
19904
20782
  }
19905
20783
  return this.shopifyCompatibility;
19906
20784
  };
19907
- if (!Object.keys(configs.editor).length && configs.settings_schema) {
20785
+ if (Object.keys(configs.editor).length <= 0 && configs.settings_schema) {
19908
20786
  const store = await this.swell.storefront.settings.get("store");
19909
20787
  configs.editor = shopifyCompatibility().getEditorConfig(
19910
20788
  configs.settings_schema
@@ -19915,13 +20793,13 @@ var SwellTheme3 = class {
19915
20793
  store.locale || "en-US"
19916
20794
  );
19917
20795
  }
19918
- if (!Object.keys(configs.theme).length && configs.settings_data) {
20796
+ if (Object.keys(configs.theme).length <= 0 && configs.settings_data) {
19919
20797
  configs.theme = shopifyCompatibility().getThemeConfig(
19920
20798
  configs.settings_data
19921
20799
  );
19922
20800
  this.themeSettingFilePath = "theme/config/settings_data.json";
19923
20801
  }
19924
- if (!Object.keys(configs.presets).length && configs.settings_data) {
20802
+ if (Object.keys(configs.presets).length <= 0 && configs.settings_data) {
19925
20803
  configs.presets = shopifyCompatibility().getPresetsConfig(
19926
20804
  configs.settings_data
19927
20805
  );
@@ -19939,7 +20817,7 @@ var SwellTheme3 = class {
19939
20817
  this.shopifyCompatibility.adaptPageData(pageData);
19940
20818
  }
19941
20819
  async getLocaleConfig(localeCode = "en", suffix = ".json") {
19942
- const allLocaleConfigs = await this.getThemeConfigsByPath(
20820
+ const allLocaleConfigs = this.getThemeConfigsByPath(
19943
20821
  "theme/locales/",
19944
20822
  suffix
19945
20823
  );
@@ -20043,22 +20921,24 @@ var SwellTheme3 = class {
20043
20921
  }
20044
20922
  return resolvedUrl;
20045
20923
  }
20046
- async getAllThemeConfigs() {
20047
- if (this.themeConfigs === null) {
20048
- const configs = await this.themeLoader.loadTheme();
20049
- const configsByPath = /* @__PURE__ */ new Map();
20050
- for (const config of configs) {
20051
- configsByPath.set(config.file_path, config);
20052
- }
20053
- this.themeConfigs = configsByPath;
20054
- }
20055
- return this.themeConfigs;
20056
- }
20057
- /**
20058
- * Preloads updated theme configs. Used to optimize initial theme load.
20059
- */
20060
20924
  async preloadThemeConfigs(payload) {
20061
- await this.themeLoader.preloadTheme(payload);
20925
+ const result = await this.themeLoader.updateThemeCache(payload);
20926
+ if (result.warnings && result.warnings.length > 0) {
20927
+ const rejected = result.warnings.filter(
20928
+ (w) => w.reason === "rejected_5mb" || w.reason === "exceeded_25mb"
20929
+ ).length;
20930
+ const warned = result.warnings.filter(
20931
+ (w) => w.reason === "warning_1mb"
20932
+ ).length;
20933
+ logger.warn("[Theme] File size issues detected during cache update", {
20934
+ totalWarnings: result.warnings.length,
20935
+ rejected,
20936
+ warned,
20937
+ details: result.warnings.slice(0, 5)
20938
+ // Log first 5 warnings for debugging
20939
+ });
20940
+ }
20941
+ return result;
20062
20942
  }
20063
20943
  getPageConfigPath(pageId, altTemplate) {
20064
20944
  if (this.shopifyCompatibility) {
@@ -20071,16 +20951,10 @@ var SwellTheme3 = class {
20071
20951
  return `${withSuffix(`theme/templates/${pageId}`, altTemplate)}.json`;
20072
20952
  }
20073
20953
  async getThemeConfig(filePath) {
20074
- if (this.themeConfigs !== null) {
20075
- return this.themeConfigs.get(filePath) ?? null;
20076
- }
20077
- return this.themeLoader.fetchThemeConfig(filePath);
20954
+ return this.themeLoader.getConfig(filePath);
20078
20955
  }
20079
- async getThemeConfigsByPath(pathPrefix, pathSuffix) {
20080
- const configs = await this.themeLoader.fetchThemeConfigsByPath(
20081
- pathPrefix,
20082
- pathSuffix
20083
- );
20956
+ getThemeConfigsByPath(pathPrefix, pathSuffix) {
20957
+ const configs = this.themeLoader.getConfigsByPath(pathPrefix, pathSuffix);
20084
20958
  const configsByPath = /* @__PURE__ */ new Map();
20085
20959
  for (const config of configs) {
20086
20960
  configsByPath.set(config.file_path, config);
@@ -20088,33 +20962,47 @@ var SwellTheme3 = class {
20088
20962
  return configsByPath;
20089
20963
  }
20090
20964
  async getThemeTemplateConfig(filePath) {
20091
- if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
20092
- return this.getThemeConfig(filePath);
20093
- }
20094
- const jsonTemplate = await this.getThemeConfig(`${filePath}.json`);
20095
- if (jsonTemplate) {
20096
- return jsonTemplate;
20097
- }
20098
- return this.getThemeConfig(`${filePath}.liquid`);
20965
+ return this._getTemplateConfig(filePath);
20099
20966
  }
20100
- async getThemeTemplateConfigByType(type, name, suffix) {
20967
+ /**
20968
+ * Internal synchronous helper for getting template configs by type.
20969
+ * Used internally within theme.ts to avoid async overhead.
20970
+ */
20971
+ _getTemplateConfigByType(type, name, suffix) {
20101
20972
  const templatesByPriority = [withSuffix(`${type}/${name}`, suffix)];
20102
20973
  if (this.shopifyCompatibility) {
20103
20974
  const path = this.shopifyCompatibility.getThemeFilePath(type, name);
20104
20975
  templatesByPriority.push(withSuffix(path, suffix));
20105
20976
  }
20106
20977
  for (const filePath of templatesByPriority) {
20107
- const templateConfig = await this.getThemeTemplateConfig(
20108
- `theme/${filePath}`
20109
- );
20978
+ const templateConfig = this._getTemplateConfig(`theme/${filePath}`);
20110
20979
  if (templateConfig) {
20111
20980
  return templateConfig;
20112
20981
  }
20113
20982
  }
20114
20983
  return null;
20115
20984
  }
20985
+ /**
20986
+ * Internal synchronous helper for getting template configs.
20987
+ */
20988
+ _getTemplateConfig(filePath) {
20989
+ if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
20990
+ return this.themeLoader.getConfig(filePath);
20991
+ }
20992
+ const jsonTemplate = this.themeLoader.getConfig(`${filePath}.json`);
20993
+ if (jsonTemplate) {
20994
+ return jsonTemplate;
20995
+ }
20996
+ return this.themeLoader.getConfig(`${filePath}.liquid`);
20997
+ }
20998
+ async getThemeTemplateConfigByType(type, name, suffix) {
20999
+ return this._getTemplateConfigByType(type, name, suffix);
21000
+ }
21001
+ async getAssetConfig(assetName) {
21002
+ return await this.getThemeConfig(`theme/assets/${assetName}`) ?? await this.getThemeConfig(`assets/${assetName}`) ?? null;
21003
+ }
20116
21004
  async getAssetUrl(filePath) {
20117
- const assetConfig = await this.getThemeConfig(`theme/assets/${filePath}`) || await this.getThemeConfig(`assets/${filePath}`);
21005
+ const assetConfig = await this.getAssetConfig(filePath);
20118
21006
  const file = assetConfig?.file;
20119
21007
  if (!file) {
20120
21008
  return null;
@@ -20131,17 +21019,8 @@ var SwellTheme3 = class {
20131
21019
  return "";
20132
21020
  }
20133
21021
  template = unescapeLiquidSyntax(template);
20134
- const trace = createTraceId();
20135
21022
  try {
20136
- logger.debug("[SDK] Render template start", {
20137
- config: config.name,
20138
- trace
20139
- });
20140
21023
  const result = await this.liquidSwell.parseAndRender(template, data);
20141
- logger.debug("[SDK] Render template end", {
20142
- config: config.name,
20143
- trace
20144
- });
20145
21024
  return result;
20146
21025
  } catch (err) {
20147
21026
  logger.error(err);
@@ -20158,10 +21037,7 @@ var SwellTheme3 = class {
20158
21037
  }
20159
21038
  async getSectionSchema(sectionName) {
20160
21039
  let result;
20161
- const config = await this.getThemeTemplateConfigByType(
20162
- "sections",
20163
- sectionName
20164
- );
21040
+ const config = this._getTemplateConfigByType("sections", sectionName);
20165
21041
  if (config?.file_path?.endsWith(".json")) {
20166
21042
  try {
20167
21043
  result = import_json56.default.parse(config.file_data) || void 0;
@@ -20239,10 +21115,7 @@ var SwellTheme3 = class {
20239
21115
  return content;
20240
21116
  }
20241
21117
  async renderLayoutTemplate(name, data) {
20242
- const templateConfig = await this.getThemeTemplateConfigByType(
20243
- "layouts",
20244
- name
20245
- );
21118
+ const templateConfig = this._getTemplateConfigByType("layouts", name);
20246
21119
  if (!templateConfig) {
20247
21120
  throw new Error(`Layout template not found: ${name}`);
20248
21121
  }
@@ -20277,17 +21150,14 @@ ${content.slice(pos)}`;
20277
21150
  async renderPageTemplate(name, data, altTemplateId) {
20278
21151
  let templateConfig = null;
20279
21152
  if (altTemplateId) {
20280
- templateConfig = await this.getThemeTemplateConfigByType(
21153
+ templateConfig = this._getTemplateConfigByType(
20281
21154
  "templates",
20282
21155
  name,
20283
21156
  altTemplateId
20284
21157
  );
20285
21158
  }
20286
21159
  if (!templateConfig) {
20287
- templateConfig = await this.getThemeTemplateConfigByType(
20288
- "templates",
20289
- name
20290
- );
21160
+ templateConfig = this._getTemplateConfigByType("templates", name);
20291
21161
  }
20292
21162
  if (templateConfig) {
20293
21163
  const templatePath = name.split("/").splice(1).join("/") || null;
@@ -20388,7 +21258,7 @@ ${content.slice(pos)}`;
20388
21258
  }
20389
21259
  const [sectionKey, originalPageId] = sectionId.split(/__/).reverse();
20390
21260
  const pageId = (originalPageId || "").replaceAll("_", "/");
20391
- const templateConfig = await this.getThemeTemplateConfigByType(
21261
+ const templateConfig = this._getTemplateConfigByType(
20392
21262
  pageId ? "templates" : "sections",
20393
21263
  pageId ? pageId : sectionKey
20394
21264
  );
@@ -20416,13 +21286,42 @@ ${content.slice(pos)}`;
20416
21286
  }
20417
21287
  return "";
20418
21288
  }
20419
- async renderLayout(layoutName, data) {
21289
+ async renderLayout(layoutName, data, contentForLayout, contentForHeader) {
20420
21290
  layoutName = layoutName || this.liquidSwell.layoutName;
20421
21291
  if (layoutName) {
20422
- return this.renderLayoutTemplate(layoutName, data);
21292
+ if (data) {
21293
+ data = await this.renderDataFields(data, [
21294
+ "page_title",
21295
+ "page_description"
21296
+ ]);
21297
+ }
21298
+ return this.renderLayoutTemplate(layoutName, {
21299
+ ...data,
21300
+ content_for_layout: contentForLayout,
21301
+ content_for_header: contentForHeader
21302
+ });
20423
21303
  } else {
20424
- return data?.content_for_layout || "";
21304
+ return contentForLayout || "";
21305
+ }
21306
+ }
21307
+ async renderDataFields(data, fields) {
21308
+ const promises = [];
21309
+ for (const key of fields) {
21310
+ if (typeof data[key] === "string") {
21311
+ promises.push(
21312
+ this.renderTemplateString(data[key], data).then((value) => {
21313
+ if (data) {
21314
+ data[key] = value;
21315
+ }
21316
+ })
21317
+ );
21318
+ }
21319
+ }
21320
+ if (promises.length > 0) {
21321
+ data = { ...data };
21322
+ await Promise.all(promises);
20425
21323
  }
21324
+ return data;
20426
21325
  }
20427
21326
  getContentForHeader() {
20428
21327
  let content = "\n";
@@ -20534,7 +21433,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20534
21433
  return defaults;
20535
21434
  }
20536
21435
  async getAllSections() {
20537
- const configs = await this.getThemeConfigsByPath("theme/sections/");
21436
+ const configs = this.getThemeConfigsByPath("theme/sections/");
20538
21437
  return getAllSections(configs, this.getTemplateSchema.bind(this));
20539
21438
  }
20540
21439
  async getPageSections(sectionGroup, resolveSettings = true) {
@@ -20561,7 +21460,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20561
21460
  const sectionName = sectionSchema?.label || sectionFileName;
20562
21461
  let sourcePath = "";
20563
21462
  if (group) {
20564
- const sectionConfig = await this.getThemeTemplateConfigByType(
21463
+ const sectionConfig = this._getTemplateConfigByType(
20565
21464
  "sections",
20566
21465
  `${sectionFileName}.json`
20567
21466
  );
@@ -20578,7 +21477,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20578
21477
  * Get a list of sections and section groups in a page layout.
20579
21478
  */
20580
21479
  async getPageSectionGroups(pageId, altTemplate) {
20581
- const pageConfig = await this.getThemeTemplateConfigByType(
21480
+ const pageConfig = this._getTemplateConfigByType(
20582
21481
  "templates",
20583
21482
  pageId,
20584
21483
  altTemplate
@@ -20590,8 +21489,10 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20590
21489
  const pageLayout = pageSchema.layout || "";
20591
21490
  const pageSectionGroups = [];
20592
21491
  this.pageSectionGroups = pageSectionGroups;
20593
- await this.renderLayout(pageLayout, {
20594
- content_for_layout: new RenderDrop(() => {
21492
+ await this.renderLayout(
21493
+ pageLayout,
21494
+ {},
21495
+ new RenderDrop(() => {
20595
21496
  pageSectionGroups.push({
20596
21497
  prop: SECTION_GROUP_CONTENT,
20597
21498
  label: "Template",
@@ -20600,7 +21501,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20600
21501
  });
20601
21502
  return "";
20602
21503
  })
20603
- });
21504
+ );
20604
21505
  this.pageSectionGroups = null;
20605
21506
  return pageSectionGroups;
20606
21507
  }
@@ -20642,8 +21543,8 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20642
21543
  return Promise.all(
20643
21544
  sectionConfigs.map(async (sectionConfig, index) => {
20644
21545
  const { section, schema } = sectionConfig;
20645
- const settings = schema?.fields && this.globals ? resolveSectionSettings(this, sectionConfig) : { ...sectionConfig.settings };
20646
- const templateConfig = await this.getThemeTemplateConfigByType(
21546
+ const settings = schema?.fields && this.globals ? resolveSectionSettings(this, sectionConfig, index) : { ...sectionConfig.settings };
21547
+ const templateConfig = this._getTemplateConfigByType(
20647
21548
  "sections",
20648
21549
  `${section.type}.liquid`
20649
21550
  );
@@ -20656,7 +21557,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20656
21557
  });
20657
21558
  if (settings?.section?.custom_css) {
20658
21559
  output += `<style>${scopeCustomCSS(
20659
- settings?.section.custom_css,
21560
+ settings.section.custom_css,
20660
21561
  sectionConfig.id
20661
21562
  )}</style>`;
20662
21563
  }
@@ -20686,8 +21587,8 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20686
21587
  const keyParts = key?.split(".") || [];
20687
21588
  const keyName = keyParts.pop() || "";
20688
21589
  const keyPath = keyParts.join(".");
20689
- const langObject = (0, import_lodash_es11.get)(langConfig, keyPath);
20690
- let localeValue = (0, import_lodash_es11.get)(langObject?.[localeCode], keyName) || (0, import_lodash_es11.get)(langObject?.[localeCode.split("-")[0]], keyName) || langObject?.[keyName];
21590
+ const langObject = (0, import_lodash_es13.get)(langConfig, keyPath);
21591
+ let localeValue = (0, import_lodash_es13.get)(langObject?.[localeCode], keyName) || (0, import_lodash_es13.get)(langObject?.[localeCode.split("-")[0]], keyName) || langObject?.[keyName];
20691
21592
  if (data?.count !== void 0 && localeValue?.one) {
20692
21593
  localeValue = data.count === 1 ? localeValue.one : localeValue.other;
20693
21594
  }
@@ -20721,7 +21622,7 @@ var PageNotFound = class extends PageError {
20721
21622
  super(title, status, description);
20722
21623
  }
20723
21624
  };
20724
- function resolveSectionSettings(theme, sectionConfig) {
21625
+ function resolveSectionSettings(theme, sectionConfig, index) {
20725
21626
  const { settings, schema } = sectionConfig;
20726
21627
  if (!schema || !settings?.section?.settings) {
20727
21628
  return settings;
@@ -20752,7 +21653,10 @@ function resolveSectionSettings(theme, sectionConfig) {
20752
21653
  settings.section.settings,
20753
21654
  editorSettings
20754
21655
  ),
20755
- blocks
21656
+ blocks,
21657
+ index0: index,
21658
+ index: typeof index === "number" ? index + 1 : void 0,
21659
+ location: getSectionLocation(settings.section.type)
20756
21660
  }
20757
21661
  };
20758
21662
  }
@@ -20766,7 +21670,7 @@ function fillDefaultThemeSettings(themeSettings, editorSchemaSettings) {
20766
21670
  }
20767
21671
  }
20768
21672
  function resolveThemeSettings(theme, themeSettings, editorSchemaSettings) {
20769
- const settings = (0, import_lodash_es11.cloneDeep)(themeSettings);
21673
+ const settings = (0, import_lodash_es13.cloneDeep)(themeSettings);
20770
21674
  if (settings.$locale) {
20771
21675
  const { locale } = theme.swell.getStorefrontLocalization();
20772
21676
  const localeConfig = settings.$locale[locale] || {};
@@ -20776,16 +21680,16 @@ function resolveThemeSettings(theme, themeSettings, editorSchemaSettings) {
20776
21680
  }
20777
21681
  }
20778
21682
  }
20779
- (0, import_lodash_es11.each)(settings, (value, key) => {
21683
+ (0, import_lodash_es13.each)(settings, (value, key) => {
20780
21684
  const setting = (editorSchemaSettings && findEditorSetting(editorSchemaSettings, key)) ?? null;
20781
21685
  if (isObject2(value) && !(value instanceof StorefrontResource)) {
20782
21686
  switch (setting?.type) {
20783
21687
  case "color_scheme_group": {
20784
- (0, import_lodash_es11.each)(value, (scheme, schemeId) => {
21688
+ (0, import_lodash_es13.each)(value, (scheme, schemeId) => {
20785
21689
  if (isObject2(scheme) && typeof scheme.settings === "object" && scheme.settings) {
20786
21690
  const settings2 = scheme.settings;
20787
- (0, import_lodash_es11.each)(settings2, (colorValue, colorId) => {
20788
- const fieldDef = (0, import_lodash_es11.find)(setting.fields, { id: colorId });
21691
+ (0, import_lodash_es13.each)(settings2, (colorValue, colorId) => {
21692
+ const fieldDef = (0, import_lodash_es13.find)(setting.fields, { id: colorId });
20789
21693
  if (fieldDef?.type === "color" && colorValue) {
20790
21694
  scheme.id = schemeId;
20791
21695
  settings2[colorId] = new ThemeColor(colorValue);
@@ -20836,7 +21740,7 @@ function resolveThemeSettings(theme, themeSettings, editorSchemaSettings) {
20836
21740
  }
20837
21741
  function findThemeSettingsByType(type, themeSettings, editorSchemaSettings) {
20838
21742
  const foundSettings = [];
20839
- (0, import_lodash_es11.each)(themeSettings, (value, key) => {
21743
+ (0, import_lodash_es13.each)(themeSettings, (value, key) => {
20840
21744
  if (isObject2(value) && !(value instanceof ThemeFont) && !(value instanceof StorefrontResource)) {
20841
21745
  foundSettings.push(
20842
21746
  ...findThemeSettingsByType(type, value, editorSchemaSettings)
@@ -21005,7 +21909,7 @@ function isChildItemActive(items) {
21005
21909
  }
21006
21910
  function getMenuItemValueId(value) {
21007
21911
  const fallback = typeof value === "string" ? value : "";
21008
- const slug = (0, import_lodash_es12.get)(value, "id", (0, import_lodash_es12.get)(value, "slug", fallback)) || "";
21912
+ const slug = (0, import_lodash_es14.get)(value, "id", (0, import_lodash_es14.get)(value, "slug", fallback)) || "";
21009
21913
  return slug;
21010
21914
  }
21011
21915
  async function getMenuItemUrlAndResource(theme, menuItem) {
@@ -21379,6 +22283,7 @@ function getResourceQuery(slug, query) {
21379
22283
  getEasyblocksComponentDefinitions,
21380
22284
  getEasyblocksPagePropsWithConfigs,
21381
22285
  getEasyblocksPageTemplate,
22286
+ getKVFlavor,
21382
22287
  getLayoutSectionGroups,
21383
22288
  getMenuItemStorefrontUrl,
21384
22289
  getMenuItemUrlAndResource,
@@ -21386,6 +22291,7 @@ function getResourceQuery(slug, query) {
21386
22291
  getPage,
21387
22292
  getPageSections,
21388
22293
  getSectionGroupProp,
22294
+ getSectionLocation,
21389
22295
  getSectionSettingsFromProps,
21390
22296
  getThemeSettingsFromProps,
21391
22297
  isArray,
@@ -21393,6 +22299,7 @@ function getResourceQuery(slug, query) {
21393
22299
  isObject,
21394
22300
  md5,
21395
22301
  removeCircularReferences,
22302
+ resetKVFlavorCache,
21396
22303
  resolveAsyncResources,
21397
22304
  resolveLookupCollection,
21398
22305
  resolveMenuItemUrlAndResource,