@zappie/sdk 0.1.1 → 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.
Files changed (188) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cart/errors.js +13 -0
  3. package/dist/cart/errors.js.map +7 -0
  4. package/dist/cart/index.js +768 -0
  5. package/dist/cart/index.js.map +7 -0
  6. package/dist/cart/item.js +12 -1
  7. package/dist/cart/item.js.map +7 -1
  8. package/dist/cart/preview.js +32 -0
  9. package/dist/cart/preview.js.map +7 -0
  10. package/dist/cart/price.js +25 -0
  11. package/dist/cart/price.js.map +7 -0
  12. package/dist/cart/quote-client.js +76 -0
  13. package/dist/cart/quote-client.js.map +7 -0
  14. package/dist/cart/quote-response.js +178 -0
  15. package/dist/cart/quote-response.js.map +7 -0
  16. package/dist/cart/reconcile.js +108 -0
  17. package/dist/cart/reconcile.js.map +7 -0
  18. package/dist/cart/source-menu.js +21 -0
  19. package/dist/cart/source-menu.js.map +7 -0
  20. package/dist/cart/storage.js +174 -0
  21. package/dist/cart/storage.js.map +7 -0
  22. package/dist/cart/types.js +1 -0
  23. package/dist/cart/types.js.map +7 -0
  24. package/dist/cart/validate.js +265 -0
  25. package/dist/cart/validate.js.map +7 -0
  26. package/dist/cart/wire.js +42 -0
  27. package/dist/cart/wire.js.map +7 -0
  28. package/dist/cart/working-set.js +80 -0
  29. package/dist/cart/working-set.js.map +7 -0
  30. package/dist/checkout/client.js +102 -0
  31. package/dist/checkout/client.js.map +7 -0
  32. package/dist/checkout/error.js +21 -0
  33. package/dist/checkout/error.js.map +7 -0
  34. package/dist/checkout/index.js +13 -1
  35. package/dist/checkout/index.js.map +7 -1
  36. package/dist/checkout/readiness.js +36 -0
  37. package/dist/checkout/readiness.js.map +7 -0
  38. package/dist/checkout/sdk.js +9 -0
  39. package/dist/checkout/sdk.js.map +7 -0
  40. package/dist/checkout/session.js +58 -0
  41. package/dist/checkout/session.js.map +7 -0
  42. package/dist/checkout/types.js +1 -0
  43. package/dist/checkout/types.js.map +7 -0
  44. package/dist/client.js +253 -0
  45. package/dist/client.js.map +7 -0
  46. package/dist/config/index.js +13 -1
  47. package/dist/config/index.js.map +7 -0
  48. package/dist/config.js +28 -0
  49. package/dist/config.js.map +7 -0
  50. package/dist/customer/client.js +149 -0
  51. package/dist/customer/client.js.map +7 -0
  52. package/dist/customer/error.js +20 -0
  53. package/dist/customer/error.js.map +7 -0
  54. package/dist/customer/index.js +8 -3
  55. package/dist/customer/index.js.map +7 -1
  56. package/dist/customer/navigation.js +45 -0
  57. package/dist/customer/navigation.js.map +7 -0
  58. package/dist/customer/react.js +67 -2
  59. package/dist/customer/react.js.map +7 -1
  60. package/dist/customer/request.js +37 -0
  61. package/dist/customer/request.js.map +7 -0
  62. package/dist/customer/sdk.js +6 -0
  63. package/dist/customer/sdk.js.map +7 -0
  64. package/dist/customer/types.js +1 -0
  65. package/dist/customer/types.js.map +7 -0
  66. package/dist/customer/watch-order.js +80 -0
  67. package/dist/customer/watch-order.js.map +7 -0
  68. package/dist/debug.js +78 -1
  69. package/dist/debug.js.map +7 -1
  70. package/dist/errors.js +69 -0
  71. package/dist/errors.js.map +7 -0
  72. package/dist/index.js +29 -1
  73. package/dist/index.js.map +7 -1
  74. package/dist/internal/abort.js +29 -0
  75. package/dist/internal/abort.js.map +7 -0
  76. package/dist/internal/cache.js +142 -0
  77. package/dist/internal/cache.js.map +7 -0
  78. package/dist/internal/customer-http.js +74 -0
  79. package/dist/internal/customer-http.js.map +7 -0
  80. package/dist/internal/digest.js +15 -0
  81. package/dist/internal/digest.js.map +7 -0
  82. package/dist/internal/emitter.js +27 -0
  83. package/dist/internal/emitter.js.map +7 -0
  84. package/dist/internal/money-math.js +30 -0
  85. package/dist/internal/money-math.js.map +7 -0
  86. package/dist/internal.js +9 -1
  87. package/dist/internal.js.map +7 -1
  88. package/dist/models.js +1 -0
  89. package/dist/models.js.map +7 -0
  90. package/dist/money.js +58 -0
  91. package/dist/money.js.map +7 -0
  92. package/dist/persist/catalogue.js +206 -2
  93. package/dist/persist/catalogue.js.map +7 -1
  94. package/dist/places/client.js +97 -0
  95. package/dist/places/client.js.map +7 -0
  96. package/dist/places/error.js +20 -0
  97. package/dist/places/error.js.map +7 -0
  98. package/dist/places/index.js +8 -1
  99. package/dist/places/index.js.map +7 -1
  100. package/dist/places/request.js +40 -0
  101. package/dist/places/request.js.map +7 -0
  102. package/dist/places/sdk.js +10 -0
  103. package/dist/places/sdk.js.map +7 -0
  104. package/dist/places/search.js +80 -0
  105. package/dist/places/search.js.map +7 -0
  106. package/dist/places/types.js +1 -0
  107. package/dist/places/types.js.map +7 -0
  108. package/dist/react/cart.js +30 -0
  109. package/dist/react/cart.js.map +7 -0
  110. package/dist/react/hooks.js +91 -0
  111. package/dist/react/hooks.js.map +7 -0
  112. package/dist/react/index.js +18 -1
  113. package/dist/react/index.js.map +7 -1
  114. package/dist/react/provider.js +34 -0
  115. package/dist/react/provider.js.map +7 -0
  116. package/dist/server/index.js +32 -2
  117. package/dist/server/index.js.map +7 -1
  118. package/dist/server/storefront-url/build-v1.js +75 -0
  119. package/dist/server/storefront-url/build-v1.js.map +7 -0
  120. package/dist/server/storefront-url/build-v2.js +79 -0
  121. package/dist/server/storefront-url/build-v2.js.map +7 -0
  122. package/dist/server/storefront-url/build.js +19 -0
  123. package/dist/server/storefront-url/build.js.map +7 -0
  124. package/dist/server/storefront-url/contract.js +25 -0
  125. package/dist/server/storefront-url/contract.js.map +7 -0
  126. package/dist/server/storefront-url/index.js +16 -0
  127. package/dist/server/storefront-url/index.js.map +7 -0
  128. package/dist/server/storefront-url/installation.js +58 -0
  129. package/dist/server/storefront-url/installation.js.map +7 -0
  130. package/dist/server/storefront-url/params.js +72 -0
  131. package/dist/server/storefront-url/params.js.map +7 -0
  132. package/dist/server/storefront-url/parse-v1.js +135 -0
  133. package/dist/server/storefront-url/parse-v1.js.map +7 -0
  134. package/dist/server/storefront-url/parse-v2.js +139 -0
  135. package/dist/server/storefront-url/parse-v2.js.map +7 -0
  136. package/dist/server/storefront-url/parse.js +9 -0
  137. package/dist/server/storefront-url/parse.js.map +7 -0
  138. package/dist/server/storefront-url/redirect.js +30 -0
  139. package/dist/server/storefront-url/redirect.js.map +7 -0
  140. package/dist/server/storefront-url/sign-in.js +17 -0
  141. package/dist/server/storefront-url/sign-in.js.map +7 -0
  142. package/dist/server/storefront-url/types.js +55 -0
  143. package/dist/server/storefront-url/types.js.map +7 -0
  144. package/dist/server/storefront-url/url.js +61 -0
  145. package/dist/server/storefront-url/url.js.map +7 -0
  146. package/dist/server/webhook.js +96 -0
  147. package/dist/server/webhook.js.map +7 -0
  148. package/dist/site-config/helpers.js +59 -0
  149. package/dist/site-config/helpers.js.map +7 -0
  150. package/dist/site-config/sections.js +105 -0
  151. package/dist/site-config/sections.js.map +7 -0
  152. package/dist/site-config/types.js +31 -0
  153. package/dist/site-config/types.js.map +7 -0
  154. package/dist/site-config/validate.js +165 -0
  155. package/dist/site-config/validate.js.map +7 -0
  156. package/dist/template/index.js +46 -3
  157. package/dist/template/index.js.map +7 -1
  158. package/dist/template/react.js +81 -2
  159. package/dist/template/react.js.map +7 -1
  160. package/dist/testing/index.js +451 -2
  161. package/dist/testing/index.js.map +7 -1
  162. package/dist/transport/category-page.js +21 -2
  163. package/dist/transport/category-page.js.map +7 -1
  164. package/dist/transport/mappers.js +326 -0
  165. package/dist/transport/mappers.js.map +7 -0
  166. package/dist/transport/rest.js +313 -0
  167. package/dist/transport/rest.js.map +7 -0
  168. package/dist/transport/search.js +32 -2
  169. package/dist/transport/search.js.map +7 -1
  170. package/dist/transport/selected-menu.js +79 -2
  171. package/dist/transport/selected-menu.js.map +7 -1
  172. package/dist/transport/store.js +120 -2
  173. package/dist/transport/store.js.map +7 -1
  174. package/dist/transport/types.js +1 -0
  175. package/dist/transport/types.js.map +7 -0
  176. package/dist/url/index.js +16 -2
  177. package/dist/url/index.js.map +7 -1
  178. package/dist/visits/index.js +62 -1
  179. package/dist/visits/index.js.map +7 -1
  180. package/dist/visits/page-views.js +103 -0
  181. package/dist/visits/page-views.js.map +7 -0
  182. package/package.json +3 -3
  183. package/dist/cart/quote.js +0 -1
  184. package/dist/cart/quote.js.map +0 -1
  185. package/dist/chunk-I55A5QXJ.js +0 -2
  186. package/dist/chunk-I55A5QXJ.js.map +0 -1
  187. package/dist/chunk-LFBQICYU.js +0 -2
  188. package/dist/chunk-LFBQICYU.js.map +0 -1
@@ -0,0 +1,326 @@
1
+ // src/transport/mappers.ts
2
+ import { ZappieError } from "../errors.js";
3
+ import { money } from "../money.js";
4
+ var UnsupportedValue = class extends Error {
5
+ };
6
+ function record(value) {
7
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
8
+ throw new ZappieError("server_error", "Invalid storefront response object");
9
+ }
10
+ return value;
11
+ }
12
+ function text(value) {
13
+ if (typeof value !== "string") {
14
+ throw new ZappieError("server_error", "Invalid storefront response string");
15
+ }
16
+ return value;
17
+ }
18
+ function list(value) {
19
+ if (!Array.isArray(value)) {
20
+ throw new ZappieError("server_error", "Invalid storefront response list");
21
+ }
22
+ return value;
23
+ }
24
+ function number(value) {
25
+ if (typeof value !== "number" || !Number.isFinite(value)) {
26
+ throw new ZappieError("server_error", "Invalid storefront response number");
27
+ }
28
+ return value;
29
+ }
30
+ function nonnegative(value) {
31
+ const result = number(value);
32
+ if (result < 0) {
33
+ throw new ZappieError(
34
+ "server_error",
35
+ "Negative selection or quantity bound"
36
+ );
37
+ }
38
+ return result;
39
+ }
40
+ function bounds(value) {
41
+ const input = record(value);
42
+ const min = nonnegative(input.min);
43
+ if (input.max == null) {
44
+ return { min };
45
+ }
46
+ const max = nonnegative(input.max);
47
+ if (max < min) {
48
+ throw new ZappieError(
49
+ "server_error",
50
+ "Maximum quantity is below its minimum"
51
+ );
52
+ }
53
+ return { min, max };
54
+ }
55
+ var isString = (value) => typeof value === "string";
56
+ var isBoolean = (value) => typeof value === "boolean";
57
+ function opt(key, value, is) {
58
+ return is(value) ? { [key]: value } : {};
59
+ }
60
+ function mapMoney(value) {
61
+ const v = record(value);
62
+ return money(
63
+ number(v.amount_minor ?? v.amountMinor),
64
+ text(v.currency)
65
+ );
66
+ }
67
+ function hours(value) {
68
+ return list(value ?? []).map((slot) => {
69
+ const v = record(slot);
70
+ const day = number(v.day);
71
+ if (!Number.isInteger(day) || day < 0 || day > 6) {
72
+ throw new ZappieError("server_error", "Invalid opening day");
73
+ }
74
+ return {
75
+ day,
76
+ opens: text(v.opens ?? v.open ?? v.start),
77
+ closes: text(v.closes ?? v.close ?? v.end),
78
+ ...opt("disabled", v.disabled, isBoolean)
79
+ };
80
+ });
81
+ }
82
+ function schedule(value) {
83
+ if (value === void 0) {
84
+ return void 0;
85
+ }
86
+ const v = record(value);
87
+ return {
88
+ openingTimes: hours(v.opening_times ?? v.openingTimes ?? v.hours ?? []),
89
+ ...opt("preorder", v.preorder, isBoolean),
90
+ ...v.preorder_notice_hours !== void 0 ? { preorderNoticeHours: number(v.preorder_notice_hours) } : {},
91
+ ...v.preorder_max_days !== void 0 ? { preorderMaxDays: number(v.preorder_max_days) } : {}
92
+ };
93
+ }
94
+ function mapGroup(value) {
95
+ const g = record(value);
96
+ const selection = record(g.selection ?? { min: g.min ?? 0, max: g.max });
97
+ const isAttribute = g.is_attribute === true;
98
+ const kind = text(g.kind ?? "select");
99
+ if (!["select", "text", "number", "date", "time", "toggle"].includes(kind)) {
100
+ throw new UnsupportedValue(`option input kind "${kind}"`);
101
+ }
102
+ return {
103
+ id: text(g.id),
104
+ name: text(g.name),
105
+ kind,
106
+ required: typeof g.required === "boolean" ? g.required : number(selection.min) > 0,
107
+ selection: bounds(selection),
108
+ isAttribute,
109
+ choices: list(g.choices ?? []).map((raw) => {
110
+ const c = record(raw);
111
+ const price = mapMoney(c.price);
112
+ return {
113
+ id: text(c.id),
114
+ name: text(c.name),
115
+ price: isAttribute ? money(0, price.currency) : price,
116
+ ...isAttribute && c.price_hint !== void 0 ? { priceHint: mapMoney(c.price_hint) } : {},
117
+ isAvailable: (c.is_available ?? c.available) === true,
118
+ ...c.quantity ? { quantity: bounds(c.quantity) } : {}
119
+ };
120
+ }),
121
+ ...opt("helpText", g.help_text, isString),
122
+ ...g.input ? {
123
+ input: {
124
+ ...record(g.input).max_length !== void 0 ? { maxLength: number(record(g.input).max_length) } : {},
125
+ ...typeof record(g.input).placeholder === "string" ? { placeholder: text(record(g.input).placeholder) } : {}
126
+ }
127
+ } : {},
128
+ ...opt("isAvailable", g.is_available, isBoolean),
129
+ ...opt("isInformational", g.is_informational, isBoolean),
130
+ ...g.conditions ? {
131
+ conditions: list(g.conditions).map((raw) => {
132
+ const c = record(raw);
133
+ const count = c.count ? record(c.count) : void 0;
134
+ if (count && !["eq", "gte", "lte"].includes(text(count.op))) {
135
+ throw new UnsupportedValue(
136
+ `option count operator "${text(count.op)}"`
137
+ );
138
+ }
139
+ if (c.match !== void 0 && c.match !== "any" && c.match !== "all") {
140
+ throw new UnsupportedValue(
141
+ `option match operator "${String(c.match)}"`
142
+ );
143
+ }
144
+ if (c.set_required != null && typeof c.set_required !== "boolean") {
145
+ throw new ZappieError(
146
+ "server_error",
147
+ "Invalid conditional required flag"
148
+ );
149
+ }
150
+ return {
151
+ groupId: text(c.group_id),
152
+ choiceIds: list(c.choice_ids ?? []).map(text),
153
+ match: c.match === "all" ? "all" : "any",
154
+ ...count ? {
155
+ count: {
156
+ op: text(count.op),
157
+ value: nonnegative(count.value)
158
+ }
159
+ } : {},
160
+ ...c.set_required !== void 0 ? { setRequired: c.set_required } : {},
161
+ ...c.set_min !== void 0 ? { setMin: c.set_min === null ? null : nonnegative(c.set_min) } : {},
162
+ ...c.set_max !== void 0 ? { setMax: c.set_max === null ? null : nonnegative(c.set_max) } : {}
163
+ };
164
+ })
165
+ } : {}
166
+ };
167
+ }
168
+ function mapPromo(value) {
169
+ const p = record(value);
170
+ return { name: text(p.name) };
171
+ }
172
+ function mapItem(value) {
173
+ const v = record(value);
174
+ const id = text(v.id);
175
+ const optionGroups = [];
176
+ let unsupported = false;
177
+ for (const raw of list(v.option_groups ?? [])) {
178
+ try {
179
+ optionGroups.push(mapGroup(raw));
180
+ } catch (error) {
181
+ if (!(error instanceof UnsupportedValue)) {
182
+ throw error;
183
+ }
184
+ unsupported = true;
185
+ console.warn(
186
+ `Zappie item ${id} uses an ${error.message} this SDK can't validate; it is shown as unavailable`
187
+ );
188
+ }
189
+ }
190
+ const variants = list(v.variants).map((raw) => {
191
+ const x = record(raw);
192
+ return {
193
+ id: text(x.id),
194
+ attributeChoices: Object.fromEntries(
195
+ Object.entries(record(x.attribute_choices ?? {})).map(([k, v2]) => [
196
+ k,
197
+ text(v2)
198
+ ])
199
+ ),
200
+ price: mapMoney(x.price),
201
+ ...opt("lowStock", x.low_stock, isBoolean),
202
+ isAvailable: !unsupported && (x.is_available ?? x.available) === true
203
+ };
204
+ });
205
+ if (!variants.length) {
206
+ throw new ZappieError(
207
+ "server_error",
208
+ "An item must carry at least one exact variant"
209
+ );
210
+ }
211
+ const quantity = record(
212
+ v.quantity ?? { min: 1, step: 1, max: v.max_quantity }
213
+ );
214
+ const step = number(quantity.step);
215
+ if (step <= 0 || new Set(variants.map((variant) => variant.id)).size !== variants.length) {
216
+ throw new ZappieError(
217
+ "server_error",
218
+ "Invalid quantity step or duplicate variant identity"
219
+ );
220
+ }
221
+ return {
222
+ ...v.placements !== void 0 ? {
223
+ placements: list(v.placements).map((raw) => {
224
+ const placement = record(raw);
225
+ return {
226
+ menuId: text(placement.menu_id),
227
+ categoryId: text(placement.category_id)
228
+ };
229
+ })
230
+ } : {},
231
+ id,
232
+ name: text(v.name),
233
+ images: list(v.images ?? []).map(text),
234
+ price: mapMoney(v.price),
235
+ isAvailable: !unsupported && (v.is_available ?? v.available) === true,
236
+ quantity: { ...bounds(quantity), step },
237
+ variants,
238
+ optionGroups,
239
+ tags: list(v.tags ?? []).map(text),
240
+ ...opt("stockLabel", v.stock_label, isString),
241
+ ...v.promo !== void 0 ? { promo: mapPromo(v.promo) } : {},
242
+ ...opt("description", v.description, isString),
243
+ ...v.compare_at_price ? { compareAtPrice: mapMoney(v.compare_at_price) } : {}
244
+ };
245
+ }
246
+ function mapMenu(value, branchId) {
247
+ const v = record(value);
248
+ const sources = list(v.menus ?? [v]);
249
+ if (v.completeness !== void 0 && v.completeness !== "partial" && v.completeness !== "complete") {
250
+ throw new ZappieError("server_error", "Invalid catalogue completeness");
251
+ }
252
+ const categories = /* @__PURE__ */ new Map();
253
+ const menus = sources.map((raw) => {
254
+ const m = record(raw);
255
+ if (m.placement_count !== void 0 && (!Number.isSafeInteger(m.placement_count) || Number(m.placement_count) < 0) || m.paginated !== void 0 && typeof m.paginated !== "boolean") {
256
+ throw new ZappieError("server_error", "Invalid menu loading metadata");
257
+ }
258
+ const categoryIds = list(m.categories ?? []).map((rawCategory) => {
259
+ const c = record(rawCategory);
260
+ const id = text(c.id);
261
+ const previous = categories.get(id);
262
+ const itemIds = [
263
+ .../* @__PURE__ */ new Set([
264
+ ...previous?.itemIds ?? [],
265
+ ...list(c.item_ids ?? []).map(text)
266
+ ])
267
+ ];
268
+ categories.set(id, {
269
+ id,
270
+ name: text(c.name),
271
+ itemIds,
272
+ ...opt("description", c.description, isString)
273
+ });
274
+ return id;
275
+ });
276
+ const s = schedule(m.schedule);
277
+ return {
278
+ id: text(m.id),
279
+ name: text(m.name),
280
+ categoryIds,
281
+ ...m.placement_count !== void 0 ? { placementCount: m.placement_count } : {},
282
+ ...opt("paginated", m.paginated, isBoolean),
283
+ ...s ? { schedule: s } : {},
284
+ ...opt("infoBanner", m.info_banner, isString)
285
+ };
286
+ });
287
+ const items = Object.fromEntries(
288
+ Object.entries(record(v.items)).map(([id, item]) => {
289
+ const mapped = mapItem(item);
290
+ if (mapped.id !== id) {
291
+ throw new ZappieError(
292
+ "server_error",
293
+ "Item map key does not match its identity"
294
+ );
295
+ }
296
+ return [id, mapped];
297
+ })
298
+ );
299
+ if (v.branch_id !== void 0 && v.branch_id !== branchId) {
300
+ throw new ZappieError("server_error", "Menu branch mismatch");
301
+ }
302
+ return {
303
+ id: menus.length === 1 ? menus[0].id : `branch:${branchId}`,
304
+ branchId,
305
+ ...v.completeness === "partial" ? { completeness: "partial" } : {},
306
+ name: menus.length === 1 ? menus[0].name : "Catalogue",
307
+ version: text(v.menu_version ?? v.version),
308
+ categories: [...categories.values()],
309
+ items,
310
+ menus,
311
+ ...menus.length === 1 && menus[0].schedule ? { schedule: menus[0].schedule } : {}
312
+ };
313
+ }
314
+ export {
315
+ hours,
316
+ isBoolean,
317
+ isString,
318
+ list,
319
+ mapItem,
320
+ mapMenu,
321
+ mapMoney,
322
+ opt,
323
+ record,
324
+ text
325
+ };
326
+ //# sourceMappingURL=mappers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/transport/mappers.ts"],
4
+ "sourcesContent": ["import { ZappieError } from \"../errors\";\nimport type {\n Item,\n Menu,\n MenuSchedule,\n OpeningTime,\n OptionGroup,\n Variant,\n} from \"../models\";\nimport type { Currency, Money } from \"../money\";\nimport { money } from \"../money\";\n/** A value newer than this SDK; the item that carries it is shown as unavailable. */\nclass UnsupportedValue extends Error {}\nexport function record(value: unknown): Record<string, unknown> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n throw new ZappieError(\"server_error\", \"Invalid storefront response object\");\n }\n return value as Record<string, unknown>;\n}\nexport function text(value: unknown): string {\n if (typeof value !== \"string\") {\n throw new ZappieError(\"server_error\", \"Invalid storefront response string\");\n }\n return value;\n}\nexport function list(value: unknown): unknown[] {\n if (!Array.isArray(value)) {\n throw new ZappieError(\"server_error\", \"Invalid storefront response list\");\n }\n return value;\n}\nfunction number(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n throw new ZappieError(\"server_error\", \"Invalid storefront response number\");\n }\n return value;\n}\nfunction nonnegative(value: unknown): number {\n const result = number(value);\n if (result < 0) {\n throw new ZappieError(\n \"server_error\",\n \"Negative selection or quantity bound\",\n );\n }\n return result;\n}\nfunction bounds(value: unknown): { min: number; max?: number } {\n const input = record(value);\n const min = nonnegative(input.min);\n if (input.max == null) {\n return { min };\n }\n const max = nonnegative(input.max);\n if (max < min) {\n throw new ZappieError(\n \"server_error\",\n \"Maximum quantity is below its minimum\",\n );\n }\n return { min, max };\n}\nexport const isString = (value: unknown): value is string =>\n typeof value === \"string\";\nexport const isBoolean = (value: unknown): value is boolean =>\n typeof value === \"boolean\";\n/** `{ [key]: value }` when the value passes the guard, otherwise `{}`, for optional wire fields. */\nexport function opt<K extends string, T>(\n key: K,\n value: unknown,\n is: (value: unknown) => value is T,\n): { [P in K]?: T } {\n return is(value) ? ({ [key]: value } as { [P in K]?: T }) : {};\n}\nexport function mapMoney(value: unknown): Money {\n const v = record(value);\n return money(\n number(v.amount_minor ?? v.amountMinor),\n text(v.currency) as Currency,\n );\n}\nexport function hours(value: unknown): OpeningTime[] {\n return list(value ?? []).map((slot) => {\n const v = record(slot);\n const day = number(v.day);\n if (!Number.isInteger(day) || day < 0 || day > 6) {\n throw new ZappieError(\"server_error\", \"Invalid opening day\");\n }\n return {\n day: day as OpeningTime[\"day\"],\n opens: text(v.opens ?? v.open ?? v.start),\n closes: text(v.closes ?? v.close ?? v.end),\n ...opt(\"disabled\", v.disabled, isBoolean),\n };\n });\n}\nfunction schedule(value: unknown): MenuSchedule | undefined {\n if (value === undefined) {\n return undefined;\n }\n const v = record(value);\n return {\n openingTimes: hours(v.opening_times ?? v.openingTimes ?? v.hours ?? []),\n ...opt(\"preorder\", v.preorder, isBoolean),\n ...(v.preorder_notice_hours !== undefined\n ? { preorderNoticeHours: number(v.preorder_notice_hours) }\n : {}),\n ...(v.preorder_max_days !== undefined\n ? { preorderMaxDays: number(v.preorder_max_days) }\n : {}),\n };\n}\nfunction mapGroup(value: unknown): OptionGroup {\n const g = record(value);\n const selection = record(g.selection ?? { min: g.min ?? 0, max: g.max });\n const isAttribute = g.is_attribute === true;\n const kind = text(g.kind ?? \"select\");\n if (![\"select\", \"text\", \"number\", \"date\", \"time\", \"toggle\"].includes(kind)) {\n throw new UnsupportedValue(`option input kind \"${kind}\"`);\n }\n return {\n id: text(g.id),\n name: text(g.name),\n kind: kind as OptionGroup[\"kind\"],\n required:\n typeof g.required === \"boolean\" ? g.required : number(selection.min) > 0,\n selection: bounds(selection),\n isAttribute,\n choices: list(g.choices ?? []).map((raw) => {\n const c = record(raw);\n const price = mapMoney(c.price);\n return {\n id: text(c.id),\n name: text(c.name),\n price: isAttribute ? money(0, price.currency) : price,\n ...(isAttribute && c.price_hint !== undefined\n ? { priceHint: mapMoney(c.price_hint) }\n : {}),\n isAvailable: (c.is_available ?? c.available) === true,\n ...(c.quantity ? { quantity: bounds(c.quantity) } : {}),\n };\n }),\n ...opt(\"helpText\", g.help_text, isString),\n ...(g.input\n ? {\n input: {\n ...(record(g.input).max_length !== undefined\n ? { maxLength: number(record(g.input).max_length) }\n : {}),\n ...(typeof record(g.input).placeholder === \"string\"\n ? { placeholder: text(record(g.input).placeholder) }\n : {}),\n },\n }\n : {}),\n ...opt(\"isAvailable\", g.is_available, isBoolean),\n ...opt(\"isInformational\", g.is_informational, isBoolean),\n ...(g.conditions\n ? {\n conditions: list(g.conditions).map((raw) => {\n const c = record(raw);\n const count = c.count ? record(c.count) : undefined;\n if (count && ![\"eq\", \"gte\", \"lte\"].includes(text(count.op))) {\n throw new UnsupportedValue(\n `option count operator \"${text(count.op)}\"`,\n );\n }\n if (\n c.match !== undefined &&\n c.match !== \"any\" &&\n c.match !== \"all\"\n ) {\n throw new UnsupportedValue(\n `option match operator \"${String(c.match)}\"`,\n );\n }\n if (c.set_required != null && typeof c.set_required !== \"boolean\") {\n throw new ZappieError(\n \"server_error\",\n \"Invalid conditional required flag\",\n );\n }\n return {\n groupId: text(c.group_id),\n choiceIds: list(c.choice_ids ?? []).map(text),\n match: c.match === \"all\" ? (\"all\" as const) : (\"any\" as const),\n ...(count\n ? {\n count: {\n op: text(count.op) as \"eq\" | \"gte\" | \"lte\",\n value: nonnegative(count.value),\n },\n }\n : {}),\n ...(c.set_required !== undefined\n ? { setRequired: c.set_required as boolean | null }\n : {}),\n ...(c.set_min !== undefined\n ? { setMin: c.set_min === null ? null : nonnegative(c.set_min) }\n : {}),\n ...(c.set_max !== undefined\n ? { setMax: c.set_max === null ? null : nonnegative(c.set_max) }\n : {}),\n };\n }),\n }\n : {}),\n };\n}\nfunction mapPromo(value: unknown): NonNullable<Item[\"promo\"]> {\n const p = record(value);\n return { name: text(p.name) };\n}\nexport function mapItem(value: unknown): Item {\n const v = record(value);\n const id = text(v.id);\n const optionGroups: OptionGroup[] = [];\n let unsupported = false;\n for (const raw of list(v.option_groups ?? [])) {\n try {\n optionGroups.push(mapGroup(raw));\n } catch (error) {\n if (!(error instanceof UnsupportedValue)) {\n throw error;\n }\n unsupported = true;\n console.warn(\n `Zappie item ${id} uses an ${error.message} this SDK can't validate; it is shown as unavailable`,\n );\n }\n }\n const variants = list(v.variants).map((raw) => {\n const x = record(raw);\n return {\n id: text(x.id),\n attributeChoices: Object.fromEntries(\n Object.entries(record(x.attribute_choices ?? {})).map(([k, v]) => [\n k,\n text(v),\n ]),\n ),\n price: mapMoney(x.price),\n ...opt(\"lowStock\", x.low_stock, isBoolean),\n isAvailable: !unsupported && (x.is_available ?? x.available) === true,\n };\n });\n if (!variants.length) {\n throw new ZappieError(\n \"server_error\",\n \"An item must carry at least one exact variant\",\n );\n }\n const quantity = record(\n v.quantity ?? { min: 1, step: 1, max: v.max_quantity },\n );\n const step = number(quantity.step);\n if (\n step <= 0 ||\n new Set(variants.map((variant) => variant.id)).size !== variants.length\n ) {\n throw new ZappieError(\n \"server_error\",\n \"Invalid quantity step or duplicate variant identity\",\n );\n }\n return {\n ...(v.placements !== undefined\n ? {\n placements: list(v.placements).map((raw) => {\n const placement = record(raw);\n return {\n menuId: text(placement.menu_id),\n categoryId: text(placement.category_id),\n };\n }),\n }\n : {}),\n id,\n name: text(v.name),\n images: list(v.images ?? []).map(text),\n price: mapMoney(v.price),\n isAvailable: !unsupported && (v.is_available ?? v.available) === true,\n quantity: { ...bounds(quantity), step },\n variants: variants as [Variant, ...Variant[]],\n optionGroups,\n tags: list(v.tags ?? []).map(text),\n ...opt(\"stockLabel\", v.stock_label, isString),\n ...(v.promo !== undefined ? { promo: mapPromo(v.promo) } : {}),\n ...opt(\"description\", v.description, isString),\n ...(v.compare_at_price\n ? { compareAtPrice: mapMoney(v.compare_at_price) }\n : {}),\n };\n}\nexport function mapMenu(value: unknown, branchId: string): Menu {\n const v = record(value);\n const sources = list(v.menus ?? [v]);\n if (\n v.completeness !== undefined &&\n v.completeness !== \"partial\" &&\n v.completeness !== \"complete\"\n ) {\n throw new ZappieError(\"server_error\", \"Invalid catalogue completeness\");\n }\n const categories = new Map<string, Menu[\"categories\"][number]>();\n const menus = sources.map((raw) => {\n const m = record(raw);\n if (\n (m.placement_count !== undefined &&\n (!Number.isSafeInteger(m.placement_count) ||\n Number(m.placement_count) < 0)) ||\n (m.paginated !== undefined && typeof m.paginated !== \"boolean\")\n ) {\n throw new ZappieError(\"server_error\", \"Invalid menu loading metadata\");\n }\n const categoryIds = list(m.categories ?? []).map((rawCategory) => {\n const c = record(rawCategory);\n const id = text(c.id);\n const previous = categories.get(id);\n const itemIds = [\n ...new Set([\n ...(previous?.itemIds ?? []),\n ...list(c.item_ids ?? []).map(text),\n ]),\n ];\n categories.set(id, {\n id,\n name: text(c.name),\n itemIds,\n ...opt(\"description\", c.description, isString),\n });\n return id;\n });\n const s = schedule(m.schedule);\n return {\n id: text(m.id),\n name: text(m.name),\n categoryIds,\n ...(m.placement_count !== undefined\n ? { placementCount: m.placement_count as number }\n : {}),\n ...opt(\"paginated\", m.paginated, isBoolean),\n ...(s ? { schedule: s } : {}),\n ...opt(\"infoBanner\", m.info_banner, isString),\n };\n });\n const items = Object.fromEntries(\n Object.entries(record(v.items)).map(([id, item]) => {\n const mapped = mapItem(item);\n if (mapped.id !== id) {\n throw new ZappieError(\n \"server_error\",\n \"Item map key does not match its identity\",\n );\n }\n return [id, mapped];\n }),\n );\n if (v.branch_id !== undefined && v.branch_id !== branchId) {\n throw new ZappieError(\"server_error\", \"Menu branch mismatch\");\n }\n return {\n id: menus.length === 1 ? menus[0]!.id : `branch:${branchId}`,\n branchId,\n ...(v.completeness === \"partial\"\n ? { completeness: \"partial\" as const }\n : {}),\n name: menus.length === 1 ? menus[0]!.name : \"Catalogue\",\n version: text(v.menu_version ?? v.version),\n categories: [...categories.values()],\n items,\n menus,\n ...(menus.length === 1 && menus[0]!.schedule\n ? { schedule: menus[0]!.schedule }\n : {}),\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["v"]
7
+ }
@@ -0,0 +1,313 @@
1
+ // src/transport/rest.ts
2
+ import { ZappieError, wireError } from "../errors.js";
3
+ import { delay, throwIfAborted } from "../internal/abort.js";
4
+ import { payloadKey } from "../internal/digest.js";
5
+ import { money } from "../money.js";
6
+ import {
7
+ hours,
8
+ isBoolean,
9
+ isString,
10
+ list,
11
+ mapItem,
12
+ mapMenu,
13
+ mapMoney,
14
+ opt,
15
+ record,
16
+ text
17
+ } from "./mappers.js";
18
+ var MAX_RETRY_WAIT_MS = 5e3;
19
+ var RestTransport = class {
20
+ constructor(opts = {}) {
21
+ const url = new URL(opts.baseUrl ?? "https://api.zappie.ai/storefront/v1");
22
+ if (url.username || url.password || url.search || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))) {
23
+ throw new ZappieError(
24
+ "invalid_request",
25
+ "REST baseUrl requires HTTPS, or HTTP on localhost"
26
+ );
27
+ }
28
+ this.baseUrl = url.href.replace(/\/$/, "");
29
+ this.fetcher = opts.fetch ?? ((input, init) => globalThis.fetch(input, init));
30
+ }
31
+ async request(path, ctx, etag, body) {
32
+ if (typeof window !== "undefined" && ctx.key.startsWith("sk_")) {
33
+ throw new ZappieError(
34
+ "invalid_request",
35
+ "Secret keys cannot be sent from a browser"
36
+ );
37
+ }
38
+ for (let attempt = 0; ; attempt++) {
39
+ throwIfAborted(ctx.signal);
40
+ let response;
41
+ try {
42
+ response = await this.fetcher(this.baseUrl + path, {
43
+ method: body ? "POST" : "GET",
44
+ ...body ? { body: JSON.stringify(body) } : {},
45
+ // Workers reject redirect: "error"; a redirect is still refused below
46
+ // as a non-OK response (a 3xx, or opaqueredirect in browsers).
47
+ redirect: "manual",
48
+ headers: {
49
+ ...body ? { "Content-Type": "application/json" } : {},
50
+ ...ctx.idempotencyKey ? { "Idempotency-Key": ctx.idempotencyKey } : {},
51
+ Authorization: `Bearer ${ctx.key}`,
52
+ "Zappie-Version": ctx.version,
53
+ ...etag ? { "If-None-Match": etag } : {}
54
+ },
55
+ ...ctx.signal ? { signal: ctx.signal } : {}
56
+ });
57
+ } catch (error) {
58
+ throwIfAborted(ctx.signal);
59
+ if (attempt >= 2) {
60
+ throw new ZappieError(
61
+ "network_error",
62
+ "Could not reach the storefront service",
63
+ { cause: error }
64
+ );
65
+ }
66
+ await delay(100 * 2 ** attempt + Math.random() * 100, ctx.signal);
67
+ continue;
68
+ }
69
+ if (response.ok || response.status === 304) {
70
+ return response;
71
+ }
72
+ const retry = response.headers.get("Retry-After");
73
+ const seconds = retry !== null ? Number(retry) : NaN;
74
+ const wait = retry !== null ? Number.isFinite(seconds) ? Math.max(0, seconds * 1e3) : Math.max(0, Date.parse(retry) - Date.now()) : NaN;
75
+ const tooLong = Number.isFinite(wait) && wait > MAX_RETRY_WAIT_MS;
76
+ if (attempt < 2 && !tooLong && (response.status === 429 || response.status >= 500)) {
77
+ await response.body?.cancel();
78
+ await delay(
79
+ Number.isFinite(wait) ? wait : 100 * 2 ** attempt + Math.random() * 100,
80
+ ctx.signal
81
+ );
82
+ continue;
83
+ }
84
+ const errorBody = await response.json().catch(() => ({}));
85
+ throw wireError(
86
+ errorBody,
87
+ response.status,
88
+ response.headers.get("Zappie-Request-Id") ?? void 0
89
+ );
90
+ }
91
+ }
92
+ async json(response) {
93
+ try {
94
+ return await response.json();
95
+ } catch (cause) {
96
+ if (cause instanceof Error && cause.name === "AbortError") {
97
+ throw cause;
98
+ }
99
+ throw new ZappieError(
100
+ "server_error",
101
+ "The storefront service returned invalid JSON",
102
+ { status: response.status, cause }
103
+ );
104
+ }
105
+ }
106
+ async quote(ctx, body) {
107
+ const request = {
108
+ ...ctx,
109
+ idempotencyKey: ctx.idempotencyKey ?? await payloadKey(JSON.stringify(body))
110
+ };
111
+ const raw = await this.json(
112
+ await this.request("/quote", request, void 0, body)
113
+ );
114
+ return raw;
115
+ }
116
+ async getStore(ctx) {
117
+ const mapper = import("./store.js").catch((cause) => {
118
+ throw new ZappieError(
119
+ "server_error",
120
+ "Could not load the storefront store mapper",
121
+ { cause }
122
+ );
123
+ });
124
+ const response = this.request("/store", ctx).then(
125
+ (value) => this.json(value)
126
+ );
127
+ const [{ mapStore }, raw] = await Promise.all([mapper, response]);
128
+ return mapStore(
129
+ raw,
130
+ record,
131
+ text,
132
+ list,
133
+ hours,
134
+ mapMoney,
135
+ money,
136
+ opt,
137
+ isString,
138
+ isBoolean
139
+ );
140
+ }
141
+ async getMenu(ctx) {
142
+ let cursor;
143
+ const seen = /* @__PURE__ */ new Set();
144
+ let output;
145
+ let etag;
146
+ do {
147
+ const query = new URLSearchParams({
148
+ branch: ctx.branchId,
149
+ ...ctx.metadataOnly ? { metadata_only: "true" } : {},
150
+ ...cursor ? { cursor } : {}
151
+ });
152
+ const response = await this.request(
153
+ `/menus?${query}`,
154
+ ctx,
155
+ cursor ? void 0 : ctx.ifNoneMatch
156
+ );
157
+ if (response.status === 304) {
158
+ if (cursor) {
159
+ throw new ZappieError(
160
+ "server_error",
161
+ "Unexpected 304 on a continuation page"
162
+ );
163
+ }
164
+ return {
165
+ notModified: true,
166
+ ...response.headers.get("ETag") ? { etag: response.headers.get("ETag") } : {}
167
+ };
168
+ }
169
+ const raw = record(await this.json(response));
170
+ const page = mapMenu(raw, ctx.branchId);
171
+ if (ctx.metadataOnly) {
172
+ if (Object.keys(page.items).length || raw.next_cursor) {
173
+ throw new ZappieError("server_error", "Invalid metadata response");
174
+ }
175
+ return { menu: { ...page, completeness: "partial" } };
176
+ }
177
+ if (!output) {
178
+ output = page;
179
+ etag = response.headers.get("ETag") ?? void 0;
180
+ } else {
181
+ if (page.version !== output.version) {
182
+ throw new ZappieError(
183
+ "server_error",
184
+ "Menu changed while fetching pages; retry the read"
185
+ );
186
+ }
187
+ Object.assign(output.items, page.items);
188
+ for (const category of page.categories) {
189
+ const existing = output.categories.find((c) => c.id === category.id);
190
+ if (existing) {
191
+ existing.itemIds = [
192
+ .../* @__PURE__ */ new Set([...existing.itemIds, ...category.itemIds])
193
+ ];
194
+ } else {
195
+ output.categories.push(category);
196
+ }
197
+ }
198
+ for (const section of page.menus ?? []) {
199
+ const existing = output.menus?.find((m) => m.id === section.id);
200
+ if (existing) {
201
+ existing.categoryIds = [
202
+ .../* @__PURE__ */ new Set([...existing.categoryIds, ...section.categoryIds])
203
+ ];
204
+ } else {
205
+ output.menus?.push(section);
206
+ }
207
+ }
208
+ }
209
+ cursor = typeof raw.next_cursor === "string" && raw.next_cursor ? raw.next_cursor : void 0;
210
+ if (cursor) {
211
+ if (seen.has(cursor)) {
212
+ throw new ZappieError(
213
+ "server_error",
214
+ "Repeated menu pagination cursor"
215
+ );
216
+ }
217
+ seen.add(cursor);
218
+ }
219
+ } while (cursor);
220
+ if (output.menus && output.menus.length > 1) {
221
+ output.id = `branch:${ctx.branchId}`;
222
+ output.name = "Catalogue";
223
+ delete output.schedule;
224
+ }
225
+ for (const category of output.categories) {
226
+ for (const id of category.itemIds) {
227
+ if (!Object.prototype.hasOwnProperty.call(output.items, id)) {
228
+ throw new ZappieError(
229
+ "server_error",
230
+ "Category refers to a missing item"
231
+ );
232
+ }
233
+ }
234
+ }
235
+ if (Object.keys(output.items).length > 2e3) {
236
+ console.warn("Zappie menu contains more than 2,000 items");
237
+ }
238
+ return { menu: output, ...etag ? { etag } : {} };
239
+ }
240
+ async getSelectedMenu(ctx) {
241
+ return (await import("./selected-menu.js")).selectedMenu(
242
+ ctx,
243
+ (path) => this.request(path, ctx, ctx.ifNoneMatch),
244
+ (response) => this.json(response),
245
+ mapMenu,
246
+ () => new ZappieError("server_error", "Invalid selected menu")
247
+ );
248
+ }
249
+ async search(ctx) {
250
+ return (await import("./search.js")).searchPage(
251
+ ctx,
252
+ async (path) => this.json(await this.request(path, ctx)),
253
+ mapMenu,
254
+ () => new ZappieError("server_error", "Invalid search page")
255
+ );
256
+ }
257
+ async getCategoryPage(ctx) {
258
+ const query = new URLSearchParams({
259
+ branch: ctx.branchId,
260
+ ...ctx.cursor ? { cursor: ctx.cursor } : {}
261
+ });
262
+ const raw = await this.json(
263
+ await this.request(
264
+ `/menus/${encodeURIComponent(ctx.menuId)}/categories/${encodeURIComponent(ctx.categoryId)}/items?${query}`,
265
+ ctx
266
+ )
267
+ );
268
+ return (await import("./category-page.js")).categoryPage(
269
+ record(raw),
270
+ ctx,
271
+ mapMenu,
272
+ () => new ZappieError("server_error", "Invalid category page")
273
+ );
274
+ }
275
+ async getMenuVersion(ctx) {
276
+ const value = record(
277
+ await this.json(
278
+ await this.request(
279
+ `/menus/version?${new URLSearchParams({ branch: ctx.branchId })}`,
280
+ ctx
281
+ )
282
+ )
283
+ );
284
+ if (typeof value.menu_version !== "string" || typeof value.availability_hash !== "string") {
285
+ throw new ZappieError("server_error", "Invalid menu version response");
286
+ }
287
+ return {
288
+ version: value.menu_version,
289
+ availabilityHash: value.availability_hash
290
+ };
291
+ }
292
+ async getItem(ctx) {
293
+ const item = mapItem(
294
+ await this.json(
295
+ await this.request(
296
+ `/items/${encodeURIComponent(ctx.itemId)}?${new URLSearchParams({ branch: ctx.branchId })}`,
297
+ ctx
298
+ )
299
+ )
300
+ );
301
+ if (item.id !== ctx.itemId) {
302
+ throw new ZappieError(
303
+ "server_error",
304
+ "Item response identity does not match the request"
305
+ );
306
+ }
307
+ return item;
308
+ }
309
+ };
310
+ export {
311
+ RestTransport
312
+ };
313
+ //# sourceMappingURL=rest.js.map