@umxr/bagsy 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,375 @@
1
+ // ../core/src/ids.ts
2
+ function toGid(resource, ref) {
3
+ const value = String(ref).trim();
4
+ const prefix = `gid://shopify/${resource}/`;
5
+ if (value.startsWith(prefix)) {
6
+ const id = value.slice(prefix.length);
7
+ if (/^\d+$/.test(id)) return value;
8
+ } else if (/^\d+$/.test(value)) {
9
+ return `${prefix}${value}`;
10
+ }
11
+ throw new TypeError(`Expected a numeric ID or ${prefix}<id>, received "${value}"`);
12
+ }
13
+ var productGid = (ref) => toGid("Product", ref);
14
+ var variantGid = (ref) => toGid("ProductVariant", ref);
15
+ var customerGid = (ref) => toGid("Customer", ref);
16
+
17
+ // ../core/src/operations.ts
18
+ var MAX_LOOKUP = 100;
19
+ var listPath = (listId) => `/lists/${encodeURIComponent(listId)}`;
20
+ var itemPath = (listId, itemId) => `${listPath(listId)}/items/${encodeURIComponent(itemId)}`;
21
+ var listLists = async (t) => (await t.request("GET", "/lists")).data;
22
+ var getList = (t, listId = "default", { includeItems = false } = {}) => t.request("GET", listPath(listId), { query: { include: includeItems ? "items" : void 0 } });
23
+ var createList = (t, name) => t.request("POST", "/lists", { body: { name } });
24
+ var renameList = (t, listId, name) => t.request("PATCH", listPath(listId), { body: { name } });
25
+ var deleteList = async (t, listId) => {
26
+ await t.request("DELETE", listPath(listId));
27
+ };
28
+ var shareList = async (t, listId = "default", { rotate = false } = {}) => (await t.request("POST", `${listPath(listId)}/share`, { query: { rotate: rotate ? "true" : void 0 } })).shareToken;
29
+ var unshareList = async (t, listId = "default") => {
30
+ await t.request("DELETE", `${listPath(listId)}/share`);
31
+ };
32
+ var itemBody = (product, variantId, extra = {}) => ({
33
+ productId: productGid(product),
34
+ ...variantId !== void 0 && { variantId: variantGid(variantId) },
35
+ ...extra.quantity !== void 0 && { quantity: extra.quantity },
36
+ ...extra.note !== void 0 && { note: extra.note }
37
+ });
38
+ var listItems = (t, listId = "default", page = {}) => t.request("GET", `${listPath(listId)}/items`, { query: { ...page } });
39
+ async function* iterateItems(t, listId = "default", { limit = 100 } = {}) {
40
+ let after;
41
+ do {
42
+ const page = await listItems(t, listId, { limit, after });
43
+ yield* page.data;
44
+ after = page.pageInfo.hasNextPage ? page.pageInfo.endCursor ?? void 0 : void 0;
45
+ } while (after);
46
+ }
47
+ var addItem = (t, product, { listId = "default", variantId, quantity, note } = {}) => t.request("POST", `${listPath(listId)}/items`, { body: itemBody(product, variantId, { quantity, note }) });
48
+ var removeProduct = async (t, product, { listId = "default", variantId } = {}) => {
49
+ await t.request("DELETE", `${listPath(listId)}/items`, {
50
+ query: { productId: productGid(product), variantId: variantId === void 0 ? void 0 : variantGid(variantId) }
51
+ });
52
+ };
53
+ async function toggleItem(t, product, { listId = "default", variantId } = {}) {
54
+ const { status } = await t.send("POST", `${listPath(listId)}/items`, { body: itemBody(product, variantId) });
55
+ if (status === 201) return { saved: true };
56
+ await removeProduct(t, product, { listId, variantId });
57
+ return { saved: false };
58
+ }
59
+ var updateItem = (t, itemId, patch, { listId = "default" } = {}) => {
60
+ const body = {
61
+ ...patch.quantity !== void 0 && { quantity: patch.quantity },
62
+ ...patch.note !== void 0 && { note: patch.note },
63
+ ...patch.variantId !== void 0 && { variantId: patch.variantId === null ? null : variantGid(patch.variantId) }
64
+ };
65
+ return t.request("PATCH", itemPath(listId, itemId), { body });
66
+ };
67
+ var removeItem = async (t, itemId, { listId = "default" } = {}) => {
68
+ await t.request("DELETE", itemPath(listId, itemId));
69
+ };
70
+ async function lookupSaved(t, products) {
71
+ const ids = [...new Set([...products].map(productGid))];
72
+ const chunks = [];
73
+ for (let i = 0; i < ids.length; i += MAX_LOOKUP) chunks.push(ids.slice(i, i + MAX_LOOKUP));
74
+ const pages = await Promise.all(
75
+ chunks.map((productIds) => t.request("GET", "/saved", { query: { productIds } }))
76
+ );
77
+ const byProduct = {};
78
+ for (const id of ids) byProduct[id] = [];
79
+ for (const page of pages) Object.assign(byProduct, page.data);
80
+ return {
81
+ byProduct,
82
+ get: (product) => byProduct[productGid(product)] ?? [],
83
+ has(product, variant) {
84
+ const entries = byProduct[productGid(product)] ?? [];
85
+ if (variant === void 0) return entries.length > 0;
86
+ const gid = variantGid(variant);
87
+ return entries.some((entry) => entry.variantId === gid || entry.variantId == null);
88
+ }
89
+ };
90
+ }
91
+ function mergeVisitor(t, visitorId = t.options.visitorId) {
92
+ if (!visitorId) throw new TypeError("identity.merge: no visitorId to merge.");
93
+ if (t.options.mode === "server" && t.options.customerId === void 0) {
94
+ throw new TypeError("identity.merge: set `customerId` (e.g. via bagsy.as({ customerId })) first.");
95
+ }
96
+ return t.request("POST", "/identity/merge", { body: { visitorId } });
97
+ }
98
+ var getSharedList = (t, shareToken, page = {}) => t.request("GET", `/shared/${encodeURIComponent(shareToken)}`, { query: { ...page } });
99
+ async function topProducts(t, query = {}) {
100
+ if (t.options.mode !== "server") {
101
+ throw new Error("bagsy.insights.topProducts() needs a server key; call it from your server.");
102
+ }
103
+ const since = query.since instanceof Date ? query.since.toISOString() : query.since;
104
+ return (await t.request("GET", "/insights/top-products", { query: { since, limit: query.limit } })).data;
105
+ }
106
+
107
+ // ../core/src/errors.ts
108
+ var BagsyError = class extends Error {
109
+ name = "BagsyError";
110
+ status;
111
+ code;
112
+ problem;
113
+ constructor(status, problem) {
114
+ const parsed = isProblem(problem) ? problem : void 0;
115
+ super(parsed?.detail ?? parsed?.title ?? `Bagsy request failed with status ${status}`);
116
+ this.status = status;
117
+ this.code = parsed?.code ?? "unexpected_response";
118
+ this.problem = parsed;
119
+ }
120
+ };
121
+ function isProblem(value) {
122
+ return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.status === "number";
123
+ }
124
+
125
+ // ../core/src/transport.ts
126
+ function createTransport(options) {
127
+ if (options.mode === "server") {
128
+ if (!options.apiKey) throw new TypeError("createBagsy: `apiKey` is required in server mode.");
129
+ const browser = globalThis;
130
+ if (browser.window !== void 0 && browser.document !== void 0) {
131
+ throw new Error(
132
+ "createBagsy: server mode exposes your secret key and must not run in a browser. Use mode: 'proxy' in themes, or call Bagsy from your Hydrogen loaders and actions."
133
+ );
134
+ }
135
+ }
136
+ const baseUrl = (options.baseUrl ?? (options.mode === "proxy" ? `${(options.proxyPath ?? "/apps/bagsy").replace(/\/$/, "")}/v1` : "https://api.bagsy.dev/v1")).replace(/\/$/, "");
137
+ const headers = { Accept: "application/json" };
138
+ if (options.visitorId) headers["Bagsy-Visitor-Id"] = options.visitorId;
139
+ if (options.mode === "server") {
140
+ headers.Authorization = `Bearer ${options.apiKey}`;
141
+ if (options.customerId !== void 0) headers["Bagsy-Customer-Id"] = customerGid(options.customerId);
142
+ }
143
+ async function send(method, path, { query = {}, body } = {}) {
144
+ const params = new URLSearchParams();
145
+ for (const [key, value] of Object.entries(query)) {
146
+ if (value !== void 0) params.set(key, Array.isArray(value) ? value.join(",") : String(value));
147
+ }
148
+ if (options.mode === "proxy" && options.visitorId) params.set("visitor_id", options.visitorId);
149
+ const search = params.toString();
150
+ const response = await (options.fetch ?? fetch)(`${baseUrl}${path}${search ? `?${search}` : ""}`, {
151
+ method,
152
+ headers: body === void 0 ? headers : { ...headers, "Content-Type": "application/json" },
153
+ ...body !== void 0 && { body: JSON.stringify(body) }
154
+ });
155
+ const text = await response.text();
156
+ let data;
157
+ try {
158
+ data = text ? JSON.parse(text) : void 0;
159
+ } catch {
160
+ data = text;
161
+ }
162
+ if (!response.ok) throw new BagsyError(response.status, data);
163
+ return { status: response.status, data };
164
+ }
165
+ return {
166
+ options,
167
+ send,
168
+ request: async (method, path, init) => (await send(method, path, init)).data
169
+ };
170
+ }
171
+
172
+ // ../core/src/account.ts
173
+ function createAccountTransport({
174
+ sessionToken,
175
+ baseUrl = "https://api.bagsy.dev/account/v1",
176
+ fetch: fetchImpl,
177
+ ...rest
178
+ }) {
179
+ return createTransport({
180
+ ...rest,
181
+ mode: "account",
182
+ sessionToken,
183
+ baseUrl,
184
+ // Session tokens last five minutes and Shopify caches them, so ask every time.
185
+ fetch: async (input, init) => {
186
+ const headers = new Headers(init?.headers);
187
+ headers.set("Authorization", `Bearer ${await sessionToken()}`);
188
+ return (fetchImpl ?? fetch)(input, { ...init, headers });
189
+ }
190
+ });
191
+ }
192
+
193
+ // ../core/src/relay.ts
194
+ function createRelayTransport(options) {
195
+ const endpoint = options.endpoint ?? "/api/bagsy";
196
+ async function send(method, path, { query = {}, body } = {}) {
197
+ const params = {};
198
+ for (const [key, value] of Object.entries(query)) {
199
+ if (value !== void 0) params[key] = Array.isArray(value) ? value.join(",") : String(value);
200
+ }
201
+ const call = { method, path, ...Object.keys(params).length && { query: params }, ...body !== void 0 && { body } };
202
+ const response = await (options.fetch ?? fetch)(endpoint, {
203
+ method: "POST",
204
+ headers: { "Content-Type": "application/json", Accept: "application/json" },
205
+ // Same-origin fetches send the session cookie by default.
206
+ body: JSON.stringify(call)
207
+ });
208
+ let payload;
209
+ try {
210
+ payload = await response.json();
211
+ } catch {
212
+ payload = void 0;
213
+ }
214
+ if (!response.ok || !isRelayResponse(payload)) {
215
+ throw new BagsyError(response.ok ? 502 : response.status, payload);
216
+ }
217
+ if (payload.status >= 400) throw new BagsyError(payload.status, payload.data);
218
+ return payload;
219
+ }
220
+ return {
221
+ options,
222
+ send,
223
+ request: async (method, path, init) => (await send(method, path, init)).data
224
+ };
225
+ }
226
+ function isRelayResponse(value) {
227
+ return typeof value === "object" && value !== null && typeof value.status === "number";
228
+ }
229
+
230
+ // ../core/src/client.ts
231
+ function createBagsy(options) {
232
+ if (options.mode === "relay") return createRelayClient(options);
233
+ const t = options.mode === "account" ? createAccountTransport(options) : createTransport(options);
234
+ return clientFor(t, (owner) => {
235
+ if (options.mode === "account") {
236
+ throw new TypeError("bagsy.as: in customer accounts the session token identifies the customer.");
237
+ }
238
+ if (options.mode === "proxy") {
239
+ if (owner.customerId !== void 0) {
240
+ throw new TypeError("bagsy.as: on the app proxy the customer comes from Shopify; pass only `visitorId`.");
241
+ }
242
+ return createBagsy({ ...options, visitorId: owner.visitorId ?? options.visitorId });
243
+ }
244
+ return createBagsy({ ...options, ...owner });
245
+ });
246
+ }
247
+ function createRelayClient(options = {}) {
248
+ return clientFor(createRelayTransport({ ...options, mode: "relay" }), () => {
249
+ throw new TypeError("bagsy.as: through a relay, your server's session identifies the shopper.");
250
+ });
251
+ }
252
+ function clientFor(t, as) {
253
+ const items = {
254
+ list: (listId, page) => listItems(t, listId, page),
255
+ /** Every item in a list, fetched page by page. */
256
+ iterate: (listId, opts) => iterateItems(t, listId, opts),
257
+ /** Save a product. Saving it again returns the existing item. */
258
+ add: (product, opts) => addItem(t, product, opts),
259
+ /** Save if unsaved, unsave if saved. Built for heart buttons. */
260
+ toggle: (product, opts) => toggleItem(t, product, opts),
261
+ update: (itemId, patch, opts) => updateItem(t, itemId, patch, opts),
262
+ remove: (itemId, opts) => removeItem(t, itemId, opts),
263
+ /** Unsave by product, without knowing the item id. No-op if not saved. */
264
+ removeProduct: (product, opts) => removeProduct(t, product, opts)
265
+ };
266
+ return {
267
+ lists: {
268
+ /** The owner's lists, default first. */
269
+ list: () => listLists(t),
270
+ get: (listId, opts) => getList(t, listId, opts),
271
+ create: (name) => createList(t, name),
272
+ rename: (listId, name) => renameList(t, listId, name),
273
+ delete: (listId) => deleteList(t, listId),
274
+ /** The list's share token, created on first share. `rotate: true` issues a new one. */
275
+ share: (listId, opts) => shareList(t, listId, opts),
276
+ unshare: (listId) => unshareList(t, listId)
277
+ },
278
+ items,
279
+ /**
280
+ * Saved state for a set of products across all of the owner's lists.
281
+ * Accepts any number of products; batches of 100 are fetched in parallel.
282
+ */
283
+ saved: (products) => lookupSaved(t, products),
284
+ identity: {
285
+ /** Fold a guest's lists into the logged-in customer's. Safe to retry. */
286
+ merge: (visitorId) => mergeVisitor(t, visitorId)
287
+ },
288
+ shared: {
289
+ /** Public, read-only view of a shared list. */
290
+ get: (shareToken, page) => getSharedList(t, shareToken, page)
291
+ },
292
+ insights: {
293
+ /** Most-saved products. Server key only. */
294
+ topProducts: (query) => topProducts(t, query)
295
+ },
296
+ /**
297
+ * A copy of this client acting for a specific shopper. In Hydrogen, call
298
+ * once per request with the customer from your Customer Account session.
299
+ */
300
+ as
301
+ };
302
+ }
303
+
304
+ // ../core/src/visitor.ts
305
+ var STORAGE_KEY = "bagsy:visitor-id";
306
+ var memoryFallback;
307
+ function getVisitorId(storage = safeLocalStorage()) {
308
+ try {
309
+ const existing = storage?.getItem(STORAGE_KEY);
310
+ if (existing) return existing;
311
+ } catch {
312
+ }
313
+ const id = memoryFallback ?? crypto.randomUUID();
314
+ memoryFallback = id;
315
+ try {
316
+ storage?.setItem(STORAGE_KEY, id);
317
+ } catch {
318
+ }
319
+ return id;
320
+ }
321
+ function peekVisitorId(storage = safeLocalStorage()) {
322
+ try {
323
+ return storage?.getItem(STORAGE_KEY) ?? memoryFallback;
324
+ } catch {
325
+ return memoryFallback;
326
+ }
327
+ }
328
+ function clearVisitorId(storage = safeLocalStorage()) {
329
+ memoryFallback = void 0;
330
+ try {
331
+ storage?.removeItem(STORAGE_KEY);
332
+ } catch {
333
+ }
334
+ }
335
+ function safeLocalStorage() {
336
+ try {
337
+ return globalThis.localStorage;
338
+ } catch {
339
+ return void 0;
340
+ }
341
+ }
342
+
343
+ export {
344
+ productGid,
345
+ variantGid,
346
+ customerGid,
347
+ listLists,
348
+ getList,
349
+ createList,
350
+ renameList,
351
+ deleteList,
352
+ shareList,
353
+ unshareList,
354
+ listItems,
355
+ iterateItems,
356
+ addItem,
357
+ removeProduct,
358
+ toggleItem,
359
+ updateItem,
360
+ removeItem,
361
+ lookupSaved,
362
+ mergeVisitor,
363
+ getSharedList,
364
+ topProducts,
365
+ BagsyError,
366
+ createTransport,
367
+ createAccountTransport,
368
+ createRelayTransport,
369
+ createBagsy,
370
+ createRelayClient,
371
+ getVisitorId,
372
+ peekVisitorId,
373
+ clearVisitorId
374
+ };
375
+ //# sourceMappingURL=chunk-QV3JPND6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../core/src/ids.ts","../../core/src/operations.ts","../../core/src/errors.ts","../../core/src/transport.ts","../../core/src/account.ts","../../core/src/relay.ts","../../core/src/client.ts","../../core/src/visitor.ts"],"sourcesContent":["/**\n * Shopify IDs arrive in two shapes: numeric (Liquid's `product.id`) and GIDs\n * (Storefront and Customer Account APIs). Bagsy accepts either and always\n * sends GIDs on the wire.\n */\nexport type ProductRef = string | number;\nexport type VariantRef = string | number;\nexport type CustomerRef = string | number;\n\ntype Resource = 'Product' | 'ProductVariant' | 'Customer';\n\nfunction toGid(resource: Resource, ref: string | number): string {\n const value = String(ref).trim();\n const prefix = `gid://shopify/${resource}/`;\n if (value.startsWith(prefix)) {\n const id = value.slice(prefix.length);\n if (/^\\d+$/.test(id)) return value;\n } else if (/^\\d+$/.test(value)) {\n return `${prefix}${value}`;\n }\n throw new TypeError(`Expected a numeric ID or ${prefix}<id>, received \"${value}\"`);\n}\n\nexport const productGid = (ref: ProductRef) => toGid('Product', ref);\nexport const variantGid = (ref: VariantRef) => toGid('ProductVariant', ref);\nexport const customerGid = (ref: CustomerRef) => toGid('Customer', ref);\n","// Every API call as a standalone function over a Transport. `createBagsy()`\n// groups these into a client; import them directly when bundle size matters\n// (bundlers drop the ones you don't use).\nimport { productGid, variantGid, type ProductRef, type VariantRef } from './ids.js';\nimport type { Transport } from './transport.js';\nimport type {\n Item,\n ItemOptions,\n ItemPage,\n List,\n ListRef,\n MergeResult,\n PageOptions,\n SavedEntry,\n SavedLookup,\n Schemas,\n SharedList,\n TopProduct,\n} from './types.js';\n\nconst MAX_LOOKUP = 100;\nconst listPath = (listId: ListRef) => `/lists/${encodeURIComponent(listId)}`;\nconst itemPath = (listId: ListRef, itemId: string) => `${listPath(listId)}/items/${encodeURIComponent(itemId)}`;\n\n// Lists ------------------------------------------------------------------\n\n/** The owner's lists, default first. */\nexport const listLists = async (t: Transport): Promise<List[]> =>\n (await t.request<{ data: List[] }>('GET', '/lists')).data;\n\nexport const getList = (t: Transport, listId: ListRef = 'default', { includeItems = false } = {}): Promise<List> =>\n t.request('GET', listPath(listId), { query: { include: includeItems ? 'items' : undefined } });\n\nexport const createList = (t: Transport, name: string): Promise<List> =>\n t.request('POST', '/lists', { body: { name } satisfies Schemas['ListCreate'] });\n\nexport const renameList = (t: Transport, listId: ListRef, name: string): Promise<List> =>\n t.request('PATCH', listPath(listId), { body: { name } satisfies Schemas['ListUpdate'] });\n\nexport const deleteList = async (t: Transport, listId: ListRef): Promise<void> => {\n await t.request('DELETE', listPath(listId));\n};\n\n/**\n * The list's share token, creating it on first share. Stable across calls, so\n * links already sent keep working; `rotate: true` issues a new one.\n */\nexport const shareList = async (\n t: Transport,\n listId: ListRef = 'default',\n { rotate = false }: { rotate?: boolean } = {},\n): Promise<string> =>\n (await t.request<{ shareToken: string }>('POST', `${listPath(listId)}/share`, { query: { rotate: rotate ? 'true' : undefined } }))\n .shareToken;\n\nexport const unshareList = async (t: Transport, listId: ListRef = 'default'): Promise<void> => {\n await t.request('DELETE', `${listPath(listId)}/share`);\n};\n\n// Items ------------------------------------------------------------------\n\nconst itemBody = (product: ProductRef, variantId?: VariantRef, extra: { quantity?: number; note?: string } = {}) =>\n ({\n productId: productGid(product),\n ...(variantId !== undefined && { variantId: variantGid(variantId) }),\n ...(extra.quantity !== undefined && { quantity: extra.quantity }),\n ...(extra.note !== undefined && { note: extra.note }),\n }) satisfies Schemas['ItemCreate'];\n\nexport const listItems = (t: Transport, listId: ListRef = 'default', page: PageOptions = {}): Promise<ItemPage> =>\n t.request('GET', `${listPath(listId)}/items`, { query: { ...page } });\n\n/** Every item in a list, fetched page by page. */\nexport async function* iterateItems(\n t: Transport,\n listId: ListRef = 'default',\n { limit = 100 }: { limit?: number } = {},\n): AsyncGenerator<Item> {\n let after: string | undefined;\n do {\n const page = await listItems(t, listId, { limit, after });\n yield* page.data;\n after = page.pageInfo.hasNextPage ? (page.pageInfo.endCursor ?? undefined) : undefined;\n } while (after);\n}\n\n/** Save a product. Saving it again returns the existing item. */\nexport const addItem = (\n t: Transport,\n product: ProductRef,\n { listId = 'default', variantId, quantity, note }: ItemOptions & { quantity?: number; note?: string } = {},\n): Promise<Item> => t.request('POST', `${listPath(listId)}/items`, { body: itemBody(product, variantId, { quantity, note }) });\n\n/** Unsave by product, without knowing the item id. No-op if not saved. */\nexport const removeProduct = async (\n t: Transport,\n product: ProductRef,\n { listId = 'default', variantId }: ItemOptions = {},\n): Promise<void> => {\n await t.request('DELETE', `${listPath(listId)}/items`, {\n query: { productId: productGid(product), variantId: variantId === undefined ? undefined : variantGid(variantId) },\n });\n};\n\n/** Save if unsaved, unsave if saved. Built for heart buttons. */\nexport async function toggleItem(\n t: Transport,\n product: ProductRef,\n { listId = 'default', variantId }: ItemOptions = {},\n): Promise<{ saved: boolean }> {\n const { status } = await t.send('POST', `${listPath(listId)}/items`, { body: itemBody(product, variantId) });\n // 201 = newly saved. 200 = it was already saved, so this tap unsaves it.\n if (status === 201) return { saved: true };\n await removeProduct(t, product, { listId, variantId });\n return { saved: false };\n}\n\nexport const updateItem = (\n t: Transport,\n itemId: string,\n patch: { variantId?: VariantRef | null; quantity?: number; note?: string | null },\n { listId = 'default' }: { listId?: ListRef } = {},\n): Promise<Item> => {\n const body: Schemas['ItemUpdate'] = {\n ...(patch.quantity !== undefined && { quantity: patch.quantity }),\n ...(patch.note !== undefined && { note: patch.note }),\n ...(patch.variantId !== undefined && { variantId: patch.variantId === null ? null : variantGid(patch.variantId) }),\n };\n return t.request('PATCH', itemPath(listId, itemId), { body });\n};\n\nexport const removeItem = async (t: Transport, itemId: string, { listId = 'default' }: { listId?: ListRef } = {}) => {\n await t.request('DELETE', itemPath(listId, itemId));\n};\n\n// Lookup -----------------------------------------------------------------\n\n/**\n * Saved state for a set of products across all of the owner's lists.\n * Accepts any number of products; batches of 100 are fetched in parallel.\n */\nexport async function lookupSaved(t: Transport, products: Iterable<ProductRef>): Promise<SavedLookup> {\n const ids = [...new Set([...products].map(productGid))];\n const chunks: string[][] = [];\n for (let i = 0; i < ids.length; i += MAX_LOOKUP) chunks.push(ids.slice(i, i + MAX_LOOKUP));\n\n const pages = await Promise.all(\n chunks.map((productIds) => t.request<{ data: Record<string, SavedEntry[]> }>('GET', '/saved', { query: { productIds } })),\n );\n const byProduct: Record<string, SavedEntry[]> = {};\n for (const id of ids) byProduct[id] = [];\n for (const page of pages) Object.assign(byProduct, page.data);\n\n return {\n byProduct,\n get: (product) => byProduct[productGid(product)] ?? [],\n has(product, variant) {\n const entries = byProduct[productGid(product)] ?? [];\n if (variant === undefined) return entries.length > 0;\n const gid = variantGid(variant);\n return entries.some((entry) => entry.variantId === gid || entry.variantId == null);\n },\n };\n}\n\n// Identity, sharing, insights ------------------------------------------\n\n/**\n * Fold a guest's lists into the logged-in customer's. Call once after\n * login; safe to retry. Defaults to the transport's `visitorId`.\n */\nexport function mergeVisitor(t: Transport, visitorId: string | undefined = t.options.visitorId): Promise<MergeResult> {\n if (!visitorId) throw new TypeError('identity.merge: no visitorId to merge.');\n if (t.options.mode === 'server' && t.options.customerId === undefined) {\n throw new TypeError('identity.merge: set `customerId` (e.g. via bagsy.as({ customerId })) first.');\n }\n return t.request('POST', '/identity/merge', { body: { visitorId } });\n}\n\n/** Public, read-only view of a shared list. */\nexport const getSharedList = (t: Transport, shareToken: string, page: PageOptions = {}): Promise<SharedList> =>\n t.request('GET', `/shared/${encodeURIComponent(shareToken)}`, { query: { ...page } });\n\n/** Most-saved products. Server key only. */\nexport async function topProducts(\n t: Transport,\n query: { since?: Date | string; limit?: number } = {},\n): Promise<TopProduct[]> {\n if (t.options.mode !== 'server') {\n throw new Error('bagsy.insights.topProducts() needs a server key; call it from your server.');\n }\n const since = query.since instanceof Date ? query.since.toISOString() : query.since;\n return (await t.request<{ data: TopProduct[] }>('GET', '/insights/top-products', { query: { since, limit: query.limit } }))\n .data;\n}\n","import type { components } from './generated/schema.js';\n\nexport type Problem = components['schemas']['Problem'];\nexport type BagsyErrorCode = Problem['code'] | 'unexpected_response';\n\n/** Thrown for any non-2xx response. Switch on `code`, never on `message`. */\nexport class BagsyError extends Error {\n readonly name = 'BagsyError';\n readonly status: number;\n readonly code: BagsyErrorCode;\n readonly problem?: Problem;\n\n constructor(status: number, problem: unknown) {\n const parsed = isProblem(problem) ? problem : undefined;\n super(parsed?.detail ?? parsed?.title ?? `Bagsy request failed with status ${status}`);\n this.status = status;\n this.code = parsed?.code ?? 'unexpected_response';\n this.problem = parsed;\n }\n}\n\nfunction isProblem(value: unknown): value is Problem {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Problem).code === 'string' &&\n typeof (value as Problem).status === 'number'\n );\n}\n","import { BagsyError } from './errors.js';\nimport { customerGid } from './ids.js';\nimport type { BagsyOptions, RelayOptions, RequestOptions } from './types.js';\n\n/** How operations reach the API: base URL, identity headers and fetch. */\nexport interface Transport {\n readonly options: BagsyOptions;\n send<T>(method: string, path: string, init?: RequestOptions): Promise<{ status: number; data: T }>;\n request<T>(method: string, path: string, init?: RequestOptions): Promise<T>;\n}\n\n/** Proxy, server and account modes. Relay mode has its own: `createRelayTransport()`. */\nexport function createTransport(options: Exclude<BagsyOptions, RelayOptions>): Transport {\n if (options.mode === 'server') {\n if (!options.apiKey) throw new TypeError('createBagsy: `apiKey` is required in server mode.');\n const browser = globalThis as { window?: unknown; document?: unknown };\n if (browser.window !== undefined && browser.document !== undefined) {\n throw new Error(\n 'createBagsy: server mode exposes your secret key and must not run in a browser. ' +\n \"Use mode: 'proxy' in themes, or call Bagsy from your Hydrogen loaders and actions.\",\n );\n }\n }\n\n const baseUrl = (\n options.baseUrl ??\n (options.mode === 'proxy' ? `${(options.proxyPath ?? '/apps/bagsy').replace(/\\/$/, '')}/v1` : 'https://api.bagsy.dev/v1')\n ).replace(/\\/$/, '');\n\n const headers: Record<string, string> = { Accept: 'application/json' };\n if (options.visitorId) headers['Bagsy-Visitor-Id'] = options.visitorId;\n if (options.mode === 'server') {\n headers.Authorization = `Bearer ${options.apiKey}`;\n if (options.customerId !== undefined) headers['Bagsy-Customer-Id'] = customerGid(options.customerId);\n }\n\n /** One request. Query arrays are comma-joined; undefined values are dropped. */\n async function send<T>(method: string, path: string, { query = {}, body }: RequestOptions = {}) {\n const params = new URLSearchParams();\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) params.set(key, Array.isArray(value) ? value.join(',') : String(value));\n }\n // Shopify's app proxy only guarantees query parameters reach the app (and\n // signs them), so the visitor travels there too.\n if (options.mode === 'proxy' && options.visitorId) params.set('visitor_id', options.visitorId);\n\n const search = params.toString();\n const response = await (options.fetch ?? fetch)(`${baseUrl}${path}${search ? `?${search}` : ''}`, {\n method,\n headers: body === undefined ? headers : { ...headers, 'Content-Type': 'application/json' },\n ...(body !== undefined && { body: JSON.stringify(body) }),\n });\n const text = await response.text();\n let data: unknown;\n try {\n data = text ? JSON.parse(text) : undefined;\n } catch {\n data = text;\n }\n if (!response.ok) throw new BagsyError(response.status, data);\n return { status: response.status, data: data as T };\n }\n\n return {\n options,\n send,\n request: async (method, path, init) => (await send(method, path, init)).data as never,\n };\n}\n","import { createTransport, type Transport } from './transport.js';\nimport type { AccountOptions } from './types.js';\n\n/**\n * Transport for customer account UI extensions: every request carries a\n * current Shopify session token. Kept separate from createTransport so\n * storefront bundles that never use it don't pay for it.\n */\nexport function createAccountTransport({\n sessionToken,\n baseUrl = 'https://api.bagsy.dev/account/v1',\n fetch: fetchImpl,\n ...rest\n}: AccountOptions): Transport {\n return createTransport({\n ...rest,\n mode: 'account',\n sessionToken,\n baseUrl,\n // Session tokens last five minutes and Shopify caches them, so ask every time.\n fetch: async (input, init) => {\n const headers = new Headers(init?.headers);\n headers.set('Authorization', `Bearer ${await sessionToken()}`);\n return (fetchImpl ?? fetch)(input, { ...init, headers });\n },\n });\n}\n","import { BagsyError } from './errors.js';\nimport type { Transport } from './transport.js';\nimport type { RelayOptions, RequestOptions } from './types.js';\n\n/** One Bagsy API call, as the browser sends it to your server's relay endpoint. */\nexport interface RelayRequest {\n method: string;\n /** API path below `/v1`, e.g. `/lists/default/items`. */\n path: string;\n query?: Record<string, string>;\n body?: unknown;\n}\n\n/** The API's answer, passed back unchanged so errors keep their status and problem. */\nexport interface RelayResponse<T = unknown> {\n status: number;\n data: T;\n}\n\n/**\n * Transport for browsers on headless storefronts (Hydrogen and other React\n * apps). Each call goes to one endpoint on your own server, which adds the\n * secret key and the shopper's identity from its session, then calls Bagsy.\n * The browser never holds a key, a customer id or a guest id.\n */\nexport function createRelayTransport(options: RelayOptions): Transport {\n const endpoint = options.endpoint ?? '/api/bagsy';\n\n async function send<T>(method: string, path: string, { query = {}, body }: RequestOptions = {}) {\n const params: Record<string, string> = {};\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined) params[key] = Array.isArray(value) ? value.join(',') : String(value);\n }\n const call: RelayRequest = { method, path, ...(Object.keys(params).length && { query: params }), ...(body !== undefined && { body }) };\n\n const response = await (options.fetch ?? fetch)(endpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n // Same-origin fetches send the session cookie by default.\n body: JSON.stringify(call),\n });\n let payload: unknown;\n try {\n payload = await response.json();\n } catch {\n payload = undefined;\n }\n if (!response.ok || !isRelayResponse(payload)) {\n // The relay itself failed (not wired up, crashed, or rejected the call).\n throw new BagsyError(response.ok ? 502 : response.status, payload);\n }\n if (payload.status >= 400) throw new BagsyError(payload.status, payload.data);\n return payload as RelayResponse<T>;\n }\n\n return {\n options,\n send,\n request: async (method, path, init) => (await send(method, path, init)).data as never,\n };\n}\n\nfunction isRelayResponse(value: unknown): value is RelayResponse {\n return typeof value === 'object' && value !== null && typeof (value as RelayResponse).status === 'number';\n}\n","import * as op from './operations.js';\nimport { createAccountTransport } from './account.js';\nimport { createRelayTransport } from './relay.js';\nimport { createTransport } from './transport.js';\nimport type { Transport } from './transport.js';\nimport type { BagsyOptions, ItemOptions, ListRef, Owner, PageOptions, RelayOptions } from './types.js';\nimport type { ProductRef, VariantRef } from './ids.js';\n\n/**\n * The Bagsy client. Every method is a thin wrapper over a standalone function\n * in `operations` — use those directly if you want only what you import.\n */\nexport function createBagsy(options: BagsyOptions): Bagsy {\n if (options.mode === 'relay') return createRelayClient(options);\n const t = options.mode === 'account' ? createAccountTransport(options) : createTransport(options);\n return clientFor(t, (owner) => {\n if (options.mode === 'account') {\n throw new TypeError('bagsy.as: in customer accounts the session token identifies the customer.');\n }\n if (options.mode === 'proxy') {\n if (owner.customerId !== undefined) {\n throw new TypeError('bagsy.as: on the app proxy the customer comes from Shopify; pass only `visitorId`.');\n }\n return createBagsy({ ...options, visitorId: owner.visitorId ?? options.visitorId });\n }\n return createBagsy({ ...options, ...owner });\n });\n}\n\n/**\n * A client for browsers on headless storefronts, calling your server's relay\n * endpoint. Same API as `createBagsy({ mode: 'relay' })`, without bundling the\n * other transports.\n */\nexport function createRelayClient(options: Omit<RelayOptions, 'mode'> = {}): Bagsy {\n return clientFor(createRelayTransport({ ...options, mode: 'relay' }), () => {\n throw new TypeError(\"bagsy.as: through a relay, your server's session identifies the shopper.\");\n });\n}\n\nfunction clientFor(t: Transport, as: (owner: Owner) => Bagsy) {\n\n const items = {\n list: (listId?: ListRef, page?: PageOptions) => op.listItems(t, listId, page),\n /** Every item in a list, fetched page by page. */\n iterate: (listId?: ListRef, opts?: { limit?: number }) => op.iterateItems(t, listId, opts),\n /** Save a product. Saving it again returns the existing item. */\n add: (product: ProductRef, opts?: ItemOptions & { quantity?: number; note?: string }) => op.addItem(t, product, opts),\n /** Save if unsaved, unsave if saved. Built for heart buttons. */\n toggle: (product: ProductRef, opts?: ItemOptions) => op.toggleItem(t, product, opts),\n update: (\n itemId: string,\n patch: { variantId?: VariantRef | null; quantity?: number; note?: string | null },\n opts?: { listId?: ListRef },\n ) => op.updateItem(t, itemId, patch, opts),\n remove: (itemId: string, opts?: { listId?: ListRef }) => op.removeItem(t, itemId, opts),\n /** Unsave by product, without knowing the item id. No-op if not saved. */\n removeProduct: (product: ProductRef, opts?: ItemOptions) => op.removeProduct(t, product, opts),\n };\n\n return {\n lists: {\n /** The owner's lists, default first. */\n list: () => op.listLists(t),\n get: (listId?: ListRef, opts?: { includeItems?: boolean }) => op.getList(t, listId, opts),\n create: (name: string) => op.createList(t, name),\n rename: (listId: ListRef, name: string) => op.renameList(t, listId, name),\n delete: (listId: ListRef) => op.deleteList(t, listId),\n /** The list's share token, created on first share. `rotate: true` issues a new one. */\n share: (listId?: ListRef, opts?: { rotate?: boolean }) => op.shareList(t, listId, opts),\n unshare: (listId?: ListRef) => op.unshareList(t, listId),\n },\n items,\n /**\n * Saved state for a set of products across all of the owner's lists.\n * Accepts any number of products; batches of 100 are fetched in parallel.\n */\n saved: (products: Iterable<ProductRef>) => op.lookupSaved(t, products),\n identity: {\n /** Fold a guest's lists into the logged-in customer's. Safe to retry. */\n merge: (visitorId?: string) => op.mergeVisitor(t, visitorId),\n },\n shared: {\n /** Public, read-only view of a shared list. */\n get: (shareToken: string, page?: PageOptions) => op.getSharedList(t, shareToken, page),\n },\n insights: {\n /** Most-saved products. Server key only. */\n topProducts: (query?: { since?: Date | string; limit?: number }) => op.topProducts(t, query),\n },\n\n /**\n * A copy of this client acting for a specific shopper. In Hydrogen, call\n * once per request with the customer from your Customer Account session.\n */\n as,\n };\n}\n\nexport type Bagsy = ReturnType<typeof clientFor>;\n","const STORAGE_KEY = 'bagsy:visitor-id';\n\n/** The slice of Web Storage this module uses; `localStorage` satisfies it. */\nexport interface VisitorStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n}\n\nlet memoryFallback: string | undefined;\n\n/**\n * Returns this browser's guest identity, creating it on first call.\n *\n * Persisted in localStorage. Falls back to an in-memory ID (for the page's\n * lifetime) when storage is blocked, e.g. in private windows or iframes.\n */\nexport function getVisitorId(storage: Pick<VisitorStorage, 'getItem' | 'setItem'> | undefined = safeLocalStorage()): string {\n try {\n const existing = storage?.getItem(STORAGE_KEY);\n if (existing) return existing;\n } catch {\n // Storage unreadable; fall through to a fresh ID.\n }\n\n const id = memoryFallback ?? crypto.randomUUID();\n memoryFallback = id;\n try {\n storage?.setItem(STORAGE_KEY, id);\n } catch {\n // Storage unwritable; the in-memory ID still works for this page.\n }\n return id;\n}\n\n/** The guest identity if one exists, without creating it. Use to decide whether to merge on login. */\nexport function peekVisitorId(storage: Pick<VisitorStorage, 'getItem'> | undefined = safeLocalStorage()): string | undefined {\n try {\n return storage?.getItem(STORAGE_KEY) ?? memoryFallback;\n } catch {\n return memoryFallback;\n }\n}\n\n/** Forget the guest identity, e.g. after a successful merge on login. */\nexport function clearVisitorId(storage: Pick<VisitorStorage, 'removeItem'> | undefined = safeLocalStorage()): void {\n memoryFallback = undefined;\n try {\n storage?.removeItem(STORAGE_KEY);\n } catch {\n // Nothing to clear.\n }\n}\n\nfunction safeLocalStorage(): VisitorStorage | undefined {\n try {\n // Read via globalThis so this compiles without the DOM lib (Workers, Node).\n return (globalThis as { localStorage?: VisitorStorage }).localStorage;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";AAWA,SAAS,MAAM,UAAoB,KAA8B;AAC/D,QAAM,QAAQ,OAAO,GAAG,EAAE,KAAK;AAC/B,QAAM,SAAS,iBAAiB,QAAQ;AACxC,MAAI,MAAM,WAAW,MAAM,GAAG;AAC5B,UAAM,KAAK,MAAM,MAAM,OAAO,MAAM;AACpC,QAAI,QAAQ,KAAK,EAAE,EAAG,QAAO;AAAA,EAC/B,WAAW,QAAQ,KAAK,KAAK,GAAG;AAC9B,WAAO,GAAG,MAAM,GAAG,KAAK;AAAA,EAC1B;AACA,QAAM,IAAI,UAAU,4BAA4B,MAAM,mBAAmB,KAAK,GAAG;AACnF;AAEO,IAAM,aAAa,CAAC,QAAoB,MAAM,WAAW,GAAG;AAC5D,IAAM,aAAa,CAAC,QAAoB,MAAM,kBAAkB,GAAG;AACnE,IAAM,cAAc,CAAC,QAAqB,MAAM,YAAY,GAAG;;;ACLtE,IAAM,aAAa;AACnB,IAAM,WAAW,CAAC,WAAoB,UAAU,mBAAmB,MAAM,CAAC;AAC1E,IAAM,WAAW,CAAC,QAAiB,WAAmB,GAAG,SAAS,MAAM,CAAC,UAAU,mBAAmB,MAAM,CAAC;AAKtG,IAAM,YAAY,OAAO,OAC7B,MAAM,EAAE,QAA0B,OAAO,QAAQ,GAAG;AAEhD,IAAM,UAAU,CAAC,GAAc,SAAkB,WAAW,EAAE,eAAe,MAAM,IAAI,CAAC,MAC7F,EAAE,QAAQ,OAAO,SAAS,MAAM,GAAG,EAAE,OAAO,EAAE,SAAS,eAAe,UAAU,OAAU,EAAE,CAAC;AAExF,IAAM,aAAa,CAAC,GAAc,SACvC,EAAE,QAAQ,QAAQ,UAAU,EAAE,MAAM,EAAE,KAAK,EAAkC,CAAC;AAEzE,IAAM,aAAa,CAAC,GAAc,QAAiB,SACxD,EAAE,QAAQ,SAAS,SAAS,MAAM,GAAG,EAAE,MAAM,EAAE,KAAK,EAAkC,CAAC;AAElF,IAAM,aAAa,OAAO,GAAc,WAAmC;AAChF,QAAM,EAAE,QAAQ,UAAU,SAAS,MAAM,CAAC;AAC5C;AAMO,IAAM,YAAY,OACvB,GACA,SAAkB,WAClB,EAAE,SAAS,MAAM,IAA0B,CAAC,OAE3C,MAAM,EAAE,QAAgC,QAAQ,GAAG,SAAS,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,QAAQ,SAAS,SAAS,OAAU,EAAE,CAAC,GAC7H;AAEE,IAAM,cAAc,OAAO,GAAc,SAAkB,cAA6B;AAC7F,QAAM,EAAE,QAAQ,UAAU,GAAG,SAAS,MAAM,CAAC,QAAQ;AACvD;AAIA,IAAM,WAAW,CAAC,SAAqB,WAAwB,QAA8C,CAAC,OAC3G;AAAA,EACC,WAAW,WAAW,OAAO;AAAA,EAC7B,GAAI,cAAc,UAAa,EAAE,WAAW,WAAW,SAAS,EAAE;AAAA,EAClE,GAAI,MAAM,aAAa,UAAa,EAAE,UAAU,MAAM,SAAS;AAAA,EAC/D,GAAI,MAAM,SAAS,UAAa,EAAE,MAAM,MAAM,KAAK;AACrD;AAEK,IAAM,YAAY,CAAC,GAAc,SAAkB,WAAW,OAAoB,CAAC,MACxF,EAAE,QAAQ,OAAO,GAAG,SAAS,MAAM,CAAC,UAAU,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;AAGtE,gBAAuB,aACrB,GACA,SAAkB,WAClB,EAAE,QAAQ,IAAI,IAAwB,CAAC,GACjB;AACtB,MAAI;AACJ,KAAG;AACD,UAAM,OAAO,MAAM,UAAU,GAAG,QAAQ,EAAE,OAAO,MAAM,CAAC;AACxD,WAAO,KAAK;AACZ,YAAQ,KAAK,SAAS,cAAe,KAAK,SAAS,aAAa,SAAa;AAAA,EAC/E,SAAS;AACX;AAGO,IAAM,UAAU,CACrB,GACA,SACA,EAAE,SAAS,WAAW,WAAW,UAAU,KAAK,IAAwD,CAAC,MACvF,EAAE,QAAQ,QAAQ,GAAG,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,SAAS,SAAS,WAAW,EAAE,UAAU,KAAK,CAAC,EAAE,CAAC;AAGtH,IAAM,gBAAgB,OAC3B,GACA,SACA,EAAE,SAAS,WAAW,UAAU,IAAiB,CAAC,MAChC;AAClB,QAAM,EAAE,QAAQ,UAAU,GAAG,SAAS,MAAM,CAAC,UAAU;AAAA,IACrD,OAAO,EAAE,WAAW,WAAW,OAAO,GAAG,WAAW,cAAc,SAAY,SAAY,WAAW,SAAS,EAAE;AAAA,EAClH,CAAC;AACH;AAGA,eAAsB,WACpB,GACA,SACA,EAAE,SAAS,WAAW,UAAU,IAAiB,CAAC,GACrB;AAC7B,QAAM,EAAE,OAAO,IAAI,MAAM,EAAE,KAAK,QAAQ,GAAG,SAAS,MAAM,CAAC,UAAU,EAAE,MAAM,SAAS,SAAS,SAAS,EAAE,CAAC;AAE3G,MAAI,WAAW,IAAK,QAAO,EAAE,OAAO,KAAK;AACzC,QAAM,cAAc,GAAG,SAAS,EAAE,QAAQ,UAAU,CAAC;AACrD,SAAO,EAAE,OAAO,MAAM;AACxB;AAEO,IAAM,aAAa,CACxB,GACA,QACA,OACA,EAAE,SAAS,UAAU,IAA0B,CAAC,MAC9B;AAClB,QAAM,OAA8B;AAAA,IAClC,GAAI,MAAM,aAAa,UAAa,EAAE,UAAU,MAAM,SAAS;AAAA,IAC/D,GAAI,MAAM,SAAS,UAAa,EAAE,MAAM,MAAM,KAAK;AAAA,IACnD,GAAI,MAAM,cAAc,UAAa,EAAE,WAAW,MAAM,cAAc,OAAO,OAAO,WAAW,MAAM,SAAS,EAAE;AAAA,EAClH;AACA,SAAO,EAAE,QAAQ,SAAS,SAAS,QAAQ,MAAM,GAAG,EAAE,KAAK,CAAC;AAC9D;AAEO,IAAM,aAAa,OAAO,GAAc,QAAgB,EAAE,SAAS,UAAU,IAA0B,CAAC,MAAM;AACnH,QAAM,EAAE,QAAQ,UAAU,SAAS,QAAQ,MAAM,CAAC;AACpD;AAQA,eAAsB,YAAY,GAAc,UAAsD;AACpG,QAAM,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,QAAQ,EAAE,IAAI,UAAU,CAAC,CAAC;AACtD,QAAM,SAAqB,CAAC;AAC5B,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,WAAY,QAAO,KAAK,IAAI,MAAM,GAAG,IAAI,UAAU,CAAC;AAEzF,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,OAAO,IAAI,CAAC,eAAe,EAAE,QAAgD,OAAO,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC;AAAA,EAC1H;AACA,QAAM,YAA0C,CAAC;AACjD,aAAW,MAAM,IAAK,WAAU,EAAE,IAAI,CAAC;AACvC,aAAW,QAAQ,MAAO,QAAO,OAAO,WAAW,KAAK,IAAI;AAE5D,SAAO;AAAA,IACL;AAAA,IACA,KAAK,CAAC,YAAY,UAAU,WAAW,OAAO,CAAC,KAAK,CAAC;AAAA,IACrD,IAAI,SAAS,SAAS;AACpB,YAAM,UAAU,UAAU,WAAW,OAAO,CAAC,KAAK,CAAC;AACnD,UAAI,YAAY,OAAW,QAAO,QAAQ,SAAS;AACnD,YAAM,MAAM,WAAW,OAAO;AAC9B,aAAO,QAAQ,KAAK,CAAC,UAAU,MAAM,cAAc,OAAO,MAAM,aAAa,IAAI;AAAA,IACnF;AAAA,EACF;AACF;AAQO,SAAS,aAAa,GAAc,YAAgC,EAAE,QAAQ,WAAiC;AACpH,MAAI,CAAC,UAAW,OAAM,IAAI,UAAU,wCAAwC;AAC5E,MAAI,EAAE,QAAQ,SAAS,YAAY,EAAE,QAAQ,eAAe,QAAW;AACrE,UAAM,IAAI,UAAU,6EAA6E;AAAA,EACnG;AACA,SAAO,EAAE,QAAQ,QAAQ,mBAAmB,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACrE;AAGO,IAAM,gBAAgB,CAAC,GAAc,YAAoB,OAAoB,CAAC,MACnF,EAAE,QAAQ,OAAO,WAAW,mBAAmB,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC;AAGtF,eAAsB,YACpB,GACA,QAAmD,CAAC,GAC7B;AACvB,MAAI,EAAE,QAAQ,SAAS,UAAU;AAC/B,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC9F;AACA,QAAM,QAAQ,MAAM,iBAAiB,OAAO,MAAM,MAAM,YAAY,IAAI,MAAM;AAC9E,UAAQ,MAAM,EAAE,QAAgC,OAAO,0BAA0B,EAAE,OAAO,EAAE,OAAO,OAAO,MAAM,MAAM,EAAE,CAAC,GACtH;AACL;;;AC5LO,IAAM,aAAN,cAAyB,MAAM;AAAA,EAC3B,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,QAAgB,SAAkB;AAC5C,UAAM,SAAS,UAAU,OAAO,IAAI,UAAU;AAC9C,UAAM,QAAQ,UAAU,QAAQ,SAAS,oCAAoC,MAAM,EAAE;AACrF,SAAK,SAAS;AACd,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,UAAU;AAAA,EACjB;AACF;AAEA,SAAS,UAAU,OAAkC;AACnD,SACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAQ,MAAkB,SAAS,YACnC,OAAQ,MAAkB,WAAW;AAEzC;;;AChBO,SAAS,gBAAgB,SAAyD;AACvF,MAAI,QAAQ,SAAS,UAAU;AAC7B,QAAI,CAAC,QAAQ,OAAQ,OAAM,IAAI,UAAU,mDAAmD;AAC5F,UAAM,UAAU;AAChB,QAAI,QAAQ,WAAW,UAAa,QAAQ,aAAa,QAAW;AAClE,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WACJ,QAAQ,YACP,QAAQ,SAAS,UAAU,IAAI,QAAQ,aAAa,eAAe,QAAQ,OAAO,EAAE,CAAC,QAAQ,6BAC9F,QAAQ,OAAO,EAAE;AAEnB,QAAM,UAAkC,EAAE,QAAQ,mBAAmB;AACrE,MAAI,QAAQ,UAAW,SAAQ,kBAAkB,IAAI,QAAQ;AAC7D,MAAI,QAAQ,SAAS,UAAU;AAC7B,YAAQ,gBAAgB,UAAU,QAAQ,MAAM;AAChD,QAAI,QAAQ,eAAe,OAAW,SAAQ,mBAAmB,IAAI,YAAY,QAAQ,UAAU;AAAA,EACrG;AAGA,iBAAe,KAAQ,QAAgB,MAAc,EAAE,QAAQ,CAAC,GAAG,KAAK,IAAoB,CAAC,GAAG;AAC9F,UAAM,SAAS,IAAI,gBAAgB;AACnC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,UAAI,UAAU,OAAW,QAAO,IAAI,KAAK,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI,OAAO,KAAK,CAAC;AAAA,IACjG;AAGA,QAAI,QAAQ,SAAS,WAAW,QAAQ,UAAW,QAAO,IAAI,cAAc,QAAQ,SAAS;AAE7F,UAAM,SAAS,OAAO,SAAS;AAC/B,UAAM,WAAW,OAAO,QAAQ,SAAS,OAAO,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,IAAI,MAAM,KAAK,EAAE,IAAI;AAAA,MAChG;AAAA,MACA,SAAS,SAAS,SAAY,UAAU,EAAE,GAAG,SAAS,gBAAgB,mBAAmB;AAAA,MACzF,GAAI,SAAS,UAAa,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE;AAAA,IACzD,CAAC;AACD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI;AACJ,QAAI;AACF,aAAO,OAAO,KAAK,MAAM,IAAI,IAAI;AAAA,IACnC,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI,CAAC,SAAS,GAAI,OAAM,IAAI,WAAW,SAAS,QAAQ,IAAI;AAC5D,WAAO,EAAE,QAAQ,SAAS,QAAQ,KAAgB;AAAA,EACpD;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,OAAO,QAAQ,MAAM,UAAU,MAAM,KAAK,QAAQ,MAAM,IAAI,GAAG;AAAA,EAC1E;AACF;;;AC5DO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA,UAAU;AAAA,EACV,OAAO;AAAA,EACP,GAAG;AACL,GAA8B;AAC5B,SAAO,gBAAgB;AAAA,IACrB,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,IACA;AAAA;AAAA,IAEA,OAAO,OAAO,OAAO,SAAS;AAC5B,YAAM,UAAU,IAAI,QAAQ,MAAM,OAAO;AACzC,cAAQ,IAAI,iBAAiB,UAAU,MAAM,aAAa,CAAC,EAAE;AAC7D,cAAQ,aAAa,OAAO,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC;AAAA,IACzD;AAAA,EACF,CAAC;AACH;;;ACDO,SAAS,qBAAqB,SAAkC;AACrE,QAAM,WAAW,QAAQ,YAAY;AAErC,iBAAe,KAAQ,QAAgB,MAAc,EAAE,QAAQ,CAAC,GAAG,KAAK,IAAoB,CAAC,GAAG;AAC9F,UAAM,SAAiC,CAAC;AACxC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,UAAI,UAAU,OAAW,QAAO,GAAG,IAAI,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,GAAG,IAAI,OAAO,KAAK;AAAA,IAC9F;AACA,UAAM,OAAqB,EAAE,QAAQ,MAAM,GAAI,OAAO,KAAK,MAAM,EAAE,UAAU,EAAE,OAAO,OAAO,GAAI,GAAI,SAAS,UAAa,EAAE,KAAK,EAAG;AAErI,UAAM,WAAW,OAAO,QAAQ,SAAS,OAAO,UAAU;AAAA,MACxD,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,QAAQ,mBAAmB;AAAA;AAAA,MAE1E,MAAM,KAAK,UAAU,IAAI;AAAA,IAC3B,CAAC;AACD,QAAI;AACJ,QAAI;AACF,gBAAU,MAAM,SAAS,KAAK;AAAA,IAChC,QAAQ;AACN,gBAAU;AAAA,IACZ;AACA,QAAI,CAAC,SAAS,MAAM,CAAC,gBAAgB,OAAO,GAAG;AAE7C,YAAM,IAAI,WAAW,SAAS,KAAK,MAAM,SAAS,QAAQ,OAAO;AAAA,IACnE;AACA,QAAI,QAAQ,UAAU,IAAK,OAAM,IAAI,WAAW,QAAQ,QAAQ,QAAQ,IAAI;AAC5E,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,SAAS,OAAO,QAAQ,MAAM,UAAU,MAAM,KAAK,QAAQ,MAAM,IAAI,GAAG;AAAA,EAC1E;AACF;AAEA,SAAS,gBAAgB,OAAwC;AAC/D,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,OAAQ,MAAwB,WAAW;AACnG;;;ACpDO,SAAS,YAAY,SAA8B;AACxD,MAAI,QAAQ,SAAS,QAAS,QAAO,kBAAkB,OAAO;AAC9D,QAAM,IAAI,QAAQ,SAAS,YAAY,uBAAuB,OAAO,IAAI,gBAAgB,OAAO;AAChG,SAAO,UAAU,GAAG,CAAC,UAAU;AAC7B,QAAI,QAAQ,SAAS,WAAW;AAC9B,YAAM,IAAI,UAAU,2EAA2E;AAAA,IACjG;AACA,QAAI,QAAQ,SAAS,SAAS;AAC5B,UAAI,MAAM,eAAe,QAAW;AAClC,cAAM,IAAI,UAAU,oFAAoF;AAAA,MAC1G;AACA,aAAO,YAAY,EAAE,GAAG,SAAS,WAAW,MAAM,aAAa,QAAQ,UAAU,CAAC;AAAA,IACpF;AACA,WAAO,YAAY,EAAE,GAAG,SAAS,GAAG,MAAM,CAAC;AAAA,EAC7C,CAAC;AACH;AAOO,SAAS,kBAAkB,UAAsC,CAAC,GAAU;AACjF,SAAO,UAAU,qBAAqB,EAAE,GAAG,SAAS,MAAM,QAAQ,CAAC,GAAG,MAAM;AAC1E,UAAM,IAAI,UAAU,0EAA0E;AAAA,EAChG,CAAC;AACH;AAEA,SAAS,UAAU,GAAc,IAA6B;AAE5D,QAAM,QAAQ;AAAA,IACZ,MAAM,CAAC,QAAkB,SAA0B,UAAU,GAAG,QAAQ,IAAI;AAAA;AAAA,IAE5E,SAAS,CAAC,QAAkB,SAAiC,aAAa,GAAG,QAAQ,IAAI;AAAA;AAAA,IAEzF,KAAK,CAAC,SAAqB,SAAiE,QAAQ,GAAG,SAAS,IAAI;AAAA;AAAA,IAEpH,QAAQ,CAAC,SAAqB,SAA0B,WAAW,GAAG,SAAS,IAAI;AAAA,IACnF,QAAQ,CACN,QACA,OACA,SACM,WAAW,GAAG,QAAQ,OAAO,IAAI;AAAA,IACzC,QAAQ,CAAC,QAAgB,SAAmC,WAAW,GAAG,QAAQ,IAAI;AAAA;AAAA,IAEtF,eAAe,CAAC,SAAqB,SAA0B,cAAc,GAAG,SAAS,IAAI;AAAA,EAC/F;AAEA,SAAO;AAAA,IACL,OAAO;AAAA;AAAA,MAEL,MAAM,MAAS,UAAU,CAAC;AAAA,MAC1B,KAAK,CAAC,QAAkB,SAAyC,QAAQ,GAAG,QAAQ,IAAI;AAAA,MACxF,QAAQ,CAAC,SAAoB,WAAW,GAAG,IAAI;AAAA,MAC/C,QAAQ,CAAC,QAAiB,SAAoB,WAAW,GAAG,QAAQ,IAAI;AAAA,MACxE,QAAQ,CAAC,WAAuB,WAAW,GAAG,MAAM;AAAA;AAAA,MAEpD,OAAO,CAAC,QAAkB,SAAmC,UAAU,GAAG,QAAQ,IAAI;AAAA,MACtF,SAAS,CAAC,WAAwB,YAAY,GAAG,MAAM;AAAA,IACzD;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA,IAKA,OAAO,CAAC,aAAsC,YAAY,GAAG,QAAQ;AAAA,IACrE,UAAU;AAAA;AAAA,MAER,OAAO,CAAC,cAA0B,aAAa,GAAG,SAAS;AAAA,IAC7D;AAAA,IACA,QAAQ;AAAA;AAAA,MAEN,KAAK,CAAC,YAAoB,SAA0B,cAAc,GAAG,YAAY,IAAI;AAAA,IACvF;AAAA,IACA,UAAU;AAAA;AAAA,MAER,aAAa,CAAC,UAAyD,YAAY,GAAG,KAAK;AAAA,IAC7F;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA;AAAA,EACF;AACF;;;ACjGA,IAAM,cAAc;AASpB,IAAI;AAQG,SAAS,aAAa,UAAmE,iBAAiB,GAAW;AAC1H,MAAI;AACF,UAAM,WAAW,SAAS,QAAQ,WAAW;AAC7C,QAAI,SAAU,QAAO;AAAA,EACvB,QAAQ;AAAA,EAER;AAEA,QAAM,KAAK,kBAAkB,OAAO,WAAW;AAC/C,mBAAiB;AACjB,MAAI;AACF,aAAS,QAAQ,aAAa,EAAE;AAAA,EAClC,QAAQ;AAAA,EAER;AACA,SAAO;AACT;AAGO,SAAS,cAAc,UAAuD,iBAAiB,GAAuB;AAC3H,MAAI;AACF,WAAO,SAAS,QAAQ,WAAW,KAAK;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGO,SAAS,eAAe,UAA0D,iBAAiB,GAAS;AACjH,mBAAiB;AACjB,MAAI;AACF,aAAS,WAAW,WAAW;AAAA,EACjC,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,mBAA+C;AACtD,MAAI;AAEF,WAAQ,WAAiD;AAAA,EAC3D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":[]}
@@ -0,0 +1,169 @@
1
+ import { T as Transport, O as Owner, c as components, L as ListRef, P as PageOptions, a as ProductRef, I as ItemOptions, V as VariantRef, S as SavedLookup, B as BagsyOptions, R as RelayOptions } from './transport-Chh09xIP.js';
2
+
3
+ /**
4
+ * The Bagsy client. Every method is a thin wrapper over a standalone function
5
+ * in `operations` — use those directly if you want only what you import.
6
+ */
7
+ declare function createBagsy(options: BagsyOptions): Bagsy;
8
+ /**
9
+ * A client for browsers on headless storefronts, calling your server's relay
10
+ * endpoint. Same API as `createBagsy({ mode: 'relay' })`, without bundling the
11
+ * other transports.
12
+ */
13
+ declare function createRelayClient(options?: Omit<RelayOptions, 'mode'>): Bagsy;
14
+ declare function clientFor(t: Transport, as: (owner: Owner) => Bagsy): {
15
+ lists: {
16
+ /** The owner's lists, default first. */
17
+ list: () => Promise<{
18
+ id: string;
19
+ name: string;
20
+ isDefault: boolean;
21
+ visibility: "private" | "shared";
22
+ itemCount: number;
23
+ shareToken?: string | null;
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ items?: components["schemas"]["ItemPage"];
27
+ }[]>;
28
+ get: (listId?: ListRef, opts?: {
29
+ includeItems?: boolean;
30
+ }) => Promise<{
31
+ id: string;
32
+ name: string;
33
+ isDefault: boolean;
34
+ visibility: "private" | "shared";
35
+ itemCount: number;
36
+ shareToken?: string | null;
37
+ createdAt: string;
38
+ updatedAt: string;
39
+ items?: components["schemas"]["ItemPage"];
40
+ }>;
41
+ create: (name: string) => Promise<{
42
+ id: string;
43
+ name: string;
44
+ isDefault: boolean;
45
+ visibility: "private" | "shared";
46
+ itemCount: number;
47
+ shareToken?: string | null;
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ items?: components["schemas"]["ItemPage"];
51
+ }>;
52
+ rename: (listId: ListRef, name: string) => Promise<{
53
+ id: string;
54
+ name: string;
55
+ isDefault: boolean;
56
+ visibility: "private" | "shared";
57
+ itemCount: number;
58
+ shareToken?: string | null;
59
+ createdAt: string;
60
+ updatedAt: string;
61
+ items?: components["schemas"]["ItemPage"];
62
+ }>;
63
+ delete: (listId: ListRef) => Promise<void>;
64
+ /** The list's share token, created on first share. `rotate: true` issues a new one. */
65
+ share: (listId?: ListRef, opts?: {
66
+ rotate?: boolean;
67
+ }) => Promise<string>;
68
+ unshare: (listId?: ListRef) => Promise<void>;
69
+ };
70
+ items: {
71
+ list: (listId?: ListRef, page?: PageOptions) => Promise<{
72
+ data: components["schemas"]["Item"][];
73
+ pageInfo: {
74
+ hasNextPage: boolean;
75
+ endCursor?: string | null;
76
+ };
77
+ }>;
78
+ /** Every item in a list, fetched page by page. */
79
+ iterate: (listId?: ListRef, opts?: {
80
+ limit?: number;
81
+ }) => AsyncGenerator<{
82
+ id: string;
83
+ listId: string;
84
+ productId: components["schemas"]["ProductGid"];
85
+ variantId?: components["schemas"]["VariantGid"] | null;
86
+ quantity: number;
87
+ note?: string | null;
88
+ addedAt: string;
89
+ }, any, any>;
90
+ /** Save a product. Saving it again returns the existing item. */
91
+ add: (product: ProductRef, opts?: ItemOptions & {
92
+ quantity?: number;
93
+ note?: string;
94
+ }) => Promise<{
95
+ id: string;
96
+ listId: string;
97
+ productId: components["schemas"]["ProductGid"];
98
+ variantId?: components["schemas"]["VariantGid"] | null;
99
+ quantity: number;
100
+ note?: string | null;
101
+ addedAt: string;
102
+ }>;
103
+ /** Save if unsaved, unsave if saved. Built for heart buttons. */
104
+ toggle: (product: ProductRef, opts?: ItemOptions) => Promise<{
105
+ saved: boolean;
106
+ }>;
107
+ update: (itemId: string, patch: {
108
+ variantId?: VariantRef | null;
109
+ quantity?: number;
110
+ note?: string | null;
111
+ }, opts?: {
112
+ listId?: ListRef;
113
+ }) => Promise<{
114
+ id: string;
115
+ listId: string;
116
+ productId: components["schemas"]["ProductGid"];
117
+ variantId?: components["schemas"]["VariantGid"] | null;
118
+ quantity: number;
119
+ note?: string | null;
120
+ addedAt: string;
121
+ }>;
122
+ remove: (itemId: string, opts?: {
123
+ listId?: ListRef;
124
+ }) => Promise<void>;
125
+ /** Unsave by product, without knowing the item id. No-op if not saved. */
126
+ removeProduct: (product: ProductRef, opts?: ItemOptions) => Promise<void>;
127
+ };
128
+ /**
129
+ * Saved state for a set of products across all of the owner's lists.
130
+ * Accepts any number of products; batches of 100 are fetched in parallel.
131
+ */
132
+ saved: (products: Iterable<ProductRef>) => Promise<SavedLookup>;
133
+ identity: {
134
+ /** Fold a guest's lists into the logged-in customer's. Safe to retry. */
135
+ merge: (visitorId?: string) => Promise<{
136
+ mergedItems: number;
137
+ lists: components["schemas"]["List"][];
138
+ }>;
139
+ };
140
+ shared: {
141
+ /** Public, read-only view of a shared list. */
142
+ get: (shareToken: string, page?: PageOptions) => Promise<{
143
+ list: {
144
+ name: string;
145
+ itemCount: number;
146
+ };
147
+ items: components["schemas"]["ItemPage"];
148
+ }>;
149
+ };
150
+ insights: {
151
+ /** Most-saved products. Server key only. */
152
+ topProducts: (query?: {
153
+ since?: Date | string;
154
+ limit?: number;
155
+ }) => Promise<{
156
+ productId: components["schemas"]["ProductGid"];
157
+ saves: number;
158
+ }[]>;
159
+ };
160
+ /**
161
+ * A copy of this client acting for a specific shopper. In Hydrogen, call
162
+ * once per request with the customer from your Customer Account session.
163
+ */
164
+ as: (owner: Owner) => Bagsy;
165
+ };
166
+ type Bagsy = ReturnType<typeof clientFor>;
167
+
168
+ export { createRelayClient as a, createBagsy as c };
169
+ export type { Bagsy as B };