@occluze/tether 0.6.8 → 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 (259) 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 -2
  24. package/dist/presets/barba.esm.js +19 -23
  25. package/dist/tether.account-pages.js +320 -365
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +242 -491
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +66 -88
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +36 -33
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +28 -36
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +1270 -894
  36. package/dist/tether.markets.js +73 -90
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +0 -0
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +44 -68
  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 +24 -23
  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.js → order-lines.ts} +20 -41
  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.js → page-events.ts} +55 -90
  127. package/src/account/server-client.ts +183 -0
  128. package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
  129. package/src/account-api.ts +98 -0
  130. package/src/account-marks.ts +35 -0
  131. package/src/{account-pages.js → account-pages.ts} +29 -37
  132. package/src/account.ts +284 -0
  133. package/src/amounts.ts +64 -0
  134. package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
  135. package/src/analytics.ts +315 -0
  136. package/src/{api-version.js → api-version.ts} +25 -31
  137. package/src/{astro.js → astro.ts} +110 -104
  138. package/src/cart/cart-shape.ts +25 -0
  139. package/src/cart/checkout.ts +101 -0
  140. package/src/cart/{drawer.js → drawer.ts} +18 -31
  141. package/src/cart/mutations.ts +237 -0
  142. package/src/cart/{render.js → render.ts} +68 -96
  143. package/src/cart/store.ts +279 -0
  144. package/src/{cart.js → cart.ts} +82 -128
  145. package/src/client.ts +391 -0
  146. package/src/{collection.js → collection.ts} +30 -38
  147. package/src/config-sections.ts +284 -0
  148. package/src/config.ts +480 -0
  149. package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
  150. package/src/controls/{discount.js → discount.ts} +13 -34
  151. package/src/controls/{extras.js → extras.ts} +15 -24
  152. package/src/controls/{navigation.js → navigation.ts} +24 -30
  153. package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
  154. package/src/{diagnose.js → diagnose.ts} +95 -96
  155. package/src/dom.ts +268 -0
  156. package/src/{extras.js → extras.ts} +43 -52
  157. package/src/fragments.ts +261 -0
  158. package/src/globals.d.ts +15 -0
  159. package/src/{images.js → images.ts} +30 -43
  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.js → loader.ts} +149 -123
  165. package/src/market-codes.ts +6 -0
  166. package/src/{markets-ui.js → markets-ui.ts} +91 -68
  167. package/src/markets.ts +246 -0
  168. package/src/markup.ts +127 -0
  169. package/src/{mounting.js → mounting.ts} +88 -101
  170. package/src/{page-market.js → page-market.ts} +32 -38
  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.js → product-events.ts} +33 -48
  181. package/src/{product-render.js → product-render.ts} +38 -77
  182. package/src/product-shape.ts +49 -0
  183. package/src/product-state.ts +318 -0
  184. package/src/{product.js → product.ts} +90 -126
  185. package/src/{repeaters.js → repeaters.ts} +40 -52
  186. package/src/{search.js → search.ts} +85 -103
  187. package/src/selling-plans.ts +145 -0
  188. package/src/server.ts +211 -0
  189. package/src/{shopify-values.js → shopify-values.ts} +24 -22
  190. package/src/{status.js → status.ts} +34 -54
  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.js → json-ld.ts} +45 -68
  195. package/src/{variant-render.js → variant-render.ts} +24 -50
  196. package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
  197. package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
  198. package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
  199. package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
  200. package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
  201. package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
  202. package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
  203. package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
  204. package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
  205. package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
  206. package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
  207. package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
  208. package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
  209. package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
  210. package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
  211. package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
  212. package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
  213. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
  214. package/dist/esm/chunks/search-OU75RNQL.js +0 -1
  215. package/src/account/addresses-view.js +0 -92
  216. package/src/account/browser-session.js +0 -359
  217. package/src/account/markup.js +0 -48
  218. package/src/account/operations.js +0 -180
  219. package/src/account/order-list.js +0 -91
  220. package/src/account/order-page.js +0 -192
  221. package/src/account/server-client.js +0 -172
  222. package/src/account/shared.js +0 -111
  223. package/src/account/shopify-auth.js +0 -150
  224. package/src/account-api.js +0 -118
  225. package/src/account-links.js +0 -35
  226. package/src/account.js +0 -342
  227. package/src/amounts.js +0 -36
  228. package/src/analytics.js +0 -352
  229. package/src/cart/checkout.js +0 -97
  230. package/src/cart/mutations.js +0 -234
  231. package/src/cart/store.js +0 -326
  232. package/src/client.js +0 -267
  233. package/src/config.js +0 -349
  234. package/src/dom.js +0 -287
  235. package/src/fragments.js +0 -237
  236. package/src/index.js +0 -353
  237. package/src/initial.js +0 -87
  238. package/src/lazy-iife.js +0 -116
  239. package/src/lazy.js +0 -39
  240. package/src/market-codes.js +0 -35
  241. package/src/markets.js +0 -334
  242. package/src/markup.js +0 -64
  243. package/src/parts/account-pages.js +0 -6
  244. package/src/parts/account.js +0 -6
  245. package/src/parts/analytics.js +0 -6
  246. package/src/parts/diagnose.js +0 -6
  247. package/src/parts/extras.js +0 -6
  248. package/src/parts/markets.js +0 -6
  249. package/src/parts/search.js +0 -6
  250. package/src/presets/barba.js +0 -190
  251. package/src/product-state.js +0 -318
  252. package/src/selling-plans.js +0 -154
  253. package/src/server.js +0 -215
  254. package/src/store-html.js +0 -0
  255. package/src/storefront/catalog.js +0 -199
  256. package/types/astro.d.ts +0 -28
  257. package/types/barba.d.ts +0 -16
  258. package/types/index.d.ts +0 -577
  259. package/types/server.d.ts +0 -182
@@ -0,0 +1,137 @@
1
+ /**
2
+ * The order page (`tether-order`): one order in full, with its lines, totals, addresses, shipments and what can be
3
+ * returned. `tether-order-state` and `tether-order-show` follow the load: `loading`, then `ready`, `not-found` or
4
+ * `error`.
5
+ */
6
+ import { reportError } from '../config.ts';
7
+ import { isHttpsUrl } from '../shopify-values.ts';
8
+ import { attr, formatDate, formatMoney, get, qsa, repeat, sel, setText, show } from '../dom.ts';
9
+ import { addressText, bindsIn, enumText, ownValue, showIn } from './markup.ts';
10
+ import { fillLines } from './order-lines.ts';
11
+ import { order } from './operations.ts';
12
+ import type { Fulfillment, OrderDetail } from './page-answers.ts';
13
+
14
+ type OrderState = 'loading' | 'ready' | 'not-found' | 'error';
15
+
16
+ /** @returns The order `tether-order` names: an id, or `url:<param>` for one in the page's URL. */
17
+ function orderKey(el: HTMLElement): string | null {
18
+ const raw = get(el, 'order') || '';
19
+ if (!raw.startsWith('url:')) return raw;
20
+ return new URL(window.location.href).searchParams.get(raw.slice('url:'.length) || 'id');
21
+ }
22
+
23
+ /** Every state but `ready`, which shows with the order. */
24
+ export function showOrderState(el: HTMLElement, state: Exclude<OrderState, 'ready'>): void {
25
+ el.setAttribute(attr('order-state'), state);
26
+ showIn(el, 'order-show', {
27
+ loading: state === 'loading',
28
+ ready: false,
29
+ 'not-found': state === 'not-found',
30
+ error: state === 'error',
31
+ });
32
+ }
33
+
34
+ /** @returns The text of each `tether-order-bind` value. */
35
+ const orderTexts = (detail: OrderDetail): Record<string, string> => ({
36
+ name: detail.name ?? '',
37
+ date: formatDate(detail.processedAt),
38
+ total: formatMoney(detail.totalPrice),
39
+ subtotal: formatMoney(detail.subtotal),
40
+ shipping: formatMoney(detail.totalShipping),
41
+ tax: formatMoney(detail.totalTax),
42
+ status: enumText(detail.financialStatus),
43
+ fulfillment: enumText(detail.fulfillmentStatus),
44
+ 'shipping-address': addressText(detail.shippingAddress),
45
+ 'billing-address': addressText(detail.billingAddress),
46
+ items: detail.lineItems.nodes.map((line) => `${String(line.quantity)}× ${line.title || line.name || ''}`).join(', '),
47
+ cancelled: formatDate(detail.cancelledAt),
48
+ });
49
+
50
+ /** One shipment row: its status and dates, and its tracking link, hidden without an https one. */
51
+ function fillFulfillment(row: HTMLElement, fulfillment: Fulfillment): void {
52
+ const tracking = fulfillment.trackingInformation[0];
53
+ const texts: Record<string, string> = {
54
+ status: enumText(fulfillment.latestShipmentStatus || fulfillment.status),
55
+ date: formatDate(fulfillment.createdAt),
56
+ eta: formatDate(fulfillment.estimatedDeliveryAt),
57
+ company: tracking?.company || '',
58
+ number: tracking?.number || '',
59
+ };
60
+ bindsIn(row, 'fulfillment-bind').forEach((el) => {
61
+ const key = get(el, 'fulfillment-bind') ?? '';
62
+ if (key === 'url') {
63
+ const url = tracking?.url ?? null;
64
+ const linked = url !== null && isHttpsUrl(url);
65
+ if (linked) el.setAttribute('href', url);
66
+ show(el, linked);
67
+ return;
68
+ }
69
+ const text = ownValue(texts, key);
70
+ if (text !== undefined) setText(el, text);
71
+ });
72
+ }
73
+
74
+ function fillOrder(el: HTMLElement, detail: OrderDetail): void {
75
+ const returnable = new Map(
76
+ (detail.returnInformation?.returnableLineItems.nodes ?? []).map((entry) => [
77
+ entry.lineItem?.id ?? '',
78
+ entry.quantity,
79
+ ]),
80
+ );
81
+ const shipments = detail.fulfillments.nodes;
82
+ const texts = orderTexts(detail);
83
+ qsa(el, sel('order-bind')).forEach((bind) => {
84
+ const key = get(bind, 'order-bind') ?? '';
85
+ if (key === 'url') {
86
+ const statusPage = detail.statusPageUrl;
87
+ if (statusPage !== null && isHttpsUrl(statusPage)) bind.setAttribute('href', statusPage);
88
+ return;
89
+ }
90
+ const text = ownValue(texts, key);
91
+ if (text !== undefined) setText(bind, text);
92
+ });
93
+ qsa(el, sel('order-lines')).forEach((box) => {
94
+ fillLines(box, detail.lineItems.nodes, returnable);
95
+ });
96
+ qsa(el, sel('order-fulfillments')).forEach((box) => {
97
+ repeat(box, shipments, fillFulfillment);
98
+ });
99
+ const returnRequested = el.dataset['tetherReturned'] === '1';
100
+ showIn(el, 'order-show', {
101
+ loading: false,
102
+ ready: true,
103
+ error: false,
104
+ 'not-found': false,
105
+ cancelled: Boolean(detail.cancelledAt),
106
+ shipped: shipments.length > 0,
107
+ returnable: [...returnable.values()].some((quantity) => quantity > 0),
108
+ 'has-returns': !returnRequested && detail.returns.nodes.length > 0,
109
+ 'return-requested': returnRequested,
110
+ });
111
+ el.setAttribute(attr('order-state'), 'ready');
112
+ }
113
+
114
+ /** Loads and renders the order `el` names. A failure is reported and shows the error state. */
115
+ export async function renderOrder(el: HTMLElement): Promise<void> {
116
+ const key = orderKey(el);
117
+ if (!key) {
118
+ el.setAttribute(attr('order-state'), 'not-found');
119
+ showIn(el, 'order-show', { 'not-found': true });
120
+ return;
121
+ }
122
+ showOrderState(el, 'loading');
123
+ let detail: OrderDetail | null;
124
+ try {
125
+ detail = await order(key);
126
+ } catch (error) {
127
+ reportError(error, 'order');
128
+ showOrderState(el, 'error');
129
+ return;
130
+ }
131
+ if (!detail) {
132
+ showOrderState(el, 'not-found');
133
+ return;
134
+ }
135
+ el.dataset['tetherOrderId'] = detail.id;
136
+ fillOrder(el, detail);
137
+ }
@@ -0,0 +1,317 @@
1
+ /**
2
+ * The account pages' answers as typed values: addresses, the order page, subscriptions and what a change returns. The
3
+ * rules are answers.ts's: every selected field is kept, a missing one reads as `null`, and the ids the pages act on
4
+ * are required.
5
+ */
6
+ import {
7
+ countOrZero,
8
+ field,
9
+ isRecord,
10
+ listOf,
11
+ nodesOf,
12
+ numberOrNull,
13
+ textList,
14
+ textOrNull,
15
+ } from '../../server/src/shared/json.ts';
16
+ import { imageOrNull, moneyOrNull, toOrderLine } from '../../server/src/shared/account-orders.ts';
17
+ import type { AccountImage, AccountOrderLine } from '../../server/src/shared/account-orders.ts';
18
+ import type { Money } from '../shopify-values.ts';
19
+ import { requiredId, unexpected } from './answers.ts';
20
+
21
+ /** An address as the documents select it. */
22
+ export interface MailingAddress {
23
+ id: string | null;
24
+ firstName: string | null;
25
+ lastName: string | null;
26
+ company: string | null;
27
+ address1: string | null;
28
+ address2: string | null;
29
+ city: string | null;
30
+ province: string | null;
31
+ zoneCode: string | null;
32
+ zip: string | null;
33
+ country: string | null;
34
+ countryCode: string | null;
35
+ phoneNumber: string | null;
36
+ /** One line per entry. */
37
+ formatted: string[];
38
+ }
39
+ /** One of the customer's saved addresses. */
40
+ export type AccountAddress = MailingAddress & { id: string };
41
+ export type CustomerAddress = AccountAddress & { isDefault: boolean };
42
+ interface ReturnReason {
43
+ id: string;
44
+ name: string | null;
45
+ handle: string | null;
46
+ }
47
+ /** A line of the order page's document. */
48
+ export type OrderDetailLine = AccountOrderLine & {
49
+ id: string;
50
+ name: string | null;
51
+ sku: string | null;
52
+ price: Money | null;
53
+ totalPrice: Money | null;
54
+ suggestedReturnReasonDefinitions: { nodes: ReturnReason[] };
55
+ };
56
+ interface Tracking {
57
+ company: string | null;
58
+ number: string | null;
59
+ url: string | null;
60
+ }
61
+ export interface Fulfillment {
62
+ id: string | null;
63
+ status: string | null;
64
+ createdAt: string | null;
65
+ estimatedDeliveryAt: string | null;
66
+ latestShipmentStatus: string | null;
67
+ trackingInformation: Tracking[];
68
+ }
69
+ interface ReturnableLine {
70
+ quantity: number;
71
+ lineItem: { id: string } | null;
72
+ }
73
+ /** A return Shopify opened. */
74
+ export interface OpenedReturn {
75
+ id: string;
76
+ status: string | null;
77
+ }
78
+ /** The order page's document. */
79
+ export interface OrderDetail {
80
+ id: string;
81
+ name: string | null;
82
+ number: number | null;
83
+ processedAt: string | null;
84
+ cancelledAt: string | null;
85
+ financialStatus: string | null;
86
+ fulfillmentStatus: string | null;
87
+ statusPageUrl: string | null;
88
+ requiresShipping: boolean | null;
89
+ totalPrice: Money | null;
90
+ subtotal: Money | null;
91
+ totalShipping: Money | null;
92
+ totalTax: Money | null;
93
+ shippingAddress: MailingAddress | null;
94
+ billingAddress: MailingAddress | null;
95
+ lineItems: { nodes: OrderDetailLine[] };
96
+ fulfillments: { nodes: Fulfillment[] };
97
+ returnInformation: { returnableLineItems: { nodes: ReturnableLine[] } } | null;
98
+ returns: { nodes: OpenedReturn[] };
99
+ }
100
+ export interface SubscriptionLine {
101
+ id: string | null;
102
+ name: string | null;
103
+ title: string | null;
104
+ variantTitle: string | null;
105
+ quantity: number;
106
+ sku: string | null;
107
+ currentPrice: Money | null;
108
+ lineDiscountedPrice: Money | null;
109
+ image: AccountImage | null;
110
+ }
111
+ interface BillingCycle {
112
+ cycleIndex: number | null;
113
+ skipped: boolean;
114
+ billingAttemptExpectedDate: string | null;
115
+ }
116
+ /** The subscriptions document. */
117
+ export interface SubscriptionContract {
118
+ id: string;
119
+ /** `ACTIVE`, `PAUSED`, `CANCELLED`, `EXPIRED` or `FAILED`. */
120
+ status: string | null;
121
+ nextBillingDate: string | null;
122
+ currencyCode: string | null;
123
+ deliveryPrice: Money | null;
124
+ deliveryPolicy: { interval: string | null; intervalCount: { count: number } | null } | null;
125
+ lines: { nodes: SubscriptionLine[] };
126
+ upcomingBillingCycles: { nodes: BillingCycle[] };
127
+ }
128
+ export interface Profile {
129
+ firstName: string | null;
130
+ lastName: string | null;
131
+ }
132
+ /** What a subscription action changed: the contract (pause, resume, cancel) or its next billing cycle (skip, unskip). */
133
+ export type SubscriptionChange =
134
+ | { contract: { id: string | null; status: string | null } }
135
+ | { billingCycle: { cycleIndex: number | null; skipped: boolean } };
136
+
137
+ function toMailingAddress(value: unknown): MailingAddress {
138
+ const text = (name: string) => textOrNull(field(value, name));
139
+ return {
140
+ id: text('id'),
141
+ firstName: text('firstName'),
142
+ lastName: text('lastName'),
143
+ company: text('company'),
144
+ address1: text('address1'),
145
+ address2: text('address2'),
146
+ city: text('city'),
147
+ province: text('province'),
148
+ zoneCode: text('zoneCode'),
149
+ zip: text('zip'),
150
+ country: text('country'),
151
+ countryCode: text('countryCode'),
152
+ phoneNumber: text('phoneNumber'),
153
+ formatted: textList(field(value, 'formatted')),
154
+ };
155
+ }
156
+
157
+ const mailingAddressOrNull = (value: unknown): MailingAddress | null =>
158
+ isRecord(value) ? toMailingAddress(value) : null;
159
+
160
+ /** @throws {TetherError} Without an id. */
161
+ export const toAccountAddress = (value: unknown): AccountAddress => ({
162
+ ...toMailingAddress(value),
163
+ id: requiredId(field(value, 'id'), 'address id'),
164
+ });
165
+
166
+ /**
167
+ * @param value The addresses operation's list, each address with the `isDefault` it adds.
168
+ * @throws {TetherError} For an address without an id.
169
+ */
170
+ export const toCustomerAddresses = (value: unknown): CustomerAddress[] =>
171
+ listOf(value, (address) => ({ ...toAccountAddress(address), isDefault: field(address, 'isDefault') === true }));
172
+
173
+ const toOrderDetailLine = (value: unknown): OrderDetailLine => ({
174
+ ...toOrderLine(value),
175
+ id: requiredId(field(value, 'id'), 'line item id'),
176
+ name: textOrNull(field(value, 'name')),
177
+ sku: textOrNull(field(value, 'sku')),
178
+ price: moneyOrNull(field(value, 'price')),
179
+ totalPrice: moneyOrNull(field(value, 'totalPrice')),
180
+ suggestedReturnReasonDefinitions: nodesOf(field(value, 'suggestedReturnReasonDefinitions'), (reason) => ({
181
+ id: requiredId(field(reason, 'id'), 'return reason id'),
182
+ name: textOrNull(field(reason, 'name')),
183
+ handle: textOrNull(field(reason, 'handle')),
184
+ })),
185
+ });
186
+
187
+ const toFulfillment = (value: unknown): Fulfillment => ({
188
+ id: textOrNull(field(value, 'id')),
189
+ status: textOrNull(field(value, 'status')),
190
+ createdAt: textOrNull(field(value, 'createdAt')),
191
+ estimatedDeliveryAt: textOrNull(field(value, 'estimatedDeliveryAt')),
192
+ latestShipmentStatus: textOrNull(field(value, 'latestShipmentStatus')),
193
+ trackingInformation: listOf(field(value, 'trackingInformation'), (tracking) => ({
194
+ company: textOrNull(field(tracking, 'company')),
195
+ number: textOrNull(field(tracking, 'number')),
196
+ url: textOrNull(field(tracking, 'url')),
197
+ })),
198
+ });
199
+
200
+ function toReturnableLine(value: unknown): ReturnableLine {
201
+ const lineId = textOrNull(field(field(value, 'lineItem'), 'id'));
202
+ return { quantity: countOrZero(field(value, 'quantity')), lineItem: lineId === null ? null : { id: lineId } };
203
+ }
204
+
205
+ function toReturnInformation(value: unknown): OrderDetail['returnInformation'] {
206
+ if (!isRecord(value)) return null;
207
+ return { returnableLineItems: nodesOf(value['returnableLineItems'], toReturnableLine) };
208
+ }
209
+
210
+ /**
211
+ * @param value The order operation's `order`.
212
+ * @returns `null` when the customer has no such order.
213
+ * @throws {TetherError} For an order, line, reason or return without an id.
214
+ */
215
+ export function toOrderDetail(value: unknown): OrderDetail | null {
216
+ if (!isRecord(value)) return null;
217
+ const text = (name: string) => textOrNull(value[name]);
218
+ const money = (name: string) => moneyOrNull(value[name]);
219
+ const requiresShipping = value['requiresShipping'];
220
+ return {
221
+ id: requiredId(value['id'], 'order id'),
222
+ name: text('name'),
223
+ number: numberOrNull(value['number']),
224
+ processedAt: text('processedAt'),
225
+ cancelledAt: text('cancelledAt'),
226
+ financialStatus: text('financialStatus'),
227
+ fulfillmentStatus: text('fulfillmentStatus'),
228
+ statusPageUrl: text('statusPageUrl'),
229
+ requiresShipping: typeof requiresShipping === 'boolean' ? requiresShipping : null,
230
+ totalPrice: money('totalPrice'),
231
+ subtotal: money('subtotal'),
232
+ totalShipping: money('totalShipping'),
233
+ totalTax: money('totalTax'),
234
+ shippingAddress: mailingAddressOrNull(value['shippingAddress']),
235
+ billingAddress: mailingAddressOrNull(value['billingAddress']),
236
+ lineItems: nodesOf(value['lineItems'], toOrderDetailLine),
237
+ fulfillments: nodesOf(value['fulfillments'], toFulfillment),
238
+ returnInformation: toReturnInformation(value['returnInformation']),
239
+ returns: nodesOf(value['returns'], toReturn),
240
+ };
241
+ }
242
+
243
+ const toSubscriptionLine = (value: unknown): SubscriptionLine => ({
244
+ id: textOrNull(field(value, 'id')),
245
+ name: textOrNull(field(value, 'name')),
246
+ title: textOrNull(field(value, 'title')),
247
+ variantTitle: textOrNull(field(value, 'variantTitle')),
248
+ quantity: countOrZero(field(value, 'quantity')),
249
+ sku: textOrNull(field(value, 'sku')),
250
+ currentPrice: moneyOrNull(field(value, 'currentPrice')),
251
+ lineDiscountedPrice: moneyOrNull(field(value, 'lineDiscountedPrice')),
252
+ image: imageOrNull(field(value, 'image')),
253
+ });
254
+
255
+ const toBillingCycle = (value: unknown): BillingCycle => ({
256
+ cycleIndex: numberOrNull(field(value, 'cycleIndex')),
257
+ skipped: field(value, 'skipped') === true,
258
+ billingAttemptExpectedDate: textOrNull(field(value, 'billingAttemptExpectedDate')),
259
+ });
260
+
261
+ function toDeliveryPolicy(value: unknown): SubscriptionContract['deliveryPolicy'] {
262
+ if (!isRecord(value)) return null;
263
+ const intervalCount = value['intervalCount'];
264
+ return {
265
+ interval: textOrNull(value['interval']),
266
+ intervalCount: isRecord(intervalCount) ? { count: countOrZero(intervalCount['count']) } : null,
267
+ };
268
+ }
269
+
270
+ /** @throws {TetherError} Without an id. */
271
+ const toSubscriptionContract = (value: unknown): SubscriptionContract => ({
272
+ id: requiredId(field(value, 'id'), 'subscription id'),
273
+ status: textOrNull(field(value, 'status')),
274
+ nextBillingDate: textOrNull(field(value, 'nextBillingDate')),
275
+ currencyCode: textOrNull(field(value, 'currencyCode')),
276
+ deliveryPrice: moneyOrNull(field(value, 'deliveryPrice')),
277
+ deliveryPolicy: toDeliveryPolicy(field(value, 'deliveryPolicy')),
278
+ lines: nodesOf(field(value, 'lines'), toSubscriptionLine),
279
+ upcomingBillingCycles: nodesOf(field(value, 'upcomingBillingCycles'), toBillingCycle),
280
+ });
281
+
282
+ /**
283
+ * @param value The subscriptions operation's list.
284
+ * @throws {TetherError} For a contract without an id.
285
+ */
286
+ export const toSubscriptionContracts = (value: unknown): SubscriptionContract[] =>
287
+ listOf(value, toSubscriptionContract);
288
+
289
+ /**
290
+ * @param result The subscription operation's result.
291
+ * @throws {TetherError} With neither a contract nor a billing cycle.
292
+ */
293
+ export function toSubscriptionChange(result: Record<string, unknown>): SubscriptionChange {
294
+ const { contract, billingCycle } = result;
295
+ if (isRecord(contract)) {
296
+ return { contract: { id: textOrNull(contract['id']), status: textOrNull(contract['status']) } };
297
+ }
298
+ if (isRecord(billingCycle)) {
299
+ const { cycleIndex, skipped } = toBillingCycle(billingCycle);
300
+ return { billingCycle: { cycleIndex, skipped } };
301
+ }
302
+ throw unexpected('changed subscription');
303
+ }
304
+
305
+ /** @throws {TetherError} Without an id. */
306
+ export function toReturn(value: unknown): OpenedReturn {
307
+ return { id: requiredId(field(value, 'id'), 'return id'), status: textOrNull(field(value, 'status')) };
308
+ }
309
+
310
+ /** @throws {TetherError} Without an id. */
311
+ export const toDeletedId = (value: unknown): string => requiredId(value, 'deleted address id');
312
+
313
+ /** @param value The profile operation's `customer`. */
314
+ export const toProfile = (value: unknown): Profile => ({
315
+ firstName: textOrNull(field(value, 'firstName')),
316
+ lastName: textOrNull(field(value, 'lastName')),
317
+ });