@swell/apps-sdk 1.0.149 → 1.0.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/index.cjs +1101 -446
  2. package/dist/index.cjs.map +4 -4
  3. package/dist/index.js +1079 -446
  4. package/dist/index.js.map +4 -4
  5. package/dist/index.mjs +1090 -446
  6. package/dist/index.mjs.map +4 -4
  7. package/dist/src/cache/constants.d.ts +7 -0
  8. package/dist/src/cache/index.d.ts +4 -0
  9. package/dist/src/cache/kv-variety.d.ts +10 -0
  10. package/dist/src/cache/theme-file-storage.d.ts +88 -0
  11. package/dist/src/cache/worker-cache-proxy.d.ts +31 -0
  12. package/dist/src/compatibility/drops/all_products.d.ts +1 -1
  13. package/dist/src/compatibility/drops/articles.d.ts +1 -1
  14. package/dist/src/compatibility/drops/blogs.d.ts +1 -1
  15. package/dist/src/compatibility/drops/collections.d.ts +2 -3
  16. package/dist/src/compatibility/drops/images.d.ts +1 -1
  17. package/dist/src/compatibility/drops/pages.d.ts +2 -3
  18. package/dist/src/compatibility/shopify-objects/collections.d.ts +2 -2
  19. package/dist/src/content.d.ts +3 -3
  20. package/dist/src/liquid/filters/shopify/default_pagination.d.ts +1 -1
  21. package/dist/src/resources/account.d.ts +4 -4
  22. package/dist/src/resources/addresses.d.ts +7 -0
  23. package/dist/src/resources/blog.d.ts +5 -4
  24. package/dist/src/resources/blog_category.d.ts +5 -4
  25. package/dist/src/resources/cart.d.ts +4 -4
  26. package/dist/src/resources/categories.d.ts +7 -0
  27. package/dist/src/resources/category.d.ts +5 -4
  28. package/dist/src/resources/index.d.ts +18 -9
  29. package/dist/src/resources/order.d.ts +5 -4
  30. package/dist/src/resources/orders.d.ts +7 -0
  31. package/dist/src/resources/page.d.ts +5 -4
  32. package/dist/src/resources/predictive_search.d.ts +6 -0
  33. package/dist/src/resources/product.d.ts +5 -19
  34. package/dist/src/resources/product_helpers.d.ts +12 -2
  35. package/dist/src/resources/product_recommendations.d.ts +6 -0
  36. package/dist/src/resources/search.d.ts +6 -0
  37. package/dist/src/resources/subscription.d.ts +7 -0
  38. package/dist/src/resources/subscriptions.d.ts +7 -0
  39. package/dist/src/resources/swell_types.d.ts +66 -9
  40. package/dist/src/resources/variant.d.ts +8 -8
  41. package/dist/src/resources.d.ts +11 -12
  42. package/dist/src/theme/theme-loader.d.ts +36 -44
  43. package/dist/src/theme.d.ts +22 -7
  44. package/dist/src/utils/index.d.ts +1 -0
  45. package/dist/src/utils/kv-flavor.d.ts +7 -0
  46. package/dist/types/cloudflare.d.ts +14 -3
  47. package/dist/types/shopify.d.ts +2 -2
  48. package/dist/types/swell.d.ts +3 -1
  49. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -626,18 +626,12 @@ var StorefrontResource = class {
626
626
  }
627
627
  return instance[prop];
628
628
  }
629
- // add additional properties to the loaded result
630
- _transformResult(result) {
631
- return result;
632
- }
633
629
  async _get(..._args) {
634
630
  if (this._getter) {
635
631
  const getter = this._getter.bind(
636
632
  this
637
633
  );
638
634
  return Promise.resolve().then(getter).then((result) => {
639
- return this._transformResult(result);
640
- }).then((result) => {
641
635
  this._result = result ?? null;
642
636
  if (result) {
643
637
  Object.assign(this, result);
@@ -720,7 +714,6 @@ function cloneStorefrontResource(input) {
720
714
  });
721
715
  const clone = new ClonedClass(input._getter);
722
716
  clone._params = input._params;
723
- clone._transformResult = input._transformResult.bind(clone);
724
717
  Object.defineProperty(clone, "_resourceName", {
725
718
  value: resourceName
726
719
  });
@@ -774,7 +767,11 @@ var SwellStorefrontCollection = class _SwellStorefrontCollection extends SwellSt
774
767
  limit = DEFAULT_QUERY_PAGE_LIMIT;
775
768
  name;
776
769
  constructor(swell, collection, query = {}, getter) {
777
- super(swell, collection, getter);
770
+ super(
771
+ swell,
772
+ collection,
773
+ getter
774
+ );
778
775
  this._query = this._initQuery(query);
779
776
  if (!getter) {
780
777
  this._setGetter(this._defaultGetter());
@@ -950,8 +947,6 @@ var SwellStorefrontRecord = class extends SwellStorefrontResource {
950
947
  getter,
951
948
  isResourceCacheble(this._collection)
952
949
  ).then((result) => {
953
- return this._transformResult(result);
954
- }).then((result) => {
955
950
  this._result = result;
956
951
  if (result) {
957
952
  Object.assign(this, result);
@@ -6697,6 +6692,50 @@ function md5(inputString) {
6697
6692
  return rh(a) + rh(b) + rh(c) + rh(d);
6698
6693
  }
6699
6694
 
6695
+ // src/utils/kv-flavor.ts
6696
+ var cachedKVFlavor;
6697
+ function getKVFlavor(workerEnv) {
6698
+ if (cachedKVFlavor) {
6699
+ return cachedKVFlavor;
6700
+ }
6701
+ if (!workerEnv?.THEME) {
6702
+ cachedKVFlavor = "memory";
6703
+ return cachedKVFlavor;
6704
+ }
6705
+ const kvFlavor = workerEnv.KV_FLAVOR;
6706
+ if (kvFlavor) {
6707
+ const flavorLower = String(kvFlavor).toLowerCase();
6708
+ switch (flavorLower) {
6709
+ case "cloudflare":
6710
+ case "cf":
6711
+ cachedKVFlavor = "cf";
6712
+ break;
6713
+ case "miniflare":
6714
+ cachedKVFlavor = "miniflare";
6715
+ break;
6716
+ case "memory":
6717
+ cachedKVFlavor = "memory";
6718
+ break;
6719
+ default:
6720
+ logger.warn(`[KV] Unknown KV_FLAVOR: ${kvFlavor}, using miniflare`);
6721
+ cachedKVFlavor = "miniflare";
6722
+ }
6723
+ return cachedKVFlavor;
6724
+ }
6725
+ try {
6726
+ if (typeof navigator !== "undefined" && navigator.userAgent === "Cloudflare-Workers") {
6727
+ cachedKVFlavor = "cf";
6728
+ return cachedKVFlavor;
6729
+ }
6730
+ } catch {
6731
+ }
6732
+ cachedKVFlavor = "miniflare";
6733
+ return cachedKVFlavor;
6734
+ }
6735
+ function resetKVFlavorCache() {
6736
+ cachedKVFlavor = void 0;
6737
+ }
6738
+
6700
6739
  // src/utils/index.ts
6701
6740
  function isSectionConfig(config, themeConfigs) {
6702
6741
  if (!config.file_path.startsWith("theme/sections/")) {
@@ -7226,68 +7265,584 @@ function buildStores2() {
7226
7265
 
7227
7266
  // src/cache/theme-cache.ts
7228
7267
  var TTL = 90 * 24 * 60 * 60 * 1e3;
7229
- var ThemeCache = class extends Cache {
7230
- constructor(options) {
7231
- super({
7232
- ttl: TTL,
7233
- ...options
7268
+
7269
+ // src/cache/theme-file-storage.ts
7270
+ import bluebird2 from "bluebird";
7271
+
7272
+ // src/cache/kv-variety.ts
7273
+ import bluebird from "bluebird";
7274
+ var { Promise: Promise2 } = bluebird;
7275
+ var CFKV = class {
7276
+ constructor(kv) {
7277
+ this.kv = kv;
7278
+ }
7279
+ async get(keys) {
7280
+ if (keys.length === 0) {
7281
+ return /* @__PURE__ */ new Map();
7282
+ }
7283
+ const result = await this.kv.get(keys, "text");
7284
+ if (!(result instanceof Map)) {
7285
+ const map = /* @__PURE__ */ new Map();
7286
+ for (const key of keys) {
7287
+ map.set(key, null);
7288
+ }
7289
+ return map;
7290
+ }
7291
+ return result;
7292
+ }
7293
+ async put(key, value, metadata) {
7294
+ await this.kv.put(key, value, { metadata });
7295
+ }
7296
+ };
7297
+ var MiniflareKV = class {
7298
+ constructor(kv) {
7299
+ this.kv = kv;
7300
+ }
7301
+ async get(keys) {
7302
+ if (keys.length === 0) {
7303
+ return /* @__PURE__ */ new Map();
7304
+ }
7305
+ const result = /* @__PURE__ */ new Map();
7306
+ await Promise2.map(
7307
+ keys,
7308
+ async (key) => {
7309
+ const value = await this.kv.get(key, "text");
7310
+ result.set(key, value);
7311
+ },
7312
+ { concurrency: 50 }
7313
+ );
7314
+ return result;
7315
+ }
7316
+ async put(key, value, metadata) {
7317
+ await this.kv.put(key, value, { metadata });
7318
+ }
7319
+ };
7320
+ var MemoryKV = class {
7321
+ store = /* @__PURE__ */ new Map();
7322
+ async get(keys) {
7323
+ const result = /* @__PURE__ */ new Map();
7324
+ for (const key of keys) {
7325
+ const entry = this.store.get(key);
7326
+ result.set(key, entry?.value ?? null);
7327
+ }
7328
+ return result;
7329
+ }
7330
+ async put(key, value, metadata) {
7331
+ this.store.set(key, { value, metadata });
7332
+ }
7333
+ };
7334
+ function createClientKV(env, flavor = "cf") {
7335
+ if (env?.THEME) {
7336
+ if (flavor === "miniflare") {
7337
+ return new MiniflareKV(env.THEME);
7338
+ }
7339
+ return new CFKV(env.THEME);
7340
+ }
7341
+ return new MemoryKV();
7342
+ }
7343
+
7344
+ // src/cache/theme-file-storage.ts
7345
+ var { Promise: Promise3 } = bluebird2;
7346
+ var ThemeFileStorage = class {
7347
+ kv;
7348
+ maxConcurrency;
7349
+ maxBatchSize = 20 * 1024 * 1024;
7350
+ // 20MB safety margin
7351
+ constructor(env, flavor = "cf") {
7352
+ this.kv = createClientKV(env, flavor);
7353
+ this.maxConcurrency = flavor === "miniflare" ? 50 : 6;
7354
+ }
7355
+ /**
7356
+ * Build a KV storage key from a file hash
7357
+ */
7358
+ buildKey(hash) {
7359
+ return `file_data:${hash}`;
7360
+ }
7361
+ /**
7362
+ * Extract hash from a KV storage key
7363
+ */
7364
+ extractHashFromKey(key) {
7365
+ return key.replace("file_data:", "");
7366
+ }
7367
+ /**
7368
+ * Plan GET batches based on file sizes to avoid 413 errors
7369
+ * Uses round-robin distribution for even batch sizes
7370
+ */
7371
+ planGetBatches(configs) {
7372
+ if (configs.length === 0) {
7373
+ return [];
7374
+ }
7375
+ const sorted = [...configs].sort((a, b) => {
7376
+ const sizeA = a.file?.length || 0;
7377
+ const sizeB = b.file?.length || 0;
7378
+ return sizeB - sizeA;
7379
+ });
7380
+ const totalSize = sorted.reduce((sum, config) => {
7381
+ return sum + (config.file?.length || 0);
7382
+ }, 0);
7383
+ const sizeBatches = Math.ceil(totalSize / this.maxBatchSize);
7384
+ const keyBatches = Math.ceil(sorted.length / 100);
7385
+ const targetBatches = Math.max(sizeBatches, keyBatches);
7386
+ const batches = Array.from(
7387
+ { length: targetBatches },
7388
+ () => ({
7389
+ configs: [],
7390
+ keys: [],
7391
+ estimatedSize: 0
7392
+ })
7393
+ );
7394
+ sorted.forEach((config, index) => {
7395
+ const batchIndex = index % targetBatches;
7396
+ const batch = batches[batchIndex];
7397
+ batch.configs.push(config);
7398
+ batch.keys.push(this.buildKey(config.hash));
7399
+ batch.estimatedSize += config.file?.length || 0;
7400
+ });
7401
+ return batches.filter((batch) => batch.configs.length > 0);
7402
+ }
7403
+ /**
7404
+ * Load a single batch from KV storage
7405
+ */
7406
+ async loadBatch(batch) {
7407
+ return this.kv.get(batch.keys);
7408
+ }
7409
+ /**
7410
+ * Merge batch results with original configs
7411
+ */
7412
+ mergeResults(configs, batchResults) {
7413
+ const allData = /* @__PURE__ */ new Map();
7414
+ for (const batchResult of batchResults) {
7415
+ for (const [key, value] of batchResult.entries()) {
7416
+ allData.set(key, value);
7417
+ }
7418
+ }
7419
+ return configs.map((config) => {
7420
+ const key = this.buildKey(config.hash);
7421
+ const fileData = allData.get(key);
7422
+ if (fileData) {
7423
+ return {
7424
+ ...config,
7425
+ file_data: fileData
7426
+ };
7427
+ }
7428
+ return config;
7429
+ });
7430
+ }
7431
+ async getFiles(configs) {
7432
+ if (configs.length === 0) {
7433
+ return [];
7434
+ }
7435
+ const trace = createTraceId();
7436
+ const batches = this.planGetBatches(configs);
7437
+ const totalSize = batches.reduce((sum, b) => sum + b.estimatedSize, 0);
7438
+ const maxBatchSize = Math.max(...batches.map((b) => b.estimatedSize));
7439
+ logger.debug("[ThemeFileStorage] Loading files start", {
7440
+ totalConfigs: configs.length,
7441
+ batchCount: batches.length,
7442
+ maxBatchSize,
7443
+ totalSize,
7444
+ trace
7445
+ });
7446
+ const results = await Promise3.map(
7447
+ batches,
7448
+ (batch) => this.loadBatch(batch),
7449
+ { concurrency: Math.min(this.maxConcurrency, batches.length) }
7450
+ );
7451
+ const mergedConfigs = this.mergeResults(configs, results);
7452
+ const loadedCount = mergedConfigs.filter((c) => c.file_data).length;
7453
+ logger.debug("[ThemeFileStorage] Loading files end", {
7454
+ requested: configs.length,
7455
+ loaded: loadedCount,
7456
+ missing: configs.length - loadedCount,
7457
+ batches: batches.length,
7458
+ trace
7459
+ });
7460
+ return mergedConfigs;
7461
+ }
7462
+ /**
7463
+ * Validate file sizes and categorize by threshold
7464
+ */
7465
+ validateFiles(configs) {
7466
+ const valid = [];
7467
+ const warnings = [];
7468
+ for (const config of configs) {
7469
+ if (!config.file_data) {
7470
+ continue;
7471
+ }
7472
+ const size = config.file?.length || 0;
7473
+ if (size >= 25 * 1024 * 1024) {
7474
+ warnings.push({
7475
+ hash: config.hash,
7476
+ filePath: config.file_path,
7477
+ size,
7478
+ reason: "exceeded_25mb",
7479
+ action: "rejected"
7480
+ });
7481
+ } else if (size >= 5 * 1024 * 1024) {
7482
+ warnings.push({
7483
+ hash: config.hash,
7484
+ filePath: config.file_path,
7485
+ size,
7486
+ reason: "rejected_5mb",
7487
+ action: "rejected"
7488
+ });
7489
+ } else {
7490
+ if (size >= 1024 * 1024) {
7491
+ warnings.push({
7492
+ hash: config.hash,
7493
+ filePath: config.file_path,
7494
+ size,
7495
+ reason: "warning_1mb",
7496
+ action: "stored"
7497
+ });
7498
+ }
7499
+ valid.push(config);
7500
+ }
7501
+ }
7502
+ return { valid, warnings };
7503
+ }
7504
+ /**
7505
+ * Check which files already exist in KV storage
7506
+ * Uses batch planning to avoid 413 errors when checking existence
7507
+ */
7508
+ async checkExistence(configs) {
7509
+ if (configs.length === 0) {
7510
+ return /* @__PURE__ */ new Set();
7511
+ }
7512
+ const existing = /* @__PURE__ */ new Set();
7513
+ const batches = this.planGetBatches(configs);
7514
+ const results = await Promise3.map(
7515
+ batches,
7516
+ (batch) => this.kv.get(batch.keys),
7517
+ { concurrency: this.maxConcurrency }
7518
+ );
7519
+ for (const batchResult of results) {
7520
+ for (const [key, value] of batchResult.entries()) {
7521
+ if (value !== null) {
7522
+ const hash = this.extractHashFromKey(key);
7523
+ existing.add(hash);
7524
+ }
7525
+ }
7526
+ }
7527
+ return existing;
7528
+ }
7529
+ async putFiles(configs) {
7530
+ const result = {
7531
+ written: 0,
7532
+ skipped: 0,
7533
+ skippedExisting: 0,
7534
+ warnings: []
7535
+ };
7536
+ if (configs.length === 0) {
7537
+ return result;
7538
+ }
7539
+ const trace = createTraceId();
7540
+ logger.debug("[ThemeFileStorage] Put files start", {
7541
+ totalConfigs: configs.length,
7542
+ trace
7543
+ });
7544
+ const { valid, warnings } = this.validateFiles(configs);
7545
+ result.warnings = warnings;
7546
+ if (warnings.length > 0) {
7547
+ const rejectedCount = warnings.filter(
7548
+ (w) => w.action === "rejected"
7549
+ ).length;
7550
+ const warnedCount = warnings.filter((w) => w.action === "stored").length;
7551
+ logger.warn("[ThemeFileStorage] File size validation issues", {
7552
+ totalWarnings: warnings.length,
7553
+ rejected: rejectedCount,
7554
+ warned: warnedCount,
7555
+ trace
7556
+ });
7557
+ warnings.filter((w) => w.action === "rejected").forEach((w) => {
7558
+ logger.error("[ThemeFileStorage] File rejected due to size", {
7559
+ filePath: w.filePath,
7560
+ size: w.size,
7561
+ reason: w.reason,
7562
+ trace
7563
+ });
7564
+ });
7565
+ }
7566
+ const rejected = warnings.filter((w) => w.action === "rejected").length;
7567
+ result.skipped = rejected + (configs.length - configs.filter((c) => c.file_data).length);
7568
+ logger.debug("[ThemeFileStorage] Checking existence", {
7569
+ validFiles: valid.length,
7570
+ trace
7571
+ });
7572
+ const existing = await this.checkExistence(valid);
7573
+ result.skippedExisting = existing.size;
7574
+ const toWrite = valid.filter((config) => !existing.has(config.hash));
7575
+ if (toWrite.length > 0) {
7576
+ logger.debug("[ThemeFileStorage] Writing new files", {
7577
+ toWrite: toWrite.length,
7578
+ skippedExisting: existing.size,
7579
+ trace
7580
+ });
7581
+ await Promise3.map(
7582
+ toWrite,
7583
+ async (config) => {
7584
+ const key = this.buildKey(config.hash);
7585
+ const metadata = config.file?.content_type ? { content_type: config.file.content_type } : void 0;
7586
+ await this.kv.put(key, config.file_data, metadata);
7587
+ result.written++;
7588
+ },
7589
+ { concurrency: this.maxConcurrency }
7590
+ );
7591
+ }
7592
+ logger.info("[ThemeFileStorage] Put files complete", {
7593
+ written: result.written,
7594
+ skipped: result.skipped,
7595
+ skippedExisting: result.skippedExisting,
7596
+ warnings: result.warnings.length,
7597
+ trace
7234
7598
  });
7599
+ return result;
7600
+ }
7601
+ };
7602
+
7603
+ // src/cache/constants.ts
7604
+ var SECOND = 1e3;
7605
+ var MINUTE = 60 * SECOND;
7606
+ var HOUR = 60 * MINUTE;
7607
+ var DAY = 24 * HOUR;
7608
+ var YEAR = 365 * DAY;
7609
+ var MAX_TTL = YEAR;
7610
+ var SHORT_TTL = 5 * SECOND;
7611
+
7612
+ // src/cache/worker-cache-proxy.ts
7613
+ var CACHE_NAME = "swell-cache-v1";
7614
+ var CACHE_KEY_ORIGIN = "https://cache.swell.store";
7615
+ var WorkerCacheProxy = class {
7616
+ swell;
7617
+ constructor(swell) {
7618
+ this.swell = swell;
7619
+ }
7620
+ /**
7621
+ * Reads a JSON value from Worker Cache using a key built from path+query.
7622
+ * Returns null on miss or if running outside of a Worker environment.
7623
+ */
7624
+ async get(path, query, opts) {
7625
+ if (typeof caches === "undefined") {
7626
+ return null;
7627
+ }
7628
+ const { keyUrl } = await this.buildKeyUrl(path, query, opts?.version);
7629
+ try {
7630
+ const cache = await caches.open(CACHE_NAME);
7631
+ const match = await cache.match(keyUrl);
7632
+ if (!match) return null;
7633
+ const data = await match.json();
7634
+ return data;
7635
+ } catch {
7636
+ return null;
7637
+ }
7638
+ }
7639
+ /**
7640
+ * Stores a JSON value in Worker Cache under key built from path+query.
7641
+ * No-ops outside of a Worker environment.
7642
+ */
7643
+ async put(path, query, value, opts) {
7644
+ if (typeof caches === "undefined") {
7645
+ return;
7646
+ }
7647
+ const { keyUrl, hasVersion } = await this.buildKeyUrl(
7648
+ path,
7649
+ query,
7650
+ opts?.version
7651
+ );
7652
+ const ttlMs = hasVersion ? MAX_TTL : SHORT_TTL;
7653
+ try {
7654
+ const cache = await caches.open(CACHE_NAME);
7655
+ const response = new Response(JSON.stringify(value), {
7656
+ headers: {
7657
+ "Content-Type": "application/json",
7658
+ "Cache-Control": `public, max-age=${Math.floor(ttlMs / 1e3)}`
7659
+ }
7660
+ });
7661
+ await cache.put(keyUrl, response);
7662
+ } catch {
7663
+ }
7664
+ }
7665
+ /**
7666
+ * Builds a deterministic key URL for Worker Cache from the backend API URL
7667
+ * composed using path and query. Includes tenant and auth isolation and an
7668
+ * optional version segment.
7669
+ */
7670
+ async buildKeyUrl(path, query, explicitVersion) {
7671
+ const apiHost = this.swell.backend?.apiHost;
7672
+ const endpointPath = String(path).startsWith("/") ? String(path).substring(1) : String(path);
7673
+ let queryString = "";
7674
+ if (query && this.swell.backend) {
7675
+ queryString = this.swell.backend.stringifyQuery(query);
7676
+ }
7677
+ const fullUrl = `${apiHost}/${endpointPath}${queryString ? `?${queryString}` : ""}`;
7678
+ const instanceId = this.swell.instanceId || "";
7679
+ const authKey = String(this.swell.swellHeaders?.["swell-auth-key"] || "");
7680
+ const tenantHash = await this.sha256Hex(`${instanceId}|${authKey}`);
7681
+ const version = explicitVersion !== void 0 ? explicitVersion : this.swell.swellHeaders?.["theme-version-hash"] || null;
7682
+ const hasVersion = Boolean(version);
7683
+ const versionHash = hasVersion ? await this.sha256Hex(String(version)) : null;
7684
+ const urlHash = await this.sha256Hex(fullUrl);
7685
+ const keyUrl = versionHash ? `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${versionHash}/${urlHash}` : `${CACHE_KEY_ORIGIN}/v1/${tenantHash}/${urlHash}`;
7686
+ return { keyUrl, hasVersion };
7687
+ }
7688
+ /**
7689
+ * SHA-256 digest with hex encoding. Requires Worker crypto; callers
7690
+ * should avoid invoking this outside of Worker code paths.
7691
+ */
7692
+ async sha256Hex(input) {
7693
+ if (typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest) {
7694
+ const encoder = new TextEncoder();
7695
+ const digest = await crypto.subtle.digest(
7696
+ "SHA-256",
7697
+ encoder.encode(input)
7698
+ );
7699
+ const bytes = new Uint8Array(digest);
7700
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
7701
+ }
7702
+ return md5(input);
7703
+ }
7704
+ };
7705
+
7706
+ // src/resources/addresses.ts
7707
+ var SwellAddresses = class extends SwellStorefrontCollection {
7708
+ constructor(swell, query) {
7709
+ const { page, limit: limit2 } = swell.queryParams;
7710
+ super(swell, "accounts:addresses", { page, limit: limit2, ...query }, function() {
7711
+ return this._swell.storefront.account.listAddresses(
7712
+ this._query
7713
+ );
7714
+ });
7715
+ }
7716
+ };
7717
+
7718
+ // src/resources/orders.ts
7719
+ var SwellOrders = class extends SwellStorefrontCollection {
7720
+ constructor(swell, query) {
7721
+ const { page, limit: limit2 } = swell.queryParams;
7722
+ super(swell, "accounts:orders", { page, limit: limit2, ...query }, function() {
7723
+ return this._swell.storefront.account.listOrders(this._query);
7724
+ });
7725
+ }
7726
+ };
7727
+
7728
+ // src/resources/subscriptions.ts
7729
+ var SwellSubscriptions = class extends SwellStorefrontCollection {
7730
+ constructor(swell, query) {
7731
+ const { page, limit: limit2 } = swell.queryParams;
7732
+ super(
7733
+ swell,
7734
+ "accounts:subscriptions",
7735
+ { page, limit: limit2, ...query },
7736
+ function() {
7737
+ return this._swell.storefront.subscriptions.list(
7738
+ this._query
7739
+ );
7740
+ }
7741
+ );
7235
7742
  }
7236
7743
  };
7237
7744
 
7238
7745
  // src/resources/account.ts
7239
7746
  var SwellAccount = class extends SwellStorefrontSingleton {
7240
- constructor(swell, getter) {
7241
- super(swell, "account", getter);
7747
+ constructor(swell) {
7748
+ super(swell, "account", async function() {
7749
+ const account = await this._defaultGetter().call(this);
7750
+ if (!account) {
7751
+ return null;
7752
+ }
7753
+ account.addresses = new SwellAddresses(
7754
+ this._swell
7755
+ );
7756
+ account.orders = new SwellOrders(
7757
+ this._swell
7758
+ );
7759
+ account.subscriptions = new SwellSubscriptions(
7760
+ this._swell
7761
+ );
7762
+ return account;
7763
+ });
7242
7764
  return this._getProxy();
7243
7765
  }
7244
7766
  };
7245
7767
 
7246
7768
  // src/resources/blog_category.ts
7247
7769
  var SwellBlogCategory = class extends SwellStorefrontRecord {
7248
- constructor(swell, id, query = {}, getter) {
7249
- super(swell, "content/blog-categories", id, query, getter);
7770
+ constructor(swell, id, query) {
7771
+ super(swell, "content/blog-categories", id, query, async function() {
7772
+ const category = await this._defaultGetter().call(this);
7773
+ if (!category) {
7774
+ return null;
7775
+ }
7776
+ category.blogs = new SwellStorefrontCollection(
7777
+ this._swell,
7778
+ "content/blogs",
7779
+ {
7780
+ category_id: category.id,
7781
+ expand: "author"
7782
+ }
7783
+ );
7784
+ return category;
7785
+ });
7250
7786
  return this._getProxy();
7251
7787
  }
7252
7788
  };
7253
7789
 
7254
7790
  // src/resources/blog.ts
7255
7791
  var SwellBlog = class extends SwellStorefrontRecord {
7256
- constructor(swell, id, query = {}, getter) {
7257
- super(swell, "content/blogs", id, query, getter);
7792
+ constructor(swell, blogId, categoryId, query) {
7793
+ super(swell, "content/blogs", blogId, query, async function() {
7794
+ this._query = { ...this._query, expand: "author" };
7795
+ const blog = await this._defaultGetter().call(this);
7796
+ if (!blog) {
7797
+ return null;
7798
+ }
7799
+ if (categoryId) {
7800
+ blog.category = new SwellStorefrontRecord(
7801
+ this._swell,
7802
+ "content/blog-categories",
7803
+ categoryId
7804
+ );
7805
+ }
7806
+ return blog;
7807
+ });
7258
7808
  return this._getProxy();
7259
7809
  }
7260
7810
  };
7261
7811
 
7262
7812
  // src/resources/cart.ts
7263
7813
  var SwellCart = class extends SwellStorefrontSingleton {
7264
- constructor(swell, getter) {
7265
- super(swell, "cart", getter);
7266
- return this._getProxy();
7267
- }
7268
- };
7269
-
7270
- // src/resources/category.ts
7271
- var SwellCategory = class extends SwellStorefrontRecord {
7272
- constructor(swell, id, query = {}, getter) {
7273
- super(swell, "categories", id, query, getter);
7274
- return this._getProxy();
7275
- }
7276
- };
7277
-
7278
- // src/resources/order.ts
7279
- var SwellOrder = class extends SwellStorefrontRecord {
7280
- constructor(swell, id, query = {}, getter) {
7281
- super(swell, "orders", id, query, getter);
7814
+ constructor(swell) {
7815
+ super(swell, "cart");
7282
7816
  return this._getProxy();
7283
7817
  }
7284
7818
  };
7285
7819
 
7286
- // src/resources/page.ts
7287
- var SwellPage = class extends SwellStorefrontRecord {
7288
- constructor(swell, id, query = {}, getter) {
7289
- super(swell, "content/pages", id, query, getter);
7290
- return this._getProxy();
7820
+ // src/resources/categories.ts
7821
+ var SwellCategories = class extends SwellStorefrontCollection {
7822
+ constructor(swell, query) {
7823
+ super(
7824
+ swell,
7825
+ "categories",
7826
+ {
7827
+ limit: 100,
7828
+ top_id: null,
7829
+ ...query
7830
+ },
7831
+ async function() {
7832
+ const categories = await this._defaultGetter().call(this);
7833
+ if (!categories) {
7834
+ return null;
7835
+ }
7836
+ for (const category of categories.results) {
7837
+ category.products = new SwellStorefrontCollection(
7838
+ this._swell,
7839
+ "products",
7840
+ { category: category.id }
7841
+ );
7842
+ }
7843
+ return categories;
7844
+ }
7845
+ );
7291
7846
  }
7292
7847
  };
7293
7848
 
@@ -7438,15 +7993,152 @@ function getSelectedSubscriptionPurchaseOptionPlan(selectedPurchaseOptionType, s
7438
7993
  if (selectedPurchaseOptionType !== "subscription") {
7439
7994
  return null;
7440
7995
  }
7441
- const { purchase_option: purchaseOption } = queryParams;
7442
- let selectedPlan = null;
7443
- if (purchaseOption?.plan_id) {
7444
- selectedPlan = subscriptionPurchaseOption.plans.find(
7445
- (plan) => plan.id === purchaseOption.plan_id
7446
- );
7996
+ const { purchase_option: purchaseOption } = queryParams;
7997
+ let selectedPlan = null;
7998
+ if (purchaseOption?.plan_id) {
7999
+ selectedPlan = subscriptionPurchaseOption.plans.find(
8000
+ (plan) => plan.id === purchaseOption.plan_id
8001
+ );
8002
+ }
8003
+ return selectedPlan || subscriptionPurchaseOption.plans[0];
8004
+ }
8005
+ var SORT_OPTIONS = Object.freeze([
8006
+ { value: "", name: "Featured" },
8007
+ { value: "popularity", name: "Popularity", query: "popularity desc" },
8008
+ { value: "price_asc", name: "Price, low to high", query: "price asc" },
8009
+ { value: "price_desc", name: "Price, high to low", query: "price desc" },
8010
+ { value: "date_asc", name: "Date, old to new", query: "date asc" },
8011
+ { value: "date_desc", name: "Date, new to old", query: "date desc" },
8012
+ { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
8013
+ { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
8014
+ ]);
8015
+ async function getProductFilters(swell, productQuery) {
8016
+ const sortBy = swell.queryParams.sort || "";
8017
+ const filterQuery = productQueryWithFilters(swell, productQuery);
8018
+ return {
8019
+ filter_options: await getProductFiltersByQuery(swell, filterQuery),
8020
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.value,
8021
+ sort_options: [...SORT_OPTIONS]
8022
+ };
8023
+ }
8024
+ async function getProductFiltersByQuery(swell, query = {}) {
8025
+ const filters2 = await swell.get("/products/:filters", {
8026
+ ...query,
8027
+ sort: void 0
8028
+ }) || [];
8029
+ if (!Array.isArray(filters2)) {
8030
+ throw new Error("Product filters must be an array");
8031
+ }
8032
+ for (const filter of filters2) {
8033
+ filter.param_name = `filter_${filter.id}`;
8034
+ if (Array.isArray(filter.options)) {
8035
+ filter.active_options = [];
8036
+ filter.inactive_options = [];
8037
+ for (const option of filter.options) {
8038
+ const queryValue = swell.queryParams[filter.param_name];
8039
+ option.active = Array.isArray(queryValue) ? queryValue.includes(option.value) : queryValue === option.value;
8040
+ const list = option.active ? filter.active_options : filter.inactive_options;
8041
+ list.push(option);
8042
+ }
8043
+ }
8044
+ }
8045
+ return filters2;
8046
+ }
8047
+ function productQueryWithFilters(swell, query) {
8048
+ const filters2 = Object.keys(swell.queryParams).reduce(
8049
+ (acc, key) => {
8050
+ if (key.startsWith("filter_")) {
8051
+ const qkey = key.replace("filter_", "");
8052
+ const value = swell.queryParams[key];
8053
+ if (typeof value === "object" && value !== null && !Array.isArray(value) && (value.gte !== void 0 || value.lte !== void 0)) {
8054
+ acc[qkey] = [value.gte || 0, value.lte || void 0];
8055
+ } else {
8056
+ acc[qkey] = value;
8057
+ }
8058
+ }
8059
+ return acc;
8060
+ },
8061
+ {}
8062
+ );
8063
+ const sortBy = swell.queryParams.sort || "";
8064
+ return {
8065
+ sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
8066
+ $filters: filters2,
8067
+ ...query
8068
+ };
8069
+ }
8070
+
8071
+ // src/resources/category.ts
8072
+ var SwellCategory = class extends SwellStorefrontRecord {
8073
+ constructor(swell, id, query) {
8074
+ super(swell, "categories", id, query, async function() {
8075
+ let category = await this._defaultGetter().call(this);
8076
+ if (!category && this._id === "all") {
8077
+ category = {
8078
+ name: "Products",
8079
+ id: "all",
8080
+ slug: "all",
8081
+ filter_options: [],
8082
+ sort_options: []
8083
+ };
8084
+ }
8085
+ if (!category) {
8086
+ return null;
8087
+ }
8088
+ const productFilters = await getProductFilters(
8089
+ this._swell,
8090
+ category.id !== "all" ? { category: category.id, $variants: true } : { $variants: true }
8091
+ );
8092
+ Object.assign(category, productFilters);
8093
+ return category;
8094
+ });
8095
+ return this._getProxy();
8096
+ }
8097
+ };
8098
+
8099
+ // src/resources/order.ts
8100
+ var SwellOrder = class extends SwellStorefrontRecord {
8101
+ constructor(swell, id, query) {
8102
+ super(swell, "accounts:orders", id, query, function() {
8103
+ return this._swell.storefront.account.getOrder(this._id);
8104
+ });
8105
+ return this._getProxy();
8106
+ }
8107
+ };
8108
+
8109
+ // src/resources/page.ts
8110
+ var SwellPage = class extends SwellStorefrontRecord {
8111
+ constructor(swell, id, query) {
8112
+ super(swell, "content/pages", id, query);
8113
+ return this._getProxy();
8114
+ }
8115
+ };
8116
+
8117
+ // src/resources/predictive_search.ts
8118
+ var SwellPredictiveSearch = class extends StorefrontResource {
8119
+ constructor(swell, query) {
8120
+ super(async function() {
8121
+ const performed = String(query || "").length > 0;
8122
+ let products;
8123
+ if (performed) {
8124
+ products = new SwellStorefrontCollection(
8125
+ swell,
8126
+ "products",
8127
+ {
8128
+ search: query,
8129
+ limit: 10
8130
+ }
8131
+ );
8132
+ await products.resolve();
8133
+ }
8134
+ return {
8135
+ query,
8136
+ performed,
8137
+ products
8138
+ };
8139
+ });
7447
8140
  }
7448
- return selectedPlan || subscriptionPurchaseOption.plans[0];
7449
- }
8141
+ };
7450
8142
 
7451
8143
  // src/resources/variant.ts
7452
8144
  function transformSwellVariant(params, product, variant) {
@@ -7466,27 +8158,30 @@ function transformSwellVariant(params, product, variant) {
7466
8158
  )
7467
8159
  };
7468
8160
  }
8161
+ var SwellVariant = class extends SwellStorefrontRecord {
8162
+ product;
8163
+ constructor(swell, product, id, query) {
8164
+ super(swell, "products:variants", id, query, async function() {
8165
+ const variant = await this._swell.get(
8166
+ "/products:variants/{id}",
8167
+ { id: this._id }
8168
+ );
8169
+ return variant ?? null;
8170
+ });
8171
+ this.product = product;
8172
+ }
8173
+ };
7469
8174
 
7470
8175
  // src/resources/product.ts
7471
- var SORT_OPTIONS = [
7472
- { value: "", name: "Featured" },
7473
- { value: "popularity", name: "Popularity", query: "popularity desc" },
7474
- { value: "price_asc", name: "Price, low to high", query: "price asc" },
7475
- { value: "price_desc", name: "Price, high to low", query: "price desc" },
7476
- { value: "date_asc", name: "Date, old to new", query: "date asc" },
7477
- { value: "date_desc", name: "Date, new to old", query: "date desc" },
7478
- { value: "name_asc", name: "Product name, A-Z", query: "name asc" },
7479
- { value: "name_desc", name: "Product name, Z-A", query: "name desc" }
7480
- ];
7481
8176
  function transformSwellProduct(params, product) {
7482
8177
  if (!product) {
7483
- return product;
8178
+ return null;
7484
8179
  }
7485
8180
  const newProduct = {
7486
8181
  ...product,
7487
8182
  // add swell properties there
7488
8183
  selected_option_values: getSelectedVariantOptionValues(product, params),
7489
- purchase_options: getPurchaseOptions(product, params)
8184
+ purchase_options: getPurchaseOptions(product, params) ?? void 0
7490
8185
  };
7491
8186
  if (Array.isArray(newProduct.variants?.results)) {
7492
8187
  newProduct.variants = {
@@ -7499,43 +8194,49 @@ function transformSwellProduct(params, product) {
7499
8194
  return newProduct;
7500
8195
  }
7501
8196
  var SwellProduct = class extends SwellStorefrontRecord {
7502
- _params;
7503
- constructor(swell, id, query = {}, getter) {
7504
- super(swell, "products", id, query, getter);
7505
- this._params = swell.queryParams;
8197
+ constructor(swell, id, query) {
8198
+ const params = swell.queryParams;
8199
+ super(swell, "products", id, query, async function() {
8200
+ const result = await this._defaultGetter().call(this);
8201
+ return transformSwellProduct(params, result);
8202
+ });
7506
8203
  return this._getProxy();
7507
8204
  }
7508
- // add swell properties to the resolved object
7509
- _transformResult(result) {
7510
- const res = transformSwellProduct(
7511
- this._params,
7512
- result
7513
- );
7514
- return res;
8205
+ };
8206
+
8207
+ // src/resources/product_recommendations.ts
8208
+ var SwellProductRecommendations = class extends SwellProduct {
8209
+ constructor(swell, id, query) {
8210
+ super(swell, id, { ...query, $recommendations: true });
8211
+ }
8212
+ };
8213
+
8214
+ // src/resources/search.ts
8215
+ var SwellSearch = class extends StorefrontResource {
8216
+ constructor(swell, query) {
8217
+ super(async () => {
8218
+ const performed = String(query || "").length > 0;
8219
+ const productFilters = await getProductFilters(
8220
+ swell,
8221
+ performed ? { search: query } : void 0
8222
+ );
8223
+ return {
8224
+ query,
8225
+ performed,
8226
+ ...productFilters
8227
+ };
8228
+ });
8229
+ }
8230
+ };
8231
+
8232
+ // src/resources/subscription.ts
8233
+ var SwellSubscription = class extends SwellStorefrontRecord {
8234
+ constructor(swell, id, query) {
8235
+ super(swell, "accounts:subscriptions", id, query, function() {
8236
+ return this._swell.storefront.subscriptions.get(this._id, this._query);
8237
+ });
7515
8238
  }
7516
8239
  };
7517
- function productQueryWithFilters(swell, query = {}) {
7518
- const sortBy = swell.queryParams.sort || "";
7519
- const filters2 = Object.entries(swell.queryParams).reduce(
7520
- (acc, [key, value]) => {
7521
- if (key.startsWith("filter_")) {
7522
- const qkey = key.replace("filter_", "");
7523
- if (value?.gte !== void 0 || value?.lte !== void 0) {
7524
- acc[qkey] = [value.gte || 0, value.lte || void 0];
7525
- } else {
7526
- acc[qkey] = value;
7527
- }
7528
- }
7529
- return acc;
7530
- },
7531
- {}
7532
- );
7533
- return {
7534
- sort: SORT_OPTIONS.find((option) => option.value === sortBy)?.query || void 0,
7535
- $filters: filters2,
7536
- ...query
7537
- };
7538
- }
7539
8240
 
7540
8241
  // src/api.ts
7541
8242
  var DEFAULT_API_HOST = "https://api.schema.io";
@@ -7596,7 +8297,7 @@ var Swell = class _Swell {
7596
8297
  this.workerEnv = workerEnv;
7597
8298
  this.resourceLoadingIndicator = params.resourceLoadingIndicator;
7598
8299
  logger.info(
7599
- `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}`
8300
+ `[SDK] KV cache: ${this.workerEnv?.THEME ? "enabled" : "disabled"}, flavor: ${getKVFlavor(this.workerEnv)}`
7600
8301
  );
7601
8302
  if (serverHeaders) {
7602
8303
  const { headers: headers2, swellHeaders: swellHeaders2 } = _Swell.formatHeaders(serverHeaders);
@@ -15106,11 +15807,9 @@ function getProducts(instance, object, mapper) {
15106
15807
  return this._defaultGetter().call(this);
15107
15808
  }
15108
15809
  );
15109
- return products._cloneWithCompatibilityResult(
15110
- (products2) => {
15111
- return { ...products2, results: products2.results.map(mapper) };
15112
- }
15113
- );
15810
+ return products._cloneWithCompatibilityResult((products2) => {
15811
+ return { ...products2, results: products2.results.map(mapper) };
15812
+ });
15114
15813
  });
15115
15814
  }
15116
15815
  function makeProductsCollectionResolve(instance, object, mapper) {
@@ -15127,19 +15826,24 @@ function makeProductsCollectionResolve(instance, object, mapper) {
15127
15826
 
15128
15827
  // src/compatibility/shopify-objects/collections.ts
15129
15828
  function ShopifyCollections(instance, categories) {
15130
- return new SwellStorefrontCollection(instance.swell, categories._collection, categories._query, async () => {
15131
- const results = (await categories.results)?.map((category) => {
15132
- return ShopifyCollection(instance, category);
15133
- });
15134
- return {
15135
- page: categories.page ?? 1,
15136
- count: categories.count ?? 0,
15137
- results: results ?? [],
15138
- page_count: categories.page_count ?? 0,
15139
- limit: categories.limit,
15140
- pages: categories.pages ?? {}
15141
- };
15142
- });
15829
+ return new SwellStorefrontCollection(
15830
+ instance.swell,
15831
+ categories._collection,
15832
+ categories._query,
15833
+ async () => {
15834
+ const results = (await categories.results)?.map((category) => {
15835
+ return ShopifyCollection(instance, category);
15836
+ });
15837
+ return {
15838
+ page: categories.page ?? 1,
15839
+ count: categories.count ?? 0,
15840
+ results: results ?? [],
15841
+ page_count: categories.page_count ?? 0,
15842
+ limit: categories.limit,
15843
+ pages: categories.pages ?? {}
15844
+ };
15845
+ }
15846
+ );
15143
15847
  }
15144
15848
 
15145
15849
  // src/compatibility/shopify-objects/address.ts
@@ -16326,13 +17030,12 @@ var ImagesDrop = class extends Drop7 {
16326
17030
  var SwellImage = class extends StorefrontResource {
16327
17031
  constructor(swell, name) {
16328
17032
  super(async () => {
16329
- const files = await swell.get("/:files", {
17033
+ const file = await swell.get("/:files/:last", {
16330
17034
  private: { $ne: true },
16331
17035
  content_type: { $regex: "^image/" },
16332
17036
  filename: name
16333
17037
  });
16334
- const file = files?.results[0] ?? null;
16335
- if (file === null) {
17038
+ if (!file) {
16336
17039
  return null;
16337
17040
  }
16338
17041
  return { file };
@@ -19255,296 +19958,255 @@ function getLiquidFS(getThemeConfig, extName) {
19255
19958
  }
19256
19959
 
19257
19960
  // src/theme/theme-loader.ts
19258
- import bluebird from "bluebird";
19259
- var { Promise: Promise2 } = bluebird;
19260
19961
  var MAX_INDIVIDUAL_CONFIGS_TO_FETCH = 50;
19261
- var ThemeLoader = class _ThemeLoader {
19262
- static cache = null;
19962
+ var ThemeLoader = class {
19263
19963
  swell;
19264
- manifest;
19265
19964
  configs;
19266
- configPaths;
19267
19965
  constructor(swell) {
19268
19966
  this.swell = swell;
19269
- this.manifest = null;
19270
19967
  this.configs = /* @__PURE__ */ new Map();
19271
- this.configPaths = [];
19272
19968
  }
19969
+ /**
19970
+ * Initialize the theme loader with all configurations.
19971
+ * Either uses provided configs (editor mode) or loads from storage.
19972
+ */
19273
19973
  async init(themeConfigs) {
19274
19974
  if (themeConfigs) {
19275
19975
  this.setConfigs(themeConfigs);
19276
19976
  return;
19277
19977
  }
19278
19978
  if (!this.getThemeId()) {
19979
+ logger.debug("[ThemeLoader] No theme ID, skipping init");
19279
19980
  return;
19280
19981
  }
19281
- await this.fetchManifest();
19282
- if (this.manifest === null) {
19283
- console.log("ThemeLoader.init - version manifest not found");
19284
- await this.loadTheme();
19285
- }
19982
+ await this.loadAllConfigs();
19983
+ logger.info("[ThemeLoader] Initialization complete", {
19984
+ configCount: this.configs.size,
19985
+ themeId: this.getThemeId()
19986
+ });
19286
19987
  }
19287
19988
  /**
19288
- * Loads theme configs for this version.
19989
+ * Get a single config by file path (synchronous).
19990
+ * Returns null if config not found.
19289
19991
  */
19290
- async loadTheme() {
19291
- const { swellHeaders } = this.swell;
19292
- console.log("ThemeLoader.loadTheme", swellHeaders["theme-version-hash"]);
19293
- if (swellHeaders["theme-version-hash"]) {
19294
- const configs = await this.loadThemeFromManifest();
19295
- if (configs) {
19296
- return configs;
19297
- }
19298
- }
19299
- return this.loadThemeAllConfigs();
19992
+ getConfig(filePath) {
19993
+ return this.configs.get(filePath) ?? null;
19300
19994
  }
19301
19995
  /**
19302
- * Returns the cache instance for this theme loader.
19996
+ * Get all loaded configs.
19997
+ * Used by theme getter to expose configs to editor/tests.
19303
19998
  */
19304
- getCache() {
19305
- if (_ThemeLoader.cache === null) {
19306
- _ThemeLoader.cache = new ThemeCache({
19307
- kvStore: this.swell.workerEnv?.THEME
19308
- });
19309
- }
19310
- return _ThemeLoader.cache;
19999
+ getConfigs() {
20000
+ return this.configs;
19311
20001
  }
19312
20002
  /**
19313
- * Load theme configs from internal data, typically in the editor.
20003
+ * Get multiple configs by path pattern (synchronous).
20004
+ * Filters configs by prefix and optional suffix.
19314
20005
  */
19315
- setConfigs(themeConfigs) {
19316
- this.configs = new Map(themeConfigs);
19317
- this.manifest = /* @__PURE__ */ new Map();
19318
- for (const { file_path, hash } of this.configs.values()) {
19319
- this.manifest.set(file_path, hash);
20006
+ getConfigsByPath(pathPrefix, pathSuffix) {
20007
+ const results = [];
20008
+ for (const [path, config] of this.configs) {
20009
+ if (path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))) {
20010
+ results.push(config);
20011
+ }
19320
20012
  }
19321
- this.configPaths = Array.from(this.configs.keys());
20013
+ return results;
19322
20014
  }
19323
20015
  /**
19324
- * Preloads a theme version and configs. This is used to optimize initial theme load.
20016
+ * Load theme configs from internal data, typically in the editor.
20017
+ * Used when configs are provided externally (e.g., from editor).
19325
20018
  */
19326
- async preloadTheme(payload) {
19327
- const { version, configs } = payload;
19328
- console.log(
19329
- `ThemeLoader.preloadTheme${version?.hash ? ` - manifest: ${version.hash}` : ""}${configs?.length ? ` - configs: ${configs.length}` : ""}`
19330
- );
19331
- const promises = [];
19332
- if (version) {
19333
- promises.push(this.cacheManifest(version));
19334
- }
19335
- if (configs) {
19336
- const themeId = this.getThemeId();
19337
- promises.push(
19338
- Promise2.map(
19339
- configs,
19340
- async (config) => {
19341
- const promises2 = [
19342
- this.cacheThemeConfig(config)
19343
- ];
19344
- if (themeId && config.file?.url) {
19345
- promises2.push(
19346
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19347
- );
19348
- }
19349
- await Promise2.all(promises2);
19350
- },
19351
- { concurrency: 10 }
19352
- )
19353
- );
19354
- }
19355
- await Promise2.all(promises);
20019
+ setConfigs(themeConfigs) {
20020
+ this.configs = new Map(themeConfigs);
19356
20021
  }
19357
20022
  /**
19358
- * Fetches a theme config by file path.
20023
+ * Updates KV with file_data for provided theme configs (warmup path).
20024
+ * Uses the new ThemeFileStorage abstraction for optimized operations.
19359
20025
  */
19360
- async fetchThemeConfig(filePath) {
19361
- const config = this.configs.get(filePath);
19362
- if (config !== void 0) {
19363
- return config;
19364
- }
19365
- const hash = this.manifest?.get(filePath);
19366
- if (!hash) {
19367
- return null;
20026
+ async updateThemeCache(payload) {
20027
+ const configs = payload?.configs || [];
20028
+ if (configs.length === 0) {
20029
+ logger.debug("[ThemeLoader] No configs to cache");
20030
+ return {
20031
+ written: 0,
20032
+ skipped: 0,
20033
+ skippedExisting: 0,
20034
+ warnings: []
20035
+ };
19368
20036
  }
19369
- const cache = this.getCache();
19370
- const themeId = this.getThemeId();
19371
- const [themeConfig, fileUrl] = await Promise2.all([
19372
- cache.get(`config:${hash}`),
19373
- themeId ? cache.get(`file:${themeId}:${hash}`) : void 0
19374
- ]);
19375
- if (themeConfig) {
19376
- let config2 = themeConfig;
19377
- if (fileUrl && themeConfig.file?.url) {
19378
- config2 = {
19379
- ...themeConfig,
19380
- file: { ...themeConfig.file, url: fileUrl }
19381
- };
19382
- }
19383
- this.configs.set(filePath, config2);
19384
- return config2;
20037
+ const flavor = getKVFlavor(this.swell.workerEnv);
20038
+ const trace = createTraceId();
20039
+ logger.info("[ThemeLoader] Starting theme cache update", {
20040
+ totalConfigs: configs.length,
20041
+ flavor,
20042
+ trace
20043
+ });
20044
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20045
+ const result = await storage.putFiles(configs);
20046
+ if (result.warnings.length > 0) {
20047
+ logger.warn("[ThemeLoader] Theme cache updated with warnings", {
20048
+ total: configs.length,
20049
+ written: result.written,
20050
+ skipped: result.skipped,
20051
+ skippedExisting: result.skippedExisting,
20052
+ warnings: result.warnings.length,
20053
+ trace
20054
+ });
20055
+ } else {
20056
+ logger.info("[ThemeLoader] Theme cache updated successfully", {
20057
+ total: configs.length,
20058
+ written: result.written,
20059
+ skipped: result.skipped,
20060
+ skippedExisting: result.skippedExisting,
20061
+ trace
20062
+ });
19385
20063
  }
19386
- return this.fetchThemeConfigsFromSourceByPath(filePath, hash);
19387
- }
19388
- async fetchThemeConfigsByPath(pathPrefix, pathSuffix) {
19389
- const paths = this.configPaths.filter(
19390
- (path) => path.startsWith(pathPrefix) && (!pathSuffix || path.endsWith(pathSuffix))
19391
- );
19392
- const configs = await Promise2.map(
19393
- paths,
19394
- (path) => this.fetchThemeConfig(path),
19395
- { concurrency: 10 }
19396
- );
19397
- return configs.filter((config) => config !== null);
20064
+ return result;
19398
20065
  }
19399
20066
  /**
19400
- * Load all theme configs.
20067
+ * Main loading logic - loads all configs at once.
20068
+ * 1. Fetches lightweight metadata (cached when possible)
20069
+ * 2. Batch hydrates file_data from KV
20070
+ * 3. Fetches missing file_data from API if needed
19401
20071
  */
19402
- async loadThemeAllConfigs() {
19403
- const { swellHeaders } = this.swell;
19404
- const configVersion = String(swellHeaders["theme-config-version"]);
19405
- if (!configVersion) {
19406
- throw new Error("Theme version is required");
20072
+ async loadAllConfigs() {
20073
+ const configMetadata = await this.fetchConfigMetadata();
20074
+ if (configMetadata.length === 0) {
20075
+ logger.warn("[ThemeLoader] No configs found");
20076
+ return;
19407
20077
  }
19408
- const configs = await this.getCache().fetch(
19409
- `configs-all:${this.swell.instanceId}:v@${configVersion}2`,
19410
- () => this.fetchThemeConfigsFromSource()
19411
- );
19412
- return configs?.results ?? [];
20078
+ logger.debug("[ThemeLoader] Loading configs", {
20079
+ total: configMetadata.length
20080
+ });
20081
+ const flavor = getKVFlavor(this.swell.workerEnv);
20082
+ const storage = new ThemeFileStorage(this.swell.workerEnv, flavor);
20083
+ const kvHydrated = await storage.getFiles(configMetadata);
20084
+ const completeConfigs = await this.ensureConfigsHaveData(kvHydrated);
20085
+ for (const config of completeConfigs) {
20086
+ this.configs.set(config.file_path, config);
20087
+ }
20088
+ logger.info("[ThemeLoader] All configs loaded", {
20089
+ total: completeConfigs.length,
20090
+ withData: completeConfigs.filter((c) => c.file_data).length
20091
+ });
19413
20092
  }
19414
20093
  /**
19415
- * Load theme configs via manifest.
19416
- *
19417
- * This approach has the following optimizations:
19418
- * - cached manifests and configs can be shared by other clients
19419
- * - when fetching from source, only fetch the missing records
20094
+ * Fetch lightweight config metadata from API or cache.
20095
+ * Does NOT include file_data to minimize payload size.
19420
20096
  */
19421
- async loadThemeFromManifest() {
19422
- const manifest = await this.fetchManifest();
19423
- if (!manifest) {
19424
- return null;
19425
- }
19426
- const configHashesUnresolved = [];
19427
- const configsByHash = /* @__PURE__ */ new Map();
19428
- const themeId = this.getThemeId();
19429
- const cache = this.getCache();
19430
- await Promise2.map(
19431
- manifest.values(),
19432
- async (configHash) => {
19433
- const [themeConfig, fileUrl] = await Promise2.all([
19434
- cache.get(`config:${configHash}`),
19435
- themeId ? cache.get(`file:${themeId}:${configHash}`) : void 0
19436
- ]);
19437
- if (!themeConfig) {
19438
- configHashesUnresolved.push(configHash);
19439
- return;
19440
- }
19441
- let config = themeConfig;
19442
- if (fileUrl && themeConfig.file?.url) {
19443
- config = {
19444
- ...themeConfig,
19445
- file: { ...themeConfig.file, url: fileUrl }
19446
- };
20097
+ async fetchConfigMetadata() {
20098
+ const query = {
20099
+ ...this.themeVersionQueryFilter(),
20100
+ limit: 1e3,
20101
+ type: "theme",
20102
+ fields: "id, name, type, file, file_path, hash"
20103
+ // NO file_data
20104
+ };
20105
+ try {
20106
+ const cache = new WorkerCacheProxy(this.swell);
20107
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20108
+ const cached = await cache.get(
20109
+ "/:themes:configs",
20110
+ query,
20111
+ {
20112
+ version: versionHash || null
19447
20113
  }
19448
- configsByHash.set(config.hash, config);
19449
- this.configs.set(config.file_path, config);
19450
- },
19451
- { concurrency: 10 }
19452
- );
19453
- if (configHashesUnresolved.length > 0) {
19454
- const configs = await this.fetchThemeConfigsFromSource(
19455
- // If no configs were resolved, then fetch them all. otherwise fetch
19456
- // the specific subset of configs.
19457
- configsByHash.size === 0 ? void 0 : configHashesUnresolved
19458
20114
  );
19459
- const newConfigs = configs?.results ?? [];
19460
- for (const config of newConfigs) {
19461
- configsByHash.set(config.hash, config);
19462
- this.configs.set(config.file_path, config);
20115
+ if (cached) {
20116
+ logger.debug("[ThemeLoader] Config metadata cache hit");
20117
+ return cached;
19463
20118
  }
19464
- await Promise2.map(
19465
- newConfigs,
19466
- async (config) => {
19467
- const promises = [this.cacheThemeConfig(config)];
19468
- if (themeId && config.file?.url) {
19469
- promises.push(
19470
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19471
- );
19472
- }
19473
- await Promise2.all(promises);
19474
- },
19475
- { concurrency: 10 }
19476
- );
20119
+ } catch (err) {
20120
+ logger.warn("[ThemeLoader] Cache read failed, fetching from API", err);
19477
20121
  }
19478
- return Array.from(configsByHash.values());
19479
- }
19480
- /**
19481
- * Caches a theme version manifest by hash.
19482
- */
19483
- async cacheManifest(version) {
19484
- if (version?.hash) {
19485
- await this.getCache().set(`manifest:${version.hash}`, version.manifest);
20122
+ logger.debug("[ThemeLoader] Fetching config metadata from API");
20123
+ const response = await this.swell.get(
20124
+ "/:themes:configs",
20125
+ query
20126
+ );
20127
+ const configs = response?.results || [];
20128
+ try {
20129
+ const cache = new WorkerCacheProxy(this.swell);
20130
+ const versionHash = this.swell.swellHeaders["theme-version-hash"];
20131
+ await cache.put("/:themes:configs", query, configs, {
20132
+ version: versionHash || null
20133
+ });
20134
+ } catch (err) {
20135
+ logger.warn("[ThemeLoader] Cache write failed", err);
19486
20136
  }
20137
+ return configs;
19487
20138
  }
19488
20139
  /**
19489
- * Caches a theme config by hash.
20140
+ * Helper to ensure all configs have file_data.
20141
+ * Fetches missing data from API and updates KV cache.
19490
20142
  */
19491
- async cacheThemeConfig(config) {
19492
- if (config?.hash) {
19493
- await this.getCache().set(`config:${config.hash}`, config);
20143
+ async ensureConfigsHaveData(configs) {
20144
+ const missingData = configs.filter((c) => !c.file_data);
20145
+ if (missingData.length === 0) {
20146
+ logger.debug("[ThemeLoader] All configs have file_data from KV");
20147
+ return configs;
19494
20148
  }
19495
- }
19496
- /**
19497
- * Caches a CDN file url by config hash.
19498
- */
19499
- async cacheThemeFileUrl(themeId, configHash, fileUrl) {
19500
- await this.getCache().set(`file:${themeId}:${configHash}`, fileUrl);
19501
- }
19502
- /**
19503
- * Fetches the manifest (set of config hashes) for a theme version.
19504
- */
19505
- async fetchManifest() {
19506
- const { swellHeaders } = this.swell;
19507
- const versionHash = swellHeaders["theme-version-hash"];
19508
- console.log("ThemeLoader.fetchManifest", versionHash);
19509
- let manifest = await this.getCache().get(
19510
- `manifest:${versionHash}`
20149
+ const trace = createTraceId();
20150
+ logger.info(
20151
+ `[ThemeLoader] Loading ${missingData.length} missing file_data from API`,
20152
+ {
20153
+ trace
20154
+ }
19511
20155
  );
19512
- if (!manifest) {
19513
- const themeVersion = await this.swell.get(
19514
- "/:themes:versions/:last",
19515
- {
19516
- ...this.themeVersionQueryFilter(),
19517
- fields: "hash, manifest"
19518
- }
19519
- );
19520
- if (themeVersion) {
19521
- await this.cacheManifest(themeVersion);
19522
- manifest = themeVersion.manifest;
20156
+ const hashes = missingData.map((c) => c.hash);
20157
+ const apiResponse = await this.fetchThemeConfigsFromSource(hashes);
20158
+ const fetched = apiResponse.results || [];
20159
+ logger.info(`[ThemeLoader] Fetched ${fetched.length} configs from API`, {
20160
+ trace
20161
+ });
20162
+ if (fetched.length > 0) {
20163
+ const cacheResult = await this.updateThemeCache({
20164
+ api: 1,
20165
+ // Required by SwellThemePreload type
20166
+ configs: fetched
20167
+ });
20168
+ if (cacheResult.warnings.length > 0) {
20169
+ logger.warn("[ThemeLoader] Some files had size issues", {
20170
+ warnings: cacheResult.warnings.length
20171
+ });
19523
20172
  }
19524
20173
  }
19525
- if (manifest) {
19526
- this.manifest = new Map(Object.entries(manifest));
19527
- this.configPaths = [...this.manifest.keys()];
20174
+ const fetchedMap = new Map(fetched.map((c) => [c.hash, c]));
20175
+ const mergedConfigs = configs.map((config) => {
20176
+ if (!config.file_data) {
20177
+ const withData = fetchedMap.get(config.hash);
20178
+ if (withData?.file_data) {
20179
+ return { ...config, file_data: withData.file_data };
20180
+ }
20181
+ }
20182
+ return config;
20183
+ });
20184
+ const stillMissing = mergedConfigs.filter((c) => !c.file_data).length;
20185
+ if (stillMissing > 0) {
20186
+ logger.warn(
20187
+ `[ThemeLoader] ${stillMissing} configs still missing file_data after fetch`
20188
+ );
19528
20189
  }
19529
- return this.manifest;
20190
+ return mergedConfigs;
19530
20191
  }
19531
20192
  /**
19532
- * Fetches many theme configs via Swell Backend API.
20193
+ * Fetches theme configs with file_data from Swell Backend API.
20194
+ * Used to retrieve missing file_data for configs.
19533
20195
  */
19534
20196
  async fetchThemeConfigsFromSource(configHashes = void 0) {
19535
20197
  configHashes = configHashes || [];
19536
20198
  const { swellHeaders } = this.swell;
19537
20199
  const version = String(swellHeaders["theme-config-version"]);
19538
20200
  const fetchAll = configHashes.length === 0 || configHashes.length > MAX_INDIVIDUAL_CONFIGS_TO_FETCH;
19539
- console.log(
19540
- `Retrieving ${fetchAll ? "all" : "some"} theme configurations - version: ${version}`
20201
+ logger.debug(
20202
+ `[ThemeLoader] Fetching ${fetchAll ? "all" : configHashes.length} configs with file_data`,
20203
+ { version }
19541
20204
  );
19542
20205
  const configs = await this.swell.get(
19543
20206
  "/:themes:configs",
19544
20207
  {
19545
20208
  ...this.themeVersionQueryFilter(),
19546
20209
  ...fetchAll ? void 0 : { hash: { $in: configHashes } },
19547
- // TODO: paginate to support more than 1000 configs
19548
20210
  limit: 1e3,
19549
20211
  type: "theme",
19550
20212
  fields: "name, file, file_path, hash",
@@ -19556,45 +20218,13 @@ var ThemeLoader = class _ThemeLoader {
19556
20218
  return configs;
19557
20219
  }
19558
20220
  /**
19559
- * Fetches one theme config via Swell Backend API.
19560
- * This is used when a hash entry cannot be found.
19561
- * We may override the cached hash in order to ensure it is found on reload,
19562
- * but we probably need to find why that happens in the first place (TODO).
20221
+ * Get the current theme ID from headers.
19563
20222
  */
19564
- async fetchThemeConfigsFromSourceByPath(filePath, hash) {
19565
- console.log(`Retrieving theme config - ${filePath}`);
19566
- const config = await this.swell.get(
19567
- "/:themes:configs/:last",
19568
- {
19569
- ...this.themeVersionQueryFilter(),
19570
- file_path: filePath,
19571
- fields: "name, file, file_path, hash",
19572
- include: {
19573
- file_data: FILE_DATA_INCLUDE_QUERY
19574
- }
19575
- }
19576
- );
19577
- if (config) {
19578
- this.configs.set(filePath, config);
19579
- if (hash) {
19580
- config.hash = hash;
19581
- }
19582
- const themeId = this.getThemeId();
19583
- const promises = [this.cacheThemeConfig(config)];
19584
- if (themeId && config.file?.url) {
19585
- promises.push(
19586
- this.cacheThemeFileUrl(themeId, config.hash, config.file.url)
19587
- );
19588
- }
19589
- await Promise2.all(promises);
19590
- }
19591
- return config ?? null;
19592
- }
19593
20223
  getThemeId() {
19594
20224
  return this.swell.swellHeaders["theme-id"];
19595
20225
  }
19596
20226
  /**
19597
- * Generates a Swell API query filter for this theme version.
20227
+ * Generate a Swell API query filter for this theme version.
19598
20228
  */
19599
20229
  themeVersionQueryFilter() {
19600
20230
  const { swellHeaders } = this.swell;
@@ -19774,7 +20404,6 @@ var SwellTheme3 = class {
19774
20404
  resources;
19775
20405
  liquidSwell;
19776
20406
  themeLoader;
19777
- themeConfigs = null;
19778
20407
  page;
19779
20408
  pageId;
19780
20409
  shopifyCompatibility = null;
@@ -19808,6 +20437,23 @@ var SwellTheme3 = class {
19808
20437
  });
19809
20438
  this.themeLoader = new ThemeLoader(swell);
19810
20439
  }
20440
+ /**
20441
+ * Getter for theme configs - returns the configs from the loader.
20442
+ * Used by editor and tests to access loaded configs.
20443
+ */
20444
+ get themeConfigs() {
20445
+ const configs = this.themeLoader.getConfigs();
20446
+ return configs.size > 0 ? configs : null;
20447
+ }
20448
+ /**
20449
+ * Setter for theme configs - directly sets configs in the loader.
20450
+ * Used by editor and tests to inject configs without API/KV loading.
20451
+ */
20452
+ set themeConfigs(configs) {
20453
+ if (configs) {
20454
+ this.themeLoader.setConfigs(configs);
20455
+ }
20456
+ }
19811
20457
  getSwellAppThemeProps(swellConfig) {
19812
20458
  return swellConfig?.storefront?.theme || {};
19813
20459
  }
@@ -19881,10 +20527,8 @@ var SwellTheme3 = class {
19881
20527
  }
19882
20528
  async getSettingsAndConfigs() {
19883
20529
  const geo = GEO_DATA;
19884
- const [storefrontSettings, settingConfigs] = await Promise.all([
19885
- this.swell.getStorefrontSettings(),
19886
- this.getThemeConfigsByPath("theme/config/", ".json")
19887
- ]);
20530
+ const storefrontSettings = await this.swell.getStorefrontSettings();
20531
+ const settingConfigs = this.getThemeConfigsByPath("theme/config/", ".json");
19888
20532
  const configs = {
19889
20533
  theme: {},
19890
20534
  editor: {},
@@ -19979,7 +20623,7 @@ var SwellTheme3 = class {
19979
20623
  $locale: void 0
19980
20624
  };
19981
20625
  if (pageId) {
19982
- const templateConfig = await this.getThemeTemplateConfigByType(
20626
+ const templateConfig = this._getTemplateConfigByType(
19983
20627
  "templates",
19984
20628
  pageId,
19985
20629
  altTemplate
@@ -20274,7 +20918,7 @@ var SwellTheme3 = class {
20274
20918
  this.shopifyCompatibility.adaptPageData(pageData);
20275
20919
  }
20276
20920
  async getLocaleConfig(localeCode = "en", suffix = ".json") {
20277
- const allLocaleConfigs = await this.getThemeConfigsByPath(
20921
+ const allLocaleConfigs = this.getThemeConfigsByPath(
20278
20922
  "theme/locales/",
20279
20923
  suffix
20280
20924
  );
@@ -20378,22 +21022,24 @@ var SwellTheme3 = class {
20378
21022
  }
20379
21023
  return resolvedUrl;
20380
21024
  }
20381
- async getAllThemeConfigs() {
20382
- if (this.themeConfigs === null) {
20383
- const configs = await this.themeLoader.loadTheme();
20384
- const configsByPath = /* @__PURE__ */ new Map();
20385
- for (const config of configs) {
20386
- configsByPath.set(config.file_path, config);
20387
- }
20388
- this.themeConfigs = configsByPath;
20389
- }
20390
- return this.themeConfigs;
20391
- }
20392
- /**
20393
- * Preloads updated theme configs. Used to optimize initial theme load.
20394
- */
20395
21025
  async preloadThemeConfigs(payload) {
20396
- await this.themeLoader.preloadTheme(payload);
21026
+ const result = await this.themeLoader.updateThemeCache(payload);
21027
+ if (result.warnings && result.warnings.length > 0) {
21028
+ const rejected = result.warnings.filter(
21029
+ (w) => w.reason === "rejected_5mb" || w.reason === "exceeded_25mb"
21030
+ ).length;
21031
+ const warned = result.warnings.filter(
21032
+ (w) => w.reason === "warning_1mb"
21033
+ ).length;
21034
+ logger.warn("[Theme] File size issues detected during cache update", {
21035
+ totalWarnings: result.warnings.length,
21036
+ rejected,
21037
+ warned,
21038
+ details: result.warnings.slice(0, 5)
21039
+ // Log first 5 warnings for debugging
21040
+ });
21041
+ }
21042
+ return result;
20397
21043
  }
20398
21044
  getPageConfigPath(pageId, altTemplate) {
20399
21045
  if (this.shopifyCompatibility) {
@@ -20406,16 +21052,10 @@ var SwellTheme3 = class {
20406
21052
  return `${withSuffix(`theme/templates/${pageId}`, altTemplate)}.json`;
20407
21053
  }
20408
21054
  async getThemeConfig(filePath) {
20409
- if (this.themeConfigs !== null) {
20410
- return this.themeConfigs.get(filePath) ?? null;
20411
- }
20412
- return this.themeLoader.fetchThemeConfig(filePath);
21055
+ return this.themeLoader.getConfig(filePath);
20413
21056
  }
20414
- async getThemeConfigsByPath(pathPrefix, pathSuffix) {
20415
- const configs = await this.themeLoader.fetchThemeConfigsByPath(
20416
- pathPrefix,
20417
- pathSuffix
20418
- );
21057
+ getThemeConfigsByPath(pathPrefix, pathSuffix) {
21058
+ const configs = this.themeLoader.getConfigsByPath(pathPrefix, pathSuffix);
20419
21059
  const configsByPath = /* @__PURE__ */ new Map();
20420
21060
  for (const config of configs) {
20421
21061
  configsByPath.set(config.file_path, config);
@@ -20423,31 +21063,42 @@ var SwellTheme3 = class {
20423
21063
  return configsByPath;
20424
21064
  }
20425
21065
  async getThemeTemplateConfig(filePath) {
20426
- if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
20427
- return this.getThemeConfig(filePath);
20428
- }
20429
- const jsonTemplate = await this.getThemeConfig(`${filePath}.json`);
20430
- if (jsonTemplate) {
20431
- return jsonTemplate;
20432
- }
20433
- return this.getThemeConfig(`${filePath}.liquid`);
21066
+ return this._getTemplateConfig(filePath);
20434
21067
  }
20435
- async getThemeTemplateConfigByType(type, name, suffix) {
21068
+ /**
21069
+ * Internal synchronous helper for getting template configs by type.
21070
+ * Used internally within theme.ts to avoid async overhead.
21071
+ */
21072
+ _getTemplateConfigByType(type, name, suffix) {
20436
21073
  const templatesByPriority = [withSuffix(`${type}/${name}`, suffix)];
20437
21074
  if (this.shopifyCompatibility) {
20438
21075
  const path = this.shopifyCompatibility.getThemeFilePath(type, name);
20439
21076
  templatesByPriority.push(withSuffix(path, suffix));
20440
21077
  }
20441
21078
  for (const filePath of templatesByPriority) {
20442
- const templateConfig = await this.getThemeTemplateConfig(
20443
- `theme/${filePath}`
20444
- );
21079
+ const templateConfig = this._getTemplateConfig(`theme/${filePath}`);
20445
21080
  if (templateConfig) {
20446
21081
  return templateConfig;
20447
21082
  }
20448
21083
  }
20449
21084
  return null;
20450
21085
  }
21086
+ /**
21087
+ * Internal synchronous helper for getting template configs.
21088
+ */
21089
+ _getTemplateConfig(filePath) {
21090
+ if (filePath.endsWith(".json") || filePath.endsWith(".liquid")) {
21091
+ return this.themeLoader.getConfig(filePath);
21092
+ }
21093
+ const jsonTemplate = this.themeLoader.getConfig(`${filePath}.json`);
21094
+ if (jsonTemplate) {
21095
+ return jsonTemplate;
21096
+ }
21097
+ return this.themeLoader.getConfig(`${filePath}.liquid`);
21098
+ }
21099
+ async getThemeTemplateConfigByType(type, name, suffix) {
21100
+ return this._getTemplateConfigByType(type, name, suffix);
21101
+ }
20451
21102
  async getAssetConfig(assetName) {
20452
21103
  return await this.getThemeConfig(`theme/assets/${assetName}`) ?? await this.getThemeConfig(`assets/${assetName}`) ?? null;
20453
21104
  }
@@ -20469,17 +21120,8 @@ var SwellTheme3 = class {
20469
21120
  return "";
20470
21121
  }
20471
21122
  template = unescapeLiquidSyntax(template);
20472
- const trace = createTraceId();
20473
21123
  try {
20474
- logger.debug("[SDK] Render template start", {
20475
- config: config.name,
20476
- trace
20477
- });
20478
21124
  const result = await this.liquidSwell.parseAndRender(template, data);
20479
- logger.debug("[SDK] Render template end", {
20480
- config: config.name,
20481
- trace
20482
- });
20483
21125
  return result;
20484
21126
  } catch (err) {
20485
21127
  logger.error(err);
@@ -20496,10 +21138,7 @@ var SwellTheme3 = class {
20496
21138
  }
20497
21139
  async getSectionSchema(sectionName) {
20498
21140
  let result;
20499
- const config = await this.getThemeTemplateConfigByType(
20500
- "sections",
20501
- sectionName
20502
- );
21141
+ const config = this._getTemplateConfigByType("sections", sectionName);
20503
21142
  if (config?.file_path?.endsWith(".json")) {
20504
21143
  try {
20505
21144
  result = JSON56.parse(config.file_data) || void 0;
@@ -20577,10 +21216,7 @@ var SwellTheme3 = class {
20577
21216
  return content;
20578
21217
  }
20579
21218
  async renderLayoutTemplate(name, data) {
20580
- const templateConfig = await this.getThemeTemplateConfigByType(
20581
- "layouts",
20582
- name
20583
- );
21219
+ const templateConfig = this._getTemplateConfigByType("layouts", name);
20584
21220
  if (!templateConfig) {
20585
21221
  throw new Error(`Layout template not found: ${name}`);
20586
21222
  }
@@ -20615,17 +21251,14 @@ ${content.slice(pos)}`;
20615
21251
  async renderPageTemplate(name, data, altTemplateId) {
20616
21252
  let templateConfig = null;
20617
21253
  if (altTemplateId) {
20618
- templateConfig = await this.getThemeTemplateConfigByType(
21254
+ templateConfig = this._getTemplateConfigByType(
20619
21255
  "templates",
20620
21256
  name,
20621
21257
  altTemplateId
20622
21258
  );
20623
21259
  }
20624
21260
  if (!templateConfig) {
20625
- templateConfig = await this.getThemeTemplateConfigByType(
20626
- "templates",
20627
- name
20628
- );
21261
+ templateConfig = this._getTemplateConfigByType("templates", name);
20629
21262
  }
20630
21263
  if (templateConfig) {
20631
21264
  const templatePath = name.split("/").splice(1).join("/") || null;
@@ -20726,7 +21359,7 @@ ${content.slice(pos)}`;
20726
21359
  }
20727
21360
  const [sectionKey, originalPageId] = sectionId.split(/__/).reverse();
20728
21361
  const pageId = (originalPageId || "").replaceAll("_", "/");
20729
- const templateConfig = await this.getThemeTemplateConfigByType(
21362
+ const templateConfig = this._getTemplateConfigByType(
20730
21363
  pageId ? "templates" : "sections",
20731
21364
  pageId ? pageId : sectionKey
20732
21365
  );
@@ -20901,7 +21534,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20901
21534
  return defaults;
20902
21535
  }
20903
21536
  async getAllSections() {
20904
- const configs = await this.getThemeConfigsByPath("theme/sections/");
21537
+ const configs = this.getThemeConfigsByPath("theme/sections/");
20905
21538
  return getAllSections(configs, this.getTemplateSchema.bind(this));
20906
21539
  }
20907
21540
  async getPageSections(sectionGroup, resolveSettings = true) {
@@ -20928,7 +21561,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20928
21561
  const sectionName = sectionSchema?.label || sectionFileName;
20929
21562
  let sourcePath = "";
20930
21563
  if (group) {
20931
- const sectionConfig = await this.getThemeTemplateConfigByType(
21564
+ const sectionConfig = this._getTemplateConfigByType(
20932
21565
  "sections",
20933
21566
  `${sectionFileName}.json`
20934
21567
  );
@@ -20945,7 +21578,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
20945
21578
  * Get a list of sections and section groups in a page layout.
20946
21579
  */
20947
21580
  async getPageSectionGroups(pageId, altTemplate) {
20948
- const pageConfig = await this.getThemeTemplateConfigByType(
21581
+ const pageConfig = this._getTemplateConfigByType(
20949
21582
  "templates",
20950
21583
  pageId,
20951
21584
  altTemplate
@@ -21012,7 +21645,7 @@ ${this.shopifyCompatibility.getContentForHeader()}`;
21012
21645
  sectionConfigs.map(async (sectionConfig, index) => {
21013
21646
  const { section, schema } = sectionConfig;
21014
21647
  const settings = schema?.fields && this.globals ? resolveSectionSettings(this, sectionConfig, index) : { ...sectionConfig.settings };
21015
- const templateConfig = await this.getThemeTemplateConfigByType(
21648
+ const templateConfig = this._getTemplateConfigByType(
21016
21649
  "sections",
21017
21650
  `${section.type}.liquid`
21018
21651
  );
@@ -21704,21 +22337,30 @@ export {
21704
22337
  StorefrontResource,
21705
22338
  Swell,
21706
22339
  SwellAccount,
22340
+ SwellAddresses,
21707
22341
  SwellBackendAPI,
21708
22342
  SwellBlog,
21709
22343
  SwellBlogCategory,
21710
22344
  SwellCart,
22345
+ SwellCategories,
21711
22346
  SwellCategory,
21712
22347
  SwellError,
21713
22348
  SwellOrder,
22349
+ SwellOrders,
21714
22350
  SwellPage,
22351
+ SwellPredictiveSearch,
21715
22352
  SwellProduct,
22353
+ SwellProductRecommendations,
22354
+ SwellSearch,
21716
22355
  SwellStorefrontCollection,
21717
22356
  SwellStorefrontPagination,
21718
22357
  SwellStorefrontRecord,
21719
22358
  SwellStorefrontResource,
21720
22359
  SwellStorefrontSingleton,
22360
+ SwellSubscription,
22361
+ SwellSubscriptions,
21721
22362
  SwellTheme3 as SwellTheme,
22363
+ SwellVariant,
21722
22364
  ThemeColor,
21723
22365
  ThemeFont,
21724
22366
  ThemeForm,
@@ -21750,6 +22392,7 @@ export {
21750
22392
  getEasyblocksComponentDefinitions,
21751
22393
  getEasyblocksPagePropsWithConfigs,
21752
22394
  getEasyblocksPageTemplate,
22395
+ getKVFlavor,
21753
22396
  getLayoutSectionGroups,
21754
22397
  getMenuItemStorefrontUrl,
21755
22398
  getMenuItemUrlAndResource,
@@ -21765,6 +22408,7 @@ export {
21765
22408
  isObject2 as isObject,
21766
22409
  md5,
21767
22410
  removeCircularReferences,
22411
+ resetKVFlavorCache,
21768
22412
  resolveAsyncResources,
21769
22413
  resolveLookupCollection,
21770
22414
  resolveMenuItemUrlAndResource,