@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
@@ -1,137 +0,0 @@
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
- }
@@ -1,317 +0,0 @@
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
- });