@zappie/sdk 0.1.8 → 0.1.10

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @zappie/sdk
2
2
 
3
+ ## 0.1.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 488e47d: A delivery checkout keeps its region and the rider's note.
8
+
9
+ - `@zappie/sdk`: `createCheckout().open()` sends the delivery note as `fulfilment.instructions`, the field the API reads. Before, it was sent inside the address, where the API ignored it, so the rider never saw the note.
10
+ - `@zappie/kit` and the starter: the checkout button's request now carries `delivery_region_id`, so a chosen or pre-filled delivery region reaches the payment session. The `checkout-button` registry item is 1.0.2.
11
+
12
+ ## 0.1.9
13
+
14
+ ### Patch Changes
15
+
16
+ - e7cc5f0: New entry `@zappie/sdk/checkout-defaults`: the phone, delivery address, region and scheduled time a checkout starts from, by the default storefront's rule. It keeps what the shopper typed, remembers the last address on this device only, and re-runs on sign-in through `watchCheckoutDefaults()`.
17
+ - 1c3cbbf: `@zappie/sdk/pricing` now uses the `Item` and `Money` types from `@zappie/sdk` instead of its own copies. `startingPrice(item)` accepts an `Item` from `@zappie/sdk`, and `format.money(startingPrice(item))` type-checks.
18
+
3
19
  ## 0.1.8
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -55,6 +55,7 @@ Prices always come from Zappie. The cart shows a local estimate while it waits,
55
55
  | `@zappie/sdk` | The client, the cart, money formatting and errors |
56
56
  | `@zappie/sdk/react` | `ZappieProvider` and hooks such as `useStore`, `useMenu` and `useCart` |
57
57
  | `@zappie/sdk/checkout` | Opening Zappie's hosted checkout and checking it completed |
58
+ | `@zappie/sdk/checkout-defaults` | The phone, address, region and time a signed-in checkout starts from |
58
59
  | `@zappie/sdk/customer` and `/customer/react` | Signed-in customers, their orders and saved addresses |
59
60
  | `@zappie/sdk/integrations` | Public integration IDs and browser keys |
60
61
  | `@zappie/sdk/places` | Address search and delivery-zone checks |
@@ -125,3 +126,56 @@ anchors `#c-cat_<id>`. Already-prefixed IDs stay unchanged. Legacy cart,
125
126
  checkout and sign-in overlays become routes. Unsupported legacy UI parameters
126
127
  are dropped. Ad click IDs, `ref`, and every `utm_*` parameter retain their values.
127
128
  Payment-return and printed QR routes retain their existing parameters.
129
+
130
+ ### Checkout defaults
131
+
132
+ `@zappie/sdk/checkout-defaults` fills in checkout contact details the way the
133
+ default storefront does, so every template follows one rule.
134
+
135
+ - **Phone.** `defaultPhone(typed, customer)` keeps a number typed in this
136
+ session. Otherwise it shows the signed-in customer's masked number with
137
+ `fromAccount: true` and no `contactPhone`. Leave `contact_phone` out of the
138
+ checkout and Zappie uses the customer's verified number. Show the masked
139
+ number as a line or a placeholder, not as typed text.
140
+ - **Address.** `defaultAddress()` returns the last delivery address used on
141
+ this device, otherwise the customer's first saved address. Unless the fee
142
+ type is `"fixed"`, an address without a place ID counts as none. The cart's
143
+ own delivery address always wins in `checkoutDefaults()`.
144
+ - **Remembering.** `rememberAddress(address)` keeps the address line, place ID
145
+ and coordinates in `localStorage`, on this device only.
146
+ `rememberAddress(null)` forgets them. Both do nothing where storage is
147
+ blocked.
148
+ - **Region.** `defaultRegion(branch, chosen)` keeps a chosen region and
149
+ otherwise picks the branch's only one.
150
+ - **Time.** `restoreSchedule(fulfilment, branchId)` keeps the cart's scheduled
151
+ time when it belongs to this branch and parses. Drop it otherwise.
152
+ - **Applying.** `prefillFulfilment(fulfilment, branch)` fills the only region
153
+ and drops a stale time. It never changes the fulfilment type or address, and
154
+ returns the same object when nothing changes.
155
+ - **Sign-in.** `watchCheckoutDefaults(customer, read, listener)` publishes
156
+ `checkoutDefaults()` now and again each time the shopper signs in or out. On
157
+ sign-in it fetches `/me/addresses` first.
158
+
159
+ ```ts
160
+ import {
161
+ prefillFulfilment,
162
+ rememberAddress,
163
+ watchCheckoutDefaults,
164
+ } from "@zappie/sdk/checkout-defaults";
165
+
166
+ const stop = watchCheckoutDefaults(
167
+ customer,
168
+ () => ({ branch, fulfilment: cart.fulfilment, typedPhone: phone }),
169
+ (defaults) => {
170
+ setPhonePlaceholder(defaults.phone.display);
171
+ setAddressDraft(defaults.address);
172
+ const next = prefillFulfilment(cart.fulfilment, branch);
173
+ if (next && next !== cart.fulfilment) {
174
+ cart.setFulfilment(next);
175
+ }
176
+ },
177
+ );
178
+
179
+ // When the shopper picks a delivery address:
180
+ rememberAddress(address);
181
+ ```
@@ -17,6 +17,21 @@ async function error(response) {
17
17
  value.issues
18
18
  );
19
19
  }
20
+ function checkoutBody(body) {
21
+ const address = body.fulfilment.address;
22
+ if (!address) {
23
+ return body;
24
+ }
25
+ const { line2: _line2, instructions, ...rest } = address;
26
+ return {
27
+ ...body,
28
+ fulfilment: {
29
+ ...body.fulfilment,
30
+ ...body.fulfilment.instructions === void 0 && instructions ? { instructions } : {},
31
+ address: rest
32
+ }
33
+ };
34
+ }
20
35
  function createCheckout(options = {}) {
21
36
  const base = (options.customerBase ?? DEFAULT_CUSTOMER_BASE).replace(
22
37
  /\/+$/,
@@ -48,7 +63,7 @@ function createCheckout(options = {}) {
48
63
  "Content-Type": "application/json",
49
64
  "Idempotency-Key": idempotencyKey
50
65
  },
51
- body: JSON.stringify(body)
66
+ body: JSON.stringify(checkoutBody(body))
52
67
  });
53
68
  if (!session.url) {
54
69
  throw new ZappieError(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/checkout/client.ts"],
4
- "sourcesContent": ["import { apiError, requestWithRetry, wait } from \"../internal/customer-http\";\nimport { CheckoutError } from \"./error\";\nimport { ZappieError } from \"./sdk\";\nimport { mapCheckoutSession } from \"./session\";\nimport type {\n AwaitCompletionOptions,\n CheckoutClientOptions,\n CheckoutCompletion,\n CheckoutRequest,\n CheckoutSession,\n OpenCheckoutSession,\n} from \"./types\";\n\nconst DEFAULT_CUSTOMER_BASE = \"/_zappie/api\";\n\nasync function error(response: Response): Promise<CheckoutError> {\n const value = await apiError(\n response,\n `The checkout service answered ${response.status}`,\n );\n return new CheckoutError(\n response.status,\n value.apiCode,\n value.message,\n value.param,\n value.issues,\n );\n}\n\n/**\n * Create a client that opens checkout sessions, reads them and polls for completion.\n *\n * @param options - Override the default `/_zappie/api` base or fetch implementation.\n * @returns A checkout client. Retain each idempotency key when retrying the same cart.\n */\nexport function createCheckout(options: CheckoutClientOptions = {}) {\n const base = (options.customerBase ?? DEFAULT_CUSTOMER_BASE).replace(\n /\\/+$/,\n \"\",\n );\n const fetcher = options.fetch ?? globalThis.fetch;\n const request = async (\n path: string,\n init: RequestInit,\n ): Promise<CheckoutSession> => {\n const response = await requestWithRetry(\n fetcher,\n base + path,\n init,\n \"Could not reach checkout\",\n );\n if (!response.ok) {\n throw await error(response);\n }\n return mapCheckoutSession(await response.json());\n };\n const read = (id: string, signal?: AbortSignal) =>\n request(`/checkout/sessions/${encodeURIComponent(id)}`, {\n method: \"GET\",\n ...(signal ? { signal } : {}),\n });\n return {\n /** The caller retains this key and reuses it when retrying the same cart. */\n async open(\n body: CheckoutRequest,\n idempotencyKey: string,\n ): Promise<OpenCheckoutSession> {\n const session = await request(\"/checkout/sessions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n },\n body: JSON.stringify(body),\n });\n if (!session.url) {\n throw new ZappieError(\n \"server_error\",\n \"Checkout session has no payment URL\",\n );\n }\n globalThis.__zappieEmit?.(\"checkout:open\", session);\n return { ...session, url: session.url };\n },\n session(id: string): Promise<CheckoutSession> {\n return read(id);\n },\n async awaitCompletion(\n id: string,\n options: AwaitCompletionOptions = {},\n ): Promise<CheckoutCompletion> {\n const interval = options.intervalMs ?? 3_000;\n if (!Number.isFinite(interval) || interval < 0) {\n throw new ZappieError(\n \"invalid_request\",\n \"intervalMs cannot be negative\",\n );\n }\n for (;;) {\n options.signal?.throwIfAborted();\n try {\n const session = await read(id, options.signal);\n if (session.status === \"completed\" && session.order_id) {\n globalThis.__zappieEmit?.(\"checkout:completed\", session);\n return {\n ...session,\n status: \"completed\",\n order_id: session.order_id,\n };\n }\n if (session.status === \"expired\" || session.status === \"cancelled\") {\n return { ...session, status: session.status };\n }\n } catch (failure) {\n options.signal?.throwIfAborted();\n if (\n failure instanceof ZappieError &&\n failure.status !== undefined &&\n failure.status >= 400 &&\n failure.status < 500\n ) {\n throw failure;\n }\n }\n await wait(interval, options.signal);\n }\n },\n };\n}\n\n/**\n * The session creation, lookup and completion methods returned by createCheckout.\n */\nexport type CheckoutClient = ReturnType<typeof createCheckout>;\n"],
5
- "mappings": ";AAAA,SAAS,UAAU,kBAAkB,YAAY;AACjD,SAAS,qBAAqB;AAC9B,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AAUnC,IAAM,wBAAwB;AAE9B,eAAe,MAAM,UAA4C;AAC/D,QAAM,QAAQ,MAAM;AAAA,IAClB;AAAA,IACA,iCAAiC,SAAS,MAAM;AAAA,EAClD;AACA,SAAO,IAAI;AAAA,IACT,SAAS;AAAA,IACT,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AACF;AAQO,SAAS,eAAe,UAAiC,CAAC,GAAG;AAClE,QAAM,QAAQ,QAAQ,gBAAgB,uBAAuB;AAAA,IAC3D;AAAA,IACA;AAAA,EACF;AACA,QAAM,UAAU,QAAQ,SAAS,WAAW;AAC5C,QAAM,UAAU,OACd,MACA,SAC6B;AAC7B,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,OAAO;AAAA,MACP;AAAA,MACA;AAAA,IACF;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,MAAM,MAAM,QAAQ;AAAA,IAC5B;AACA,WAAO,mBAAmB,MAAM,SAAS,KAAK,CAAC;AAAA,EACjD;AACA,QAAM,OAAO,CAAC,IAAY,WACxB,QAAQ,sBAAsB,mBAAmB,EAAE,CAAC,IAAI;AAAA,IACtD,QAAQ;AAAA,IACR,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,EAC7B,CAAC;AACH,SAAO;AAAA;AAAA,IAEL,MAAM,KACJ,MACA,gBAC8B;AAC9B,YAAM,UAAU,MAAM,QAAQ,sBAAsB;AAAA,QAClD,QAAQ;AAAA,QACR,SAAS;AAAA,UACP,gBAAgB;AAAA,UAChB,mBAAmB;AAAA,QACrB;AAAA,QACA,MAAM,KAAK,UAAU,IAAI;AAAA,MAC3B,CAAC;AACD,UAAI,CAAC,QAAQ,KAAK;AAChB,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,iBAAW,eAAe,iBAAiB,OAAO;AAClD,aAAO,EAAE,GAAG,SAAS,KAAK,QAAQ,IAAI;AAAA,IACxC;AAAA,IACA,QAAQ,IAAsC;AAC5C,aAAO,KAAK,EAAE;AAAA,IAChB;AAAA,IACA,MAAM,gBACJ,IACAA,WAAkC,CAAC,GACN;AAC7B,YAAM,WAAWA,SAAQ,cAAc;AACvC,UAAI,CAAC,OAAO,SAAS,QAAQ,KAAK,WAAW,GAAG;AAC9C,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,iBAAS;AACP,QAAAA,SAAQ,QAAQ,eAAe;AAC/B,YAAI;AACF,gBAAM,UAAU,MAAM,KAAK,IAAIA,SAAQ,MAAM;AAC7C,cAAI,QAAQ,WAAW,eAAe,QAAQ,UAAU;AACtD,uBAAW,eAAe,sBAAsB,OAAO;AACvD,mBAAO;AAAA,cACL,GAAG;AAAA,cACH,QAAQ;AAAA,cACR,UAAU,QAAQ;AAAA,YACpB;AAAA,UACF;AACA,cAAI,QAAQ,WAAW,aAAa,QAAQ,WAAW,aAAa;AAClE,mBAAO,EAAE,GAAG,SAAS,QAAQ,QAAQ,OAAO;AAAA,UAC9C;AAAA,QACF,SAAS,SAAS;AAChB,UAAAA,SAAQ,QAAQ,eAAe;AAC/B,cACE,mBAAmB,eACnB,QAAQ,WAAW,UACnB,QAAQ,UAAU,OAClB,QAAQ,SAAS,KACjB;AACA,kBAAM;AAAA,UACR;AAAA,QACF;AACA,cAAM,KAAK,UAAUA,SAAQ,MAAM;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { apiError, requestWithRetry, wait } from \"../internal/customer-http\";\nimport { CheckoutError } from \"./error\";\nimport { ZappieError } from \"./sdk\";\nimport { mapCheckoutSession } from \"./session\";\nimport type {\n AwaitCompletionOptions,\n CheckoutClientOptions,\n CheckoutCompletion,\n CheckoutRequest,\n CheckoutSession,\n OpenCheckoutSession,\n} from \"./types\";\n\nconst DEFAULT_CUSTOMER_BASE = \"/_zappie/api\";\n\nasync function error(response: Response): Promise<CheckoutError> {\n const value = await apiError(\n response,\n `The checkout service answered ${response.status}`,\n );\n return new CheckoutError(\n response.status,\n value.apiCode,\n value.message,\n value.param,\n value.issues,\n );\n}\n\n// REST reads the rider note from fulfilment.instructions and accepts only its listed address\n// fields, while a cart's wire fulfilment keeps the note on the address.\nfunction checkoutBody(body: CheckoutRequest): CheckoutRequest {\n const address = body.fulfilment.address as\n | (CheckoutRequest[\"fulfilment\"][\"address\"] & {\n line2?: string;\n instructions?: string;\n })\n | undefined;\n if (!address) {\n return body;\n }\n const { line2: _line2, instructions, ...rest } = address;\n return {\n ...body,\n fulfilment: {\n ...body.fulfilment,\n ...(body.fulfilment.instructions === undefined && instructions\n ? { instructions }\n : {}),\n address: rest,\n },\n };\n}\n\n/**\n * Create a client that opens checkout sessions, reads them and polls for completion.\n *\n * @param options - Override the default `/_zappie/api` base or fetch implementation.\n * @returns A checkout client. Retain each idempotency key when retrying the same cart.\n */\nexport function createCheckout(options: CheckoutClientOptions = {}) {\n const base = (options.customerBase ?? DEFAULT_CUSTOMER_BASE).replace(\n /\\/+$/,\n \"\",\n );\n const fetcher = options.fetch ?? globalThis.fetch;\n const request = async (\n path: string,\n init: RequestInit,\n ): Promise<CheckoutSession> => {\n const response = await requestWithRetry(\n fetcher,\n base + path,\n init,\n \"Could not reach checkout\",\n );\n if (!response.ok) {\n throw await error(response);\n }\n return mapCheckoutSession(await response.json());\n };\n const read = (id: string, signal?: AbortSignal) =>\n request(`/checkout/sessions/${encodeURIComponent(id)}`, {\n method: \"GET\",\n ...(signal ? { signal } : {}),\n });\n return {\n /** The caller retains this key and reuses it when retrying the same cart. */\n async open(\n body: CheckoutRequest,\n idempotencyKey: string,\n ): Promise<OpenCheckoutSession> {\n const session = await request(\"/checkout/sessions\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n },\n body: JSON.stringify(checkoutBody(body)),\n });\n if (!session.url) {\n throw new ZappieError(\n \"server_error\",\n \"Checkout session has no payment URL\",\n );\n }\n globalThis.__zappieEmit?.(\"checkout:open\", session);\n return { ...session, url: session.url };\n },\n session(id: string): Promise<CheckoutSession> {\n return read(id);\n },\n async awaitCompletion(\n id: string,\n options: AwaitCompletionOptions = {},\n ): Promise<CheckoutCompletion> {\n const interval = options.intervalMs ?? 3_000;\n if (!Number.isFinite(interval) || interval < 0) {\n throw new ZappieError(\n \"invalid_request\",\n \"intervalMs cannot be negative\",\n );\n }\n for (;;) {\n options.signal?.throwIfAborted();\n try {\n const session = await read(id, options.signal);\n if (session.status === \"completed\" && session.order_id) {\n globalThis.__zappieEmit?.(\"checkout:completed\", session);\n return {\n ...session,\n status: \"completed\",\n order_id: session.order_id,\n };\n }\n if (session.status === \"expired\" || session.status === \"cancelled\") {\n return { ...session, status: session.status };\n }\n } catch (failure) {\n options.signal?.throwIfAborted();\n if (\n failure instanceof ZappieError &&\n failure.status !== undefined &&\n failure.status >= 400 &&\n failure.status < 500\n ) {\n throw failure;\n }\n }\n await wait(interval, options.signal);\n }\n },\n };\n}\n\n/**\n * The session creation, lookup and completion methods returned by createCheckout.\n */\nexport type CheckoutClient = ReturnType<typeof createCheckout>;\n"],
5
+ "mappings": "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",
6
6
  "names": ["options"]
7
7
  }
@@ -0,0 +1,131 @@
1
+ import { DeliveryAddress, Branch, CartFulfilment } from '@zappie/sdk';
2
+ import { SessionCustomer, SavedAddress, CustomerClient } from '@zappie/sdk/customer';
3
+
4
+ /**
5
+ * How the branch prices delivery. Only `"fixed"` lets an address without a place ID stand.
6
+ */
7
+ type DeliveryFeeType = "region" | "distance" | "fixed";
8
+ /**
9
+ * The browser storage the remembered address uses. Pass `null` to remember nothing.
10
+ */
11
+ type AddressStorage = Pick<Storage, "getItem" | "setItem" | "removeItem">;
12
+ /**
13
+ * The phone a checkout shows, and the `contact_phone` to send.
14
+ */
15
+ type PhoneDefault = {
16
+ /** The typed number, else the signed-in customer's masked number, else "". */
17
+ display: string;
18
+ /** The typed number. Absent when the account's number applies: checkout then uses the verified number. */
19
+ contactPhone?: string;
20
+ /** True when `display` is the signed-in customer's masked number. */
21
+ fromAccount: boolean;
22
+ };
23
+ /**
24
+ * What the defaults are worked out from.
25
+ */
26
+ type CheckoutDefaultsInput = {
27
+ branch: Branch;
28
+ /** The cart's fulfilment. Its type and its address are the shopper's choice and are kept. */
29
+ fulfilment: CartFulfilment | null;
30
+ customer?: SessionCustomer | null;
31
+ /** The signed-in customer's saved addresses, in the order `/me/addresses` returns them. */
32
+ savedAddresses?: readonly SavedAddress[];
33
+ /** The phone typed in this session. */
34
+ typedPhone?: string;
35
+ /** Defaults to `"distance"`, as the default storefront does. */
36
+ feeType?: DeliveryFeeType;
37
+ /** Defaults to `localStorage`. */
38
+ storage?: AddressStorage | null;
39
+ };
40
+ /**
41
+ * The contact details a checkout starts from.
42
+ */
43
+ type CheckoutDefaults = {
44
+ phone: PhoneDefault;
45
+ /** The cart's delivery address, else the last one used on this device, else the first saved one. */
46
+ address?: DeliveryAddress;
47
+ /** The cart's region, else the branch's only region. */
48
+ deliveryRegionId?: string;
49
+ /** The cart's scheduled time when it belongs to this branch and parses. */
50
+ scheduledFor?: string;
51
+ };
52
+ /**
53
+ * The phone to show: the one typed in this session, otherwise the signed-in customer's.
54
+ * The session holds only a masked number, so send no `contact_phone` for it.
55
+ *
56
+ * @param typed - The phone typed in this session.
57
+ * @param customer - The signed-in customer, or null.
58
+ */
59
+ declare function defaultPhone(typed: string | undefined, customer: SessionCustomer | null | undefined): PhoneDefault;
60
+ /**
61
+ * The last delivery address used on this device, or undefined. Under distance or region fees,
62
+ * an entry without a place ID counts as none and is removed.
63
+ *
64
+ * @param options - The fee type and storage.
65
+ */
66
+ declare function rememberedAddress(options?: {
67
+ feeType?: DeliveryFeeType;
68
+ storage?: AddressStorage | null;
69
+ }): DeliveryAddress | undefined;
70
+ /**
71
+ * Remember the delivery address the shopper used, on this device only. Stores the address
72
+ * line, place ID and coordinates, nothing else. Does nothing where storage is blocked.
73
+ *
74
+ * @param value - The address, or null to forget it.
75
+ * @param storage - Defaults to `localStorage`.
76
+ */
77
+ declare function rememberAddress(value: DeliveryAddress | null, storage?: AddressStorage | null): void;
78
+ /**
79
+ * The address to start from: the last one used on this device, otherwise the customer's
80
+ * first saved address. Under distance or region fees, an address without a place ID counts as none.
81
+ *
82
+ * @param options - Saved addresses, fee type and storage.
83
+ */
84
+ declare function defaultAddress(options?: {
85
+ savedAddresses?: readonly SavedAddress[];
86
+ feeType?: DeliveryFeeType;
87
+ storage?: AddressStorage | null;
88
+ }): DeliveryAddress | undefined;
89
+ /**
90
+ * The region to start from: the one already chosen, otherwise the branch's only region.
91
+ *
92
+ * @param branch - The cart's branch.
93
+ * @param chosen - The region already chosen.
94
+ */
95
+ declare function defaultRegion(branch: Branch, chosen?: string): string | undefined;
96
+ /**
97
+ * The cart's scheduled time if it belongs to this branch and parses, otherwise undefined.
98
+ * Drop a time this returns undefined for: it can never become valid here.
99
+ *
100
+ * @param fulfilment - The cart's fulfilment.
101
+ * @param branchId - The branch the shopper is ordering from.
102
+ */
103
+ declare function restoreSchedule(fulfilment: CartFulfilment | null, branchId: string): string | undefined;
104
+ /**
105
+ * The contact details a checkout starts from, by the default storefront's rule.
106
+ * It reads the cart and never changes it, so the fulfilment type stays the shopper's.
107
+ *
108
+ * @param input - The branch, cart fulfilment, customer and what was typed.
109
+ */
110
+ declare function checkoutDefaults(input: CheckoutDefaultsInput): CheckoutDefaults;
111
+ /**
112
+ * The cart fulfilment with the defaults applied: the only region filled in and a stale
113
+ * schedule dropped. Same type, same address. Returns the same object when nothing changes.
114
+ *
115
+ * @param fulfilment - The cart's fulfilment.
116
+ * @param branch - The cart's branch.
117
+ */
118
+ declare function prefillFulfilment<T extends CartFulfilment | null>(fulfilment: T, branch: Branch): T;
119
+ /**
120
+ * Work out the defaults now and again whenever the shopper signs in or out. On sign-in it
121
+ * fetches the saved addresses first. A failed fetch counts as none. A slower, older run never
122
+ * lands last.
123
+ *
124
+ * @param customer - The customer client.
125
+ * @param read - Returns the current inputs, read on each run.
126
+ * @param listener - Receives each result.
127
+ * @returns Stops listening.
128
+ */
129
+ declare function watchCheckoutDefaults(customer: Pick<CustomerClient, "state" | "customer" | "onChange" | "addresses">, read: () => Omit<CheckoutDefaultsInput, "customer" | "savedAddresses">, listener: (defaults: CheckoutDefaults) => void): () => void;
130
+
131
+ export { type AddressStorage, type CheckoutDefaults, type CheckoutDefaultsInput, type DeliveryFeeType, type PhoneDefault, checkoutDefaults, defaultAddress, defaultPhone, defaultRegion, prefillFulfilment, rememberAddress, rememberedAddress, restoreSchedule, watchCheckoutDefaults };
@@ -0,0 +1,192 @@
1
+ // src/checkout-defaults/index.ts
2
+ var KEY = "zappie_delivery_address";
3
+ function local(storage) {
4
+ if (storage !== void 0) {
5
+ return storage;
6
+ }
7
+ try {
8
+ return typeof window === "undefined" ? null : window.localStorage;
9
+ } catch {
10
+ return null;
11
+ }
12
+ }
13
+ function attempt(fn) {
14
+ try {
15
+ return fn();
16
+ } catch {
17
+ return void 0;
18
+ }
19
+ }
20
+ function text(value) {
21
+ return typeof value === "string" && value.trim() ? value : void 0;
22
+ }
23
+ function point(lat, lng) {
24
+ return typeof lat === "number" && typeof lng === "number" && Number.isFinite(lat) && Number.isFinite(lng) ? { coordinates: { lat, lng } } : {};
25
+ }
26
+ function address(line1, placeId, lat, lng) {
27
+ return line1 ? { line1, ...placeId ? { placeId } : {}, ...point(lat, lng) } : void 0;
28
+ }
29
+ function defaultPhone(typed, customer) {
30
+ const value = typed?.trim();
31
+ if (value) {
32
+ return { display: value, contactPhone: value, fromAccount: false };
33
+ }
34
+ const masked = customer?.phone_masked;
35
+ return masked ? { display: masked, fromAccount: true } : { display: "", fromAccount: false };
36
+ }
37
+ function rememberedAddress(options = {}) {
38
+ const storage = local(options.storage);
39
+ const raw = storage ? attempt(() => storage.getItem(KEY)) : void 0;
40
+ if (!raw) {
41
+ return void 0;
42
+ }
43
+ const stored = attempt(() => JSON.parse(raw));
44
+ const placeId = text(stored?.placeId);
45
+ if (!placeId && options.feeType !== "fixed") {
46
+ attempt(() => storage?.removeItem(KEY));
47
+ return void 0;
48
+ }
49
+ return address(
50
+ text(stored?.address),
51
+ placeId,
52
+ stored?.latitude,
53
+ stored?.longitude
54
+ );
55
+ }
56
+ function rememberAddress(value, storage) {
57
+ const target = local(storage);
58
+ if (!target) {
59
+ return;
60
+ }
61
+ if (!value?.line1.trim()) {
62
+ attempt(() => target.removeItem(KEY));
63
+ return;
64
+ }
65
+ attempt(
66
+ () => target.setItem(
67
+ KEY,
68
+ JSON.stringify({
69
+ address: value.line1,
70
+ placeId: value.placeId ?? "",
71
+ latitude: value.coordinates?.lat,
72
+ longitude: value.coordinates?.lng
73
+ })
74
+ )
75
+ );
76
+ }
77
+ function defaultAddress(options = {}) {
78
+ const remembered = rememberedAddress(options);
79
+ if (remembered) {
80
+ return remembered;
81
+ }
82
+ const first = options.savedAddresses?.[0];
83
+ const placeId = text(first?.place_id);
84
+ if (!first || !placeId && options.feeType !== "fixed") {
85
+ return void 0;
86
+ }
87
+ return address(text(first.line1), placeId, first.lat, first.lng);
88
+ }
89
+ function defaultRegion(branch, chosen) {
90
+ if (chosen) {
91
+ return chosen;
92
+ }
93
+ const regions = branch.deliveryRegions ?? [];
94
+ return regions.length === 1 ? regions[0]?.id : void 0;
95
+ }
96
+ function restoreSchedule(fulfilment, branchId) {
97
+ const at = fulfilment && fulfilment.type !== "dine_in" ? fulfilment.scheduledFor : void 0;
98
+ return at && fulfilment?.branchId === branchId && Number.isFinite(Date.parse(at)) ? at : void 0;
99
+ }
100
+ function checkoutDefaults(input) {
101
+ const delivery = input.fulfilment?.type === "delivery" ? input.fulfilment : void 0;
102
+ const address2 = delivery?.address ?? defaultAddress(input);
103
+ const deliveryRegionId = defaultRegion(
104
+ input.branch,
105
+ delivery?.deliveryRegionId
106
+ );
107
+ const scheduledFor = restoreSchedule(input.fulfilment, input.branch.id);
108
+ return {
109
+ phone: defaultPhone(input.typedPhone, input.customer),
110
+ ...address2 ? { address: address2 } : {},
111
+ ...deliveryRegionId ? { deliveryRegionId } : {},
112
+ ...scheduledFor ? { scheduledFor } : {}
113
+ };
114
+ }
115
+ function prefillFulfilment(fulfilment, branch) {
116
+ if (!fulfilment || fulfilment.type === "dine_in") {
117
+ return fulfilment;
118
+ }
119
+ const scheduledFor = restoreSchedule(fulfilment, branch.id);
120
+ const regionId = fulfilment.type === "delivery" ? defaultRegion(branch, fulfilment.deliveryRegionId) : void 0;
121
+ const dropSchedule = fulfilment.scheduledFor !== void 0 && !scheduledFor;
122
+ const addRegion = fulfilment.type === "delivery" && regionId !== fulfilment.deliveryRegionId;
123
+ if (!dropSchedule && !addRegion) {
124
+ return fulfilment;
125
+ }
126
+ const next = { ...fulfilment };
127
+ if (dropSchedule) {
128
+ delete next.scheduledFor;
129
+ }
130
+ if (addRegion && next.type === "delivery" && regionId) {
131
+ next.deliveryRegionId = regionId;
132
+ }
133
+ return next;
134
+ }
135
+ function watchCheckoutDefaults(customer, read, listener) {
136
+ let run = 0;
137
+ let stopped = false;
138
+ let signedIn;
139
+ const publish = (savedAddresses) => {
140
+ listener(
141
+ checkoutDefaults({
142
+ ...read(),
143
+ customer: customer.customer,
144
+ ...savedAddresses ? { savedAddresses } : {}
145
+ })
146
+ );
147
+ };
148
+ const refresh = () => {
149
+ const current = ++run;
150
+ const id = customer.state === "authenticated" ? customer.customer?.id : void 0;
151
+ signedIn = id;
152
+ if (!id) {
153
+ publish(void 0);
154
+ return;
155
+ }
156
+ void customer.addresses().then(
157
+ (saved) => {
158
+ if (!stopped && current === run) {
159
+ publish(saved);
160
+ }
161
+ },
162
+ () => {
163
+ if (!stopped && current === run) {
164
+ publish(void 0);
165
+ }
166
+ }
167
+ );
168
+ };
169
+ const off = customer.onChange((state, next) => {
170
+ const id = state === "authenticated" ? next?.id : void 0;
171
+ if (id !== signedIn) {
172
+ refresh();
173
+ }
174
+ });
175
+ refresh();
176
+ return () => {
177
+ stopped = true;
178
+ off();
179
+ };
180
+ }
181
+ export {
182
+ checkoutDefaults,
183
+ defaultAddress,
184
+ defaultPhone,
185
+ defaultRegion,
186
+ prefillFulfilment,
187
+ rememberAddress,
188
+ rememberedAddress,
189
+ restoreSchedule,
190
+ watchCheckoutDefaults
191
+ };
192
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/checkout-defaults/index.ts"],
4
+ "sourcesContent": ["// The declarations import the installed core so shared types keep one identity.\n// eslint-disable-next-line no-restricted-imports\nimport type { Branch, CartFulfilment, DeliveryAddress } from \"@zappie/sdk\";\n// eslint-disable-next-line no-restricted-imports\nimport type {\n CustomerClient,\n SavedAddress,\n SessionCustomer,\n} from \"@zappie/sdk/customer\";\n\n/**\n * How the branch prices delivery. Only `\"fixed\"` lets an address without a place ID stand.\n */\nexport type DeliveryFeeType = \"region\" | \"distance\" | \"fixed\";\n\n/**\n * The browser storage the remembered address uses. Pass `null` to remember nothing.\n */\nexport type AddressStorage = Pick<\n Storage,\n \"getItem\" | \"setItem\" | \"removeItem\"\n>;\n\n/**\n * The phone a checkout shows, and the `contact_phone` to send.\n */\nexport type PhoneDefault = {\n /** The typed number, else the signed-in customer's masked number, else \"\". */\n display: string;\n /** The typed number. Absent when the account's number applies: checkout then uses the verified number. */\n contactPhone?: string;\n /** True when `display` is the signed-in customer's masked number. */\n fromAccount: boolean;\n};\n\n/**\n * What the defaults are worked out from.\n */\nexport type CheckoutDefaultsInput = {\n branch: Branch;\n /** The cart's fulfilment. Its type and its address are the shopper's choice and are kept. */\n fulfilment: CartFulfilment | null;\n customer?: SessionCustomer | null;\n /** The signed-in customer's saved addresses, in the order `/me/addresses` returns them. */\n savedAddresses?: readonly SavedAddress[];\n /** The phone typed in this session. */\n typedPhone?: string;\n /** Defaults to `\"distance\"`, as the default storefront does. */\n feeType?: DeliveryFeeType;\n /** Defaults to `localStorage`. */\n storage?: AddressStorage | null;\n};\n\n/**\n * The contact details a checkout starts from.\n */\nexport type CheckoutDefaults = {\n phone: PhoneDefault;\n /** The cart's delivery address, else the last one used on this device, else the first saved one. */\n address?: DeliveryAddress;\n /** The cart's region, else the branch's only region. */\n deliveryRegionId?: string;\n /** The cart's scheduled time when it belongs to this branch and parses. */\n scheduledFor?: string;\n};\n\n// The default storefront's key and shape, so a host page and its templates share one entry.\nconst KEY = \"zappie_delivery_address\";\n\ntype Stored = {\n address?: unknown;\n placeId?: unknown;\n latitude?: unknown;\n longitude?: unknown;\n};\n\nfunction local(storage: AddressStorage | null | undefined) {\n if (storage !== undefined) {\n return storage;\n }\n try {\n return typeof window === \"undefined\" ? null : window.localStorage;\n } catch {\n return null;\n }\n}\n\nfunction attempt<T>(fn: () => T): T | undefined {\n try {\n return fn();\n } catch {\n return undefined;\n }\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value : undefined;\n}\n\nfunction point(lat: unknown, lng: unknown) {\n return typeof lat === \"number\" &&\n typeof lng === \"number\" &&\n Number.isFinite(lat) &&\n Number.isFinite(lng)\n ? { coordinates: { lat, lng } }\n : {};\n}\n\nfunction address(\n line1: string | undefined,\n placeId: string | undefined,\n lat: unknown,\n lng: unknown,\n): DeliveryAddress | undefined {\n return line1\n ? { line1, ...(placeId ? { placeId } : {}), ...point(lat, lng) }\n : undefined;\n}\n\n/**\n * The phone to show: the one typed in this session, otherwise the signed-in customer's.\n * The session holds only a masked number, so send no `contact_phone` for it.\n *\n * @param typed - The phone typed in this session.\n * @param customer - The signed-in customer, or null.\n */\nexport function defaultPhone(\n typed: string | undefined,\n customer: SessionCustomer | null | undefined,\n): PhoneDefault {\n const value = typed?.trim();\n if (value) {\n return { display: value, contactPhone: value, fromAccount: false };\n }\n const masked = customer?.phone_masked;\n return masked\n ? { display: masked, fromAccount: true }\n : { display: \"\", fromAccount: false };\n}\n\n/**\n * The last delivery address used on this device, or undefined. Under distance or region fees,\n * an entry without a place ID counts as none and is removed.\n *\n * @param options - The fee type and storage.\n */\nexport function rememberedAddress(\n options: { feeType?: DeliveryFeeType; storage?: AddressStorage | null } = {},\n): DeliveryAddress | undefined {\n const storage = local(options.storage);\n const raw = storage ? attempt(() => storage.getItem(KEY)) : undefined;\n if (!raw) {\n return undefined;\n }\n const stored = attempt(() => JSON.parse(raw) as Stored);\n const placeId = text(stored?.placeId);\n if (!placeId && options.feeType !== \"fixed\") {\n attempt(() => storage?.removeItem(KEY));\n return undefined;\n }\n return address(\n text(stored?.address),\n placeId,\n stored?.latitude,\n stored?.longitude,\n );\n}\n\n/**\n * Remember the delivery address the shopper used, on this device only. Stores the address\n * line, place ID and coordinates, nothing else. Does nothing where storage is blocked.\n *\n * @param value - The address, or null to forget it.\n * @param storage - Defaults to `localStorage`.\n */\nexport function rememberAddress(\n value: DeliveryAddress | null,\n storage?: AddressStorage | null,\n): void {\n const target = local(storage);\n if (!target) {\n return;\n }\n if (!value?.line1.trim()) {\n attempt(() => target.removeItem(KEY));\n return;\n }\n attempt(() =>\n target.setItem(\n KEY,\n JSON.stringify({\n address: value.line1,\n placeId: value.placeId ?? \"\",\n latitude: value.coordinates?.lat,\n longitude: value.coordinates?.lng,\n }),\n ),\n );\n}\n\n/**\n * The address to start from: the last one used on this device, otherwise the customer's\n * first saved address. Under distance or region fees, an address without a place ID counts as none.\n *\n * @param options - Saved addresses, fee type and storage.\n */\nexport function defaultAddress(\n options: {\n savedAddresses?: readonly SavedAddress[];\n feeType?: DeliveryFeeType;\n storage?: AddressStorage | null;\n } = {},\n): DeliveryAddress | undefined {\n const remembered = rememberedAddress(options);\n if (remembered) {\n return remembered;\n }\n const first = options.savedAddresses?.[0];\n const placeId = text(first?.place_id);\n if (!first || (!placeId && options.feeType !== \"fixed\")) {\n return undefined;\n }\n return address(text(first.line1), placeId, first.lat, first.lng);\n}\n\n/**\n * The region to start from: the one already chosen, otherwise the branch's only region.\n *\n * @param branch - The cart's branch.\n * @param chosen - The region already chosen.\n */\nexport function defaultRegion(\n branch: Branch,\n chosen?: string,\n): string | undefined {\n if (chosen) {\n return chosen;\n }\n const regions = branch.deliveryRegions ?? [];\n return regions.length === 1 ? regions[0]?.id : undefined;\n}\n\n/**\n * The cart's scheduled time if it belongs to this branch and parses, otherwise undefined.\n * Drop a time this returns undefined for: it can never become valid here.\n *\n * @param fulfilment - The cart's fulfilment.\n * @param branchId - The branch the shopper is ordering from.\n */\nexport function restoreSchedule(\n fulfilment: CartFulfilment | null,\n branchId: string,\n): string | undefined {\n const at =\n fulfilment && fulfilment.type !== \"dine_in\"\n ? fulfilment.scheduledFor\n : undefined;\n return at &&\n fulfilment?.branchId === branchId &&\n Number.isFinite(Date.parse(at))\n ? at\n : undefined;\n}\n\n/**\n * The contact details a checkout starts from, by the default storefront's rule.\n * It reads the cart and never changes it, so the fulfilment type stays the shopper's.\n *\n * @param input - The branch, cart fulfilment, customer and what was typed.\n */\nexport function checkoutDefaults(\n input: CheckoutDefaultsInput,\n): CheckoutDefaults {\n const delivery =\n input.fulfilment?.type === \"delivery\" ? input.fulfilment : undefined;\n const address = delivery?.address ?? defaultAddress(input);\n const deliveryRegionId = defaultRegion(\n input.branch,\n delivery?.deliveryRegionId,\n );\n const scheduledFor = restoreSchedule(input.fulfilment, input.branch.id);\n return {\n phone: defaultPhone(input.typedPhone, input.customer),\n ...(address ? { address } : {}),\n ...(deliveryRegionId ? { deliveryRegionId } : {}),\n ...(scheduledFor ? { scheduledFor } : {}),\n };\n}\n\n/**\n * The cart fulfilment with the defaults applied: the only region filled in and a stale\n * schedule dropped. Same type, same address. Returns the same object when nothing changes.\n *\n * @param fulfilment - The cart's fulfilment.\n * @param branch - The cart's branch.\n */\nexport function prefillFulfilment<T extends CartFulfilment | null>(\n fulfilment: T,\n branch: Branch,\n): T {\n if (!fulfilment || fulfilment.type === \"dine_in\") {\n return fulfilment;\n }\n const scheduledFor = restoreSchedule(fulfilment, branch.id);\n const regionId =\n fulfilment.type === \"delivery\"\n ? defaultRegion(branch, fulfilment.deliveryRegionId)\n : undefined;\n const dropSchedule = fulfilment.scheduledFor !== undefined && !scheduledFor;\n const addRegion =\n fulfilment.type === \"delivery\" && regionId !== fulfilment.deliveryRegionId;\n if (!dropSchedule && !addRegion) {\n return fulfilment;\n }\n const next: Exclude<CartFulfilment, { type: \"dine_in\" }> = { ...fulfilment };\n if (dropSchedule) {\n delete next.scheduledFor;\n }\n if (addRegion && next.type === \"delivery\" && regionId) {\n next.deliveryRegionId = regionId;\n }\n return next as T;\n}\n\n/**\n * Work out the defaults now and again whenever the shopper signs in or out. On sign-in it\n * fetches the saved addresses first. A failed fetch counts as none. A slower, older run never\n * lands last.\n *\n * @param customer - The customer client.\n * @param read - Returns the current inputs, read on each run.\n * @param listener - Receives each result.\n * @returns Stops listening.\n */\nexport function watchCheckoutDefaults(\n customer: Pick<\n CustomerClient,\n \"state\" | \"customer\" | \"onChange\" | \"addresses\"\n >,\n read: () => Omit<CheckoutDefaultsInput, \"customer\" | \"savedAddresses\">,\n listener: (defaults: CheckoutDefaults) => void,\n): () => void {\n let run = 0;\n let stopped = false;\n let signedIn: string | undefined;\n const publish = (savedAddresses: readonly SavedAddress[] | undefined) => {\n listener(\n checkoutDefaults({\n ...read(),\n customer: customer.customer,\n ...(savedAddresses ? { savedAddresses } : {}),\n }),\n );\n };\n const refresh = () => {\n const current = ++run;\n const id =\n customer.state === \"authenticated\" ? customer.customer?.id : undefined;\n signedIn = id;\n if (!id) {\n publish(undefined);\n return;\n }\n void customer.addresses().then(\n (saved) => {\n if (!stopped && current === run) {\n publish(saved);\n }\n },\n () => {\n if (!stopped && current === run) {\n publish(undefined);\n }\n },\n );\n };\n const off = customer.onChange((state, next) => {\n const id = state === \"authenticated\" ? next?.id : undefined;\n if (id !== signedIn) {\n refresh();\n }\n });\n refresh();\n return () => {\n stopped = true;\n off();\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["address"]
7
+ }
package/dist/debug.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/debug.ts
2
- var VERSION = true ? "0.1.8" : "0.0.0-development";
2
+ var VERSION = true ? "0.1.10" : "0.0.0-development";
3
3
  function installDebug(client) {
4
4
  if (typeof window === "undefined" || client.mode !== "test") {
5
5
  return;
package/dist/debug.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/debug.ts"],
4
4
  "sourcesContent": ["import type { StoredLine } from \"./cart/storage\";\nimport type { CartLineInput, Quote } from \"./cart/types\";\nimport type { Zappie } from \"./client\";\nimport type { Branch, Menu } from \"./models\";\n\n// Inline like VERSION in index.ts: tsup fills in the version from package.json.\ndeclare const __ZAPPIE_SDK_VERSION__: string | undefined;\nconst VERSION =\n typeof __ZAPPIE_SDK_VERSION__ === \"string\"\n ? __ZAPPIE_SDK_VERSION__\n : \"0.0.0-development\";\n\nexport type DebugEvent = {\n name: string;\n detail: unknown;\n at: string;\n};\n\nexport type ZappieDebug = {\n version: string;\n store: string;\n /** Read this cart's branch and full menu through its client. */\n catalogue(): Promise<{ menu: Menu; branch: Branch }>;\n cart: (() => ReturnType<Zappie[\"cart\"][\"getSnapshot\"]>) & {\n /**\n * Save test cart lines for the next reload. Reload to reconcile them with today's menu.\n * Your unitPrice is only a local estimate, like a price edited in browser storage.\n * The server re-prices every line and never trusts this estimate.\n */\n inject(input: {\n lines: (CartLineInput & Pick<StoredLine, \"unitPrice\">)[];\n }): Promise<void>;\n };\n quote: () => Quote | undefined;\n events: DebugEvent[];\n};\n\ndeclare global {\n interface Window {\n __zappie?: ZappieDebug;\n }\n // On globalThis so the lazy checkout and customer entries can reach it\n // with the shortest possible call; in a browser it is window's.\n var __zappieEmit: ((name: string, detail: unknown) => void) | undefined;\n}\n\nexport function installDebug(client: Zappie) {\n if (typeof window === \"undefined\" || client.mode !== \"test\") {\n return;\n }\n const events: DebugEvent[] = [];\n let quote: Quote | undefined;\n const emit = (name: string, detail: unknown) => {\n const event = { name, detail, at: new Date().toISOString() };\n events.push(event);\n window.dispatchEvent(new CustomEvent(\"zappie:event\", { detail: event }));\n };\n globalThis.__zappieEmit = emit;\n window.__zappie = {\n version: VERSION,\n store: client.config.store,\n async catalogue() {\n await client.cart.hydrate();\n const branch = (await client.store.get()).branches.find(\n (value) => value.id === client.cart.getSnapshot().branchId,\n )!;\n return { branch, menu: await client.menu.get({ branch: branch.id }) };\n },\n cart: Object.assign(() => client.cart.getSnapshot(), {\n async inject(input: {\n lines: (CartLineInput & Pick<StoredLine, \"unitPrice\">)[];\n }) {\n await client.cart.hydrate();\n if (\n input.lines.length > 100 ||\n input.lines.some(\n (line) =>\n !Number.isInteger(line.quantity ?? 1) ||\n (line.quantity ?? 1) < 1 ||\n (line.quantity ?? 1) > 99,\n )\n ) {\n throw new Error(\n \"Use up to 100 cart lines with whole quantities from 1 to 99.\",\n );\n }\n const snapshot = client.cart.getSnapshot();\n const value = {\n v: 1,\n menuVersion: client._internal.cartCatalogue()!.version,\n updatedAt: Date.now(),\n fulfilment: snapshot.fulfilment,\n promoCode: snapshot.promoCode,\n lines: input.lines.map((line, index) => ({\n ...line,\n id: `debug_${index}`,\n name: line.itemId,\n quantity: line.quantity ?? 1,\n choices: (line.choices ?? []).map((choice) => ({\n ...choice,\n quantity: choice.quantity ?? 1,\n })),\n inputs: line.inputs ?? {},\n addedAt: new Date().toISOString(),\n })),\n };\n const scope = client.config.cart?.storageScope;\n (client.config.storage ?? window.localStorage).setItem(\n `zappie:cart:v1:${client.config.store}:${snapshot.branchId}${scope ? `:${scope}` : \"\"}`,\n JSON.stringify(value),\n );\n },\n }),\n quote: () => quote,\n events,\n };\n client.on(\"add_to_cart\", (detail) => emit(\"add_to_cart\", detail));\n client.cart.subscribe(() => {\n const state = client.cart.state;\n const next = \"quote\" in state ? state.quote : undefined;\n if (next && next !== quote) {\n quote = next;\n emit(\"quote\", next);\n }\n });\n}\n"],
5
- "mappings": ";AAOA,IAAM,UACJ,OACI,UACA;AAoCC,SAAS,aAAa,QAAgB;AAC3C,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,QAAQ;AAC3D;AAAA,EACF;AACA,QAAM,SAAuB,CAAC;AAC9B,MAAI;AACJ,QAAM,OAAO,CAAC,MAAc,WAAoB;AAC9C,UAAM,QAAQ,EAAE,MAAM,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC3D,WAAO,KAAK,KAAK;AACjB,WAAO,cAAc,IAAI,YAAY,gBAAgB,EAAE,QAAQ,MAAM,CAAC,CAAC;AAAA,EACzE;AACA,aAAW,eAAe;AAC1B,SAAO,WAAW;AAAA,IAChB,SAAS;AAAA,IACT,OAAO,OAAO,OAAO;AAAA,IACrB,MAAM,YAAY;AAChB,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,UAAU,MAAM,OAAO,MAAM,IAAI,GAAG,SAAS;AAAA,QACjD,CAAC,UAAU,MAAM,OAAO,OAAO,KAAK,YAAY,EAAE;AAAA,MACpD;AACA,aAAO,EAAE,QAAQ,MAAM,MAAM,OAAO,KAAK,IAAI,EAAE,QAAQ,OAAO,GAAG,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,YAAY,GAAG;AAAA,MACnD,MAAM,OAAO,OAEV;AACD,cAAM,OAAO,KAAK,QAAQ;AAC1B,YACE,MAAM,MAAM,SAAS,OACrB,MAAM,MAAM;AAAA,UACV,CAAC,SACC,CAAC,OAAO,UAAU,KAAK,YAAY,CAAC,MACnC,KAAK,YAAY,KAAK,MACtB,KAAK,YAAY,KAAK;AAAA,QAC3B,GACA;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,cAAM,WAAW,OAAO,KAAK,YAAY;AACzC,cAAM,QAAQ;AAAA,UACZ,GAAG;AAAA,UACH,aAAa,OAAO,UAAU,cAAc,EAAG;AAAA,UAC/C,WAAW,KAAK,IAAI;AAAA,UACpB,YAAY,SAAS;AAAA,UACrB,WAAW,SAAS;AAAA,UACpB,OAAO,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,YACvC,GAAG;AAAA,YACH,IAAI,SAAS,KAAK;AAAA,YAClB,MAAM,KAAK;AAAA,YACX,UAAU,KAAK,YAAY;AAAA,YAC3B,UAAU,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,cAC7C,GAAG;AAAA,cACH,UAAU,OAAO,YAAY;AAAA,YAC/B,EAAE;AAAA,YACF,QAAQ,KAAK,UAAU,CAAC;AAAA,YACxB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,EAAE;AAAA,QACJ;AACA,cAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAC,OAAO,OAAO,WAAW,OAAO,cAAc;AAAA,UAC7C,kBAAkB,OAAO,OAAO,KAAK,IAAI,SAAS,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,UACrF,KAAK,UAAU,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,OAAO,MAAM;AAAA,IACb;AAAA,EACF;AACA,SAAO,GAAG,eAAe,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAChE,SAAO,KAAK,UAAU,MAAM;AAC1B,UAAM,QAAQ,OAAO,KAAK;AAC1B,UAAM,OAAO,WAAW,QAAQ,MAAM,QAAQ;AAC9C,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ;AACR,WAAK,SAAS,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AACH;",
5
+ "mappings": ";AAOA,IAAM,UACJ,OACI,WACA;AAoCC,SAAS,aAAa,QAAgB;AAC3C,MAAI,OAAO,WAAW,eAAe,OAAO,SAAS,QAAQ;AAC3D;AAAA,EACF;AACA,QAAM,SAAuB,CAAC;AAC9B,MAAI;AACJ,QAAM,OAAO,CAAC,MAAc,WAAoB;AAC9C,UAAM,QAAQ,EAAE,MAAM,QAAQ,KAAI,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC3D,WAAO,KAAK,KAAK;AACjB,WAAO,cAAc,IAAI,YAAY,gBAAgB,EAAE,QAAQ,MAAM,CAAC,CAAC;AAAA,EACzE;AACA,aAAW,eAAe;AAC1B,SAAO,WAAW;AAAA,IAChB,SAAS;AAAA,IACT,OAAO,OAAO,OAAO;AAAA,IACrB,MAAM,YAAY;AAChB,YAAM,OAAO,KAAK,QAAQ;AAC1B,YAAM,UAAU,MAAM,OAAO,MAAM,IAAI,GAAG,SAAS;AAAA,QACjD,CAAC,UAAU,MAAM,OAAO,OAAO,KAAK,YAAY,EAAE;AAAA,MACpD;AACA,aAAO,EAAE,QAAQ,MAAM,MAAM,OAAO,KAAK,IAAI,EAAE,QAAQ,OAAO,GAAG,CAAC,EAAE;AAAA,IACtE;AAAA,IACA,MAAM,OAAO,OAAO,MAAM,OAAO,KAAK,YAAY,GAAG;AAAA,MACnD,MAAM,OAAO,OAEV;AACD,cAAM,OAAO,KAAK,QAAQ;AAC1B,YACE,MAAM,MAAM,SAAS,OACrB,MAAM,MAAM;AAAA,UACV,CAAC,SACC,CAAC,OAAO,UAAU,KAAK,YAAY,CAAC,MACnC,KAAK,YAAY,KAAK,MACtB,KAAK,YAAY,KAAK;AAAA,QAC3B,GACA;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,cAAM,WAAW,OAAO,KAAK,YAAY;AACzC,cAAM,QAAQ;AAAA,UACZ,GAAG;AAAA,UACH,aAAa,OAAO,UAAU,cAAc,EAAG;AAAA,UAC/C,WAAW,KAAK,IAAI;AAAA,UACpB,YAAY,SAAS;AAAA,UACrB,WAAW,SAAS;AAAA,UACpB,OAAO,MAAM,MAAM,IAAI,CAAC,MAAM,WAAW;AAAA,YACvC,GAAG;AAAA,YACH,IAAI,SAAS,KAAK;AAAA,YAClB,MAAM,KAAK;AAAA,YACX,UAAU,KAAK,YAAY;AAAA,YAC3B,UAAU,KAAK,WAAW,CAAC,GAAG,IAAI,CAAC,YAAY;AAAA,cAC7C,GAAG;AAAA,cACH,UAAU,OAAO,YAAY;AAAA,YAC/B,EAAE;AAAA,YACF,QAAQ,KAAK,UAAU,CAAC;AAAA,YACxB,UAAS,oBAAI,KAAK,GAAE,YAAY;AAAA,UAClC,EAAE;AAAA,QACJ;AACA,cAAM,QAAQ,OAAO,OAAO,MAAM;AAClC,SAAC,OAAO,OAAO,WAAW,OAAO,cAAc;AAAA,UAC7C,kBAAkB,OAAO,OAAO,KAAK,IAAI,SAAS,QAAQ,GAAG,QAAQ,IAAI,KAAK,KAAK,EAAE;AAAA,UACrF,KAAK,UAAU,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,OAAO,MAAM;AAAA,IACb;AAAA,EACF;AACA,SAAO,GAAG,eAAe,CAAC,WAAW,KAAK,eAAe,MAAM,CAAC;AAChE,SAAO,KAAK,UAAU,MAAM;AAC1B,UAAM,QAAQ,OAAO,KAAK;AAC1B,UAAM,OAAO,WAAW,QAAQ,MAAM,QAAQ;AAC9C,QAAI,QAAQ,SAAS,OAAO;AAC1B,cAAQ;AACR,WAAK,SAAS,IAAI;AAAA,IACpB;AAAA,EACF,CAAC;AACH;",
6
6
  "names": []
7
7
  }
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { previewItem } from "./cart/preview.js";
10
10
  import { estimateUnitPrice } from "./cart/price.js";
11
11
  import { resolveSourceMenu } from "./cart/source-menu.js";
12
12
  import { validateLine } from "./cart/validate.js";
13
- var VERSION = true ? "0.1.8" : "0.0.0-development";
13
+ var VERSION = true ? "0.1.10" : "0.0.0-development";
14
14
  export {
15
15
  API_VERSION,
16
16
  RestTransport,
package/dist/index.js.map CHANGED
@@ -2,6 +2,6 @@
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
4
  "sourcesContent": ["// tsup replaces __ZAPPIE_SDK_VERSION__ with package.json's version. Source run\n// without a build, as in a plain Node script, gets the development label.\n// Kept inline, not in a shared module, so the core chunks don't split differently.\ndeclare const __ZAPPIE_SDK_VERSION__: string | undefined;\n\n/** The installed @zappie/sdk version. */\nexport const VERSION: string =\n typeof __ZAPPIE_SDK_VERSION__ === \"string\"\n ? __ZAPPIE_SDK_VERSION__\n : \"0.0.0-development\";\nexport { createZappie } from \"./client\";\nexport type {\n MenuFulfilment,\n MenuReadOptions,\n MenuResource,\n ReadOptions,\n StoreResource,\n Zappie,\n ZappieEvents,\n} from \"./client\";\nexport { API_VERSION } from \"./config\";\nexport type {\n StorageLike,\n ZappieConfig as ZappieClientOptions,\n ZappieConfig,\n} from \"./config\";\nexport { ZappieError, isZappieError } from \"./errors\";\nexport type { ErrorDetails, ZappieErrorCode } from \"./errors\";\nexport * from \"./models\";\nexport { format, money } from \"./money\";\nexport type { Currency, Formatters, Money, MoneyFormatOptions } from \"./money\";\nexport { RestTransport } from \"./transport/rest\";\nexport type { RestTransportOptions } from \"./transport/rest\";\nexport type {\n MenuResponse,\n MenuVersion,\n RequestCtx,\n SearchPage,\n SearchRequest,\n Transport,\n} from \"./transport/types\";\n\nexport { ZappieCartError } from \"./cart/errors\";\nexport { previewItem } from \"./cart/preview\";\nexport { estimateUnitPrice } from \"./cart/price\";\nexport { resolveSourceMenu } from \"./cart/source-menu\";\nexport type * from \"./cart/types\";\nexport { validateLine } from \"./cart/validate\";\n"],
5
- "mappings": ";AAUA,SAAS,oBAAoB;AAU7B,SAAS,mBAAmB;AAM5B,SAAS,aAAa,qBAAqB;AAE3C,cAAc;AACd,SAAS,QAAQ,aAAa;AAE9B,SAAS,qBAAqB;AAW9B,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAzCtB,IAAM,UACX,OACI,UACA;",
5
+ "mappings": ";AAUA,SAAS,oBAAoB;AAU7B,SAAS,mBAAmB;AAM5B,SAAS,aAAa,qBAAqB;AAE3C,cAAc;AACd,SAAS,QAAQ,aAAa;AAE9B,SAAS,qBAAqB;AAW9B,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAElC,SAAS,oBAAoB;AAzCtB,IAAM,UACX,OACI,WACA;",
6
6
  "names": []
7
7
  }
@@ -1,99 +1,4 @@
1
- declare const MoneyBrand: unique symbol;
2
- /** A normalized ISO 4217 currency code. */
3
- type Currency = string;
4
- /** A frozen amount in safe integer minor units. Create it with money(). */
5
- type Money = {
6
- readonly amountMinor: number;
7
- readonly currency: Currency;
8
- readonly [MoneyBrand]: true;
9
- };
10
-
11
- /** An exact variant with its selected attributes, price and availability. */
12
- type Variant = {
13
- /** A low-stock hint for shoppers. This does not give a stock balance. */
14
- lowStock?: boolean;
15
- id: string;
16
- attributeChoices: Record<string, string>;
17
- price: Money;
18
- isAvailable: boolean;
19
- };
20
- /** Item details and choices, with at least one exact variant. */
21
- type Item = {
22
- /** Fulfilment modes returned for this item. */
23
- fulfilment?: string[];
24
- /** Exact source placements returned by targeted item reads. */
25
- placements?: {
26
- menuId: string;
27
- categoryId: string;
28
- }[];
29
- /** Stock text for display. Use isAvailable and the server quote to check eligibility. */
30
- stockLabel?: string;
31
- promo?: {
32
- name: string;
33
- };
34
- id: string;
35
- name: string;
36
- description?: string;
37
- images: string[];
38
- price: Money;
39
- compareAtPrice?: Money;
40
- isAvailable: boolean;
41
- quantity: {
42
- min: number;
43
- max?: number;
44
- step: number;
45
- };
46
- variants: [Variant, ...Variant[]];
47
- optionGroups: OptionGroup[];
48
- tags: string[];
49
- };
50
- /** Conditions that change option rules when selected choices match. */
51
- type OptionCondition = {
52
- groupId: string;
53
- choiceIds: string[];
54
- match: "any" | "all";
55
- count?: {
56
- op: "eq" | "gte" | "lte";
57
- value: number;
58
- };
59
- setRequired?: boolean | null;
60
- setMin?: number | null;
61
- setMax?: number | null;
62
- };
63
- /** Addon or exact-variant attribute selection rules. */
64
- type OptionGroup = {
65
- id: string;
66
- name: string;
67
- helpText?: string;
68
- kind: "select" | "text" | "number" | "date" | "time" | "toggle";
69
- required: boolean;
70
- isAvailable?: boolean;
71
- isInformational?: boolean;
72
- selection: {
73
- min: number;
74
- max?: number;
75
- };
76
- isAttribute: boolean;
77
- choices: Choice[];
78
- input?: {
79
- maxLength?: number;
80
- placeholder?: string;
81
- };
82
- conditions?: OptionCondition[];
83
- };
84
- /** A selectable option. Attribute prices are zero to avoid charging twice. */
85
- type Choice = {
86
- /** An attribute price adjustment for display. Cart calculations exclude it. */
87
- priceHint?: Money;
88
- id: string;
89
- name: string;
90
- price: Money;
91
- isAvailable: boolean;
92
- quantity?: {
93
- min: number;
94
- max?: number;
95
- };
96
- };
1
+ import { Item, Money } from '@zappie/sdk';
97
2
 
98
3
  /**
99
4
  * The lowest price a shopper can pay for one of this item, by the same rule
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/pricing/index.ts"],
4
- "sourcesContent": ["import { sumMoney } from \"../internal/money-math\";\nimport type { Item } from \"../models\";\nimport type { Money } from \"../money\";\n\nconst cheapest = (prices: Money[]): Money | undefined =>\n prices\n .filter((price) => price.amountMinor >= 0)\n .sort((a, b) => a.amountMinor - b.amountMinor)[0];\n\n/**\n * The lowest price a shopper can pay for one of this item, by the same rule\n * Zappie uses for a product's starting price: the cheapest available variant\n * plus the cheapest choice of each required add-on group. When no variant is\n * available, the cheapest variant is used.\n *\n * @param item - The catalogue item.\n * @returns A display price. Use a server quote for checkout totals.\n */\nexport function startingPrice(item: Item): Money {\n const available = item.variants.filter((variant) => variant.isAvailable);\n const base =\n cheapest(\n (available.length ? available : item.variants).map(\n (variant) => variant.price,\n ),\n ) ?? item.price;\n return sumMoney(\n item.optionGroups.flatMap((group) =>\n !group.isAttribute && group.required && group.kind === \"select\"\n ? (cheapest(group.choices.map((choice) => choice.price)) ?? [])\n : [],\n ),\n base,\n );\n}\n\n/**\n * Whether to show the starting price as \"From\": true when available variants\n * have different prices, or a required add-on raises the starting price.\n *\n * @param item - The catalogue item.\n */\nexport function showsFromPrice(item: Item): boolean {\n const prices = [\n ...new Set(\n item.variants\n .filter((variant) => variant.isAvailable)\n .map((variant) => variant.price.amountMinor),\n ),\n ];\n return (\n prices.length > 1 ||\n (prices.length === 1 && startingPrice(item).amountMinor > prices[0]!)\n );\n}\n"],
5
- "mappings": ";AAAA,SAAS,gBAAgB;AAIzB,IAAM,WAAW,CAAC,WAChB,OACG,OAAO,CAAC,UAAU,MAAM,eAAe,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;AAW7C,SAAS,cAAc,MAAmB;AAC/C,QAAM,YAAY,KAAK,SAAS,OAAO,CAAC,YAAY,QAAQ,WAAW;AACvE,QAAM,OACJ;AAAA,KACG,UAAU,SAAS,YAAY,KAAK,UAAU;AAAA,MAC7C,CAAC,YAAY,QAAQ;AAAA,IACvB;AAAA,EACF,KAAK,KAAK;AACZ,SAAO;AAAA,IACL,KAAK,aAAa;AAAA,MAAQ,CAAC,UACzB,CAAC,MAAM,eAAe,MAAM,YAAY,MAAM,SAAS,WAClD,SAAS,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,KAAK,CAAC,IAC3D,CAAC;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,eAAe,MAAqB;AAClD,QAAM,SAAS;AAAA,IACb,GAAG,IAAI;AAAA,MACL,KAAK,SACF,OAAO,CAAC,YAAY,QAAQ,WAAW,EACvC,IAAI,CAAC,YAAY,QAAQ,MAAM,WAAW;AAAA,IAC/C;AAAA,EACF;AACA,SACE,OAAO,SAAS,KACf,OAAO,WAAW,KAAK,cAAc,IAAI,EAAE,cAAc,OAAO,CAAC;AAEtE;",
4
+ "sourcesContent": ["import { sumMoney } from \"../internal/money-math\";\n// The declarations import the installed core so Money keeps the core's brand.\n// eslint-disable-next-line no-restricted-imports\nimport type { Item, Money } from \"@zappie/sdk\";\n\nconst cheapest = (prices: Money[]): Money | undefined =>\n prices\n .filter((price) => price.amountMinor >= 0)\n .sort((a, b) => a.amountMinor - b.amountMinor)[0];\n\n/**\n * The lowest price a shopper can pay for one of this item, by the same rule\n * Zappie uses for a product's starting price: the cheapest available variant\n * plus the cheapest choice of each required add-on group. When no variant is\n * available, the cheapest variant is used.\n *\n * @param item - The catalogue item.\n * @returns A display price. Use a server quote for checkout totals.\n */\nexport function startingPrice(item: Item): Money {\n const available = item.variants.filter((variant) => variant.isAvailable);\n const base =\n cheapest(\n (available.length ? available : item.variants).map(\n (variant) => variant.price,\n ),\n ) ?? item.price;\n return sumMoney(\n item.optionGroups.flatMap((group) =>\n !group.isAttribute && group.required && group.kind === \"select\"\n ? (cheapest(group.choices.map((choice) => choice.price)) ?? [])\n : [],\n ),\n base,\n );\n}\n\n/**\n * Whether to show the starting price as \"From\": true when available variants\n * have different prices, or a required add-on raises the starting price.\n *\n * @param item - The catalogue item.\n */\nexport function showsFromPrice(item: Item): boolean {\n const prices = [\n ...new Set(\n item.variants\n .filter((variant) => variant.isAvailable)\n .map((variant) => variant.price.amountMinor),\n ),\n ];\n return (\n prices.length > 1 ||\n (prices.length === 1 && startingPrice(item).amountMinor > prices[0]!)\n );\n}\n"],
5
+ "mappings": ";AAAA,SAAS,gBAAgB;AAKzB,IAAM,WAAW,CAAC,WAChB,OACG,OAAO,CAAC,UAAU,MAAM,eAAe,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW,EAAE,CAAC;AAW7C,SAAS,cAAc,MAAmB;AAC/C,QAAM,YAAY,KAAK,SAAS,OAAO,CAAC,YAAY,QAAQ,WAAW;AACvE,QAAM,OACJ;AAAA,KACG,UAAU,SAAS,YAAY,KAAK,UAAU;AAAA,MAC7C,CAAC,YAAY,QAAQ;AAAA,IACvB;AAAA,EACF,KAAK,KAAK;AACZ,SAAO;AAAA,IACL,KAAK,aAAa;AAAA,MAAQ,CAAC,UACzB,CAAC,MAAM,eAAe,MAAM,YAAY,MAAM,SAAS,WAClD,SAAS,MAAM,QAAQ,IAAI,CAAC,WAAW,OAAO,KAAK,CAAC,KAAK,CAAC,IAC3D,CAAC;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,eAAe,MAAqB;AAClD,QAAM,SAAS;AAAA,IACb,GAAG,IAAI;AAAA,MACL,KAAK,SACF,OAAO,CAAC,YAAY,QAAQ,WAAW,EACvC,IAAI,CAAC,YAAY,QAAQ,MAAM,WAAW;AAAA,IAC/C;AAAA,EACF;AACA,SACE,OAAO,SAAS,KACf,OAAO,WAAW,KAAK,cAAc,IAAI,EAAE,cAAc,OAAO,CAAC;AAEtE;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zappie/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Typed, framework-agnostic client for Zappie menus, carts, checkout, auth and events.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -45,6 +45,10 @@
45
45
  "types": "./dist/checkout/index.d.ts",
46
46
  "import": "./dist/checkout/index.js"
47
47
  },
48
+ "./checkout-defaults": {
49
+ "types": "./dist/checkout-defaults/index.d.ts",
50
+ "import": "./dist/checkout-defaults/index.js"
51
+ },
48
52
  "./customer": {
49
53
  "types": "./dist/customer/index.d.ts",
50
54
  "import": "./dist/customer/index.js"
@@ -113,7 +117,7 @@
113
117
  "url": "https://github.com/zappie/storefront/issues"
114
118
  },
115
119
  "scripts": {
116
- "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs",
120
+ "build": "tsup && tsup --config tsup.checkout.config.ts && tsup --config tsup.checkout-defaults.config.ts && tsup --config tsup.customer.config.ts && tsup --config tsup.places.config.ts && tsup --config tsup.url.config.ts && tsup --config tsup.visits.config.ts && tsup --config tsup.pricing.config.ts && tsup --config tsup.site-config.config.ts && tsup --config tsup.template.config.ts && node ../../scripts/release-packages/build-modules.mjs",
117
121
  "test": "vitest run --root ../.. packages/sdk/test",
118
122
  "size": "node ../../scripts/release-packages/size-sdk.mjs && size-limit"
119
123
  }