@zappie/verify 0.0.0-stage → 0.1.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.
@@ -0,0 +1,253 @@
1
+ import {
2
+ ZappieError,
3
+ mapMoney,
4
+ money,
5
+ payloadKey,
6
+ record,
7
+ sumMoney
8
+ } from "./chunk-WUWUDCYO.js";
9
+
10
+ // ../sdk/src/cart/quote-response.ts
11
+ function fail() {
12
+ throw new ZappieError("server_error", "Invalid quote response");
13
+ }
14
+ function text(value) {
15
+ if (typeof value !== "string") {
16
+ return fail();
17
+ }
18
+ return value;
19
+ }
20
+ function list(value) {
21
+ if (!Array.isArray(value)) {
22
+ return fail();
23
+ }
24
+ return value;
25
+ }
26
+ function fix(value) {
27
+ const v = record(value);
28
+ switch (v.action) {
29
+ case "remove_line":
30
+ case "accept_price":
31
+ case "remove_promo":
32
+ case "change_address":
33
+ return { action: v.action };
34
+ case "choose_option":
35
+ case "choose_other_option":
36
+ return { action: v.action, groupId: text(v.group_id) };
37
+ case "set_quantity": {
38
+ if (!Number.isInteger(v.quantity) || Number(v.quantity) < 1 || Number(v.quantity) > 99) {
39
+ return fail();
40
+ }
41
+ return { action: v.action, quantity: v.quantity };
42
+ }
43
+ case "schedule":
44
+ return {
45
+ action: v.action,
46
+ ...v.next_open_at ? { nextOpenAt: text(v.next_open_at) } : {},
47
+ ...v.slots ? { slots: list(v.slots).map(text) } : {}
48
+ };
49
+ case "switch_fulfilment": {
50
+ const types = list(v.types).map(text).filter((t) => ["pickup", "delivery", "dine_in"].includes(t));
51
+ return types.length ? { action: v.action, types } : void 0;
52
+ }
53
+ default:
54
+ return void 0;
55
+ }
56
+ }
57
+ var codes = [
58
+ "item_unavailable",
59
+ "item_not_found",
60
+ "option_unavailable",
61
+ "option_required",
62
+ "option_limit",
63
+ "quantity_limit",
64
+ "price_changed",
65
+ "branch_closed",
66
+ "fulfilment_unavailable",
67
+ "outside_delivery_zone",
68
+ "delivery_region_required",
69
+ "schedule_invalid",
70
+ "promo_invalid",
71
+ "promo_min_spend",
72
+ "min_order",
73
+ "menu_version_stale"
74
+ ];
75
+ function mapQuote(value, branchId) {
76
+ const v = record(value);
77
+ if (v.branch_id !== branchId || !Number.isFinite(Date.parse(text(v.expires_at)))) {
78
+ return fail();
79
+ }
80
+ const subtotal = mapMoney(v.subtotal);
81
+ const checkout = record(v.checkout);
82
+ if (typeof v.can_checkout !== "boolean" || typeof checkout.ready !== "boolean" || checkout.reason !== void 0 && checkout.reason !== "issues" && checkout.reason !== "customer_required") {
83
+ return fail();
84
+ }
85
+ const amount = (raw) => {
86
+ const result = mapMoney(raw);
87
+ if (result.currency !== subtotal.currency) {
88
+ return fail();
89
+ }
90
+ return result;
91
+ };
92
+ const charge = (raw) => {
93
+ const c = record(raw);
94
+ return {
95
+ code: text(c.code),
96
+ label: text(c.label),
97
+ amount: amount(c.amount)
98
+ };
99
+ };
100
+ const charges = list(v.charges).map(charge);
101
+ const discounts = list(v.discounts).map(charge);
102
+ const total = amount(v.total);
103
+ if (sumMoney(
104
+ [...charges, ...discounts].map((c) => c.amount),
105
+ subtotal
106
+ ).amountMinor !== total.amountMinor) {
107
+ return fail();
108
+ }
109
+ const deliveryCharges = charges.filter((c) => c.code === "delivery");
110
+ const lines = list(v.lines).map((raw) => {
111
+ const l = record(raw);
112
+ if ((l.unit_price == null || l.line_total == null) && v.lines_unpriced !== true) {
113
+ return fail();
114
+ }
115
+ return {
116
+ lineId: text(l.line_id),
117
+ unitPrice: l.unit_price == null ? null : amount(l.unit_price),
118
+ total: l.line_total == null ? null : amount(l.line_total)
119
+ };
120
+ });
121
+ if (v.lines_unpriced !== true && sumMoney(
122
+ lines.map((line) => line.total),
123
+ money(0, subtotal.currency)
124
+ ).amountMinor !== subtotal.amountMinor) {
125
+ return fail();
126
+ }
127
+ return {
128
+ lines,
129
+ ...v.lines_unpriced === true ? { linesUnpriced: true } : {},
130
+ subtotal,
131
+ total,
132
+ fees: charges.filter((c) => c.code !== "delivery"),
133
+ discounts,
134
+ ...deliveryCharges.length ? {
135
+ delivery: sumMoney(
136
+ deliveryCharges.map((c) => c.amount),
137
+ money(0, subtotal.currency)
138
+ )
139
+ } : {},
140
+ menuVersion: text(v.menu_version),
141
+ expiresAt: text(v.expires_at),
142
+ issues: list(v.issues).flatMap((raw) => {
143
+ const i = record(raw);
144
+ const code = text(i.code);
145
+ const severity = text(i.severity);
146
+ const known = codes.includes(code) && ["blocking", "warning", "info"].includes(severity);
147
+ const fixes = list(i.fixes).map(fix).filter((f) => f !== void 0);
148
+ const lineId = i.line_id ? text(i.line_id) : void 0;
149
+ if (!known && (severity === "warning" || severity === "info")) {
150
+ console.warn(
151
+ `Zappie quote issue "${code}" is newer than this SDK and was skipped`
152
+ );
153
+ return [];
154
+ }
155
+ return [
156
+ {
157
+ code: known ? code : "item_unavailable",
158
+ severity: known ? severity : "blocking",
159
+ message: text(i.message),
160
+ source: "quote",
161
+ ...lineId ? { lineId } : {},
162
+ ...i.group_id ? { groupId: text(i.group_id) } : {},
163
+ ...i.data || !known ? {
164
+ data: {
165
+ ...i.data ? record(i.data) : {},
166
+ ...known ? {} : { code }
167
+ }
168
+ } : {},
169
+ fixes: !known && !fixes.length && lineId ? [{ action: "remove_line" }] : fixes
170
+ }
171
+ ];
172
+ }),
173
+ canCheckout: v.can_checkout,
174
+ checkout: {
175
+ ready: checkout.ready,
176
+ ...checkout.reason !== void 0 ? { reason: checkout.reason } : {}
177
+ }
178
+ };
179
+ }
180
+
181
+ // ../sdk/src/cart/quote-client.ts
182
+ function createQuoteClient(transport, context) {
183
+ let controller;
184
+ let sequence = 0;
185
+ let cached;
186
+ let flight;
187
+ const cancel = () => {
188
+ sequence++;
189
+ controller?.abort();
190
+ flight = void 0;
191
+ };
192
+ return {
193
+ cancel,
194
+ async get(body, force, apply) {
195
+ const key = JSON.stringify(body);
196
+ if (!force && cached?.key === key && Date.parse(cached.quote.expiresAt) > Date.now()) {
197
+ apply(cached.quote);
198
+ return cached.quote;
199
+ }
200
+ if (!force && flight?.key === key) {
201
+ return flight.promise;
202
+ }
203
+ cancel();
204
+ const seq = sequence;
205
+ controller = new AbortController();
206
+ const signal = controller.signal;
207
+ const promise = (async () => {
208
+ const idempotencyKey = await payloadKey(key);
209
+ signal.throwIfAborted();
210
+ const raw = await transport.quote(
211
+ { ...context(), signal, idempotencyKey },
212
+ body
213
+ );
214
+ signal.throwIfAborted();
215
+ const quote = mapQuote(raw, body.branch_id);
216
+ const ids = new Set(quote.lines.map((line) => line.lineId));
217
+ if (ids.size !== quote.lines.length || quote.lines.length !== body.lines.length || body.lines.some((line) => !ids.has(line.line_id))) {
218
+ throw new ZappieError(
219
+ "server_error",
220
+ "Quote line identities do not match the cart"
221
+ );
222
+ }
223
+ if (seq === sequence) {
224
+ cached = { key, quote };
225
+ apply(quote);
226
+ }
227
+ return quote;
228
+ })();
229
+ flight = { key, promise };
230
+ try {
231
+ return await promise;
232
+ } finally {
233
+ if (seq === sequence) {
234
+ flight = void 0;
235
+ }
236
+ }
237
+ },
238
+ accept(issue) {
239
+ if (cached) {
240
+ cached.quote = {
241
+ ...cached.quote,
242
+ issues: cached.quote.issues.filter(
243
+ (i) => !(i.code === issue.code && i.lineId === issue.lineId)
244
+ )
245
+ };
246
+ }
247
+ }
248
+ };
249
+ }
250
+ export {
251
+ createQuoteClient
252
+ };
253
+ //# sourceMappingURL=quote-client-6XJJHITL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../sdk/src/cart/quote-response.ts","../../sdk/src/cart/quote-client.ts"],"sourcesContent":["// Quote responses load lazily with the quote client, outside the synchronous core.\nimport { ZappieError } from \"../errors\";\nimport { sumMoney } from \"../internal/money-math\";\nimport { money } from \"../money\";\nimport { mapMoney, record } from \"../transport/mappers\";\nimport type { CartFix, CartFulfilment, CartIssue, Quote } from \"./types\";\nfunction fail(): never {\n throw new ZappieError(\"server_error\", \"Invalid quote response\");\n}\nfunction text(value: unknown): string {\n if (typeof value !== \"string\") {\n return fail();\n }\n return value;\n}\nfunction list(value: unknown): unknown[] {\n if (!Array.isArray(value)) {\n return fail();\n }\n return value;\n}\n/** A fix newer than this SDK is dropped (undefined); a malformed known fix still fails. */\nfunction fix(value: unknown): CartFix | undefined {\n const v = record(value);\n switch (v.action) {\n case \"remove_line\":\n case \"accept_price\":\n case \"remove_promo\":\n case \"change_address\":\n return { action: v.action };\n case \"choose_option\":\n case \"choose_other_option\":\n return { action: v.action, groupId: text(v.group_id) };\n case \"set_quantity\": {\n if (\n !Number.isInteger(v.quantity) ||\n Number(v.quantity) < 1 ||\n Number(v.quantity) > 99\n ) {\n return fail();\n }\n return { action: v.action, quantity: v.quantity as number };\n }\n case \"schedule\":\n return {\n action: v.action,\n ...(v.next_open_at ? { nextOpenAt: text(v.next_open_at) } : {}),\n ...(v.slots ? { slots: list(v.slots).map(text) } : {}),\n };\n case \"switch_fulfilment\": {\n const types = list(v.types)\n .map(text)\n .filter((t) => [\"pickup\", \"delivery\", \"dine_in\"].includes(t));\n return types.length\n ? { action: v.action, types: types as CartFulfilment[\"type\"][] }\n : undefined;\n }\n default:\n return undefined;\n }\n}\nconst codes = [\n \"item_unavailable\",\n \"item_not_found\",\n \"option_unavailable\",\n \"option_required\",\n \"option_limit\",\n \"quantity_limit\",\n \"price_changed\",\n \"branch_closed\",\n \"fulfilment_unavailable\",\n \"outside_delivery_zone\",\n \"delivery_region_required\",\n \"schedule_invalid\",\n \"promo_invalid\",\n \"promo_min_spend\",\n \"min_order\",\n \"menu_version_stale\",\n];\nexport function mapQuote(value: unknown, branchId: string): Quote {\n const v = record(value);\n if (\n v.branch_id !== branchId ||\n !Number.isFinite(Date.parse(text(v.expires_at)))\n ) {\n return fail();\n }\n const subtotal = mapMoney(v.subtotal);\n const checkout = record(v.checkout);\n if (\n typeof v.can_checkout !== \"boolean\" ||\n typeof checkout.ready !== \"boolean\" ||\n (checkout.reason !== undefined &&\n checkout.reason !== \"issues\" &&\n checkout.reason !== \"customer_required\")\n ) {\n return fail();\n }\n const amount = (raw: unknown) => {\n const result = mapMoney(raw);\n if (result.currency !== subtotal.currency) {\n return fail();\n }\n return result;\n };\n const charge = (raw: unknown) => {\n const c = record(raw);\n return {\n code: text(c.code),\n label: text(c.label),\n amount: amount(c.amount),\n };\n };\n const charges = list(v.charges).map(charge);\n const discounts = list(v.discounts).map(charge);\n const total = amount(v.total);\n if (\n sumMoney(\n [...charges, ...discounts].map((c) => c.amount),\n subtotal,\n ).amountMinor !== total.amountMinor\n ) {\n return fail();\n }\n const deliveryCharges = charges.filter((c) => c.code === \"delivery\");\n const lines = list(v.lines).map((raw) => {\n const l = record(raw);\n if (\n (l.unit_price == null || l.line_total == null) &&\n v.lines_unpriced !== true\n ) {\n return fail();\n }\n return {\n lineId: text(l.line_id),\n unitPrice: l.unit_price == null ? null : amount(l.unit_price),\n total: l.line_total == null ? null : amount(l.line_total),\n };\n });\n if (\n v.lines_unpriced !== true &&\n sumMoney(\n lines.map((line) => line.total!),\n money(0, subtotal.currency),\n ).amountMinor !== subtotal.amountMinor\n ) {\n return fail();\n }\n return {\n lines,\n ...(v.lines_unpriced === true ? { linesUnpriced: true } : {}),\n subtotal,\n total,\n fees: charges.filter((c) => c.code !== \"delivery\"),\n discounts,\n ...(deliveryCharges.length\n ? {\n delivery: sumMoney(\n deliveryCharges.map((c) => c.amount),\n money(0, subtotal.currency),\n ),\n }\n : {}),\n menuVersion: text(v.menu_version),\n expiresAt: text(v.expires_at),\n issues: list(v.issues).flatMap((raw): CartIssue[] => {\n const i = record(raw);\n const code = text(i.code);\n const severity = text(i.severity);\n const known =\n codes.includes(code) &&\n [\"blocking\", \"warning\", \"info\"].includes(severity);\n const fixes = list(i.fixes)\n .map(fix)\n .filter((f): f is CartFix => f !== undefined);\n const lineId = i.line_id ? text(i.line_id) : undefined;\n if (!known && (severity === \"warning\" || severity === \"info\")) {\n console.warn(\n `Zappie quote issue \"${code}\" is newer than this SDK and was skipped`,\n );\n return [];\n }\n // A blocking (or unknown-severity) issue newer than the SDK keeps checkout blocked.\n return [\n {\n code: known ? (code as CartIssue[\"code\"]) : \"item_unavailable\",\n severity: known ? (severity as CartIssue[\"severity\"]) : \"blocking\",\n message: text(i.message),\n source: \"quote\",\n ...(lineId ? { lineId } : {}),\n ...(i.group_id ? { groupId: text(i.group_id) } : {}),\n ...(i.data || !known\n ? {\n data: {\n ...(i.data ? record(i.data) : {}),\n ...(known ? {} : { code }),\n },\n }\n : {}),\n fixes:\n !known && !fixes.length && lineId\n ? [{ action: \"remove_line\" }]\n : fixes,\n },\n ];\n }),\n canCheckout: v.can_checkout,\n checkout: {\n ready: checkout.ready,\n ...(checkout.reason !== undefined ? { reason: checkout.reason } : {}),\n },\n };\n}\n","import { ZappieError } from \"../errors\";\nimport { payloadKey } from \"../internal/digest\";\nimport type { RequestCtx, Transport } from \"../transport/types\";\nimport { mapQuote } from \"./quote-response\";\nimport type { Quote, QuoteRequest } from \"./types\";\nexport function createQuoteClient(\n transport: Transport,\n context: () => RequestCtx,\n) {\n let controller: AbortController | undefined;\n let sequence = 0;\n let cached: { key: string; quote: Quote } | undefined;\n let flight: { key: string; promise: Promise<Quote> } | undefined;\n const cancel = () => {\n sequence++;\n controller?.abort();\n flight = undefined;\n };\n return {\n cancel,\n async get(\n body: QuoteRequest,\n force: boolean,\n apply: (quote: Quote) => void,\n ): Promise<Quote> {\n const key = JSON.stringify(body);\n if (\n !force &&\n cached?.key === key &&\n Date.parse(cached.quote.expiresAt) > Date.now()\n ) {\n apply(cached.quote);\n return cached.quote;\n }\n if (!force && flight?.key === key) {\n return flight.promise;\n }\n cancel();\n const seq = sequence;\n controller = new AbortController();\n const signal = controller.signal;\n const promise = (async () => {\n const idempotencyKey = await payloadKey(key);\n signal.throwIfAborted();\n const raw = await transport.quote(\n { ...context(), signal, idempotencyKey },\n body,\n );\n signal.throwIfAborted();\n const quote = mapQuote(raw, body.branch_id);\n const ids = new Set(quote.lines.map((line) => line.lineId));\n if (\n ids.size !== quote.lines.length ||\n quote.lines.length !== body.lines.length ||\n body.lines.some((line) => !ids.has(line.line_id))\n ) {\n throw new ZappieError(\n \"server_error\",\n \"Quote line identities do not match the cart\",\n );\n }\n if (seq === sequence) {\n cached = { key, quote };\n apply(quote);\n }\n return quote;\n })();\n flight = { key, promise };\n try {\n return await promise;\n } finally {\n if (seq === sequence) {\n flight = undefined;\n }\n }\n },\n accept(issue: CartIssueForAcceptance) {\n if (cached) {\n cached.quote = {\n ...cached.quote,\n issues: cached.quote.issues.filter(\n (i) => !(i.code === issue.code && i.lineId === issue.lineId),\n ),\n };\n }\n },\n };\n}\ntype CartIssueForAcceptance = { code: string; lineId?: string };\n\nexport type QuoteClient = ReturnType<typeof createQuoteClient>;\n"],"mappings":";;;;;;;;;;AAMA,SAAS,OAAc;AACrB,QAAM,IAAI,YAAY,gBAAgB,wBAAwB;AAChE;AACA,SAAS,KAAK,OAAwB;AACpC,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AACA,SAAS,KAAK,OAA2B;AACvC,MAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;AACzB,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AACT;AAEA,SAAS,IAAI,OAAqC;AAChD,QAAM,IAAI,OAAO,KAAK;AACtB,UAAQ,EAAE,QAAQ;AAAA,IAChB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,QAAQ,EAAE,OAAO;AAAA,IAC5B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,QAAQ,EAAE,QAAQ,SAAS,KAAK,EAAE,QAAQ,EAAE;AAAA,IACvD,KAAK,gBAAgB;AACnB,UACE,CAAC,OAAO,UAAU,EAAE,QAAQ,KAC5B,OAAO,EAAE,QAAQ,IAAI,KACrB,OAAO,EAAE,QAAQ,IAAI,IACrB;AACA,eAAO,KAAK;AAAA,MACd;AACA,aAAO,EAAE,QAAQ,EAAE,QAAQ,UAAU,EAAE,SAAmB;AAAA,IAC5D;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL,QAAQ,EAAE;AAAA,QACV,GAAI,EAAE,eAAe,EAAE,YAAY,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC;AAAA,QAC7D,GAAI,EAAE,QAAQ,EAAE,OAAO,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,EAAE,IAAI,CAAC;AAAA,MACtD;AAAA,IACF,KAAK,qBAAqB;AACxB,YAAM,QAAQ,KAAK,EAAE,KAAK,EACvB,IAAI,IAAI,EACR,OAAO,CAAC,MAAM,CAAC,UAAU,YAAY,SAAS,EAAE,SAAS,CAAC,CAAC;AAC9D,aAAO,MAAM,SACT,EAAE,QAAQ,EAAE,QAAQ,MAAyC,IAC7D;AAAA,IACN;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AACA,IAAM,QAAQ;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AACO,SAAS,SAAS,OAAgB,UAAyB;AAChE,QAAM,IAAI,OAAO,KAAK;AACtB,MACE,EAAE,cAAc,YAChB,CAAC,OAAO,SAAS,KAAK,MAAM,KAAK,EAAE,UAAU,CAAC,CAAC,GAC/C;AACA,WAAO,KAAK;AAAA,EACd;AACA,QAAM,WAAW,SAAS,EAAE,QAAQ;AACpC,QAAM,WAAW,OAAO,EAAE,QAAQ;AAClC,MACE,OAAO,EAAE,iBAAiB,aAC1B,OAAO,SAAS,UAAU,aACzB,SAAS,WAAW,UACnB,SAAS,WAAW,YACpB,SAAS,WAAW,qBACtB;AACA,WAAO,KAAK;AAAA,EACd;AACA,QAAM,SAAS,CAAC,QAAiB;AAC/B,UAAM,SAAS,SAAS,GAAG;AAC3B,QAAI,OAAO,aAAa,SAAS,UAAU;AACzC,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,EACT;AACA,QAAM,SAAS,CAAC,QAAiB;AAC/B,UAAM,IAAI,OAAO,GAAG;AACpB,WAAO;AAAA,MACL,MAAM,KAAK,EAAE,IAAI;AAAA,MACjB,OAAO,KAAK,EAAE,KAAK;AAAA,MACnB,QAAQ,OAAO,EAAE,MAAM;AAAA,IACzB;AAAA,EACF;AACA,QAAM,UAAU,KAAK,EAAE,OAAO,EAAE,IAAI,MAAM;AAC1C,QAAM,YAAY,KAAK,EAAE,SAAS,EAAE,IAAI,MAAM;AAC9C,QAAM,QAAQ,OAAO,EAAE,KAAK;AAC5B,MACE;AAAA,IACE,CAAC,GAAG,SAAS,GAAG,SAAS,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,IAC9C;AAAA,EACF,EAAE,gBAAgB,MAAM,aACxB;AACA,WAAO,KAAK;AAAA,EACd;AACA,QAAM,kBAAkB,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU;AACnE,QAAM,QAAQ,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ;AACvC,UAAM,IAAI,OAAO,GAAG;AACpB,SACG,EAAE,cAAc,QAAQ,EAAE,cAAc,SACzC,EAAE,mBAAmB,MACrB;AACA,aAAO,KAAK;AAAA,IACd;AACA,WAAO;AAAA,MACL,QAAQ,KAAK,EAAE,OAAO;AAAA,MACtB,WAAW,EAAE,cAAc,OAAO,OAAO,OAAO,EAAE,UAAU;AAAA,MAC5D,OAAO,EAAE,cAAc,OAAO,OAAO,OAAO,EAAE,UAAU;AAAA,IAC1D;AAAA,EACF,CAAC;AACD,MACE,EAAE,mBAAmB,QACrB;AAAA,IACE,MAAM,IAAI,CAAC,SAAS,KAAK,KAAM;AAAA,IAC/B,MAAM,GAAG,SAAS,QAAQ;AAAA,EAC5B,EAAE,gBAAgB,SAAS,aAC3B;AACA,WAAO,KAAK;AAAA,EACd;AACA,SAAO;AAAA,IACL;AAAA,IACA,GAAI,EAAE,mBAAmB,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;AAAA,IAC3D;AAAA,IACA;AAAA,IACA,MAAM,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU;AAAA,IACjD;AAAA,IACA,GAAI,gBAAgB,SAChB;AAAA,MACE,UAAU;AAAA,QACR,gBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,QACnC,MAAM,GAAG,SAAS,QAAQ;AAAA,MAC5B;AAAA,IACF,IACA,CAAC;AAAA,IACL,aAAa,KAAK,EAAE,YAAY;AAAA,IAChC,WAAW,KAAK,EAAE,UAAU;AAAA,IAC5B,QAAQ,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,QAAqB;AACnD,YAAM,IAAI,OAAO,GAAG;AACpB,YAAM,OAAO,KAAK,EAAE,IAAI;AACxB,YAAM,WAAW,KAAK,EAAE,QAAQ;AAChC,YAAM,QACJ,MAAM,SAAS,IAAI,KACnB,CAAC,YAAY,WAAW,MAAM,EAAE,SAAS,QAAQ;AACnD,YAAM,QAAQ,KAAK,EAAE,KAAK,EACvB,IAAI,GAAG,EACP,OAAO,CAAC,MAAoB,MAAM,MAAS;AAC9C,YAAM,SAAS,EAAE,UAAU,KAAK,EAAE,OAAO,IAAI;AAC7C,UAAI,CAAC,UAAU,aAAa,aAAa,aAAa,SAAS;AAC7D,gBAAQ;AAAA,UACN,uBAAuB,IAAI;AAAA,QAC7B;AACA,eAAO,CAAC;AAAA,MACV;AAEA,aAAO;AAAA,QACL;AAAA,UACE,MAAM,QAAS,OAA6B;AAAA,UAC5C,UAAU,QAAS,WAAqC;AAAA,UACxD,SAAS,KAAK,EAAE,OAAO;AAAA,UACvB,QAAQ;AAAA,UACR,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,UAC3B,GAAI,EAAE,WAAW,EAAE,SAAS,KAAK,EAAE,QAAQ,EAAE,IAAI,CAAC;AAAA,UAClD,GAAI,EAAE,QAAQ,CAAC,QACX;AAAA,YACE,MAAM;AAAA,cACJ,GAAI,EAAE,OAAO,OAAO,EAAE,IAAI,IAAI,CAAC;AAAA,cAC/B,GAAI,QAAQ,CAAC,IAAI,EAAE,KAAK;AAAA,YAC1B;AAAA,UACF,IACA,CAAC;AAAA,UACL,OACE,CAAC,SAAS,CAAC,MAAM,UAAU,SACvB,CAAC,EAAE,QAAQ,cAAc,CAAC,IAC1B;AAAA,QACR;AAAA,MACF;AAAA,IACF,CAAC;AAAA,IACD,aAAa,EAAE;AAAA,IACf,UAAU;AAAA,MACR,OAAO,SAAS;AAAA,MAChB,GAAI,SAAS,WAAW,SAAY,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,IACrE;AAAA,EACF;AACF;;;AC/MO,SAAS,kBACd,WACA,SACA;AACA,MAAI;AACJ,MAAI,WAAW;AACf,MAAI;AACJ,MAAI;AACJ,QAAM,SAAS,MAAM;AACnB;AACA,gBAAY,MAAM;AAClB,aAAS;AAAA,EACX;AACA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,IACJ,MACA,OACA,OACgB;AAChB,YAAM,MAAM,KAAK,UAAU,IAAI;AAC/B,UACE,CAAC,SACD,QAAQ,QAAQ,OAChB,KAAK,MAAM,OAAO,MAAM,SAAS,IAAI,KAAK,IAAI,GAC9C;AACA,cAAM,OAAO,KAAK;AAClB,eAAO,OAAO;AAAA,MAChB;AACA,UAAI,CAAC,SAAS,QAAQ,QAAQ,KAAK;AACjC,eAAO,OAAO;AAAA,MAChB;AACA,aAAO;AACP,YAAM,MAAM;AACZ,mBAAa,IAAI,gBAAgB;AACjC,YAAM,SAAS,WAAW;AAC1B,YAAM,WAAW,YAAY;AAC3B,cAAM,iBAAiB,MAAM,WAAW,GAAG;AAC3C,eAAO,eAAe;AACtB,cAAM,MAAM,MAAM,UAAU;AAAA,UAC1B,EAAE,GAAG,QAAQ,GAAG,QAAQ,eAAe;AAAA,UACvC;AAAA,QACF;AACA,eAAO,eAAe;AACtB,cAAM,QAAQ,SAAS,KAAK,KAAK,SAAS;AAC1C,cAAM,MAAM,IAAI,IAAI,MAAM,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC;AAC1D,YACE,IAAI,SAAS,MAAM,MAAM,UACzB,MAAM,MAAM,WAAW,KAAK,MAAM,UAClC,KAAK,MAAM,KAAK,CAAC,SAAS,CAAC,IAAI,IAAI,KAAK,OAAO,CAAC,GAChD;AACA,gBAAM,IAAI;AAAA,YACR;AAAA,YACA;AAAA,UACF;AAAA,QACF;AACA,YAAI,QAAQ,UAAU;AACpB,mBAAS,EAAE,KAAK,MAAM;AACtB,gBAAM,KAAK;AAAA,QACb;AACA,eAAO;AAAA,MACT,GAAG;AACH,eAAS,EAAE,KAAK,QAAQ;AACxB,UAAI;AACF,eAAO,MAAM;AAAA,MACf,UAAE;AACA,YAAI,QAAQ,UAAU;AACpB,mBAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,IACA,OAAO,OAA+B;AACpC,UAAI,QAAQ;AACV,eAAO,QAAQ;AAAA,UACb,GAAG,OAAO;AAAA,UACV,QAAQ,OAAO,MAAM,OAAO;AAAA,YAC1B,CAAC,MAAM,EAAE,EAAE,SAAS,MAAM,QAAQ,EAAE,WAAW,MAAM;AAAA,UACvD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,32 @@
1
+ // ../sdk/src/transport/search.ts
2
+ async function searchPage(ctx, read, mapMenu, invalid) {
3
+ ctx.signal?.throwIfAborted();
4
+ const query = new URLSearchParams({
5
+ branch: ctx.branchId,
6
+ query: ctx.query.trim(),
7
+ limit: "25",
8
+ ...ctx.cursor ? { cursor: ctx.cursor } : {}
9
+ });
10
+ const raw = await read(`/items/search?${query}`);
11
+ ctx.signal?.throwIfAborted();
12
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
13
+ throw invalid();
14
+ }
15
+ const cursor = raw.next_cursor;
16
+ const menu = mapMenu(raw, ctx.branchId);
17
+ if (Object.keys(menu.items).length > 25 || cursor != null && (typeof cursor !== "string" || !cursor || cursor === ctx.cursor) || Object.keys(menu.items).some(
18
+ (id) => !menu.categories.some((category) => category.itemIds.includes(id))
19
+ ) || menu.categories.some(
20
+ (category) => !menu.menus?.some((source) => source.categoryIds.includes(category.id))
21
+ )) {
22
+ throw invalid();
23
+ }
24
+ return {
25
+ menu: { ...menu, completeness: "partial" },
26
+ ...typeof cursor === "string" ? { nextCursor: cursor } : {}
27
+ };
28
+ }
29
+ export {
30
+ searchPage
31
+ };
32
+ //# sourceMappingURL=search-YIHT6CXN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../sdk/src/transport/search.ts"],"sourcesContent":["import type { Menu } from \"../models\";\nimport type { SearchPage, SearchRequest } from \"./types\";\n\n/** Loaded only when search is used; request authentication/retries stay in REST. */\nexport async function searchPage(\n ctx: SearchRequest,\n read: (path: string) => Promise<unknown>,\n mapMenu: (value: unknown, branchId: string) => Menu,\n invalid: () => Error,\n): Promise<SearchPage> {\n ctx.signal?.throwIfAborted();\n const query = new URLSearchParams({\n branch: ctx.branchId,\n query: ctx.query.trim(),\n limit: \"25\",\n ...(ctx.cursor ? { cursor: ctx.cursor } : {}),\n });\n const raw = await read(`/items/search?${query}`);\n ctx.signal?.throwIfAborted();\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) {\n throw invalid();\n }\n const cursor = (raw as Record<string, unknown>).next_cursor;\n const menu = mapMenu(raw, ctx.branchId);\n if (\n Object.keys(menu.items).length > 25 ||\n (cursor != null &&\n (typeof cursor !== \"string\" || !cursor || cursor === ctx.cursor)) ||\n Object.keys(menu.items).some(\n (id) =>\n !menu.categories.some((category) => category.itemIds.includes(id)),\n ) ||\n menu.categories.some(\n (category) =>\n !menu.menus?.some((source) => source.categoryIds.includes(category.id)),\n )\n ) {\n throw invalid();\n }\n return {\n menu: { ...menu, completeness: \"partial\" },\n ...(typeof cursor === \"string\" ? { nextCursor: cursor } : {}),\n };\n}\n"],"mappings":";AAIA,eAAsB,WACpB,KACA,MACA,SACA,SACqB;AACrB,MAAI,QAAQ,eAAe;AAC3B,QAAM,QAAQ,IAAI,gBAAgB;AAAA,IAChC,QAAQ,IAAI;AAAA,IACZ,OAAO,IAAI,MAAM,KAAK;AAAA,IACtB,OAAO;AAAA,IACP,GAAI,IAAI,SAAS,EAAE,QAAQ,IAAI,OAAO,IAAI,CAAC;AAAA,EAC7C,CAAC;AACD,QAAM,MAAM,MAAM,KAAK,iBAAiB,KAAK,EAAE;AAC/C,MAAI,QAAQ,eAAe;AAC3B,MAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG;AACzD,UAAM,QAAQ;AAAA,EAChB;AACA,QAAM,SAAU,IAAgC;AAChD,QAAM,OAAO,QAAQ,KAAK,IAAI,QAAQ;AACtC,MACE,OAAO,KAAK,KAAK,KAAK,EAAE,SAAS,MAChC,UAAU,SACR,OAAO,WAAW,YAAY,CAAC,UAAU,WAAW,IAAI,WAC3D,OAAO,KAAK,KAAK,KAAK,EAAE;AAAA,IACtB,CAAC,OACC,CAAC,KAAK,WAAW,KAAK,CAAC,aAAa,SAAS,QAAQ,SAAS,EAAE,CAAC;AAAA,EACrE,KACA,KAAK,WAAW;AAAA,IACd,CAAC,aACC,CAAC,KAAK,OAAO,KAAK,CAAC,WAAW,OAAO,YAAY,SAAS,SAAS,EAAE,CAAC;AAAA,EAC1E,GACA;AACA,UAAM,QAAQ;AAAA,EAChB;AACA,SAAO;AAAA,IACL,MAAM,EAAE,GAAG,MAAM,cAAc,UAAU;AAAA,IACzC,GAAI,OAAO,WAAW,WAAW,EAAE,YAAY,OAAO,IAAI,CAAC;AAAA,EAC7D;AACF;","names":[]}
@@ -0,0 +1,79 @@
1
+ // ../sdk/src/transport/selected-menu.ts
2
+ async function selectedMenu(ctx, request, json, mapMenu, invalid) {
3
+ const record = (value) => {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
5
+ throw invalid();
6
+ }
7
+ return value;
8
+ };
9
+ const query = new URLSearchParams({
10
+ branch: ctx.branchId,
11
+ order_context: ctx.orderContext
12
+ });
13
+ const response = await request(
14
+ `/menus/${encodeURIComponent(ctx.menuId)}?${query}`
15
+ );
16
+ const etag = response.headers.get("ETag");
17
+ if (!etag) {
18
+ throw invalid();
19
+ }
20
+ if (response.status === 304) {
21
+ return { notModified: true, etag };
22
+ }
23
+ const raw = record(await json(response));
24
+ const coverage = record(raw.coverage);
25
+ const menu = mapMenu(raw, ctx.branchId);
26
+ if (raw.next_cursor || coverage.complete !== true || !Array.isArray(coverage.menuIds) || coverage.menuIds.length !== 1 || coverage.menuIds[0] !== ctx.menuId || menu.menus?.length !== 1 || menu.menus[0]?.id !== ctx.menuId) {
27
+ throw invalid();
28
+ }
29
+ return {
30
+ menu: { ...menu, completeness: "partial" },
31
+ coverage: { menuIds: [ctx.menuId], complete: true },
32
+ etag
33
+ };
34
+ }
35
+ function selectedMenuKey(scope) {
36
+ return JSON.stringify([
37
+ "selected-menu:v1",
38
+ scope.store,
39
+ scope.branchId,
40
+ scope.orderContext,
41
+ scope.menuId
42
+ ]);
43
+ }
44
+ function createSelectedMenuReader(client) {
45
+ const transport = client._internal.transport;
46
+ return {
47
+ async read(scope, options = {}) {
48
+ if (!transport.getSelectedMenu) {
49
+ throw new Error("Transport does not support selected menus");
50
+ }
51
+ const response = await transport.getSelectedMenu({
52
+ key: client.config.key,
53
+ version: client.config.version,
54
+ branchId: scope.branchId,
55
+ menuId: scope.menuId,
56
+ orderContext: scope.orderContext,
57
+ ...options
58
+ });
59
+ options.signal?.throwIfAborted();
60
+ if (!("notModified" in response)) {
61
+ if (response.menu.branchId !== scope.branchId || response.coverage.menuIds.length !== 1 || response.coverage.menuIds[0] !== scope.menuId) {
62
+ throw new Error("Selected menu belongs to another scope");
63
+ }
64
+ const key = selectedMenuKey(scope);
65
+ const previous = client._internal.cache.snapshot(key).value;
66
+ if (JSON.stringify(previous) !== JSON.stringify(response)) {
67
+ client._internal.cache.set(key, response, response.etag);
68
+ }
69
+ }
70
+ return response;
71
+ }
72
+ };
73
+ }
74
+ export {
75
+ createSelectedMenuReader,
76
+ selectedMenu,
77
+ selectedMenuKey
78
+ };
79
+ //# sourceMappingURL=selected-menu-3GCAICBQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../sdk/src/transport/selected-menu.ts"],"sourcesContent":["import type { Zappie } from \"../client\";\nimport type { Menu } from \"../models\";\nimport type { CatalogueScope } from \"../persist/catalogue\";\nimport type { RequestCtx } from \"./types\";\n\n/** Complete for the named source, never complete for the branch. */\nexport type SelectedMenu = {\n menu: Menu & { completeness: \"partial\" };\n coverage: { menuIds: [string]; complete: true };\n};\n/**\n * A source menu with its ETag, or an unchanged response carrying the matching ETag.\n */\nexport type SelectedMenuResponse =\n (SelectedMenu & { etag: string }) | { notModified: true; etag: string };\n/**\n * A branch-scoped source-menu request with order context and an optional conditional ETag.\n */\nexport type SelectedMenuRequest = RequestCtx & {\n branchId: string;\n menuId: string;\n orderContext: CatalogueScope[\"orderContext\"];\n ifNoneMatch?: string;\n};\n/**\n * A transport that reads one complete source menu without claiming branch-wide coverage.\n */\nexport type SelectedMenuTransport = {\n getSelectedMenu(request: SelectedMenuRequest): Promise<SelectedMenuResponse>;\n};\n/** Dependencies are passed by REST to avoid splitting core shared chunks. */\nexport async function selectedMenu(\n ctx: SelectedMenuRequest,\n request: (path: string) => Promise<Response>,\n json: (response: Response) => Promise<unknown>,\n mapMenu: (value: unknown, branchId: string) => Menu,\n invalid: () => Error,\n): Promise<SelectedMenuResponse> {\n // Checked here rather than passed in: every argument REST passes costs the\n // core bytes, and the core has none to spare.\n const record = (value: unknown) => {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw invalid();\n }\n return value as Record<string, unknown>;\n };\n const query = new URLSearchParams({\n branch: ctx.branchId,\n order_context: ctx.orderContext,\n });\n const response = await request(\n `/menus/${encodeURIComponent(ctx.menuId)}?${query}`,\n );\n const etag = response.headers.get(\"ETag\");\n if (!etag) {\n throw invalid();\n }\n if (response.status === 304) {\n return { notModified: true, etag };\n }\n const raw = record(await json(response));\n const coverage = record(raw.coverage);\n const menu = mapMenu(raw, ctx.branchId);\n if (\n raw.next_cursor ||\n coverage.complete !== true ||\n !Array.isArray(coverage.menuIds) ||\n coverage.menuIds.length !== 1 ||\n coverage.menuIds[0] !== ctx.menuId ||\n menu.menus?.length !== 1 ||\n menu.menus[0]?.id !== ctx.menuId\n ) {\n throw invalid();\n }\n return {\n menu: { ...menu, completeness: \"partial\" },\n coverage: { menuIds: [ctx.menuId], complete: true },\n etag,\n };\n}\n/**\n * Build a versioned cache key for a source menu within a store, branch and order context.\n */\nexport function selectedMenuKey(scope: CatalogueScope) {\n return JSON.stringify([\n \"selected-menu:v1\",\n scope.store,\n scope.branchId,\n scope.orderContext,\n scope.menuId,\n ]);\n}\n/** Dedicated lazy resource: deliberately does not call menu.hydrate(). */\nexport function createSelectedMenuReader(client: Zappie) {\n const transport = client._internal.transport;\n return {\n async read(\n scope: CatalogueScope,\n options: { signal?: AbortSignal; ifNoneMatch?: string } = {},\n ) {\n if (!transport.getSelectedMenu) {\n throw new Error(\"Transport does not support selected menus\");\n }\n const response = await transport.getSelectedMenu({\n key: client.config.key,\n version: client.config.version,\n branchId: scope.branchId,\n menuId: scope.menuId,\n orderContext: scope.orderContext,\n ...options,\n });\n options.signal?.throwIfAborted();\n if (!(\"notModified\" in response)) {\n if (\n response.menu.branchId !== scope.branchId ||\n response.coverage.menuIds.length !== 1 ||\n response.coverage.menuIds[0] !== scope.menuId\n ) {\n throw new Error(\"Selected menu belongs to another scope\");\n }\n const key = selectedMenuKey(scope);\n const previous =\n client._internal.cache.snapshot<SelectedMenuResponse>(key).value;\n if (JSON.stringify(previous) !== JSON.stringify(response)) {\n client._internal.cache.set(key, response, response.etag);\n }\n }\n return response;\n },\n };\n}\n"],"mappings":";AA+BA,eAAsB,aACpB,KACA,SACA,MACA,SACA,SAC+B;AAG/B,QAAM,SAAS,CAAC,UAAmB;AACjC,QAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;AAC/D,YAAM,QAAQ;AAAA,IAChB;AACA,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,IAAI,gBAAgB;AAAA,IAChC,QAAQ,IAAI;AAAA,IACZ,eAAe,IAAI;AAAA,EACrB,CAAC;AACD,QAAM,WAAW,MAAM;AAAA,IACrB,UAAU,mBAAmB,IAAI,MAAM,CAAC,IAAI,KAAK;AAAA,EACnD;AACA,QAAM,OAAO,SAAS,QAAQ,IAAI,MAAM;AACxC,MAAI,CAAC,MAAM;AACT,UAAM,QAAQ;AAAA,EAChB;AACA,MAAI,SAAS,WAAW,KAAK;AAC3B,WAAO,EAAE,aAAa,MAAM,KAAK;AAAA,EACnC;AACA,QAAM,MAAM,OAAO,MAAM,KAAK,QAAQ,CAAC;AACvC,QAAM,WAAW,OAAO,IAAI,QAAQ;AACpC,QAAM,OAAO,QAAQ,KAAK,IAAI,QAAQ;AACtC,MACE,IAAI,eACJ,SAAS,aAAa,QACtB,CAAC,MAAM,QAAQ,SAAS,OAAO,KAC/B,SAAS,QAAQ,WAAW,KAC5B,SAAS,QAAQ,CAAC,MAAM,IAAI,UAC5B,KAAK,OAAO,WAAW,KACvB,KAAK,MAAM,CAAC,GAAG,OAAO,IAAI,QAC1B;AACA,UAAM,QAAQ;AAAA,EAChB;AACA,SAAO;AAAA,IACL,MAAM,EAAE,GAAG,MAAM,cAAc,UAAU;AAAA,IACzC,UAAU,EAAE,SAAS,CAAC,IAAI,MAAM,GAAG,UAAU,KAAK;AAAA,IAClD;AAAA,EACF;AACF;AAIO,SAAS,gBAAgB,OAAuB;AACrD,SAAO,KAAK,UAAU;AAAA,IACpB;AAAA,IACA,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,IACN,MAAM;AAAA,EACR,CAAC;AACH;AAEO,SAAS,yBAAyB,QAAgB;AACvD,QAAM,YAAY,OAAO,UAAU;AACnC,SAAO;AAAA,IACL,MAAM,KACJ,OACA,UAA0D,CAAC,GAC3D;AACA,UAAI,CAAC,UAAU,iBAAiB;AAC9B,cAAM,IAAI,MAAM,2CAA2C;AAAA,MAC7D;AACA,YAAM,WAAW,MAAM,UAAU,gBAAgB;AAAA,QAC/C,KAAK,OAAO,OAAO;AAAA,QACnB,SAAS,OAAO,OAAO;AAAA,QACvB,UAAU,MAAM;AAAA,QAChB,QAAQ,MAAM;AAAA,QACd,cAAc,MAAM;AAAA,QACpB,GAAG;AAAA,MACL,CAAC;AACD,cAAQ,QAAQ,eAAe;AAC/B,UAAI,EAAE,iBAAiB,WAAW;AAChC,YACE,SAAS,KAAK,aAAa,MAAM,YACjC,SAAS,SAAS,QAAQ,WAAW,KACrC,SAAS,SAAS,QAAQ,CAAC,MAAM,MAAM,QACvC;AACA,gBAAM,IAAI,MAAM,wCAAwC;AAAA,QAC1D;AACA,cAAM,MAAM,gBAAgB,KAAK;AACjC,cAAM,WACJ,OAAO,UAAU,MAAM,SAA+B,GAAG,EAAE;AAC7D,YAAI,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,QAAQ,GAAG;AACzD,iBAAO,UAAU,MAAM,IAAI,KAAK,UAAU,SAAS,IAAI;AAAA,QACzD;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,120 @@
1
+ // ../sdk/src/transport/store.ts
2
+ function mapStore(value, record, text, list, hours, mapMoney, money, opt, isString, isBoolean) {
3
+ const coordinates = (input) => {
4
+ const coordinates2 = record(input ?? {});
5
+ return typeof coordinates2.lat === "number" && typeof coordinates2.lng === "number" ? { lat: coordinates2.lat, lng: coordinates2.lng } : void 0;
6
+ };
7
+ const fulfilment = (input) => {
8
+ const value2 = record(input);
9
+ return {
10
+ pickup: value2.pickup === true,
11
+ delivery: value2.delivery === true,
12
+ dineIn: (value2.dine_in ?? value2.dineIn) === true,
13
+ scheduling: (value2.scheduling ?? value2.scheduled) === true
14
+ };
15
+ };
16
+ const source = record(value);
17
+ const brand = record(source.brand ?? {});
18
+ const branches = list(source.branches).map((raw) => {
19
+ const branch = record(raw);
20
+ const pickup = record(branch.pickup_address ?? {});
21
+ const pickupCoordinates = coordinates(pickup.coordinates);
22
+ const pickupAddress = {
23
+ ...opt("placeId", pickup.place_id, isString),
24
+ ...pickupCoordinates ? { coordinates: pickupCoordinates } : {}
25
+ };
26
+ const address = typeof branch.address === "string" ? branch.address : branch.address ? Object.values(record(branch.address)).filter((entry) => typeof entry === "string").join(", ") : void 0;
27
+ const addressFields = branch.address && typeof branch.address === "object" ? record(branch.address) : {};
28
+ const branchCoordinates = coordinates(addressFields);
29
+ return {
30
+ id: text(branch.id),
31
+ slug: text(branch.slug ?? branch.id),
32
+ name: text(branch.name),
33
+ openingTimes: hours(branch.opening_times ?? branch.hours),
34
+ isOpen: branch.is_open === true,
35
+ fulfilment: fulfilment(branch.fulfilment),
36
+ ...address !== void 0 ? { address } : {},
37
+ ...isString(branch.phone) ? { phone: branch.phone.trim() } : {},
38
+ ...opt("timezone", branch.timezone, isString),
39
+ ...branchCoordinates ? { coordinates: branchCoordinates } : {},
40
+ ...opt("closedUntil", branch.closed_until, isString),
41
+ ...opt("nextChangeAt", branch.next_change_at, isString),
42
+ ...opt("isDefault", branch.is_default, isBoolean),
43
+ ...Object.keys(pickupAddress).length > 0 ? { pickupAddress } : {},
44
+ ...branch.social_links !== void 0 ? {
45
+ socialLinks: list(branch.social_links).flatMap((rawLink) => {
46
+ const link = record(rawLink);
47
+ try {
48
+ const url = new URL(text(link.url));
49
+ return isString(link.type) && (url.protocol === "https:" || url.protocol === "http:") ? [{ type: link.type, url: url.href }] : [];
50
+ } catch {
51
+ return [];
52
+ }
53
+ })
54
+ } : {},
55
+ ...branch.minimum_order ? { minimumOrder: mapMoney(branch.minimum_order) } : {},
56
+ ...branch.special_hours ? {
57
+ specialHours: list(branch.special_hours).map((rawHours) => {
58
+ const special = record(rawHours);
59
+ return {
60
+ date: text(special.date),
61
+ closed: special.closed === true,
62
+ ...opt("opens", special.open, isString),
63
+ ...opt("closes", special.close, isString)
64
+ };
65
+ })
66
+ } : {}
67
+ };
68
+ });
69
+ const allowed = [
70
+ "card",
71
+ "bank_transfer",
72
+ "wallet",
73
+ "zap",
74
+ "apple_pay",
75
+ "google_pay"
76
+ ];
77
+ const paymentMethods = list(source.payment_methods).map(text).filter((method) => {
78
+ if (allowed.includes(method)) {
79
+ return true;
80
+ }
81
+ console.warn(
82
+ `Zappie payment method "${method}" is newer than this SDK and was skipped`
83
+ );
84
+ return false;
85
+ });
86
+ const aggregate = {
87
+ pickup: false,
88
+ delivery: false,
89
+ dineIn: false,
90
+ scheduling: false
91
+ };
92
+ for (const branch of branches) {
93
+ for (const key of Object.keys(aggregate)) {
94
+ aggregate[key] ||= branch.fulfilment[key];
95
+ }
96
+ }
97
+ const accent = brand.accent ?? (brand.colors ? record(brand.colors).accent : void 0);
98
+ const templateSettings = record(source.template_settings ?? {});
99
+ return {
100
+ id: text(source.id),
101
+ slug: text(source.slug),
102
+ name: text(source.name),
103
+ currency: money(0, text(source.currency)).currency,
104
+ timezone: text(source.timezone),
105
+ ...opt("description", source.description, isString),
106
+ ...source.template_settings !== void 0 ? { templateSettings } : {},
107
+ brand: {
108
+ ...opt("logoUrl", brand.logo_url, isString),
109
+ ...opt("coverImageUrl", brand.cover_image_url, isString),
110
+ ...isString(accent) ? { accent } : {}
111
+ },
112
+ fulfilment: source.fulfilment ? fulfilment(source.fulfilment) : aggregate,
113
+ paymentMethods,
114
+ branches
115
+ };
116
+ }
117
+ export {
118
+ mapStore
119
+ };
120
+ //# sourceMappingURL=store-KIO5RCZO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../sdk/src/transport/store.ts"],"sourcesContent":["import type { Branch, Fulfilment, OpeningTime, Store } from \"../models\";\nimport type { Currency, Money } from \"../money\";\n\ntype Guard<T> = (value: unknown) => value is T;\ntype Optional = <K extends string, T>(\n key: K,\n value: unknown,\n is: Guard<T>,\n) => { [P in K]?: T };\n\n/** Core parsing helpers are passed in, in this order, so this lazy entry has no runtime import. */\nexport function mapStore(\n value: unknown,\n record: (value: unknown) => Record<string, unknown>,\n text: (value: unknown) => string,\n list: (value: unknown) => unknown[],\n hours: (value: unknown) => OpeningTime[],\n mapMoney: (value: unknown) => Money,\n money: (amountMinor: number, currency: Currency) => Money,\n opt: Optional,\n isString: Guard<string>,\n isBoolean: Guard<boolean>,\n): Store {\n const coordinates = (input: unknown) => {\n const coordinates = record(input ?? {});\n return typeof coordinates.lat === \"number\" &&\n typeof coordinates.lng === \"number\"\n ? { lat: coordinates.lat, lng: coordinates.lng }\n : undefined;\n };\n const fulfilment = (input: unknown): Fulfilment => {\n const value = record(input);\n return {\n pickup: value.pickup === true,\n delivery: value.delivery === true,\n dineIn: (value.dine_in ?? value.dineIn) === true,\n scheduling: (value.scheduling ?? value.scheduled) === true,\n };\n };\n const source = record(value);\n const brand = record(source.brand ?? {});\n const branches: Branch[] = list(source.branches).map((raw) => {\n const branch = record(raw);\n const pickup = record(branch.pickup_address ?? {});\n const pickupCoordinates = coordinates(pickup.coordinates);\n const pickupAddress = {\n ...opt(\"placeId\", pickup.place_id, isString),\n ...(pickupCoordinates ? { coordinates: pickupCoordinates } : {}),\n };\n const address =\n typeof branch.address === \"string\"\n ? branch.address\n : branch.address\n ? Object.values(record(branch.address))\n .filter((entry) => typeof entry === \"string\")\n .join(\", \")\n : undefined;\n const addressFields =\n branch.address && typeof branch.address === \"object\"\n ? record(branch.address)\n : {};\n const branchCoordinates = coordinates(addressFields);\n return {\n id: text(branch.id),\n slug: text(branch.slug ?? branch.id),\n name: text(branch.name),\n openingTimes: hours(branch.opening_times ?? branch.hours),\n isOpen: branch.is_open === true,\n fulfilment: fulfilment(branch.fulfilment),\n ...(address !== undefined ? { address } : {}),\n ...(isString(branch.phone) ? { phone: branch.phone.trim() } : {}),\n ...opt(\"timezone\", branch.timezone, isString),\n ...(branchCoordinates ? { coordinates: branchCoordinates } : {}),\n ...opt(\"closedUntil\", branch.closed_until, isString),\n ...opt(\"nextChangeAt\", branch.next_change_at, isString),\n ...opt(\"isDefault\", branch.is_default, isBoolean),\n ...(Object.keys(pickupAddress).length > 0 ? { pickupAddress } : {}),\n ...(branch.social_links !== undefined\n ? {\n socialLinks: list(branch.social_links).flatMap((rawLink) => {\n const link = record(rawLink);\n // Templates render these as links; only http(s) URLs are kept.\n try {\n const url = new URL(text(link.url));\n return isString(link.type) &&\n (url.protocol === \"https:\" || url.protocol === \"http:\")\n ? [{ type: link.type, url: url.href }]\n : [];\n } catch {\n return [];\n }\n }),\n }\n : {}),\n ...(branch.minimum_order\n ? { minimumOrder: mapMoney(branch.minimum_order) }\n : {}),\n ...(branch.special_hours\n ? {\n specialHours: list(branch.special_hours).map((rawHours) => {\n const special = record(rawHours);\n return {\n date: text(special.date),\n closed: special.closed === true,\n ...opt(\"opens\", special.open, isString),\n ...opt(\"closes\", special.close, isString),\n };\n }),\n }\n : {}),\n };\n });\n const allowed = [\n \"card\",\n \"bank_transfer\",\n \"wallet\",\n \"zap\",\n \"apple_pay\",\n \"google_pay\",\n ];\n const paymentMethods = list(source.payment_methods)\n .map(text)\n .filter((method) => {\n if (allowed.includes(method)) {\n return true;\n }\n console.warn(\n `Zappie payment method \"${method}\" is newer than this SDK and was skipped`,\n );\n return false;\n });\n const aggregate = {\n pickup: false,\n delivery: false,\n dineIn: false,\n scheduling: false,\n };\n for (const branch of branches) {\n for (const key of Object.keys(aggregate) as (keyof Fulfilment)[]) {\n aggregate[key] ||= branch.fulfilment[key];\n }\n }\n const accent =\n brand.accent ?? (brand.colors ? record(brand.colors).accent : undefined);\n const templateSettings = record(source.template_settings ?? {}) as Record<\n string,\n string | number | boolean\n >;\n return {\n id: text(source.id),\n slug: text(source.slug),\n name: text(source.name),\n currency: money(0, text(source.currency) as Currency).currency,\n timezone: text(source.timezone),\n ...opt(\"description\", source.description, isString),\n ...(source.template_settings !== undefined ? { templateSettings } : {}),\n brand: {\n ...opt(\"logoUrl\", brand.logo_url, isString),\n ...opt(\"coverImageUrl\", brand.cover_image_url, isString),\n ...(isString(accent) ? { accent } : {}),\n },\n fulfilment: source.fulfilment ? fulfilment(source.fulfilment) : aggregate,\n paymentMethods: paymentMethods as Store[\"paymentMethods\"],\n branches,\n };\n}\n"],"mappings":"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","names":["coordinates","value"]}
package/package.json CHANGED
@@ -1,6 +1,52 @@
1
1
  {
2
2
  "name": "@zappie/verify",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.2",
4
+ "description": "Deterministic verification for Zappie storefronts.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/zappie/storefront.git",
9
+ "directory": "packages/verify"
10
+ },
11
+ "homepage": "https://github.com/zappie/storefront#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/zappie/storefront/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./schema/verify-report.v1.json": "./schema/verify-report.v1.json"
25
+ },
26
+ "dependencies": {
27
+ "@axe-core/playwright": "4.13.0",
28
+ "@zappie/sdk": "^0.1.2"
29
+ },
30
+ "devDependencies": {
31
+ "ajv": "^8.18.0",
32
+ "@playwright/test": "^1.62.1"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "schema",
37
+ "CHANGELOG.md"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": false
42
+ },
43
+ "peerDependencies": {
44
+ "@playwright/test": "^1.62.1"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup",
48
+ "test": "vitest run",
49
+ "e2e": "playwright test --config playwright.config.ts",
50
+ "check-types": "tsc --noEmit -p ."
51
+ }
6
52
  }