@occluze/tether 0.7.0 → 0.7.2

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 (139) hide show
  1. package/README.md +51 -109
  2. package/dist/esm/astro.js +1 -1
  3. package/dist/esm/chunks/{account-UIPWJQ3Y.js → account-W4FBRKXA.js} +1 -1
  4. package/dist/esm/chunks/{account-pages-6ROA6Y4V.js → account-pages-IM3OQTOP.js} +1 -1
  5. package/dist/esm/chunks/analytics-QDVOLZY3.js +1 -0
  6. package/dist/esm/chunks/{chunk-T44MUL4Y.js → chunk-AUVAN3KZ.js} +1 -1
  7. package/dist/esm/chunks/chunk-BMDYNMRC.js +1 -0
  8. package/dist/esm/chunks/chunk-CUL66KRA.js +1 -0
  9. package/dist/esm/chunks/{chunk-6OZJCAGZ.js → chunk-CVNRODH3.js} +1 -1
  10. package/dist/esm/chunks/{chunk-5Z5O3767.js → chunk-CXHPBT7N.js} +1 -1
  11. package/dist/esm/chunks/chunk-GF6JTI65.js +1 -0
  12. package/dist/esm/chunks/chunk-MRSDJPBC.js +1 -0
  13. package/dist/esm/chunks/chunk-NYDZPTLP.js +1 -0
  14. package/dist/esm/chunks/chunk-QDHLXXAZ.js +1 -0
  15. package/dist/esm/chunks/{chunk-4GCXM764.js → chunk-QUECUXHD.js} +1 -1
  16. package/dist/esm/chunks/chunk-RJY76ESL.js +1 -0
  17. package/dist/esm/chunks/chunk-RPI5C3EJ.js +1 -0
  18. package/dist/esm/chunks/{chunk-BFTZR4OB.js → chunk-YC4JXWRG.js} +2 -2
  19. package/dist/esm/chunks/{diagnose-3FEKRIEK.js → diagnose-3XHPZZNP.js} +1 -1
  20. package/dist/esm/chunks/extras-C724KKG6.js +1 -0
  21. package/dist/esm/chunks/{markets-ui-FD6NI2OG.js → markets-ui-WBWWQAGU.js} +1 -1
  22. package/dist/esm/chunks/{search-UJ73YBQ5.js → search-5NFIZBPJ.js} +1 -1
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/server.js +2 -2
  25. package/dist/tether.account-pages.js +13 -3
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +13 -3
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +18 -3
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +20 -5
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +52 -28
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +353 -288
  36. package/dist/tether.markets.js +31 -5
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +2 -2
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +18 -3
  41. package/dist/tether.search.min.js +1 -1
  42. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  43. package/dist/types/src/cart/mutations.d.ts +6 -0
  44. package/dist/types/src/cart/store.d.ts +5 -2
  45. package/dist/types/src/cart.d.ts +3 -2
  46. package/dist/types/src/client.d.ts +61 -22
  47. package/dist/types/src/config.d.ts +6 -1
  48. package/dist/types/src/index.d.ts +2 -2
  49. package/dist/types/src/markets.d.ts +9 -2
  50. package/dist/types/src/product-shape.d.ts +3 -1
  51. package/dist/types/src/uri.d.ts +6 -0
  52. package/package.json +54 -6
  53. package/dist/esm/chunks/analytics-2V632IRV.js +0 -1
  54. package/dist/esm/chunks/chunk-56FCLJJD.js +0 -1
  55. package/dist/esm/chunks/chunk-A5SYCVQM.js +0 -1
  56. package/dist/esm/chunks/chunk-BA4LQJJQ.js +0 -1
  57. package/dist/esm/chunks/chunk-C5JYQCWH.js +0 -1
  58. package/dist/esm/chunks/chunk-E5YO6274.js +0 -1
  59. package/dist/esm/chunks/chunk-MPDWM2NN.js +0 -1
  60. package/dist/esm/chunks/chunk-YLUULATY.js +0 -1
  61. package/dist/esm/chunks/extras-6POFBNOP.js +0 -1
  62. package/src/account/addresses-view.ts +0 -58
  63. package/src/account/answers.ts +0 -79
  64. package/src/account/markup.ts +0 -36
  65. package/src/account/operations.ts +0 -145
  66. package/src/account/order-lines.ts +0 -75
  67. package/src/account/order-list.ts +0 -107
  68. package/src/account/order-page.ts +0 -137
  69. package/src/account/page-answers.ts +0 -317
  70. package/src/account/page-events.ts +0 -253
  71. package/src/account/server-client.ts +0 -183
  72. package/src/account/subscriptions-view.ts +0 -115
  73. package/src/account-api.ts +0 -98
  74. package/src/account-marks.ts +0 -35
  75. package/src/account-pages.ts +0 -81
  76. package/src/account.ts +0 -284
  77. package/src/amounts.ts +0 -64
  78. package/src/analytics-shopify.ts +0 -246
  79. package/src/analytics.ts +0 -315
  80. package/src/api-version.ts +0 -79
  81. package/src/astro.ts +0 -272
  82. package/src/cart/cart-shape.ts +0 -25
  83. package/src/cart/checkout.ts +0 -101
  84. package/src/cart/drawer.ts +0 -125
  85. package/src/cart/mutations.ts +0 -237
  86. package/src/cart/render.ts +0 -266
  87. package/src/cart/store.ts +0 -279
  88. package/src/cart.ts +0 -345
  89. package/src/client.ts +0 -391
  90. package/src/collection.ts +0 -175
  91. package/src/config-sections.ts +0 -284
  92. package/src/config.ts +0 -480
  93. package/src/controls/cart-controls.ts +0 -123
  94. package/src/controls/discount.ts +0 -75
  95. package/src/controls/extras.ts +0 -54
  96. package/src/controls/navigation.ts +0 -77
  97. package/src/diagnose-markup.ts +0 -298
  98. package/src/diagnose.ts +0 -294
  99. package/src/dom.ts +0 -268
  100. package/src/extras.ts +0 -136
  101. package/src/fragments.ts +0 -261
  102. package/src/globals.d.ts +0 -15
  103. package/src/images.ts +0 -91
  104. package/src/index.ts +0 -415
  105. package/src/initial.ts +0 -101
  106. package/src/lazy-iife.ts +0 -197
  107. package/src/lazy.ts +0 -42
  108. package/src/loader.ts +0 -363
  109. package/src/market-codes.ts +0 -6
  110. package/src/markets-ui.ts +0 -227
  111. package/src/markets.ts +0 -246
  112. package/src/markup.ts +0 -127
  113. package/src/mounting.ts +0 -264
  114. package/src/page-market.ts +0 -110
  115. package/src/part-integrity.ts +0 -7
  116. package/src/parts/account-pages.ts +0 -7
  117. package/src/parts/account.ts +0 -7
  118. package/src/parts/analytics.ts +0 -7
  119. package/src/parts/diagnose.ts +0 -7
  120. package/src/parts/extras.ts +0 -7
  121. package/src/parts/markets.ts +0 -7
  122. package/src/parts/search.ts +0 -7
  123. package/src/presets/barba.ts +0 -194
  124. package/src/product-events.ts +0 -115
  125. package/src/product-render.ts +0 -218
  126. package/src/product-shape.ts +0 -49
  127. package/src/product-state.ts +0 -318
  128. package/src/product.ts +0 -308
  129. package/src/repeaters.ts +0 -161
  130. package/src/search.ts +0 -305
  131. package/src/selling-plans.ts +0 -145
  132. package/src/server.ts +0 -211
  133. package/src/shopify-values.ts +0 -91
  134. package/src/status.ts +0 -106
  135. package/src/storage.ts +0 -34
  136. package/src/store-html.ts +0 -359
  137. package/src/storefront/catalog.ts +0 -196
  138. package/src/storefront/json-ld.ts +0 -144
  139. package/src/variant-render.ts +0 -139
package/dist/tether.js CHANGED
@@ -452,7 +452,7 @@
452
452
  if (typeof shape === "function") return shape(value);
453
453
  if (typeof shape === "string") return shape === "object" ? isRecord(value) : typeof value === shape;
454
454
  if (isShapeList(shape)) return Array.isArray(value) && value.every((item) => matches(item, shape[0]));
455
- return isRecord(value) && Object.entries(shape).every(([key, inner]) => matches(value[key], inner));
455
+ return isRecord(value) && Object.entries(shape).every(([key, inner]) => inner === void 0 || matches(value[key], inner));
456
456
  }
457
457
  var isShapeList = (shape) => Array.isArray(shape);
458
458
  var orNull = (shape) => (value) => value === null || matches(value, shape);
@@ -462,6 +462,9 @@
462
462
  }
463
463
  var STRINGS_AND_COMMENTS = /"(?:\\.|[^"\\\n])*"|#[^\n]*/g;
464
464
  var OPERATION_START = /^\s*(?:query|mutation)(?:\s+\w+)?\s*(?:\([^)]*\))?/;
465
+ function operationOf(document2) {
466
+ return /\bmutation\b/.test(document2.replace(STRINGS_AND_COMMENTS, "")) ? "mutation" : "query";
467
+ }
465
468
  function inContext(query2, { country, language }) {
466
469
  const args = [];
467
470
  if (country) args.push(`country: ${country}`);
@@ -489,10 +492,10 @@
489
492
  var INVENTORY_FIELD = "quantityAvailable";
490
493
  var storesWithoutInventory = /* @__PURE__ */ new Set();
491
494
  var storeOf = (conf) => `${conf.domain} ${conf.apiVersion} ${conf.token}`;
492
- function queryText(conf, query2, access, context) {
495
+ function queryText(conf, query2, access, { context, operation }) {
493
496
  const written = typeof query2 === "string" ? query2 : query2(access);
494
497
  const text2 = context ? inContext(written, conf) : written;
495
- if (conf.maxAge <= 0 || /^\s*mutation\b/.test(text2)) return text2;
498
+ if (conf.maxAge <= 0 || operation === "mutation") return text2;
496
499
  return `${text2} #${Math.floor(Date.now() / (conf.maxAge * MS_PER_SECOND))}`;
497
500
  }
498
501
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -520,16 +523,30 @@
520
523
  return { data, errors: errors.map(toGraphQLError) };
521
524
  }
522
525
  function invalidResponse(res, conf) {
523
- const text2 = res.ok ? `Invalid response (${res.status})` : statusText(res.status, conf);
524
- return new TetherError(text2, { status: res.status });
526
+ return res.ok ? new TetherError(`Invalid response (${res.status})`, { code: "INVALID_RESPONSE", status: res.status }) : new TetherError(statusText(res.status, conf), { status: res.status });
525
527
  }
526
- function graphQLFailure(res, errors, conf) {
528
+ function graphQLFailure(res, errors, conf, code) {
527
529
  const message = errors.map((error) => error.message).filter(Boolean).join(" | ");
528
530
  let text2 = message || "Storefront API error";
529
531
  if (!res.ok) text2 = res.status === 401 || res.status === 403 || !message ? statusText(res.status, conf) : message;
530
- return new TetherError(text2, { status: res.status, errors });
532
+ return new TetherError(text2, { status: res.status, errors, code });
533
+ }
534
+ function networkFailure(error, retry) {
535
+ if (retry === "any") return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
536
+ throw new TetherError(`Network error: ${messageOf(error)}`, { code: "NETWORK" });
537
+ }
538
+ function graphQLOutcome(res, conf, query2, errors, data, retry, operation) {
539
+ if (query2.includes(INVENTORY_FIELD) && errors.some((error) => error.message.includes(INVENTORY_FIELD))) {
540
+ return { kind: "inventory-refused", error: graphQLFailure(res, errors, conf) };
541
+ }
542
+ const codes = errors.map((error) => error.extensions?.code);
543
+ if (retry !== "none" && codes.includes("THROTTLED")) return { kind: "retry", backoffMs: THROTTLE_BACKOFF_MS };
544
+ const serverError = codes.includes("INTERNAL_SERVER_ERROR");
545
+ if (retry === "any" && serverError) return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
546
+ const unclear = res.ok && (serverError || operation === "mutation" && data == null);
547
+ throw graphQLFailure(res, errors, conf, unclear ? "SERVER_ERROR" : void 0);
531
548
  }
532
- async function attempt(conf, query2, variables, isData, canRetry) {
549
+ async function attempt(conf, query2, variables, isData, retry, operation) {
533
550
  let res;
534
551
  try {
535
552
  res = await (conf.fetch ?? fetch)(endpoint(conf), {
@@ -538,53 +555,46 @@
538
555
  body: JSON.stringify({ query: query2, variables })
539
556
  });
540
557
  } catch (error) {
541
- if (canRetry) return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
542
- throw new TetherError(`Network error: ${messageOf(error)}`, {
543
- code: "NETWORK"
544
- });
558
+ return networkFailure(error, retry);
545
559
  }
546
560
  if (res.status === SECURITY_REJECTION) {
547
561
  throw new TetherError("Shopify security rejection (430)", { status: SECURITY_REJECTION });
548
562
  }
549
- if (canRetry && (res.status === 429 || res.status >= 500)) {
563
+ if (retry !== "none" && res.status === 429 || retry === "any" && res.status >= 500) {
550
564
  return { kind: "retry", backoffMs: RETRY_BACKOFF_MS };
551
565
  }
552
566
  let json;
553
567
  try {
554
568
  json = await res.json();
555
- } catch {
556
- throw invalidResponse(res, conf);
569
+ } catch (error) {
570
+ if (error instanceof SyntaxError) throw invalidResponse(res, conf);
571
+ return networkFailure(error, retry);
557
572
  }
558
573
  const body = toGraphQLResponse(json);
559
574
  if (!body) throw invalidResponse(res, conf);
560
575
  const { data, errors = [] } = body;
561
- if (errors.length) {
562
- if (query2.includes(INVENTORY_FIELD) && errors.some((error) => error.message.includes(INVENTORY_FIELD))) {
563
- return { kind: "inventory-refused", error: graphQLFailure(res, errors, conf) };
564
- }
565
- if (canRetry && errors.some((error) => error.extensions?.code === "THROTTLED")) {
566
- return { kind: "retry", backoffMs: THROTTLE_BACKOFF_MS };
567
- }
568
- throw graphQLFailure(res, errors, conf);
569
- }
576
+ if (errors.length) return graphQLOutcome(res, conf, query2, errors, data, retry, operation);
570
577
  if (!res.ok) throw new TetherError(statusText(res.status, conf), { status: res.status });
571
578
  if (!isData(data)) throw invalidResponse(res, conf);
572
579
  return { kind: "data", data };
573
580
  }
574
- async function request(conf, query2, variables, isData, { context = true } = {}) {
581
+ async function request(conf, query2, variables, isData, { context = true, operation = "query" } = {}) {
575
582
  if (!conf.domain || !conf.token) {
576
583
  throw new TetherError("Tether is not configured: domain and token are required", { code: "NOT_CONFIGURED" });
577
584
  }
578
585
  const store = storeOf(conf);
586
+ const retryWhileLeft = operation === "mutation" ? "not-run" : "any";
579
587
  let retries = 0;
580
588
  for (; ; ) {
581
- const sent = queryText(conf, query2, { inventory: !storesWithoutInventory.has(store) }, context);
582
- const outcome = await attempt(conf, sent, variables, isData, retries < conf.retries);
589
+ const access = { inventory: !storesWithoutInventory.has(store) };
590
+ const sent = queryText(conf, query2, access, { context, operation });
591
+ const retry = retries < conf.retries ? retryWhileLeft : "none";
592
+ const outcome = await attempt(conf, sent, variables, isData, retry, operation);
583
593
  switch (outcome.kind) {
584
594
  case "data":
585
595
  return outcome.data;
586
596
  case "inventory-refused":
587
- if (typeof query2 === "string") throw outcome.error;
597
+ if (typeof query2 === "string" || !access.inventory) throw outcome.error;
588
598
  log(`the token can't read inventory: ${INVENTORY_FIELD} is left out from now on`);
589
599
  storesWithoutInventory.add(store);
590
600
  break;
@@ -600,8 +610,11 @@
600
610
  return request(cfg, query2, variables, isData, options);
601
611
  }
602
612
  var hasData = (data) => typeof data === "object" && data !== null;
603
- function query(text2, variables = {}, options) {
604
- return gql(text2, variables, hasData, options);
613
+ function untypedQuery(conf, text2, variables = {}, options) {
614
+ return request(conf, text2, variables, hasData, { ...options, operation: operationOf(text2) });
615
+ }
616
+ function query(text2, variables, options) {
617
+ return untypedQuery(cfg, text2, variables, options);
605
618
  }
606
619
  function unwrap(payload, name) {
607
620
  if (!payload) throw new TetherError(`${name}: empty response`);
@@ -1354,7 +1367,10 @@
1354
1367
  priceRange: { minVariantPrice: MONEY2, maxVariantPrice: MONEY2 },
1355
1368
  variants: { nodes: [{ id: "string", price: MONEY2, selectedOptions: ["object"] }] }
1356
1369
  };
1357
- var PRODUCT_CONNECTION = { nodes: [PRODUCT], pageInfo: { hasNextPage: "boolean" } };
1370
+ var PRODUCT_CONNECTION = {
1371
+ nodes: [PRODUCT],
1372
+ pageInfo: { hasNextPage: "boolean" }
1373
+ };
1358
1374
  var isProductShape = (value) => field(value, "id") !== "" && field(value, "handle") !== "" && matches(value, PRODUCT);
1359
1375
  var PRODUCT_NODE = (node) => isRecord(node) && (node["id"] === void 0 || matches(node, PRODUCT));
1360
1376
 
@@ -1670,6 +1686,160 @@
1670
1686
  return list;
1671
1687
  }
1672
1688
 
1689
+ // src/product-render.ts
1690
+ var NTH_IMAGE_BIND = /^image:(\d+)$/;
1691
+ var METAFIELD_BIND = "metafield:";
1692
+ var MULTI_LINE_METAFIELD = /^(rich_text|multi_line_text)_field$/;
1693
+ var ALL_PLAN_GROUPS = ["", "true", "all", "*"];
1694
+ var isSelect = (el) => el.tagName === "SELECT";
1695
+ var selectsIn = (scope, selector) => scope.$(`select${selector}`).filter(isSelect);
1696
+ function renderGallery(el, product) {
1697
+ const images = product.images.nodes;
1698
+ const rendered = [...el.children].map((node) => node.tagName === "IMG" ? node : node.querySelector("img"));
1699
+ const same = !hasTemplate(el) && rendered.length === images.length && rendered.every((img, index) => sameImage(img?.getAttribute("src"), images[index]?.url));
1700
+ if (same) return;
1701
+ repeat(el, images, (node, image) => {
1702
+ const target = node.matches(`img,${sel("bind", "image")}`) ? node : qsa(node, "img")[0] ?? node;
1703
+ setImage(target, image.url, image.altText || product.title);
1704
+ });
1705
+ }
1706
+ function renderMetafield(el, product, ref) {
1707
+ const dot = ref.indexOf(".");
1708
+ const namespace = ref.slice(0, dot);
1709
+ const key = ref.slice(dot + 1);
1710
+ const metafield = (product.metafields ?? []).find((field2) => field2?.namespace === namespace && field2.key === key);
1711
+ if (!metafield) {
1712
+ setText(el, "");
1713
+ return;
1714
+ }
1715
+ const image = metafield.reference?.image;
1716
+ if (image) {
1717
+ setImage(el, image.url, image.altText);
1718
+ return;
1719
+ }
1720
+ if (MULTI_LINE_METAFIELD.test(metafield.type)) el.style.whiteSpace = "pre-line";
1721
+ setText(el, metafield.value);
1722
+ }
1723
+ var PRODUCT_TEXTS = new Map(
1724
+ Object.entries({
1725
+ title: (product) => product.title,
1726
+ "description-text": (product) => product.description,
1727
+ vendor: (product) => product.vendor,
1728
+ type: (product) => product.productType,
1729
+ handle: (product) => product.handle,
1730
+ "published-at": (product) => formatDate(product.publishedAt),
1731
+ "min-price": (product) => formatMoney2(product.priceRange.minVariantPrice),
1732
+ "max-price": (product) => formatMoney2(product.priceRange.maxVariantPrice)
1733
+ })
1734
+ );
1735
+ function renderTags(el, product) {
1736
+ const exclude = (el.getAttribute("exclude") ?? "").split(",").map((tag) => tag.trim()).filter(Boolean);
1737
+ const tags = product.tags.filter((tag) => !exclude.includes(tag));
1738
+ repeat(el, tags, (node, tag) => {
1739
+ setText(node, tag);
1740
+ });
1741
+ }
1742
+ function renderProductBind(el, key, product) {
1743
+ const text2 = PRODUCT_TEXTS.get(key);
1744
+ if (text2) {
1745
+ setText(el, text2(product));
1746
+ return;
1747
+ }
1748
+ switch (key) {
1749
+ case "description":
1750
+ setStoreHtml(el, product.descriptionHtml);
1751
+ break;
1752
+ case "featured-image":
1753
+ setImage(el, product.featuredImage?.url, product.featuredImage?.altText || product.title);
1754
+ break;
1755
+ case "url":
1756
+ case "link": {
1757
+ const url2 = productUrl2(product.handle, product.id);
1758
+ if (url2) el.setAttribute("href", url2);
1759
+ if (key === "url" && !el.firstElementChild && !el.textContent.trim()) setText(el, product.title);
1760
+ break;
1761
+ }
1762
+ case "images":
1763
+ renderGallery(el, product);
1764
+ break;
1765
+ case "tags":
1766
+ renderTags(el, product);
1767
+ break;
1768
+ default:
1769
+ renderIndexedBind(el, key, product);
1770
+ }
1771
+ }
1772
+ function renderIndexedBind(el, key, product) {
1773
+ const nth = NTH_IMAGE_BIND.exec(key);
1774
+ if (nth) {
1775
+ const image = product.images.nodes[Number(nth[1]) - 1] ?? product.featuredImage;
1776
+ setImage(el, image?.url, image?.altText || product.title);
1777
+ } else if (key.startsWith(METAFIELD_BIND)) {
1778
+ renderMetafield(el, product, key.slice(METAFIELD_BIND.length));
1779
+ }
1780
+ }
1781
+ function renderOptionLists(scope) {
1782
+ const { product } = scope;
1783
+ scope.$(sel("option-list")).forEach((container) => {
1784
+ const name = scope.optName(get(container, "option-list"));
1785
+ const option = product.options.find((candidate) => candidate.name === name);
1786
+ if (!option || option.optionValues.length <= 1 && has(container, "hide-single")) {
1787
+ show(container, false);
1788
+ return;
1789
+ }
1790
+ repeat(container, option.optionValues, (node, value) => {
1791
+ node.setAttribute(attr("option-value"), value.name);
1792
+ const text2 = qsa(node, sel("bind", "option-value"))[0];
1793
+ if (text2) setText(text2, value.name);
1794
+ else if (!node.firstElementChild) setText(node, value.name);
1795
+ const swatchEl = qsa(node, sel("bind", "swatch"))[0];
1796
+ const { swatch } = value;
1797
+ if (!swatchEl || !swatch) return;
1798
+ if (swatch.color) swatchEl.style.backgroundColor = swatch.color;
1799
+ if (swatch.image?.previewImage) setImage(swatchEl, swatch.image.previewImage.url, value.name);
1800
+ });
1801
+ });
1802
+ }
1803
+ function renderSelects(scope) {
1804
+ const { product } = scope;
1805
+ selectsIn(scope, sel("option")).forEach((select) => {
1806
+ const option = product.options.find((candidate) => candidate.name === scope.optName(get(select, "option")));
1807
+ if (!option) {
1808
+ show(select, false);
1809
+ return;
1810
+ }
1811
+ select.replaceChildren(...option.optionValues.map((value) => new Option(value.name, value.name)));
1812
+ });
1813
+ selectsIn(scope, sel("variant-select")).forEach((select) => {
1814
+ select.replaceChildren(...product.variants.nodes.map((variant) => new Option(variant.title, variant.id)));
1815
+ });
1816
+ selectsIn(scope, sel("plan")).forEach((select) => {
1817
+ const plans = scope.plans.map((plan) => new Option(plan.name, plan.id));
1818
+ const oneTime = !product.requiresSellingPlan && get(select, "plan") !== "subscription-only";
1819
+ const oneTimeLabel = select.getAttribute(attr("one-time-label")) || "One-time purchase";
1820
+ select.replaceChildren(...oneTime ? [new Option(oneTimeLabel, "")] : [], ...plans);
1821
+ });
1822
+ }
1823
+ function renderPlanLists(scope) {
1824
+ scope.$(sel("plan-list")).forEach((container) => {
1825
+ const raw = (get(container, "plan-list") ?? "").trim();
1826
+ const group = ALL_PLAN_GROUPS.includes(raw.toLowerCase()) ? null : raw;
1827
+ const plans = scope.plans.filter((plan) => !group || plan.group.name === group);
1828
+ repeat(container, plans, (node, plan) => {
1829
+ node.setAttribute(attr("plan-value"), plan.id);
1830
+ if (!node.firstElementChild && !node.querySelector(sel("plan-bind"))) setText(node, plan.name);
1831
+ });
1832
+ });
1833
+ }
1834
+ function writeProduct(scope) {
1835
+ scope.$(sel("bind")).forEach((el) => {
1836
+ renderProductBind(el, get(el, "bind") ?? "", scope.product);
1837
+ });
1838
+ renderOptionLists(scope);
1839
+ renderSelects(scope);
1840
+ renderPlanLists(scope);
1841
+ }
1842
+
1673
1843
  // src/cart/render.ts
1674
1844
  var DEFAULT_TITLE = "Default Title";
1675
1845
  var quantityOf = (line, pending2) => pending2.get(line.id)?.quantity ?? line.quantity;
@@ -1771,7 +1941,8 @@
1771
1941
  if (value !== void 0) setTextTracked(el, value);
1772
1942
  });
1773
1943
  renderLineState(node, line, view);
1774
- qsa(node, `select${sel("line-plan")}`).forEach((select) => {
1944
+ const planSelects = qsa(node, sel("line-plan")).filter(isSelect);
1945
+ planSelects.forEach((select) => {
1775
1946
  getProduct(variant.product.handle).then((product) => {
1776
1947
  fillPlanSelect(select, line, product);
1777
1948
  }).catch((error) => {
@@ -1876,6 +2047,9 @@
1876
2047
  }
1877
2048
  }
1878
2049
 
2050
+ // server/src/shared/cart-id.ts
2051
+ var CART_ID = /^gid:\/\/shopify\/Cart\/[\w-]+(\?key=[\w-]+)?$/;
2052
+
1879
2053
  // src/cart/store.ts
1880
2054
  var status = { kind: "unknown" };
1881
2055
  var warnings = [];
@@ -1894,7 +2068,13 @@
1894
2068
  var currentWarnings = () => warnings;
1895
2069
  var statusOf = (cart) => cart ? { kind: "ready", cart } : { kind: "empty" };
1896
2070
  var seenKey = () => `${cfg.storageKey}:seen`;
1897
- var savedCartId = () => getStored(cfg.storageKey) || null;
2071
+ function savedCartId() {
2072
+ const stored = getStored(cfg.storageKey);
2073
+ if (!stored) return null;
2074
+ if (CART_ID.test(stored)) return stored;
2075
+ forgetSavedCart();
2076
+ return null;
2077
+ }
1898
2078
  function forgetSavedCart() {
1899
2079
  removeStored(cfg.storageKey);
1900
2080
  }
@@ -1996,6 +2176,11 @@
1996
2176
  if (error.userErrors?.some((userError) => userError.field?.[0] === "cartId")) return true;
1997
2177
  return (error.errors ?? []).some((graphQLError) => UNKNOWN_CART_MESSAGE.test(graphQLError.message));
1998
2178
  }
2179
+ function isUnclearFailure(error) {
2180
+ if (!(error instanceof TetherError)) return false;
2181
+ const { code, status: status2 = 0 } = error;
2182
+ return code === "NETWORK" || code === "INVALID_RESPONSE" || code === "SERVER_ERROR" || status2 >= 500;
2183
+ }
1999
2184
  function toLineInput(input, quantity = 1, options = {}) {
2000
2185
  const spec = typeof input === "object" && input !== null ? input : { ...options, quantity, ...input === null || input === void 0 ? {} : { variantId: input } };
2001
2186
  const merchandiseId = GID.variant(spec.merchandiseId || spec.variantId || spec.id);
@@ -2012,7 +2197,7 @@
2012
2197
  return (access) => `mutation Tether_${name}(${args}){${name}(${inner}){cart{...TetherCart}userErrors{field message}warnings{code message target}}}${cartFragment(access)}`;
2013
2198
  }
2014
2199
  async function send(name, args, inner, variables) {
2015
- const data = await gql(mutationText(name, args, inner), variables, isPayloadOf(name));
2200
+ const data = await gql(mutationText(name, args, inner), variables, isPayloadOf(name), { operation: "mutation" });
2016
2201
  const payload = unwrap(data[name], name);
2017
2202
  setWarnings(payload.warnings ?? []);
2018
2203
  return payload.cart;
@@ -2139,6 +2324,25 @@
2139
2324
 
2140
2325
  // src/cart.ts
2141
2326
  var currentLines = () => currentCart()?.lines.nodes ?? [];
2327
+ async function reloadAfterUnclearFailure() {
2328
+ const id = savedCartId();
2329
+ if (!id) return;
2330
+ try {
2331
+ const cart = await fetchCart(id);
2332
+ if (!cart) forgetSavedCart();
2333
+ commit(cart, "load");
2334
+ } catch (error) {
2335
+ reportError(error, "cart reload after a failed change");
2336
+ }
2337
+ }
2338
+ function runChange(task) {
2339
+ return enqueue(
2340
+ () => task().catch(async (error) => {
2341
+ if (isUnclearFailure(error)) await reloadAfterUnclearFailure();
2342
+ throw error;
2343
+ })
2344
+ );
2345
+ }
2142
2346
  function changeQuantity(lineId, delta) {
2143
2347
  const line = currentLines().find((candidate) => candidate.id === lineId);
2144
2348
  if (!line) return Promise.resolve(currentCart());
@@ -2147,7 +2351,7 @@
2147
2351
  pendingQuantities.set(lineId, job);
2148
2352
  announce("pending");
2149
2353
  const isLatest = () => pendingQuantities.get(lineId) === job;
2150
- const sent = enqueue(async () => {
2354
+ const sent = runChange(async () => {
2151
2355
  if (!isLatest()) return currentCart();
2152
2356
  const now = currentLines().find((candidate) => candidate.id === lineId);
2153
2357
  if (now?.quantity === target) return currentCart();
@@ -2255,7 +2459,7 @@
2255
2459
  const lines = (Array.isArray(input) ? input : [input]).map(
2256
2460
  (line) => toLineInput(line, quantity, options)
2257
2461
  );
2258
- return enqueue(async () => {
2462
+ return runChange(async () => {
2259
2463
  const cart = await mutateCart({
2260
2464
  name: "cartLinesAdd",
2261
2465
  args: "$lines: [CartLineInput!]!",
@@ -2279,23 +2483,26 @@
2279
2483
  const quantity = typeof patch === "number" ? patch : patch.quantity;
2280
2484
  if (quantity !== void 0 && quantity <= 0) return Cart.removeLine(lineId);
2281
2485
  const line = toLineUpdate(lineId, patch);
2282
- return enqueue(async () => commit(await updateLines([line]), "update"));
2486
+ return runChange(async () => commit(await updateLines([line]), "update"));
2283
2487
  },
2284
2488
  changeQuantity,
2285
2489
  /** The quantity to show: the one asked for while an update is on its way. */
2286
2490
  quantityOf: pendingQuantityOf,
2287
2491
  removeLine(lineIds) {
2288
2492
  const ids = Array.isArray(lineIds) ? lineIds : [lineIds];
2289
- return enqueue(async () => commit(await removeLines(ids), "remove"));
2493
+ return runChange(async () => commit(await removeLines(ids), "remove"));
2290
2494
  },
2495
+ /** Removes the lines the cart has when the change runs, so one queued just before it is cleared too. */
2291
2496
  clear() {
2292
- const ids = currentLines().map((line) => line.id);
2293
- if (!ids.length) return Promise.resolve(currentCart());
2294
- return Cart.removeLine(ids);
2497
+ return runChange(async () => {
2498
+ const ids = currentLines().map((line) => line.id);
2499
+ if (!ids.length) return currentCart();
2500
+ return commit(await removeLines(ids), "remove");
2501
+ });
2295
2502
  },
2296
2503
  setNote(note) {
2297
2504
  const variables = { note: note ?? "" };
2298
- return enqueue(
2505
+ return runChange(
2299
2506
  async () => commit(
2300
2507
  await mutateCart({
2301
2508
  name: "cartNoteUpdate",
@@ -2314,7 +2521,7 @@
2314
2521
  * @param attributes The value `''` removes a key.
2315
2522
  */
2316
2523
  setAttributes(attributes) {
2317
- return enqueue(async () => {
2524
+ return runChange(async () => {
2318
2525
  const variables = { attributes: mergedAttributes(attributes) };
2319
2526
  const cart = await mutateCart({
2320
2527
  name: "cartAttributesUpdate",
@@ -2328,7 +2535,7 @@
2328
2535
  },
2329
2536
  /** Replaces the discount codes; `[]` removes them all. */
2330
2537
  setDiscountCodes(codes) {
2331
- return enqueue(async () => {
2538
+ return runChange(async () => {
2332
2539
  const variables = { discountCodes: discountCodesFrom(codes) };
2333
2540
  const cart = await mutateCart({
2334
2541
  name: "cartDiscountCodesUpdate",
@@ -2367,7 +2574,7 @@
2367
2574
  * @param options `create: false` forgets an expired cart instead of creating an empty one.
2368
2575
  */
2369
2576
  setBuyerIdentity(buyerIdentity, { create = true } = {}) {
2370
- return enqueue(async () => {
2577
+ return runChange(async () => {
2371
2578
  const cart = await mutateCart({
2372
2579
  name: "cartBuyerIdentityUpdate",
2373
2580
  args: "$buyerIdentity: CartBuyerIdentityInput!",
@@ -2541,170 +2748,18 @@
2541
2748
  return new Map(Object.entries(texts));
2542
2749
  }
2543
2750
 
2544
- // src/product-render.ts
2545
- var NTH_IMAGE_BIND = /^image:(\d+)$/;
2546
- var METAFIELD_BIND = "metafield:";
2547
- var MULTI_LINE_METAFIELD = /^(rich_text|multi_line_text)_field$/;
2548
- var ALL_PLAN_GROUPS = ["", "true", "all", "*"];
2549
- var isSelect = (el) => el.tagName === "SELECT";
2550
- var selectsIn = (scope, selector) => scope.$(`select${selector}`).filter(isSelect);
2551
- function renderGallery(el, product) {
2552
- const images = product.images.nodes;
2553
- const rendered = [...el.children].map((node) => node.tagName === "IMG" ? node : node.querySelector("img"));
2554
- const same = !hasTemplate(el) && rendered.length === images.length && rendered.every((img, index) => sameImage(img?.getAttribute("src"), images[index]?.url));
2555
- if (same) return;
2556
- repeat(el, images, (node, image) => {
2557
- const target = node.matches(`img,${sel("bind", "image")}`) ? node : qsa(node, "img")[0] ?? node;
2558
- setImage(target, image.url, image.altText || product.title);
2559
- });
2560
- }
2561
- function renderMetafield(el, product, ref) {
2562
- const dot = ref.indexOf(".");
2563
- const namespace = ref.slice(0, dot);
2564
- const key = ref.slice(dot + 1);
2565
- const metafield = (product.metafields ?? []).find((field2) => field2?.namespace === namespace && field2.key === key);
2566
- if (!metafield) {
2567
- setText(el, "");
2568
- return;
2569
- }
2570
- const image = metafield.reference?.image;
2571
- if (image) {
2572
- setImage(el, image.url, image.altText);
2573
- return;
2574
- }
2575
- if (MULTI_LINE_METAFIELD.test(metafield.type)) el.style.whiteSpace = "pre-line";
2576
- setText(el, metafield.value);
2577
- }
2578
- var PRODUCT_TEXTS = new Map(
2579
- Object.entries({
2580
- title: (product) => product.title,
2581
- "description-text": (product) => product.description,
2582
- vendor: (product) => product.vendor,
2583
- type: (product) => product.productType,
2584
- handle: (product) => product.handle,
2585
- "published-at": (product) => formatDate(product.publishedAt),
2586
- "min-price": (product) => formatMoney2(product.priceRange.minVariantPrice),
2587
- "max-price": (product) => formatMoney2(product.priceRange.maxVariantPrice)
2588
- })
2589
- );
2590
- function renderTags(el, product) {
2591
- const exclude = (el.getAttribute("exclude") ?? "").split(",").map((tag) => tag.trim()).filter(Boolean);
2592
- const tags = product.tags.filter((tag) => !exclude.includes(tag));
2593
- repeat(el, tags, (node, tag) => {
2594
- setText(node, tag);
2595
- });
2596
- }
2597
- function renderProductBind(el, key, product) {
2598
- const text2 = PRODUCT_TEXTS.get(key);
2599
- if (text2) {
2600
- setText(el, text2(product));
2601
- return;
2602
- }
2603
- switch (key) {
2604
- case "description":
2605
- setStoreHtml(el, product.descriptionHtml);
2606
- break;
2607
- case "featured-image":
2608
- setImage(el, product.featuredImage?.url, product.featuredImage?.altText || product.title);
2609
- break;
2610
- case "url":
2611
- case "link": {
2612
- const url2 = productUrl2(product.handle, product.id);
2613
- if (url2) el.setAttribute("href", url2);
2614
- if (key === "url" && !el.firstElementChild && !el.textContent.trim()) setText(el, product.title);
2615
- break;
2616
- }
2617
- case "images":
2618
- renderGallery(el, product);
2619
- break;
2620
- case "tags":
2621
- renderTags(el, product);
2622
- break;
2623
- default:
2624
- renderIndexedBind(el, key, product);
2625
- }
2626
- }
2627
- function renderIndexedBind(el, key, product) {
2628
- const nth = NTH_IMAGE_BIND.exec(key);
2629
- if (nth) {
2630
- const image = product.images.nodes[Number(nth[1]) - 1] ?? product.featuredImage;
2631
- setImage(el, image?.url, image?.altText || product.title);
2632
- } else if (key.startsWith(METAFIELD_BIND)) {
2633
- renderMetafield(el, product, key.slice(METAFIELD_BIND.length));
2634
- }
2635
- }
2636
- function renderOptionLists(scope) {
2637
- const { product } = scope;
2638
- scope.$(sel("option-list")).forEach((container) => {
2639
- const name = scope.optName(get(container, "option-list"));
2640
- const option = product.options.find((candidate) => candidate.name === name);
2641
- if (!option || option.optionValues.length <= 1 && has(container, "hide-single")) {
2642
- show(container, false);
2643
- return;
2644
- }
2645
- repeat(container, option.optionValues, (node, value) => {
2646
- node.setAttribute(attr("option-value"), value.name);
2647
- const text2 = qsa(node, sel("bind", "option-value"))[0];
2648
- if (text2) setText(text2, value.name);
2649
- else if (!node.firstElementChild) setText(node, value.name);
2650
- const swatchEl = qsa(node, sel("bind", "swatch"))[0];
2651
- const { swatch } = value;
2652
- if (!swatchEl || !swatch) return;
2653
- if (swatch.color) swatchEl.style.backgroundColor = swatch.color;
2654
- if (swatch.image?.previewImage) setImage(swatchEl, swatch.image.previewImage.url, value.name);
2655
- });
2656
- });
2657
- }
2658
- function renderSelects(scope) {
2659
- const { product } = scope;
2660
- selectsIn(scope, sel("option")).forEach((select) => {
2661
- const option = product.options.find((candidate) => candidate.name === scope.optName(get(select, "option")));
2662
- if (!option) {
2663
- show(select, false);
2664
- return;
2665
- }
2666
- select.replaceChildren(...option.optionValues.map((value) => new Option(value.name, value.name)));
2667
- });
2668
- selectsIn(scope, sel("variant-select")).forEach((select) => {
2669
- select.replaceChildren(...product.variants.nodes.map((variant) => new Option(variant.title, variant.id)));
2670
- });
2671
- selectsIn(scope, sel("plan")).forEach((select) => {
2672
- const plans = scope.plans.map((plan) => new Option(plan.name, plan.id));
2673
- const oneTime = !product.requiresSellingPlan && get(select, "plan") !== "subscription-only";
2674
- const oneTimeLabel = select.getAttribute(attr("one-time-label")) || "One-time purchase";
2675
- select.replaceChildren(...oneTime ? [new Option(oneTimeLabel, "")] : [], ...plans);
2676
- });
2677
- }
2678
- function renderPlanLists(scope) {
2679
- scope.$(sel("plan-list")).forEach((container) => {
2680
- const raw = (get(container, "plan-list") ?? "").trim();
2681
- const group = ALL_PLAN_GROUPS.includes(raw.toLowerCase()) ? null : raw;
2682
- const plans = scope.plans.filter((plan) => !group || plan.group.name === group);
2683
- repeat(container, plans, (node, plan) => {
2684
- node.setAttribute(attr("plan-value"), plan.id);
2685
- if (!node.firstElementChild && !node.querySelector(sel("plan-bind"))) setText(node, plan.name);
2686
- });
2687
- });
2688
- }
2689
- function writeProduct(scope) {
2690
- scope.$(sel("bind")).forEach((el) => {
2691
- renderProductBind(el, get(el, "bind") ?? "", scope.product);
2692
- });
2693
- renderOptionLists(scope);
2694
- renderSelects(scope);
2695
- renderPlanLists(scope);
2696
- }
2697
-
2698
- // src/status.ts
2699
- var URL_SOURCE = "url:";
2700
- function decodePathSegment(segment) {
2751
+ // src/uri.ts
2752
+ function decodeOrKeep(text2) {
2701
2753
  try {
2702
- return decodeURIComponent(segment);
2754
+ return decodeURIComponent(text2);
2703
2755
  } catch (error) {
2704
- if (error instanceof URIError) return segment;
2756
+ if (error instanceof URIError) return text2;
2705
2757
  throw error;
2706
2758
  }
2707
2759
  }
2760
+
2761
+ // src/status.ts
2762
+ var URL_SOURCE = "url:";
2708
2763
  function resolveProductKey(el, location2 = window.location, name = "product") {
2709
2764
  const raw = (get(el, name) ?? "").trim();
2710
2765
  if (!raw.toLowerCase().startsWith(URL_SOURCE)) return { key: raw || null, fromUrl: false, raw };
@@ -2713,7 +2768,7 @@
2713
2768
  if (source.toLowerCase() === "path") {
2714
2769
  const segments = location2.pathname.split("/").filter(Boolean);
2715
2770
  const segment = segments[segments.length - 1];
2716
- key = segment === void 0 ? null : decodePathSegment(segment);
2771
+ key = segment === void 0 ? null : decodeOrKeep(segment);
2717
2772
  } else {
2718
2773
  key = new URLSearchParams(location2.search).get(source || "handle");
2719
2774
  }
@@ -3447,8 +3502,92 @@
3447
3502
  });
3448
3503
  }
3449
3504
 
3505
+ // src/markets.ts
3506
+ var storageKey = () => `${cfg.prefix}:market:${cfg.domain}`;
3507
+ var pageStorage = { getItem: (key) => getStored(key) };
3508
+ function parseJsonOrNull(text2) {
3509
+ try {
3510
+ return JSON.parse(text2);
3511
+ } catch (error) {
3512
+ if (error instanceof SyntaxError) return null;
3513
+ throw error;
3514
+ }
3515
+ }
3516
+ function toMarket(value) {
3517
+ if (typeof value !== "object" || value === null) return null;
3518
+ const country = toCountry(Reflect.get(value, "country"));
3519
+ if (!country) return null;
3520
+ const locale = Reflect.get(value, "locale");
3521
+ return {
3522
+ country,
3523
+ language: toLanguage(Reflect.get(value, "language")) ?? void 0,
3524
+ locale: typeof locale === "string" && locale ? locale : void 0
3525
+ };
3526
+ }
3527
+ var parseSavedMarket = (text2) => toMarket(parseJsonOrNull(text2));
3528
+ function readSavedMarket(storage = pageStorage) {
3529
+ const text2 = storage.getItem(storageKey());
3530
+ return text2 ? parseSavedMarket(text2) : null;
3531
+ }
3532
+
3533
+ // src/page-market.ts
3534
+ var configuredMarket = { country: void 0, language: void 0, locale: void 0 };
3535
+ var lastPageHadOwnMarket = false;
3536
+ function pageMarket() {
3537
+ const meta2 = (name) => document.querySelector(`meta[name="${cfg.prefix}-page-${name}"]`)?.getAttribute("content") || void 0;
3538
+ const country = toCountry(meta2("country"));
3539
+ if (!country) return null;
3540
+ return { country, language: toLanguage(meta2("language")) ?? void 0, locale: meta2("locale") };
3541
+ }
3542
+ async function visitorMarket() {
3543
+ const markets = cfg.markets ?? {};
3544
+ if (markets.detect === false) return null;
3545
+ if (!markets.detect) return markets.remember === false ? null : readSavedMarket();
3546
+ try {
3547
+ return (await loadPart("markets")).detectMarket(markets);
3548
+ } catch (error) {
3549
+ reportError(error, "markets");
3550
+ return null;
3551
+ }
3552
+ }
3553
+ async function applyStartMarket(overrides) {
3554
+ configuredMarket = { country: cfg.country, language: cfg.language, locale: cfg.locale };
3555
+ const page = pageMarket();
3556
+ lastPageHadOwnMarket = page !== null;
3557
+ if (page) {
3558
+ cfg.country = page.country;
3559
+ cfg.language = page.language;
3560
+ if (page.locale) cfg.locale = page.locale;
3561
+ return;
3562
+ }
3563
+ const market = await visitorMarket();
3564
+ if (!market) return;
3565
+ if (market.country) cfg.country = market.country;
3566
+ if (market.language) cfg.language = market.language;
3567
+ if (market.locale && !overrides.locale) cfg.locale = market.locale;
3568
+ log("market", market);
3569
+ }
3570
+ var sameCode = (a, b) => (a ?? "").toUpperCase() === (b ?? "").toUpperCase();
3571
+ function marketForArrival() {
3572
+ const page = pageMarket();
3573
+ const leftOwnMarket = page === null && lastPageHadOwnMarket;
3574
+ lastPageHadOwnMarket = page !== null;
3575
+ const target = page ?? (leftOwnMarket ? readSavedMarket() ?? configuredMarket : null);
3576
+ if (!target?.country) return null;
3577
+ if (sameCode(target.country, cfg.country) && sameCode(target.language, cfg.language)) return null;
3578
+ return { country: target.country, language: target.language, locale: target.locale };
3579
+ }
3580
+ function setCountry(code, options) {
3581
+ return loadPart("markets").then((part) => part.setCountry(code, options));
3582
+ }
3583
+
3450
3584
  // src/part-integrity.ts
3451
- var PART_INTEGRITY = { "tether.diagnose.js": "sha384-OKLlZJrSZ5IF9SOjKnEe485fi5xZlb4oLLvSfGjAssABzqd3h9JH1yA/hDczhkjJ", "tether.markets.js": "sha384-QVvTGiZJKx2hOPIpQPrGycWnKVTfDMEJ8z9heHIYSqib+6WyUoTmzKf2oBiFE3le", "tether.search.js": "sha384-P63pnKFjWTIVS8kEbBr4sfZZI91u1tImDTe+y50l2pdOM+W/KQsMymB6YRH801Dc", "tether.extras.js": "sha384-DJwYPZWf7o63ooQuDRvvhLrl5quZwhWO1YQ9RpySNQKjlDIS3uCwxDpDfZYiEjla", "tether.account.js": "sha384-q3UfPH5vDMbKm05wkvEPTDoA/aaIPmRQBDrqmXA4cNF4VFZAo4dgt61TmrNM69mi", "tether.account-pages.js": "sha384-YiSgDwM6qeHXJBcbWLRuk50h2ntAamNadS0MrZByc3cl5PKXZO297l38zYWm0lLe", "tether.analytics.js": "sha384-I93erLFdRG9uy3t+mnaDk/MgECoYOSG3q+9TfIQ39NROWZLvFjYrpHOAnBfsXaJc" };
3585
+ var PART_INTEGRITY = { "tether.diagnose.js": "sha384-exWttcEw4tQTiJ4OU0r/Y+0T5vVIYIo3kcmieqDPkUUmJQGr2HZzWxZCOmMhsvaY", "tether.markets.js": "sha384-hn9741hCQun49mIj7A2bazMG2wXxTpMDScOQK+8wMeY6KT1p+ovXcKDi3m6BK04C", "tether.search.js": "sha384-ZrByfRsWNOHbbWOocY4WOD2RWe4QI2qQRZ4Eb+fM5v/UHIQ8mlWxbSwrxJt8yLbc", "tether.extras.js": "sha384-lt7I8DOnCx8wwVHQM4ZT39+Jlv95WVED5eVDezUKchNXo4+j1oBXYtxzPFiPyxgm", "tether.account.js": "sha384-UF36poTg1vnXE7Pg+FYFGMJ4qjKhx9mbOug9bqTYhzgqRppOKS9M/1uY5D/cz9KF", "tether.account-pages.js": "sha384-jJYvZswrRw+pGDYrk0FswWcYqGpZkbR6ooxJGFu0lTYkUlVOcsCXuLLLU6M5kO54", "tether.analytics.js": "sha384-10v5pBMzgfF7BuLGQBg3cdbgMk701Uto5rGjhByiRD2jI9+/WF8DFcSWplIcdRNg" };
3586
+
3587
+ // src/part-registry.ts
3588
+ function isTetherInternals(value) {
3589
+ return isRecord(value) && isRecord(value["__core"]) && isRecord(value["__parts"]);
3590
+ }
3452
3591
 
3453
3592
  // src/lazy-iife.ts
3454
3593
  var coreModules = {
@@ -3504,6 +3643,9 @@
3504
3643
  get "market-codes.ts"() {
3505
3644
  return [toCountry, toLanguage];
3506
3645
  },
3646
+ get "page-market.ts"() {
3647
+ return [setCountry];
3648
+ },
3507
3649
  get "shopify-values.ts"() {
3508
3650
  return [isHttpsUrl];
3509
3651
  },
@@ -3533,14 +3675,15 @@
3533
3675
  script.async = true;
3534
3676
  applyIntegrity(script, integrity);
3535
3677
  script.onload = () => {
3536
- const { Tether } = window;
3537
- const part = Tether?.__parts[name];
3678
+ const tether = Reflect.get(window, "Tether");
3679
+ const part = isTetherInternals(tether) ? tether.__parts[name] : void 0;
3538
3680
  if (part) {
3539
3681
  resolve(part);
3540
3682
  return;
3541
3683
  }
3542
3684
  parts.delete(name);
3543
- reject(new Error(`${url2} did not register`));
3685
+ const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's, another script replaced it";
3686
+ reject(new Error(`${url2} ${problem}`));
3544
3687
  };
3545
3688
  script.onerror = () => {
3546
3689
  parts.delete(name);
@@ -3574,84 +3717,6 @@
3574
3717
  return loading;
3575
3718
  }
3576
3719
 
3577
- // src/markets.ts
3578
- var storageKey = () => `${cfg.prefix}:market:${cfg.domain}`;
3579
- var pageStorage = { getItem: (key) => getStored(key) };
3580
- function parseJsonOrNull(text2) {
3581
- try {
3582
- return JSON.parse(text2);
3583
- } catch (error) {
3584
- if (error instanceof SyntaxError) return null;
3585
- throw error;
3586
- }
3587
- }
3588
- function toMarket(value) {
3589
- if (typeof value !== "object" || value === null) return null;
3590
- const country = toCountry(Reflect.get(value, "country"));
3591
- if (!country) return null;
3592
- const locale = Reflect.get(value, "locale");
3593
- return {
3594
- country,
3595
- language: toLanguage(Reflect.get(value, "language")) ?? void 0,
3596
- locale: typeof locale === "string" && locale ? locale : void 0
3597
- };
3598
- }
3599
- function readSavedMarket(storage = pageStorage) {
3600
- const text2 = storage.getItem(storageKey());
3601
- return text2 ? toMarket(parseJsonOrNull(text2)) : null;
3602
- }
3603
-
3604
- // src/page-market.ts
3605
- var configuredMarket = { country: void 0, language: void 0, locale: void 0 };
3606
- var lastPageHadOwnMarket = false;
3607
- function pageMarket() {
3608
- const meta2 = (name) => document.querySelector(`meta[name="${cfg.prefix}-page-${name}"]`)?.getAttribute("content") || void 0;
3609
- const country = toCountry(meta2("country"));
3610
- if (!country) return null;
3611
- return { country, language: toLanguage(meta2("language")) ?? void 0, locale: meta2("locale") };
3612
- }
3613
- async function visitorMarket() {
3614
- const markets = cfg.markets ?? {};
3615
- if (markets.detect === false) return null;
3616
- if (!markets.detect) return markets.remember === false ? null : readSavedMarket();
3617
- try {
3618
- return (await loadPart("markets")).detectMarket(markets);
3619
- } catch (error) {
3620
- reportError(error, "markets");
3621
- return null;
3622
- }
3623
- }
3624
- async function applyStartMarket(overrides) {
3625
- configuredMarket = { country: cfg.country, language: cfg.language, locale: cfg.locale };
3626
- const page = pageMarket();
3627
- lastPageHadOwnMarket = page !== null;
3628
- if (page) {
3629
- cfg.country = page.country;
3630
- cfg.language = page.language;
3631
- if (page.locale) cfg.locale = page.locale;
3632
- return;
3633
- }
3634
- const market = await visitorMarket();
3635
- if (!market) return;
3636
- if (market.country) cfg.country = market.country;
3637
- if (market.language) cfg.language = market.language;
3638
- if (market.locale && !overrides.locale) cfg.locale = market.locale;
3639
- log("market", market);
3640
- }
3641
- var sameCode = (a, b) => (a ?? "").toUpperCase() === (b ?? "").toUpperCase();
3642
- function marketForArrival() {
3643
- const page = pageMarket();
3644
- const leftOwnMarket = page === null && lastPageHadOwnMarket;
3645
- lastPageHadOwnMarket = page !== null;
3646
- const target = page ?? (leftOwnMarket ? readSavedMarket() ?? configuredMarket : null);
3647
- if (!target?.country) return null;
3648
- if (sameCode(target.country, cfg.country) && sameCode(target.language, cfg.language)) return null;
3649
- return { country: target.country, language: target.language, locale: target.locale };
3650
- }
3651
- function setCountry(code, options) {
3652
- return loadPart("markets").then((part) => part.setCountry(code, options));
3653
- }
3654
-
3655
3720
  // src/account-marks.ts
3656
3721
  var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
3657
3722
  function removeFromBothAreas(key) {
@@ -3951,7 +4016,7 @@
3951
4016
  }
3952
4017
 
3953
4018
  // src/index.ts
3954
- var VERSION = "0.7.0";
4019
+ var VERSION = "0.7.2";
3955
4020
  var OBSERVE_DEBOUNCE_MS = 50;
3956
4021
  function pageConfig() {
3957
4022
  const config = pageGlobal("TETHER_CONFIG");