@occluze/tether 0.6.7 → 0.7.0

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 (250) hide show
  1. package/README.md +11 -11
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
  4. package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
  5. package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
  6. package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
  7. package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
  8. package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
  9. package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
  10. package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
  11. package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
  12. package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
  13. package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
  14. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  15. package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
  16. package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
  17. package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
  18. package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
  19. package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
  20. package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
  21. package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
  22. package/dist/esm/index.js +1 -1
  23. package/dist/esm/server.js +2 -1
  24. package/dist/presets/barba.esm.js +55 -39
  25. package/dist/tether.account-pages.js +781 -495
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +462 -381
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +299 -194
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +354 -262
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +82 -45
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +3653 -2116
  36. package/dist/tether.markets.js +238 -150
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +3 -3
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +207 -136
  41. package/dist/tether.search.min.js +1 -1
  42. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  43. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  44. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  45. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  46. package/dist/types/server/src/shared/json.d.ts +25 -0
  47. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  48. package/dist/types/src/account/addresses-view.d.ts +5 -0
  49. package/dist/types/src/account/answers.d.ts +40 -0
  50. package/dist/types/src/account/markup.d.ts +19 -0
  51. package/dist/types/src/account/operations.d.ts +41 -0
  52. package/dist/types/src/account/order-lines.d.ts +13 -0
  53. package/dist/types/src/account/order-list.d.ts +12 -0
  54. package/dist/types/src/account/order-page.d.ts +6 -0
  55. package/dist/types/src/account/page-answers.d.ts +182 -0
  56. package/dist/types/src/account/page-events.d.ts +2 -0
  57. package/dist/types/src/account/server-client.d.ts +60 -0
  58. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  59. package/dist/types/src/account-api.d.ts +18 -0
  60. package/dist/types/src/account-marks.d.ts +15 -0
  61. package/dist/types/src/account-pages.d.ts +8 -0
  62. package/dist/types/src/account.d.ts +64 -0
  63. package/dist/types/src/amounts.d.ts +12 -0
  64. package/dist/types/src/analytics-shopify.d.ts +41 -0
  65. package/dist/types/src/analytics.d.ts +2 -0
  66. package/dist/types/src/api-version.d.ts +28 -0
  67. package/dist/types/src/astro.d.ts +42 -0
  68. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  69. package/dist/types/src/cart/checkout.d.ts +16 -0
  70. package/dist/types/src/cart/drawer.d.ts +18 -0
  71. package/dist/types/src/cart/mutations.d.ts +79 -0
  72. package/dist/types/src/cart/render.d.ts +17 -0
  73. package/dist/types/src/cart/store.d.ts +182 -0
  74. package/dist/types/src/cart.d.ts +106 -0
  75. package/dist/types/src/client.d.ts +100 -0
  76. package/dist/types/src/collection.d.ts +25 -0
  77. package/dist/types/src/config-sections.d.ts +40 -0
  78. package/dist/types/src/config.d.ts +185 -0
  79. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  80. package/dist/types/src/controls/discount.d.ts +2 -0
  81. package/dist/types/src/controls/extras.d.ts +5 -0
  82. package/dist/types/src/controls/navigation.d.ts +12 -0
  83. package/dist/types/src/diagnose-markup.d.ts +19 -0
  84. package/dist/types/src/diagnose.d.ts +60 -0
  85. package/dist/types/src/dom.d.ts +75 -0
  86. package/dist/types/src/extras.d.ts +21 -0
  87. package/dist/types/src/fragments.d.ts +158 -0
  88. package/dist/types/src/globals.d.ts +15 -0
  89. package/dist/types/src/images.d.ts +20 -0
  90. package/dist/types/src/index.d.ts +167 -0
  91. package/dist/types/src/initial.d.ts +56 -0
  92. package/dist/types/src/lazy.d.ts +28 -0
  93. package/dist/types/src/loader.d.ts +84 -0
  94. package/dist/types/src/market-codes.d.ts +6 -0
  95. package/dist/types/src/markets-ui.d.ts +32 -0
  96. package/dist/types/src/markets.d.ts +72 -0
  97. package/dist/types/src/markup.d.ts +131 -0
  98. package/dist/types/src/mounting.d.ts +46 -0
  99. package/dist/types/src/page-market.d.ts +27 -0
  100. package/dist/types/src/presets/barba.d.ts +56 -0
  101. package/dist/types/src/product-events.d.ts +2 -0
  102. package/dist/types/src/product-render.d.ts +17 -0
  103. package/dist/types/src/product-shape.d.ts +88 -0
  104. package/dist/types/src/product-state.d.ts +110 -0
  105. package/dist/types/src/product.d.ts +66 -0
  106. package/dist/types/src/repeaters.d.ts +16 -0
  107. package/dist/types/src/search.d.ts +33 -0
  108. package/dist/types/src/selling-plans.d.ts +64 -0
  109. package/dist/types/src/server.d.ts +88 -0
  110. package/dist/types/src/shopify-values.d.ts +46 -0
  111. package/dist/types/src/status.d.ts +33 -0
  112. package/dist/types/src/storage.d.ts +12 -0
  113. package/dist/types/src/store-html.d.ts +39 -0
  114. package/dist/types/src/storefront/catalog.d.ts +38 -0
  115. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  116. package/dist/types/src/variant-render.d.ts +14 -0
  117. package/package.json +42 -22
  118. package/src/account/addresses-view.ts +58 -0
  119. package/src/account/answers.ts +79 -0
  120. package/src/account/markup.ts +36 -0
  121. package/src/account/operations.ts +145 -0
  122. package/src/account/order-lines.ts +75 -0
  123. package/src/account/order-list.ts +107 -0
  124. package/src/account/order-page.ts +137 -0
  125. package/src/account/page-answers.ts +317 -0
  126. package/src/account/page-events.ts +253 -0
  127. package/src/account/server-client.ts +183 -0
  128. package/src/account/subscriptions-view.ts +115 -0
  129. package/src/account-api.ts +98 -0
  130. package/src/account-marks.ts +35 -0
  131. package/src/account-pages.ts +81 -0
  132. package/src/account.ts +284 -0
  133. package/src/amounts.ts +64 -0
  134. package/src/analytics-shopify.ts +246 -0
  135. package/src/analytics.ts +315 -0
  136. package/src/api-version.ts +79 -0
  137. package/src/astro.ts +272 -0
  138. package/src/cart/cart-shape.ts +25 -0
  139. package/src/cart/checkout.ts +101 -0
  140. package/src/cart/drawer.ts +125 -0
  141. package/src/cart/mutations.ts +237 -0
  142. package/src/cart/render.ts +266 -0
  143. package/src/cart/store.ts +279 -0
  144. package/src/cart.ts +345 -0
  145. package/src/client.ts +391 -0
  146. package/src/collection.ts +175 -0
  147. package/src/config-sections.ts +284 -0
  148. package/src/config.ts +480 -0
  149. package/src/controls/cart-controls.ts +123 -0
  150. package/src/controls/discount.ts +75 -0
  151. package/src/controls/extras.ts +54 -0
  152. package/src/controls/navigation.ts +77 -0
  153. package/src/diagnose-markup.ts +298 -0
  154. package/src/diagnose.ts +294 -0
  155. package/src/dom.ts +268 -0
  156. package/src/extras.ts +136 -0
  157. package/src/fragments.ts +261 -0
  158. package/src/globals.d.ts +15 -0
  159. package/src/images.ts +91 -0
  160. package/src/index.ts +415 -0
  161. package/src/initial.ts +101 -0
  162. package/src/lazy-iife.ts +197 -0
  163. package/src/lazy.ts +42 -0
  164. package/src/loader.ts +363 -0
  165. package/src/market-codes.ts +6 -0
  166. package/src/markets-ui.ts +227 -0
  167. package/src/markets.ts +246 -0
  168. package/src/markup.ts +127 -0
  169. package/src/mounting.ts +264 -0
  170. package/src/page-market.ts +110 -0
  171. package/src/part-integrity.ts +7 -0
  172. package/src/parts/account-pages.ts +7 -0
  173. package/src/parts/account.ts +7 -0
  174. package/src/parts/analytics.ts +7 -0
  175. package/src/parts/diagnose.ts +7 -0
  176. package/src/parts/extras.ts +7 -0
  177. package/src/parts/markets.ts +7 -0
  178. package/src/parts/search.ts +7 -0
  179. package/src/presets/barba.ts +194 -0
  180. package/src/product-events.ts +115 -0
  181. package/src/product-render.ts +218 -0
  182. package/src/product-shape.ts +49 -0
  183. package/src/product-state.ts +318 -0
  184. package/src/product.ts +308 -0
  185. package/src/repeaters.ts +161 -0
  186. package/src/search.ts +305 -0
  187. package/src/selling-plans.ts +145 -0
  188. package/src/server.ts +211 -0
  189. package/src/shopify-values.ts +91 -0
  190. package/src/status.ts +106 -0
  191. package/src/storage.ts +34 -0
  192. package/src/store-html.ts +359 -0
  193. package/src/storefront/catalog.ts +196 -0
  194. package/src/storefront/json-ld.ts +144 -0
  195. package/src/variant-render.ts +139 -0
  196. package/dist/esm/chunks/account-Q6E2TFEV.js +0 -1
  197. package/dist/esm/chunks/account-pages-UZXR4R5F.js +0 -1
  198. package/dist/esm/chunks/analytics-IJBKATUP.js +0 -1
  199. package/dist/esm/chunks/chunk-4QJKLPYR.js +0 -1
  200. package/dist/esm/chunks/chunk-64TUD3QR.js +0 -1
  201. package/dist/esm/chunks/chunk-AJLPU2JC.js +0 -4
  202. package/dist/esm/chunks/chunk-FIOZPPC5.js +0 -1
  203. package/dist/esm/chunks/chunk-K2YEQAIH.js +0 -1
  204. package/dist/esm/chunks/chunk-KIDH76B7.js +0 -1
  205. package/dist/esm/chunks/chunk-PAYT5ZD7.js +0 -1
  206. package/dist/esm/chunks/chunk-RREHGH4A.js +0 -1
  207. package/dist/esm/chunks/chunk-SSJT43CJ.js +0 -1
  208. package/dist/esm/chunks/chunk-VC46IEJQ.js +0 -1
  209. package/dist/esm/chunks/diagnose-3WVAWNTX.js +0 -14
  210. package/dist/esm/chunks/extras-7I7WJX35.js +0 -1
  211. package/dist/esm/chunks/markets-ui-545XLFTE.js +0 -1
  212. package/dist/esm/chunks/search-NILMGYXG.js +0 -1
  213. package/src/account-pages.js +0 -318
  214. package/src/account.js +0 -385
  215. package/src/analytics.js +0 -232
  216. package/src/api-version.js +0 -35
  217. package/src/astro.js +0 -101
  218. package/src/cart.js +0 -559
  219. package/src/client.js +0 -131
  220. package/src/collection.js +0 -114
  221. package/src/config.js +0 -85
  222. package/src/diagnose.js +0 -246
  223. package/src/dom.js +0 -288
  224. package/src/extras.js +0 -63
  225. package/src/fragments.js +0 -144
  226. package/src/index.js +0 -395
  227. package/src/initial.js +0 -75
  228. package/src/lazy-iife.js +0 -46
  229. package/src/lazy.js +0 -18
  230. package/src/loader.js +0 -198
  231. package/src/markets-ui.js +0 -90
  232. package/src/markets.js +0 -133
  233. package/src/mounting.js +0 -154
  234. package/src/parts/account-pages.js +0 -3
  235. package/src/parts/account.js +0 -3
  236. package/src/parts/analytics.js +0 -3
  237. package/src/parts/diagnose.js +0 -3
  238. package/src/parts/extras.js +0 -3
  239. package/src/parts/markets.js +0 -3
  240. package/src/parts/search.js +0 -3
  241. package/src/presets/barba.js +0 -100
  242. package/src/product.js +0 -570
  243. package/src/search.js +0 -153
  244. package/src/selling-plans.js +0 -94
  245. package/src/server.js +0 -252
  246. package/src/status.js +0 -66
  247. package/types/astro.d.ts +0 -17
  248. package/types/barba.d.ts +0 -16
  249. package/types/index.d.ts +0 -426
  250. package/types/server.d.ts +0 -110
@@ -1,176 +1,247 @@
1
1
  (() => {
2
2
  var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
4
  var __export = (target, all) => {
4
5
  for (var name in all)
5
6
  __defProp(target, name, { get: all[name], enumerable: true });
6
7
  };
8
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
7
9
 
8
- // src/search.js
10
+ // src/search.ts
9
11
  var search_exports = {};
10
12
  __export(search_exports, {
11
13
  mountSearch: () => mountSearch
12
14
  });
13
15
 
14
- // tether-core:config.js
15
- var m = window.Tether.__core["config.js"];
16
- var { DEFAULTS, cfg, configure, isConfigured, log, reportError, warn } = m;
16
+ // tether-core:config.ts
17
+ var m = window.Tether.__core["config.ts"];
18
+ var [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn] = m;
17
19
 
18
- // tether-core:client.js
19
- var m2 = window.Tether.__core["client.js"];
20
- var { TetherError, endpoint, gql, request, unwrap, withContext } = m2;
20
+ // tether-core:client.ts
21
+ var m2 = window.Tether.__core["client.ts"];
22
+ var [endpoint, gql, orNull, shaped, TetherError] = m2;
21
23
 
22
- // tether-core:dom.js
23
- var m3 = window.Tether.__core["dom.js"];
24
- var { attr, busy, cdnUrl, dataKey, emit, formatDate, formatMoney, formatPercent, get, has, hasTemplate, imageUrl, notifyLayoutChanged, parseJSONAttr, productUrl, qsa, readText, repeat, repeatKeyed, sameImage, scoped, sel, setImage, setSelected, setText, setTextTracked, show, toAttributes, watchImages } = m3;
24
+ // tether-core:dom.ts
25
+ var m3 = window.Tether.__core["dom.ts"];
26
+ var [ANALYTICS_EVENTS, attr, attributesFromJson, emit, formatDate, formatMoney, get, has, hasTemplate, isOwnEvent, parseJSONAttr, productUrl, qsa, repeat, sel, setImage, setText, show] = m3;
25
27
 
26
- // src/search.js
28
+ // tether-core:mounting.ts
29
+ var m4 = window.Tether.__core["mounting.ts"];
30
+ var [liveLists, liveProducts, mountedProducts, mountProduct, pruneLive, within] = m4;
31
+
32
+ // src/search.ts
33
+ var isSearchAnswer = shaped({
34
+ predictiveSearch: orNull({
35
+ products: [{ priceRange: "object" }],
36
+ collections: ["object"],
37
+ queries: [{ text: "string" }]
38
+ })
39
+ });
27
40
  var QUERY = `query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}`;
41
+ var DEFAULT_LIMIT = 6;
42
+ var DEFAULT_MIN_CHARACTERS = 2;
43
+ var DEFAULT_ACTION = "/search";
28
44
  var mounted = /* @__PURE__ */ new WeakSet();
29
- var uid = 0;
30
- function mountSearch(root = document) {
31
- const roots = [...root.matches?.(sel("search")) ? [root] : [], ...qsa(root, sel("search"))];
32
- roots.forEach((el) => {
33
- if (!mounted.has(el)) {
34
- mounted.add(el);
35
- setup(el);
45
+ var searchBoxCount = 0;
46
+ function integerAttribute(el, name, fallback) {
47
+ return parseInt(get(el, name) || "", 10) || fallback;
48
+ }
49
+ var linkIn = (node) => node.tagName === "A" ? node : node.querySelector("a");
50
+ function bindValues(node, values) {
51
+ qsa(node, sel("search-bind")).forEach((el) => {
52
+ const field = get(el, "search-bind");
53
+ if (field === "image") setImage(el, values.image?.url, values.image?.altText || values.title);
54
+ else if (field === "title" || field === "price" || field === "text") {
55
+ if (values[field] != null) setText(el, values[field]);
36
56
  }
37
57
  });
38
58
  }
39
- function setup(rootEl) {
40
- const input = rootEl.querySelector(sel("search-input")) || rootEl.querySelector("input");
41
- const results = rootEl.querySelector(sel("search-results"));
42
- if (!input || !results) return;
43
- const limit = parseInt(get(rootEl, "search-limit") || "6", 10) || 6;
44
- const min = parseInt(get(rootEl, "search-min") || "2", 10) || 2;
45
- const id = results.id || (results.id = `${cfg.prefix}-search-${++uid}`);
46
- input.setAttribute("role", "combobox");
47
- input.setAttribute("aria-autocomplete", "list");
48
- input.setAttribute("aria-controls", id);
49
- input.setAttribute("aria-expanded", "false");
50
- input.setAttribute("autocomplete", "off");
51
- if (!results.hasAttribute("role")) results.setAttribute("role", "listbox");
52
- results.hidden = true;
53
- let token = 0;
54
- let timer = null;
55
- let last = "";
56
- const cache = /* @__PURE__ */ new Map();
57
- const setState = (state) => rootEl.setAttribute(attr("search-state"), state);
58
- const open = (yes) => {
59
- rootEl.classList.toggle("is-open", yes);
60
- results.hidden = !yes;
61
- input.setAttribute("aria-expanded", String(yes));
62
- if (!yes) input.removeAttribute("aria-activedescendant");
63
- };
64
- const items = () => qsa(results, '[role="option"]');
65
- async function run(term) {
66
- const t = ++token;
67
- if (term.length < min) {
68
- open(false);
69
- setState("idle");
59
+ function linkOption(node, href) {
60
+ const link = linkIn(node);
61
+ if (!link) return;
62
+ if (href) link.setAttribute("href", href);
63
+ link.setAttribute("role", "option");
64
+ }
65
+ var SearchBox = class {
66
+ constructor(root, input, results) {
67
+ __publicField(this, "root");
68
+ __publicField(this, "input");
69
+ __publicField(this, "results");
70
+ __publicField(this, "limit");
71
+ __publicField(this, "minCharacters");
72
+ __publicField(this, "resultsId");
73
+ /** Counts searches, so only the latest one renders. */
74
+ __publicField(this, "searchNumber", 0);
75
+ __publicField(this, "timer");
76
+ __publicField(this, "lastTerm", "");
77
+ /** Results per term, for typing back and forth. */
78
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
79
+ this.root = root;
80
+ this.input = input;
81
+ this.results = results;
82
+ this.limit = integerAttribute(root, "search-limit", DEFAULT_LIMIT);
83
+ this.minCharacters = integerAttribute(root, "search-min", DEFAULT_MIN_CHARACTERS);
84
+ if (!results.id) {
85
+ searchBoxCount += 1;
86
+ results.id = `${cfg.prefix}-search-${searchBoxCount}`;
87
+ }
88
+ this.resultsId = results.id;
89
+ this.setUpAria();
90
+ this.listen();
91
+ }
92
+ setUpAria() {
93
+ this.input.setAttribute("role", "combobox");
94
+ this.input.setAttribute("aria-autocomplete", "list");
95
+ this.input.setAttribute("aria-controls", this.resultsId);
96
+ this.input.setAttribute("aria-expanded", "false");
97
+ this.input.setAttribute("autocomplete", "off");
98
+ if (!this.results.hasAttribute("role")) this.results.setAttribute("role", "listbox");
99
+ this.results.hidden = true;
100
+ }
101
+ listen() {
102
+ this.input.addEventListener("input", () => {
103
+ const term = this.input.value.trim();
104
+ if (term === this.lastTerm) return;
105
+ this.lastTerm = term;
106
+ clearTimeout(this.timer);
107
+ this.timer = setTimeout(() => void this.search(term), cfg.searchDelay);
108
+ });
109
+ this.input.addEventListener("focus", () => {
110
+ if (this.input.value.trim().length >= this.minCharacters && this.options().length) this.open(true);
111
+ });
112
+ this.input.addEventListener("keydown", (event) => {
113
+ this.onKeydown(event);
114
+ });
115
+ document.addEventListener("click", (event) => {
116
+ if (!this.root.contains(event.target)) this.open(false);
117
+ });
118
+ }
119
+ setState(state) {
120
+ this.root.setAttribute(attr("search-state"), state);
121
+ }
122
+ open(open) {
123
+ this.root.classList.toggle("is-open", open);
124
+ this.results.hidden = !open;
125
+ this.input.setAttribute("aria-expanded", String(open));
126
+ if (!open) this.input.removeAttribute("aria-activedescendant");
127
+ }
128
+ options() {
129
+ return qsa(this.results, '[role="option"]');
130
+ }
131
+ async fetchResults(term) {
132
+ const cached = this.cache.get(term);
133
+ if (cached) return cached;
134
+ const data = await gql(QUERY, { q: term, limit: this.limit }, isSearchAnswer);
135
+ this.cache.set(term, data.predictiveSearch);
136
+ return data.predictiveSearch;
137
+ }
138
+ async search(term) {
139
+ this.searchNumber += 1;
140
+ const searchNumber = this.searchNumber;
141
+ if (term.length < this.minCharacters) {
142
+ this.open(false);
143
+ this.setState("idle");
70
144
  return;
71
145
  }
72
- setState("loading");
146
+ this.setState("loading");
73
147
  try {
74
- const data = cache.get(term) || (await gql(QUERY, { q: term, limit })).predictiveSearch;
75
- cache.set(term, data);
76
- if (t !== token) return;
77
- render(data, term);
78
- } catch (err) {
79
- if (t !== token) return;
80
- setState("error");
81
- reportError(err, "search");
148
+ const results = await this.fetchResults(term);
149
+ if (searchNumber === this.searchNumber) this.render(results, term);
150
+ } catch (error) {
151
+ if (searchNumber !== this.searchNumber) {
152
+ log("a superseded search failed", error);
153
+ return;
154
+ }
155
+ this.setState("error");
156
+ reportError(error, "search");
82
157
  }
83
158
  }
84
- function render(data, term) {
85
- const products = data?.products || [];
86
- const collections = data?.collections || [];
87
- const queries = (data?.queries || []).filter((q) => q.text.toLowerCase() !== term.toLowerCase());
88
- const fill = (name, list, each) => qsa(results, sel(name)).forEach((c) => {
89
- repeat(c, list, each);
90
- show(c, list.length > 0);
159
+ /** @param name The list attribute, e.g. `search-products`. */
160
+ fillList(name, items, render) {
161
+ qsa(this.results, sel(name)).forEach((list) => {
162
+ repeat(list, items, render);
163
+ show(list, items.length > 0);
91
164
  });
92
- fill("search-products", products, (node, p) => {
93
- binds(node, { title: p.title, price: formatMoney(p.priceRange?.minVariantPrice), image: p.featuredImage });
94
- link(node, productUrl(p.handle, p.id));
95
- node.classList.toggle("is-sold-out", !p.availableForSale);
165
+ }
166
+ queryUrl(text) {
167
+ const action = this.root.getAttribute("action") || DEFAULT_ACTION;
168
+ const separator = action.includes("?") ? "&" : "?";
169
+ return `${action}${separator}${this.input.name || "q"}=${encodeURIComponent(text)}`;
170
+ }
171
+ render(results, term) {
172
+ const products = results?.products ?? [];
173
+ const collections = results?.collections ?? [];
174
+ const queries = (results?.queries ?? []).filter((query) => query.text.toLowerCase() !== term.toLowerCase());
175
+ this.fillList("search-products", products, (node, product) => {
176
+ const price = formatMoney(product.priceRange.minVariantPrice);
177
+ bindValues(node, { title: product.title, price, image: product.featuredImage });
178
+ linkOption(node, productUrl(product.handle, product.id));
179
+ node.classList.toggle("is-sold-out", !product.availableForSale);
96
180
  });
97
- fill("search-collections", collections, (node, c) => {
98
- binds(node, { title: c.title, image: c.image });
99
- link(node, `${(cfg.collectionPath || "/collection").replace(/\/$/, "")}/${c.handle}`);
181
+ this.fillList("search-collections", collections, (node, collection) => {
182
+ bindValues(node, { title: collection.title, image: collection.image });
183
+ linkOption(node, `${(cfg.collectionPath || "/collection").replace(/\/$/, "")}/${collection.handle}`);
100
184
  });
101
- fill("search-queries", queries, (node, q) => {
102
- binds(node, { text: q.text });
103
- const a = node.tagName === "A" ? node : node.querySelector("a");
104
- const action = rootEl.getAttribute("action") || "/search";
105
- if (a) a.setAttribute("href", `${action}${action.includes("?") ? "&" : "?"}${input.name || "q"}=${encodeURIComponent(q.text)}`);
185
+ this.fillList("search-queries", queries, (node, query) => {
186
+ bindValues(node, { text: query.text });
187
+ linkIn(node)?.setAttribute("href", this.queryUrl(query.text));
106
188
  });
107
189
  const count = products.length + collections.length + queries.length;
108
- qsa(rootEl, sel("show")).forEach((el) => {
109
- const k = get(el, "show");
110
- if (k === "search-empty") show(el, count === 0);
111
- if (k === "search-results") show(el, count > 0);
190
+ this.showCount(count);
191
+ this.setState(count ? "ready" : "empty");
192
+ this.open(true);
193
+ emit("search", { term, products, collections, queries }, this.root);
194
+ }
195
+ showCount(count) {
196
+ qsa(this.root, sel("show")).forEach((el) => {
197
+ const condition = get(el, "show");
198
+ if (condition === "search-empty") show(el, count === 0);
199
+ if (condition === "search-results") show(el, count > 0);
112
200
  });
113
- items().forEach((el, i) => {
114
- el.id || (el.id = `${id}-${i}`);
115
- el.setAttribute("aria-selected", "false");
201
+ this.options().forEach((option, index) => {
202
+ option.id || (option.id = `${this.resultsId}-${index}`);
203
+ option.setAttribute("aria-selected", "false");
116
204
  });
117
- setState(count ? "ready" : "empty");
118
- open(true);
119
- emit("search", { term, products, collections, queries }, rootEl);
120
205
  }
121
- function binds(node, values) {
122
- qsa(node, sel("search-bind")).forEach((el) => {
123
- const k = get(el, "search-bind");
124
- if (k === "image") setImage(el, values.image?.url, values.image?.altText || values.title);
125
- else if (values[k] != null) setText(el, values[k]);
206
+ highlight(options, index) {
207
+ options.forEach((option2, optionIndex) => {
208
+ option2.setAttribute("aria-selected", String(optionIndex === index));
126
209
  });
210
+ const option = options[index];
211
+ if (!option) return;
212
+ this.input.setAttribute("aria-activedescendant", option.id);
213
+ option.scrollIntoView?.({ block: "nearest" });
127
214
  }
128
- function link(node, href) {
129
- const a = node.tagName === "A" ? node : node.querySelector("a");
130
- if (a) {
131
- a.setAttribute("href", href);
132
- a.setAttribute("role", "option");
215
+ onKeydown(event) {
216
+ const options = this.options();
217
+ const current = options.findIndex((option) => option.getAttribute("aria-selected") === "true");
218
+ if (event.key === "ArrowDown" && options.length) {
219
+ event.preventDefault();
220
+ this.open(true);
221
+ this.highlight(options, (current + 1) % options.length);
222
+ } else if (event.key === "ArrowUp" && options.length) {
223
+ event.preventDefault();
224
+ this.highlight(options, current <= 0 ? options.length - 1 : current - 1);
225
+ } else if (event.key === "Enter" && current >= 0) {
226
+ event.preventDefault();
227
+ options[current]?.click();
228
+ } else if (event.key === "Escape") {
229
+ this.open(false);
133
230
  }
134
231
  }
135
- input.addEventListener("input", () => {
136
- const term = input.value.trim();
137
- if (term === last) return;
138
- last = term;
139
- clearTimeout(timer);
140
- timer = setTimeout(() => run(term), cfg.searchDelay ?? 150);
141
- });
142
- input.addEventListener("focus", () => {
143
- if (input.value.trim().length >= min && items().length) open(true);
144
- });
145
- input.addEventListener("keydown", (e) => {
146
- const list = items();
147
- const cur = list.findIndex((el) => el.getAttribute("aria-selected") === "true");
148
- const move = (i) => {
149
- list.forEach((el, j) => el.setAttribute("aria-selected", String(j === i)));
150
- if (list[i]) {
151
- input.setAttribute("aria-activedescendant", list[i].id);
152
- list[i].scrollIntoView?.({ block: "nearest" });
153
- }
154
- };
155
- if (e.key === "ArrowDown" && list.length) {
156
- e.preventDefault();
157
- open(true);
158
- move((cur + 1) % list.length);
159
- } else if (e.key === "ArrowUp" && list.length) {
160
- e.preventDefault();
161
- move(cur <= 0 ? list.length - 1 : cur - 1);
162
- } else if (e.key === "Enter" && cur >= 0) {
163
- e.preventDefault();
164
- list[cur].click();
165
- } else if (e.key === "Escape") {
166
- open(false);
167
- }
168
- });
169
- document.addEventListener("click", (e) => {
170
- if (!rootEl.contains(e.target)) open(false);
171
- });
232
+ };
233
+ function mountSearch(root = document) {
234
+ for (const el of within(root, sel("search"))) {
235
+ if (mounted.has(el)) continue;
236
+ mounted.add(el);
237
+ const input = el.querySelector(sel("search-input")) ?? el.querySelector("input");
238
+ const results = el.querySelector(sel("search-results"));
239
+ if (input && results) new SearchBox(el, input, results);
240
+ }
172
241
  }
173
242
 
174
- // src/parts/search.js
175
- window.Tether.__parts.search = search_exports;
243
+ // src/parts/search.ts
244
+ var { Tether } = window;
245
+ if (!Tether) throw new Error("tether.search.js runs after the Tether script, which loads it");
246
+ Tether.__parts.search = search_exports;
176
247
  })();
@@ -1 +1 @@
1
- (()=>{var Q=Object.defineProperty;var Y=(n,s)=>{for(var c in s)Q(n,c,{get:s[c],enumerable:!0})};var S={};Y(S,{mountSearch:()=>G});var H=window.Tether.__core["config.js"],{DEFAULTS:ee,cfg:A,configure:te,isConfigured:re,log:ie,reportError:E,warn:ae}=H;var J=window.Tether.__core["client.js"],{TetherError:ne,endpoint:ce,gql:U,request:oe,unwrap:le,withContext:ue}=J;var W=window.Tether.__core["dom.js"],{attr:D,busy:he,cdnUrl:fe,dataKey:me,emit:_,formatDate:pe,formatMoney:P,formatPercent:ge,get:g,has:be,hasTemplate:we,imageUrl:Ae,notifyLayoutChanged:ye,parseJSONAttr:xe,productUrl:R,qsa:h,readText:Te,repeat:N,repeatKeyed:qe,sameImage:Se,scoped:$e,sel:u,setImage:O,setSelected:ke,setText:j,setTextTracked:ve,show:y,toAttributes:Ie,watchImages:Le}=W;var z="query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}",F=new WeakSet,B=0;function G(n=document){[...n.matches?.(u("search"))?[n]:[],...h(n,u("search"))].forEach(c=>{F.has(c)||(F.add(c),X(c))})}function X(n){let s=n.querySelector(u("search-input"))||n.querySelector("input"),c=n.querySelector(u("search-results"));if(!s||!c)return;let M=parseInt(g(n,"search-limit")||"6",10)||6,$=parseInt(g(n,"search-min")||"2",10)||2,k=c.id||(c.id=`${A.prefix}-search-${++B}`);s.setAttribute("role","combobox"),s.setAttribute("aria-autocomplete","list"),s.setAttribute("aria-controls",k),s.setAttribute("aria-expanded","false"),s.setAttribute("autocomplete","off"),c.hasAttribute("role")||c.setAttribute("role","listbox"),c.hidden=!0;let x=0,v=null,I="",L=new Map,b=e=>n.setAttribute(D("search-state"),e),d=e=>{n.classList.toggle("is-open",e),c.hidden=!e,s.setAttribute("aria-expanded",String(e)),e||s.removeAttribute("aria-activedescendant")},T=()=>h(c,'[role="option"]');async function V(e){let t=++x;if(e.length<$){d(!1),b("idle");return}b("loading");try{let i=L.get(e)||(await U(z,{q:e,limit:M})).predictiveSearch;if(L.set(e,i),t!==x)return;K(i,e)}catch(i){if(t!==x)return;b("error"),E(i,"search")}}function K(e,t){let i=e?.products||[],o=e?.collections||[],l=(e?.queries||[]).filter(r=>r.text.toLowerCase()!==t.toLowerCase()),f=(r,a,w)=>h(c,u(r)).forEach(p=>{N(p,a,w),y(p,a.length>0)});f("search-products",i,(r,a)=>{q(r,{title:a.title,price:P(a.priceRange?.minVariantPrice),image:a.featuredImage}),C(r,R(a.handle,a.id)),r.classList.toggle("is-sold-out",!a.availableForSale)}),f("search-collections",o,(r,a)=>{q(r,{title:a.title,image:a.image}),C(r,`${(A.collectionPath||"/collection").replace(/\/$/,"")}/${a.handle}`)}),f("search-queries",l,(r,a)=>{q(r,{text:a.text});let w=r.tagName==="A"?r:r.querySelector("a"),p=n.getAttribute("action")||"/search";w&&w.setAttribute("href",`${p}${p.includes("?")?"&":"?"}${s.name||"q"}=${encodeURIComponent(a.text)}`)});let m=i.length+o.length+l.length;h(n,u("show")).forEach(r=>{let a=g(r,"show");a==="search-empty"&&y(r,m===0),a==="search-results"&&y(r,m>0)}),T().forEach((r,a)=>{r.id||(r.id=`${k}-${a}`),r.setAttribute("aria-selected","false")}),b(m?"ready":"empty"),d(!0),_("search",{term:t,products:i,collections:o,queries:l},n)}function q(e,t){h(e,u("search-bind")).forEach(i=>{let o=g(i,"search-bind");o==="image"?O(i,t.image?.url,t.image?.altText||t.title):t[o]!=null&&j(i,t[o])})}function C(e,t){let i=e.tagName==="A"?e:e.querySelector("a");i&&(i.setAttribute("href",t),i.setAttribute("role","option"))}s.addEventListener("input",()=>{let e=s.value.trim();e!==I&&(I=e,clearTimeout(v),v=setTimeout(()=>V(e),A.searchDelay??150))}),s.addEventListener("focus",()=>{s.value.trim().length>=$&&T().length&&d(!0)}),s.addEventListener("keydown",e=>{let t=T(),i=t.findIndex(l=>l.getAttribute("aria-selected")==="true"),o=l=>{t.forEach((f,m)=>f.setAttribute("aria-selected",String(m===l))),t[l]&&(s.setAttribute("aria-activedescendant",t[l].id),t[l].scrollIntoView?.({block:"nearest"}))};e.key==="ArrowDown"&&t.length?(e.preventDefault(),d(!0),o((i+1)%t.length)):e.key==="ArrowUp"&&t.length?(e.preventDefault(),o(i<=0?t.length-1:i-1)):e.key==="Enter"&&i>=0?(e.preventDefault(),t[i].click()):e.key==="Escape"&&d(!1)}),document.addEventListener("click",e=>{n.contains(e.target)||d(!1)})}window.Tether.__parts.search=S;})();
1
+ (()=>{var T=Object.defineProperty;var D=(i,t,e)=>t in i?T(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var V=(i,t)=>{for(var e in t)T(i,e,{get:t[e],enumerable:!0})};var a=(i,t,e)=>D(i,typeof t!="symbol"?t+"":t,e);var g={};V(g,{mountSearch:()=>W});var O=window.Tether.__core["config.ts"],[u,X,b,Z,tt,y,et]=O;var F=window.Tether.__core["client.ts"],[it,E,w,A,st]=F;var K=window.Tether.__core["dom.ts"],[at,I,ot,L,lt,x,m,ct,ht,ut,mt,C,h,q,l,M,v,d]=K;var Q=window.Tether.__core["mounting.ts"],[pt,ft,gt,St,Tt,R]=Q;var j=A({predictiveSearch:w({products:[{priceRange:"object"}],collections:["object"],queries:[{text:"string"}]})}),Y="query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}",B=6,J=2,z="/search",N=new WeakSet,P=0;function _(i,t,e){return parseInt(m(i,t)||"",10)||e}var U=i=>i.tagName==="A"?i:i.querySelector("a");function p(i,t){h(i,l("search-bind")).forEach(e=>{let r=m(e,"search-bind");r==="image"?M(e,t.image?.url,t.image?.altText||t.title):(r==="title"||r==="price"||r==="text")&&t[r]!=null&&v(e,t[r])})}function k(i,t){let e=U(i);e&&(t&&e.setAttribute("href",t),e.setAttribute("role","option"))}var f=class{constructor(t,e,r){a(this,"root");a(this,"input");a(this,"results");a(this,"limit");a(this,"minCharacters");a(this,"resultsId");a(this,"searchNumber",0);a(this,"timer");a(this,"lastTerm","");a(this,"cache",new Map);this.root=t,this.input=e,this.results=r,this.limit=_(t,"search-limit",B),this.minCharacters=_(t,"search-min",J),r.id||(P+=1,r.id=`${u.prefix}-search-${P}`),this.resultsId=r.id,this.setUpAria(),this.listen()}setUpAria(){this.input.setAttribute("role","combobox"),this.input.setAttribute("aria-autocomplete","list"),this.input.setAttribute("aria-controls",this.resultsId),this.input.setAttribute("aria-expanded","false"),this.input.setAttribute("autocomplete","off"),this.results.hasAttribute("role")||this.results.setAttribute("role","listbox"),this.results.hidden=!0}listen(){this.input.addEventListener("input",()=>{let t=this.input.value.trim();t!==this.lastTerm&&(this.lastTerm=t,clearTimeout(this.timer),this.timer=setTimeout(()=>void this.search(t),u.searchDelay))}),this.input.addEventListener("focus",()=>{this.input.value.trim().length>=this.minCharacters&&this.options().length&&this.open(!0)}),this.input.addEventListener("keydown",t=>{this.onKeydown(t)}),document.addEventListener("click",t=>{this.root.contains(t.target)||this.open(!1)})}setState(t){this.root.setAttribute(I("search-state"),t)}open(t){this.root.classList.toggle("is-open",t),this.results.hidden=!t,this.input.setAttribute("aria-expanded",String(t)),t||this.input.removeAttribute("aria-activedescendant")}options(){return h(this.results,'[role="option"]')}async fetchResults(t){let e=this.cache.get(t);if(e)return e;let r=await E(Y,{q:t,limit:this.limit},j);return this.cache.set(t,r.predictiveSearch),r.predictiveSearch}async search(t){this.searchNumber+=1;let e=this.searchNumber;if(t.length<this.minCharacters){this.open(!1),this.setState("idle");return}this.setState("loading");try{let r=await this.fetchResults(t);e===this.searchNumber&&this.render(r,t)}catch(r){if(e!==this.searchNumber){b("a superseded search failed",r);return}this.setState("error"),y(r,"search")}}fillList(t,e,r){h(this.results,l(t)).forEach(o=>{q(o,e,r),d(o,e.length>0)})}queryUrl(t){let e=this.root.getAttribute("action")||z,r=e.includes("?")?"&":"?";return`${e}${r}${this.input.name||"q"}=${encodeURIComponent(t)}`}render(t,e){let r=t?.products??[],o=t?.collections??[],c=(t?.queries??[]).filter(n=>n.text.toLowerCase()!==e.toLowerCase());this.fillList("search-products",r,(n,s)=>{let H=x(s.priceRange.minVariantPrice);p(n,{title:s.title,price:H,image:s.featuredImage}),k(n,C(s.handle,s.id)),n.classList.toggle("is-sold-out",!s.availableForSale)}),this.fillList("search-collections",o,(n,s)=>{p(n,{title:s.title,image:s.image}),k(n,`${(u.collectionPath||"/collection").replace(/\/$/,"")}/${s.handle}`)}),this.fillList("search-queries",c,(n,s)=>{p(n,{text:s.text}),U(n)?.setAttribute("href",this.queryUrl(s.text))});let S=r.length+o.length+c.length;this.showCount(S),this.setState(S?"ready":"empty"),this.open(!0),L("search",{term:e,products:r,collections:o,queries:c},this.root)}showCount(t){h(this.root,l("show")).forEach(e=>{let r=m(e,"show");r==="search-empty"&&d(e,t===0),r==="search-results"&&d(e,t>0)}),this.options().forEach((e,r)=>{e.id||(e.id=`${this.resultsId}-${r}`),e.setAttribute("aria-selected","false")})}highlight(t,e){t.forEach((o,c)=>{o.setAttribute("aria-selected",String(c===e))});let r=t[e];r&&(this.input.setAttribute("aria-activedescendant",r.id),r.scrollIntoView?.({block:"nearest"}))}onKeydown(t){let e=this.options(),r=e.findIndex(o=>o.getAttribute("aria-selected")==="true");t.key==="ArrowDown"&&e.length?(t.preventDefault(),this.open(!0),this.highlight(e,(r+1)%e.length)):t.key==="ArrowUp"&&e.length?(t.preventDefault(),this.highlight(e,r<=0?e.length-1:r-1)):t.key==="Enter"&&r>=0?(t.preventDefault(),e[r]?.click()):t.key==="Escape"&&this.open(!1)}};function W(i=document){for(let t of R(i,l("search"))){if(N.has(t))continue;N.add(t);let e=t.querySelector(l("search-input"))??t.querySelector("input"),r=t.querySelector(l("search-results"));e&&r&&new f(t,e,r)}}var{Tether:$}=window;if(!$)throw new Error("tether.search.js runs after the Tether script, which loads it");$.__parts.search=g;})();
@@ -0,0 +1,44 @@
1
+ /** A `MoneyV2`. */
2
+ interface Money {
3
+ amount: string;
4
+ currencyCode: string;
5
+ }
6
+ export interface AccountImage {
7
+ url: string;
8
+ altText: string | null;
9
+ }
10
+ /** A line of an order. */
11
+ export interface AccountOrderLine {
12
+ title: string | null;
13
+ variantTitle: string | null;
14
+ quantity: number;
15
+ image: AccountImage | null;
16
+ }
17
+ /** An order in the customer's list. */
18
+ export interface AccountOrder {
19
+ id: string;
20
+ name: string | null;
21
+ processedAt: string | null;
22
+ financialStatus: string | null;
23
+ fulfillmentStatus: string | null;
24
+ statusPageUrl: string | null;
25
+ totalPrice: Money | null;
26
+ lineItems: {
27
+ nodes: AccountOrderLine[];
28
+ };
29
+ }
30
+ /** `missing-id` when an order has no id, which each side reports in its own error type. */
31
+ type OrdersReading = {
32
+ kind: 'read';
33
+ orders: AccountOrder[];
34
+ } | {
35
+ kind: 'missing-id';
36
+ };
37
+ export declare function moneyOrNull(value: unknown): Money | null;
38
+ export declare function imageOrNull(value: unknown): AccountImage | null;
39
+ export declare const toOrderLine: (value: unknown) => AccountOrderLine;
40
+ /**
41
+ * @param value A list of orders: Shopify's `orders.nodes`, or the `orders` of the Worker's `/orders`.
42
+ */
43
+ export declare function readOrders(value: unknown): OrdersReading;
44
+ export {};
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The Worker's routes for the customer account operations, the contract between the browser's account part and the
3
+ * Worker: what the browser sends for each operation, and which method and path it has under `/_tether/account`. The
4
+ * operations themselves (their documents and input checks) run only in the Worker, in `account/operations.ts`.
5
+ */
6
+ export type SubscriptionAction = 'pause' | 'resume' | 'skip' | 'unskip' | 'cancel';
7
+ /** Shopify's `CustomerAddressInput` fields; the Worker keeps only these, with bounded lengths. */
8
+ export interface AddressFields {
9
+ firstName?: string;
10
+ lastName?: string;
11
+ company?: string;
12
+ address1?: string;
13
+ address2?: string;
14
+ city?: string;
15
+ zip?: string;
16
+ zoneCode?: string;
17
+ /** Two letters. */
18
+ countryCode?: string;
19
+ phoneNumber?: string;
20
+ }
21
+ export interface ReturnLine {
22
+ lineItemId: string;
23
+ quantity: number;
24
+ reasonId?: string;
25
+ note?: string;
26
+ }
27
+ /** What the browser sends for each operation. Ids are gids, or the number at their end. */
28
+ export interface OperationInputs {
29
+ order: {
30
+ id: string | number;
31
+ };
32
+ addresses: Record<string, never>;
33
+ subscriptions: Record<string, never>;
34
+ profile: {
35
+ firstName?: string | undefined;
36
+ lastName?: string | undefined;
37
+ };
38
+ /** Without `id`, a new address; without fields, only makes the address the default. */
39
+ saveAddress: {
40
+ id?: string | undefined;
41
+ address: AddressFields | null;
42
+ isDefault?: boolean | undefined;
43
+ };
44
+ deleteAddress: {
45
+ id: string;
46
+ };
47
+ subscription: {
48
+ id: string | number;
49
+ action: SubscriptionAction;
50
+ };
51
+ requestReturn: {
52
+ orderId: string | number;
53
+ lines: ReturnLine[];
54
+ };
55
+ }
56
+ export type OperationName = keyof OperationInputs;
57
+ /**
58
+ * Each operation's input fields as they reach the Worker: from a browser, a page script or anyone else, so each value
59
+ * is unchecked until the operation checks it.
60
+ */
61
+ export type UncheckedInputs = {
62
+ [Name in OperationName]: {
63
+ readonly [Field in keyof OperationInputs[Name]]?: unknown;
64
+ };
65
+ };
66
+ /** One operation a Worker request asks for, with the fields its route reads, each one unchecked. */
67
+ export interface OperationCall {
68
+ name: OperationName;
69
+ input: Readonly<Record<string, unknown>>;
70
+ }
71
+ export declare const OPERATION_ROUTES: {
72
+ order: {
73
+ name: "order";
74
+ method: "GET";
75
+ path: string;
76
+ fields: {
77
+ id: true;
78
+ };
79
+ };
80
+ addresses: {
81
+ name: "addresses";
82
+ method: "GET";
83
+ path: string;
84
+ fields: {};
85
+ };
86
+ subscriptions: {
87
+ name: "subscriptions";
88
+ method: "GET";
89
+ path: string;
90
+ fields: {};
91
+ };
92
+ profile: {
93
+ name: "profile";
94
+ method: "POST";
95
+ path: string;
96
+ fields: {
97
+ firstName: true;
98
+ lastName: true;
99
+ };
100
+ };
101
+ saveAddress: {
102
+ name: "saveAddress";
103
+ method: "POST";
104
+ path: string;
105
+ fields: {
106
+ id: true;
107
+ address: true;
108
+ isDefault: true;
109
+ };
110
+ };
111
+ deleteAddress: {
112
+ name: "deleteAddress";
113
+ method: "POST";
114
+ path: string;
115
+ fields: {
116
+ id: true;
117
+ };
118
+ };
119
+ subscription: {
120
+ name: "subscription";
121
+ method: "POST";
122
+ path: string;
123
+ fields: {
124
+ id: true;
125
+ action: true;
126
+ };
127
+ };
128
+ requestReturn: {
129
+ name: "requestReturn";
130
+ method: "POST";
131
+ path: string;
132
+ fields: {
133
+ orderId: true;
134
+ lines: true;
135
+ };
136
+ };
137
+ };
138
+ /**
139
+ * The operation a Worker request asks for, with its input read from the query string or the body.
140
+ *
141
+ * @param path The path under `/_tether/account`.
142
+ * @param body The JSON body of a POST; `{}` for a GET.
143
+ * @returns `null` when no operation has this method and path.
144
+ */
145
+ export declare function operationCall(method: string, path: string, url: URL, body: Record<string, unknown>): OperationCall | null;
146
+ /**
147
+ * The method and path (with its query string, for a GET) the browser requests an operation with. A POST sends the
148
+ * input as its JSON body.
149
+ */
150
+ export declare function operationRequest<Name extends OperationName>(name: Name, input: OperationInputs[Name]): {
151
+ method: 'GET' | 'POST';
152
+ path: string;
153
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Shopify API versions Tether is built and smoke-tested against, for the browser script and the Worker alike. The
3
+ * three APIs are versioned separately, so each has its own constant even while they hold the same quarter. A new
4
+ * version must pass `npm run smoke` on both the pinned version and the next one.
5
+ */
6
+ /** A dated, quarterly version such as `2026-07`. */
7
+ export type ApiVersion = `${number}-${'01' | '04' | '07' | '10'}`;
8
+ /** The Storefront API: products, carts, cart buyer identity. */
9
+ export declare const STOREFRONT_API_VERSION: ApiVersion;
10
+ /** The Customer Account API: the logged-in customer's orders, addresses and subscriptions. */
11
+ export declare const CUSTOMER_ACCOUNT_API_VERSION: ApiVersion;
12
+ /** The Admin API the sync reads products with. */
13
+ export declare const ADMIN_API_VERSION: ApiVersion;
@@ -0,0 +1,2 @@
1
+ /** The text of anything thrown: an `Error`'s message, or the value itself written as text. */
2
+ export declare function messageOf(error: unknown): string;