@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.
- package/LICENSE +21 -0
- package/README.md +257 -2
- package/dist/chunk-CJV4SIV7.js +244 -0
- package/dist/chunk-CJV4SIV7.js.map +1 -0
- package/dist/chunk-QV3JPND6.js +375 -0
- package/dist/chunk-QV3JPND6.js.map +1 -0
- package/dist/client-CXCgpFGa.d.ts +169 -0
- package/dist/element.d.ts +169 -0
- package/dist/element.js +566 -0
- package/dist/element.js.map +1 -0
- package/dist/hydrogen.d.ts +73 -0
- package/dist/hydrogen.js +89 -0
- package/dist/hydrogen.js.map +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.js +65 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +174 -0
- package/dist/react.js +403 -0
- package/dist/react.js.map +1 -0
- package/dist/relay-0l6VcQp9.d.ts +25 -0
- package/dist/server.d.ts +96 -0
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +157 -0
- package/dist/transport-Chh09xIP.d.ts +1022 -0
- package/package.json +89 -4
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { RelayResponse } from './index.js';
|
|
2
|
+
import { CreateBagsyServerOptions, BagsyServer, Shopper, WritableShopper, CustomerResolver } from './server.js';
|
|
3
|
+
import * as _shopify_hydrogen from '@shopify/hydrogen';
|
|
4
|
+
import { ShopifyRequestContext } from '@shopify/hydrogen';
|
|
5
|
+
import { ReadonlyCustomerSessionManager, WritableCustomerSessionManager, CustomerSession } from '@shopify/hydrogen/customer-account';
|
|
6
|
+
import './transport-Chh09xIP.js';
|
|
7
|
+
import './client-CXCgpFGa.js';
|
|
8
|
+
|
|
9
|
+
/** For loaders and server components: a read-only session. */
|
|
10
|
+
interface HydrogenReadContext {
|
|
11
|
+
sessionManager: ReadonlyCustomerSessionManager;
|
|
12
|
+
requestContext: ShopifyRequestContext;
|
|
13
|
+
}
|
|
14
|
+
/** For route handlers and actions: a session the response commits. */
|
|
15
|
+
interface HydrogenWriteContext {
|
|
16
|
+
sessionManager: WritableCustomerSessionManager;
|
|
17
|
+
requestContext: ShopifyRequestContext;
|
|
18
|
+
}
|
|
19
|
+
interface CreateHydrogenBagsyOptions extends Omit<CreateBagsyServerOptions<HydrogenReadContext, HydrogenWriteContext>, 'customer'> {
|
|
20
|
+
/**
|
|
21
|
+
* Your `createCustomerSession()` result, so signed-in customers keep one
|
|
22
|
+
* wishlist across devices. Omit for a guest-only storefront.
|
|
23
|
+
*/
|
|
24
|
+
customerSession?: Pick<CustomerSession, 'getAccessToken' | 'getOrRefreshAccessToken'>;
|
|
25
|
+
/** Numeric shop id, the same one you pass to `createCustomerSession()`. Required with `customerSession`. */
|
|
26
|
+
shopId?: string;
|
|
27
|
+
/** Fetch for Customer Account API calls (tests, custom runtimes). */
|
|
28
|
+
customerAccountFetch?: typeof globalThis.fetch;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Bagsy for Hydrogen 2026-10. Create once at module scope, register
|
|
32
|
+
* `bagsy.handlers` with `handleShopifyRoutes`, and call `bagsy.shopper()` in
|
|
33
|
+
* loaders.
|
|
34
|
+
*/
|
|
35
|
+
declare function createHydrogenBagsy(options: CreateHydrogenBagsyOptions): {
|
|
36
|
+
handlers: {
|
|
37
|
+
bagsyRelay: _shopify_hydrogen.CallableRouteHandler<HydrogenWriteContext & {
|
|
38
|
+
request: Request;
|
|
39
|
+
}, _shopify_hydrogen.ShopifyRouteHandlerResult, string, "POST">;
|
|
40
|
+
};
|
|
41
|
+
endpoint: string;
|
|
42
|
+
shopper: (context: HydrogenReadContext) => Promise<Shopper>;
|
|
43
|
+
writableShopper: (context: HydrogenWriteContext) => Promise<WritableShopper>;
|
|
44
|
+
relay: (request: Request, context: HydrogenWriteContext) => Promise<{
|
|
45
|
+
response: RelayResponse;
|
|
46
|
+
} | {
|
|
47
|
+
rejected: {
|
|
48
|
+
status: number;
|
|
49
|
+
code: string;
|
|
50
|
+
message: string;
|
|
51
|
+
};
|
|
52
|
+
}>;
|
|
53
|
+
handle: (request: Request, context: HydrogenWriteContext) => Promise<Response | null>;
|
|
54
|
+
};
|
|
55
|
+
/** `handleShopifyRoutes` handlers for the relay endpoint `@umxr/bagsy/react` calls. */
|
|
56
|
+
declare function bagsyRouteHandlers(server: BagsyServer<HydrogenReadContext, HydrogenWriteContext>): {
|
|
57
|
+
bagsyRelay: _shopify_hydrogen.CallableRouteHandler<HydrogenWriteContext & {
|
|
58
|
+
request: Request;
|
|
59
|
+
}, _shopify_hydrogen.ShopifyRouteHandlerResult, string, "POST">;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Who is signed in, from Hydrogen's customer session. Reads never refresh
|
|
63
|
+
* tokens or write the session; writes may refresh, and cache the customer id
|
|
64
|
+
* against the token so the Customer Account API is asked once per sign-in.
|
|
65
|
+
*/
|
|
66
|
+
declare function hydrogenCustomerId({ customerSession, shopId, fetch, }: {
|
|
67
|
+
customerSession: Pick<CustomerSession, 'getAccessToken' | 'getOrRefreshAccessToken'>;
|
|
68
|
+
shopId: string;
|
|
69
|
+
fetch?: typeof globalThis.fetch | undefined;
|
|
70
|
+
}): CustomerResolver<HydrogenReadContext, HydrogenWriteContext>;
|
|
71
|
+
|
|
72
|
+
export { Shopper, WritableShopper, bagsyRouteHandlers, createHydrogenBagsy, hydrogenCustomerId };
|
|
73
|
+
export type { CreateHydrogenBagsyOptions, HydrogenReadContext, HydrogenWriteContext };
|
package/dist/hydrogen.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBagsyServer,
|
|
3
|
+
remember,
|
|
4
|
+
trackWrites,
|
|
5
|
+
warnIfNotPersisted
|
|
6
|
+
} from "./chunk-CJV4SIV7.js";
|
|
7
|
+
import "./chunk-QV3JPND6.js";
|
|
8
|
+
|
|
9
|
+
// ../hydrogen/src/hydrogen.ts
|
|
10
|
+
import { createShopifyRouteHandler } from "@shopify/hydrogen";
|
|
11
|
+
import {
|
|
12
|
+
createCustomerAccountClient,
|
|
13
|
+
gql
|
|
14
|
+
} from "@shopify/hydrogen/customer-account";
|
|
15
|
+
function createHydrogenBagsy(options) {
|
|
16
|
+
const { customerSession, shopId, customerAccountFetch, ...rest } = options;
|
|
17
|
+
if (customerSession && !shopId) throw new TypeError("createHydrogenBagsy: `shopId` is required with `customerSession`.");
|
|
18
|
+
const server = createBagsyServer({
|
|
19
|
+
...rest,
|
|
20
|
+
...customerSession && shopId && { customer: hydrogenCustomerId({ customerSession, shopId, fetch: customerAccountFetch }) }
|
|
21
|
+
});
|
|
22
|
+
return { ...server, handlers: bagsyRouteHandlers(server) };
|
|
23
|
+
}
|
|
24
|
+
function bagsyRouteHandlers(server) {
|
|
25
|
+
return {
|
|
26
|
+
bagsyRelay: createShopifyRouteHandler(
|
|
27
|
+
server.endpoint,
|
|
28
|
+
"POST",
|
|
29
|
+
async (context) => {
|
|
30
|
+
const tracked = trackWrites(context.sessionManager);
|
|
31
|
+
const result = await server.relay(context.request, { ...context, sessionManager: tracked.sessionManager });
|
|
32
|
+
let headers;
|
|
33
|
+
if (tracked.dirty()) {
|
|
34
|
+
headers = await context.sessionManager.commit?.() ?? void 0;
|
|
35
|
+
warnIfNotPersisted(new Headers(headers));
|
|
36
|
+
}
|
|
37
|
+
if ("rejected" in result) {
|
|
38
|
+
const { status, code, message } = result.rejected;
|
|
39
|
+
return { type: "error", status, error: { code, message }, ...headers && { headers } };
|
|
40
|
+
}
|
|
41
|
+
return { type: "json", data: result.response, ...headers && { headers } };
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
var CUSTOMER_ID_QUERY = gql(`query BagsyCustomerId { customer { id } }`);
|
|
47
|
+
var CACHE_KEY = "bagsy:customer";
|
|
48
|
+
var customerIds = /* @__PURE__ */ new Map();
|
|
49
|
+
function hydrogenCustomerId({
|
|
50
|
+
customerSession,
|
|
51
|
+
shopId,
|
|
52
|
+
fetch
|
|
53
|
+
}) {
|
|
54
|
+
async function lookup(accessToken, { sessionManager, requestContext }) {
|
|
55
|
+
const token = await fingerprint(accessToken);
|
|
56
|
+
const cached = await sessionManager.getSessionItem(CACHE_KEY);
|
|
57
|
+
if (cached?.token === token && cached.id) return { id: cached.id, token, cached: true };
|
|
58
|
+
const known = customerIds.get(`${shopId}|${token}`);
|
|
59
|
+
if (known) return { id: known, token, cached: false };
|
|
60
|
+
const client = createCustomerAccountClient({ shopId, requestContext, ...fetch && { fetch } });
|
|
61
|
+
const result = await client.graphql(CUSTOMER_ID_QUERY, { accessToken });
|
|
62
|
+
const id = result.errors ? void 0 : result.data?.customer?.id ?? void 0;
|
|
63
|
+
if (id) remember(customerIds, `${shopId}|${token}`, id);
|
|
64
|
+
return { id, token, cached: false };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
async read(context) {
|
|
68
|
+
const accessToken = await customerSession.getAccessToken(context.sessionManager, context.requestContext);
|
|
69
|
+
return accessToken ? (await lookup(accessToken, context)).id : void 0;
|
|
70
|
+
},
|
|
71
|
+
async write(context) {
|
|
72
|
+
const accessToken = await customerSession.getOrRefreshAccessToken(context.sessionManager, context.requestContext);
|
|
73
|
+
if (!accessToken) return void 0;
|
|
74
|
+
const { id, token, cached } = await lookup(accessToken, context);
|
|
75
|
+
if (id && !cached) await context.sessionManager.setSessionItem(CACHE_KEY, { token, id });
|
|
76
|
+
return id;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async function fingerprint(token) {
|
|
81
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
|
|
82
|
+
return [...new Uint8Array(digest).slice(0, 16)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
bagsyRouteHandlers,
|
|
86
|
+
createHydrogenBagsy,
|
|
87
|
+
hydrogenCustomerId
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=hydrogen.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../hydrogen/src/hydrogen.ts"],"sourcesContent":["import { createShopifyRouteHandler, type ShopifyRequestContext } from '@shopify/hydrogen';\nimport {\n createCustomerAccountClient,\n gql,\n type CustomerSession,\n type ReadonlyCustomerSessionManager,\n type WritableCustomerSessionManager,\n} from '@shopify/hydrogen/customer-account';\nimport {\n createBagsyServer,\n remember,\n trackWrites,\n warnIfNotPersisted,\n type BagsyServer,\n type CreateBagsyServerOptions,\n type CustomerResolver,\n} from './server.js';\n\n/** For loaders and server components: a read-only session. */\nexport interface HydrogenReadContext {\n sessionManager: ReadonlyCustomerSessionManager;\n requestContext: ShopifyRequestContext;\n}\n\n/** For route handlers and actions: a session the response commits. */\nexport interface HydrogenWriteContext {\n sessionManager: WritableCustomerSessionManager;\n requestContext: ShopifyRequestContext;\n}\n\nexport interface CreateHydrogenBagsyOptions\n extends Omit<CreateBagsyServerOptions<HydrogenReadContext, HydrogenWriteContext>, 'customer'> {\n /**\n * Your `createCustomerSession()` result, so signed-in customers keep one\n * wishlist across devices. Omit for a guest-only storefront.\n */\n customerSession?: Pick<CustomerSession, 'getAccessToken' | 'getOrRefreshAccessToken'>;\n /** Numeric shop id, the same one you pass to `createCustomerSession()`. Required with `customerSession`. */\n shopId?: string;\n /** Fetch for Customer Account API calls (tests, custom runtimes). */\n customerAccountFetch?: typeof globalThis.fetch;\n}\n\n/**\n * Bagsy for Hydrogen 2026-10. Create once at module scope, register\n * `bagsy.handlers` with `handleShopifyRoutes`, and call `bagsy.shopper()` in\n * loaders.\n */\nexport function createHydrogenBagsy(options: CreateHydrogenBagsyOptions) {\n const { customerSession, shopId, customerAccountFetch, ...rest } = options;\n if (customerSession && !shopId) throw new TypeError('createHydrogenBagsy: `shopId` is required with `customerSession`.');\n\n const server = createBagsyServer<HydrogenReadContext, HydrogenWriteContext>({\n ...rest,\n ...(customerSession && shopId && { customer: hydrogenCustomerId({ customerSession, shopId, fetch: customerAccountFetch }) }),\n });\n\n return { ...server, handlers: bagsyRouteHandlers(server) };\n}\n\n/** `handleShopifyRoutes` handlers for the relay endpoint `@umxr/bagsy/react` calls. */\nexport function bagsyRouteHandlers(server: BagsyServer<HydrogenReadContext, HydrogenWriteContext>) {\n return {\n bagsyRelay: createShopifyRouteHandler(\n server.endpoint,\n 'POST',\n async (context: HydrogenWriteContext & { request: Request }) => {\n // Matched Shopify routes skip your app's response boundary, so commit here.\n const tracked = trackWrites(context.sessionManager);\n const result = await server.relay(context.request, { ...context, sessionManager: tracked.sessionManager });\n let headers: HeadersInit | undefined;\n if (tracked.dirty()) {\n headers = (await context.sessionManager.commit?.()) ?? undefined;\n warnIfNotPersisted(new Headers(headers));\n }\n if ('rejected' in result) {\n const { status, code, message } = result.rejected;\n return { type: 'error' as const, status, error: { code, message }, ...(headers && { headers }) };\n }\n return { type: 'json' as const, data: result.response, ...(headers && { headers }) };\n },\n ),\n };\n}\n\nconst CUSTOMER_ID_QUERY = gql(`query BagsyCustomerId { customer { id } }`);\nconst CACHE_KEY = 'bagsy:customer';\n/** Customer ids by token fingerprint, so read-only loaders don't ask the Customer Account API every request. */\nconst customerIds = new Map<string, string>();\n\n/**\n * Who is signed in, from Hydrogen's customer session. Reads never refresh\n * tokens or write the session; writes may refresh, and cache the customer id\n * against the token so the Customer Account API is asked once per sign-in.\n */\nexport function hydrogenCustomerId({\n customerSession,\n shopId,\n fetch,\n}: {\n customerSession: Pick<CustomerSession, 'getAccessToken' | 'getOrRefreshAccessToken'>;\n shopId: string;\n fetch?: typeof globalThis.fetch | undefined;\n}): CustomerResolver<HydrogenReadContext, HydrogenWriteContext> {\n async function lookup(accessToken: string, { sessionManager, requestContext }: HydrogenReadContext) {\n const token = await fingerprint(accessToken);\n const cached = (await sessionManager.getSessionItem(CACHE_KEY)) as { token?: string; id?: string } | undefined;\n if (cached?.token === token && cached.id) return { id: cached.id, token, cached: true };\n const known = customerIds.get(`${shopId}|${token}`);\n if (known) return { id: known, token, cached: false };\n const client = createCustomerAccountClient({ shopId, requestContext, ...(fetch && { fetch }) });\n const result = await client.graphql(CUSTOMER_ID_QUERY, { accessToken });\n const id = result.errors ? undefined : (result.data?.customer?.id ?? undefined);\n if (id) remember(customerIds, `${shopId}|${token}`, id);\n return { id, token, cached: false };\n }\n\n return {\n async read(context) {\n const accessToken = await customerSession.getAccessToken(context.sessionManager, context.requestContext);\n return accessToken ? (await lookup(accessToken, context)).id : undefined;\n },\n async write(context) {\n const accessToken = await customerSession.getOrRefreshAccessToken(context.sessionManager, context.requestContext);\n if (!accessToken) return undefined;\n const { id, token, cached } = await lookup(accessToken, context);\n if (id && !cached) await context.sessionManager.setSessionItem(CACHE_KEY, { token, id });\n return id;\n },\n };\n}\n\n/** A short hash of the token, so the session never holds a second copy of it. */\nasync function fingerprint(token: string) {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(token));\n return [...new Uint8Array(digest).slice(0, 16)].map((b) => b.toString(16).padStart(2, '0')).join('');\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,iCAA6D;AACtE;AAAA,EACE;AAAA,EACA;AAAA,OAIK;AAyCA,SAAS,oBAAoB,SAAqC;AACvE,QAAM,EAAE,iBAAiB,QAAQ,sBAAsB,GAAG,KAAK,IAAI;AACnE,MAAI,mBAAmB,CAAC,OAAQ,OAAM,IAAI,UAAU,mEAAmE;AAEvH,QAAM,SAAS,kBAA6D;AAAA,IAC1E,GAAG;AAAA,IACH,GAAI,mBAAmB,UAAU,EAAE,UAAU,mBAAmB,EAAE,iBAAiB,QAAQ,OAAO,qBAAqB,CAAC,EAAE;AAAA,EAC5H,CAAC;AAED,SAAO,EAAE,GAAG,QAAQ,UAAU,mBAAmB,MAAM,EAAE;AAC3D;AAGO,SAAS,mBAAmB,QAAgE;AACjG,SAAO;AAAA,IACL,YAAY;AAAA,MACV,OAAO;AAAA,MACP;AAAA,MACA,OAAO,YAAyD;AAE9D,cAAM,UAAU,YAAY,QAAQ,cAAc;AAClD,cAAM,SAAS,MAAM,OAAO,MAAM,QAAQ,SAAS,EAAE,GAAG,SAAS,gBAAgB,QAAQ,eAAe,CAAC;AACzG,YAAI;AACJ,YAAI,QAAQ,MAAM,GAAG;AACnB,oBAAW,MAAM,QAAQ,eAAe,SAAS,KAAM;AACvD,6BAAmB,IAAI,QAAQ,OAAO,CAAC;AAAA,QACzC;AACA,YAAI,cAAc,QAAQ;AACxB,gBAAM,EAAE,QAAQ,MAAM,QAAQ,IAAI,OAAO;AACzC,iBAAO,EAAE,MAAM,SAAkB,QAAQ,OAAO,EAAE,MAAM,QAAQ,GAAG,GAAI,WAAW,EAAE,QAAQ,EAAG;AAAA,QACjG;AACA,eAAO,EAAE,MAAM,QAAiB,MAAM,OAAO,UAAU,GAAI,WAAW,EAAE,QAAQ,EAAG;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAM,oBAAoB,IAAI,2CAA2C;AACzE,IAAM,YAAY;AAElB,IAAM,cAAc,oBAAI,IAAoB;AAOrC,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF,GAIgE;AAC9D,iBAAe,OAAO,aAAqB,EAAE,gBAAgB,eAAe,GAAwB;AAClG,UAAM,QAAQ,MAAM,YAAY,WAAW;AAC3C,UAAM,SAAU,MAAM,eAAe,eAAe,SAAS;AAC7D,QAAI,QAAQ,UAAU,SAAS,OAAO,GAAI,QAAO,EAAE,IAAI,OAAO,IAAI,OAAO,QAAQ,KAAK;AACtF,UAAM,QAAQ,YAAY,IAAI,GAAG,MAAM,IAAI,KAAK,EAAE;AAClD,QAAI,MAAO,QAAO,EAAE,IAAI,OAAO,OAAO,QAAQ,MAAM;AACpD,UAAM,SAAS,4BAA4B,EAAE,QAAQ,gBAAgB,GAAI,SAAS,EAAE,MAAM,EAAG,CAAC;AAC9F,UAAM,SAAS,MAAM,OAAO,QAAQ,mBAAmB,EAAE,YAAY,CAAC;AACtE,UAAM,KAAK,OAAO,SAAS,SAAa,OAAO,MAAM,UAAU,MAAM;AACrE,QAAI,GAAI,UAAS,aAAa,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE;AACtD,WAAO,EAAE,IAAI,OAAO,QAAQ,MAAM;AAAA,EACpC;AAEA,SAAO;AAAA,IACL,MAAM,KAAK,SAAS;AAClB,YAAM,cAAc,MAAM,gBAAgB,eAAe,QAAQ,gBAAgB,QAAQ,cAAc;AACvG,aAAO,eAAe,MAAM,OAAO,aAAa,OAAO,GAAG,KAAK;AAAA,IACjE;AAAA,IACA,MAAM,MAAM,SAAS;AACnB,YAAM,cAAc,MAAM,gBAAgB,wBAAwB,QAAQ,gBAAgB,QAAQ,cAAc;AAChH,UAAI,CAAC,YAAa,QAAO;AACzB,YAAM,EAAE,IAAI,OAAO,OAAO,IAAI,MAAM,OAAO,aAAa,OAAO;AAC/D,UAAI,MAAM,CAAC,OAAQ,OAAM,QAAQ,eAAe,eAAe,WAAW,EAAE,OAAO,GAAG,CAAC;AACvF,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAGA,eAAe,YAAY,OAAe;AACxC,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AACpF,SAAO,CAAC,GAAG,IAAI,WAAW,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AACrG;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export { B as Bagsy, c as createBagsy, a as createRelayClient } from './client-CXCgpFGa.js';
|
|
2
|
+
import { A as AccountOptions, T as Transport, a as ProductRef, I as ItemOptions, d as Item, e as List, L as ListRef, P as PageOptions, f as SharedList, g as ItemPage, S as SavedLookup, M as MergeResult, h as TopProduct, V as VariantRef, c as components } from './transport-Chh09xIP.js';
|
|
3
|
+
export { B as BagsyOptions, C as CustomerRef, O as Owner, i as ProxyOptions, R as RelayOptions, b as SavedEntry, j as ServerOptions, k as createTransport, l as customerGid, o as operations, p as paths, m as productGid, v as variantGid } from './transport-Chh09xIP.js';
|
|
4
|
+
export { a as RelayRequest, R as RelayResponse, c as createRelayTransport } from './relay-0l6VcQp9.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transport for customer account UI extensions: every request carries a
|
|
8
|
+
* current Shopify session token. Kept separate from createTransport so
|
|
9
|
+
* storefront bundles that never use it don't pay for it.
|
|
10
|
+
*/
|
|
11
|
+
declare function createAccountTransport({ sessionToken, baseUrl, fetch: fetchImpl, ...rest }: AccountOptions): Transport;
|
|
12
|
+
|
|
13
|
+
/** The owner's lists, default first. */
|
|
14
|
+
declare const listLists: (t: Transport) => Promise<List[]>;
|
|
15
|
+
declare const getList: (t: Transport, listId?: ListRef, { includeItems }?: {
|
|
16
|
+
includeItems?: boolean | undefined;
|
|
17
|
+
}) => Promise<List>;
|
|
18
|
+
declare const createList: (t: Transport, name: string) => Promise<List>;
|
|
19
|
+
declare const renameList: (t: Transport, listId: ListRef, name: string) => Promise<List>;
|
|
20
|
+
declare const deleteList: (t: Transport, listId: ListRef) => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* The list's share token, creating it on first share. Stable across calls, so
|
|
23
|
+
* links already sent keep working; `rotate: true` issues a new one.
|
|
24
|
+
*/
|
|
25
|
+
declare const shareList: (t: Transport, listId?: ListRef, { rotate }?: {
|
|
26
|
+
rotate?: boolean;
|
|
27
|
+
}) => Promise<string>;
|
|
28
|
+
declare const unshareList: (t: Transport, listId?: ListRef) => Promise<void>;
|
|
29
|
+
declare const listItems: (t: Transport, listId?: ListRef, page?: PageOptions) => Promise<ItemPage>;
|
|
30
|
+
/** Every item in a list, fetched page by page. */
|
|
31
|
+
declare function iterateItems(t: Transport, listId?: ListRef, { limit }?: {
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): AsyncGenerator<Item>;
|
|
34
|
+
/** Save a product. Saving it again returns the existing item. */
|
|
35
|
+
declare const addItem: (t: Transport, product: ProductRef, { listId, variantId, quantity, note }?: ItemOptions & {
|
|
36
|
+
quantity?: number;
|
|
37
|
+
note?: string;
|
|
38
|
+
}) => Promise<Item>;
|
|
39
|
+
/** Unsave by product, without knowing the item id. No-op if not saved. */
|
|
40
|
+
declare const removeProduct: (t: Transport, product: ProductRef, { listId, variantId }?: ItemOptions) => Promise<void>;
|
|
41
|
+
/** Save if unsaved, unsave if saved. Built for heart buttons. */
|
|
42
|
+
declare function toggleItem(t: Transport, product: ProductRef, { listId, variantId }?: ItemOptions): Promise<{
|
|
43
|
+
saved: boolean;
|
|
44
|
+
}>;
|
|
45
|
+
declare const updateItem: (t: Transport, itemId: string, patch: {
|
|
46
|
+
variantId?: VariantRef | null;
|
|
47
|
+
quantity?: number;
|
|
48
|
+
note?: string | null;
|
|
49
|
+
}, { listId }?: {
|
|
50
|
+
listId?: ListRef;
|
|
51
|
+
}) => Promise<Item>;
|
|
52
|
+
declare const removeItem: (t: Transport, itemId: string, { listId }?: {
|
|
53
|
+
listId?: ListRef;
|
|
54
|
+
}) => Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Saved state for a set of products across all of the owner's lists.
|
|
57
|
+
* Accepts any number of products; batches of 100 are fetched in parallel.
|
|
58
|
+
*/
|
|
59
|
+
declare function lookupSaved(t: Transport, products: Iterable<ProductRef>): Promise<SavedLookup>;
|
|
60
|
+
/**
|
|
61
|
+
* Fold a guest's lists into the logged-in customer's. Call once after
|
|
62
|
+
* login; safe to retry. Defaults to the transport's `visitorId`.
|
|
63
|
+
*/
|
|
64
|
+
declare function mergeVisitor(t: Transport, visitorId?: string | undefined): Promise<MergeResult>;
|
|
65
|
+
/** Public, read-only view of a shared list. */
|
|
66
|
+
declare const getSharedList: (t: Transport, shareToken: string, page?: PageOptions) => Promise<SharedList>;
|
|
67
|
+
/** Most-saved products. Server key only. */
|
|
68
|
+
declare function topProducts(t: Transport, query?: {
|
|
69
|
+
since?: Date | string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
}): Promise<TopProduct[]>;
|
|
72
|
+
|
|
73
|
+
type Problem = components['schemas']['Problem'];
|
|
74
|
+
type BagsyErrorCode = Problem['code'] | 'unexpected_response';
|
|
75
|
+
/** Thrown for any non-2xx response. Switch on `code`, never on `message`. */
|
|
76
|
+
declare class BagsyError extends Error {
|
|
77
|
+
readonly name = "BagsyError";
|
|
78
|
+
readonly status: number;
|
|
79
|
+
readonly code: BagsyErrorCode;
|
|
80
|
+
readonly problem?: Problem;
|
|
81
|
+
constructor(status: number, problem: unknown);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** The slice of Web Storage this module uses; `localStorage` satisfies it. */
|
|
85
|
+
interface VisitorStorage {
|
|
86
|
+
getItem(key: string): string | null;
|
|
87
|
+
setItem(key: string, value: string): void;
|
|
88
|
+
removeItem(key: string): void;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Returns this browser's guest identity, creating it on first call.
|
|
92
|
+
*
|
|
93
|
+
* Persisted in localStorage. Falls back to an in-memory ID (for the page's
|
|
94
|
+
* lifetime) when storage is blocked, e.g. in private windows or iframes.
|
|
95
|
+
*/
|
|
96
|
+
declare function getVisitorId(storage?: Pick<VisitorStorage, 'getItem' | 'setItem'> | undefined): string;
|
|
97
|
+
/** The guest identity if one exists, without creating it. Use to decide whether to merge on login. */
|
|
98
|
+
declare function peekVisitorId(storage?: Pick<VisitorStorage, 'getItem'> | undefined): string | undefined;
|
|
99
|
+
/** Forget the guest identity, e.g. after a successful merge on login. */
|
|
100
|
+
declare function clearVisitorId(storage?: Pick<VisitorStorage, 'removeItem'> | undefined): void;
|
|
101
|
+
|
|
102
|
+
export { AccountOptions, BagsyError, Item, ItemOptions, ItemPage, List, ListRef, MergeResult, PageOptions, ProductRef, SavedLookup, SharedList, TopProduct, Transport, VariantRef, addItem, clearVisitorId, components, createAccountTransport, createList, deleteList, getList, getSharedList, getVisitorId, iterateItems, listItems, listLists, lookupSaved, mergeVisitor, peekVisitorId, removeItem, removeProduct, renameList, shareList, toggleItem, topProducts, unshareList, updateItem };
|
|
103
|
+
export type { BagsyErrorCode, Problem, VisitorStorage };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BagsyError,
|
|
3
|
+
addItem,
|
|
4
|
+
clearVisitorId,
|
|
5
|
+
createAccountTransport,
|
|
6
|
+
createBagsy,
|
|
7
|
+
createList,
|
|
8
|
+
createRelayClient,
|
|
9
|
+
createRelayTransport,
|
|
10
|
+
createTransport,
|
|
11
|
+
customerGid,
|
|
12
|
+
deleteList,
|
|
13
|
+
getList,
|
|
14
|
+
getSharedList,
|
|
15
|
+
getVisitorId,
|
|
16
|
+
iterateItems,
|
|
17
|
+
listItems,
|
|
18
|
+
listLists,
|
|
19
|
+
lookupSaved,
|
|
20
|
+
mergeVisitor,
|
|
21
|
+
peekVisitorId,
|
|
22
|
+
productGid,
|
|
23
|
+
removeItem,
|
|
24
|
+
removeProduct,
|
|
25
|
+
renameList,
|
|
26
|
+
shareList,
|
|
27
|
+
toggleItem,
|
|
28
|
+
topProducts,
|
|
29
|
+
unshareList,
|
|
30
|
+
updateItem,
|
|
31
|
+
variantGid
|
|
32
|
+
} from "./chunk-QV3JPND6.js";
|
|
33
|
+
export {
|
|
34
|
+
BagsyError,
|
|
35
|
+
addItem,
|
|
36
|
+
clearVisitorId,
|
|
37
|
+
createAccountTransport,
|
|
38
|
+
createBagsy,
|
|
39
|
+
createList,
|
|
40
|
+
createRelayClient,
|
|
41
|
+
createRelayTransport,
|
|
42
|
+
createTransport,
|
|
43
|
+
customerGid,
|
|
44
|
+
deleteList,
|
|
45
|
+
getList,
|
|
46
|
+
getSharedList,
|
|
47
|
+
getVisitorId,
|
|
48
|
+
iterateItems,
|
|
49
|
+
listItems,
|
|
50
|
+
listLists,
|
|
51
|
+
lookupSaved,
|
|
52
|
+
mergeVisitor,
|
|
53
|
+
peekVisitorId,
|
|
54
|
+
productGid,
|
|
55
|
+
removeItem,
|
|
56
|
+
removeProduct,
|
|
57
|
+
renameList,
|
|
58
|
+
shareList,
|
|
59
|
+
toggleItem,
|
|
60
|
+
topProducts,
|
|
61
|
+
unshareList,
|
|
62
|
+
updateItem,
|
|
63
|
+
variantGid
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
import { Bagsy } from './index.js';
|
|
4
|
+
import { SavedEntry, ProductRef, VariantRef, ListRef, Item } from './index.js';
|
|
5
|
+
|
|
6
|
+
interface SavedState {
|
|
7
|
+
/**
|
|
8
|
+
* Saved in any list. With a variant: that variant, or the product without a
|
|
9
|
+
* variant (which means "any variant"). False until known.
|
|
10
|
+
*/
|
|
11
|
+
saved: boolean;
|
|
12
|
+
/** True once the server has answered (or the state was server-rendered). */
|
|
13
|
+
ready: boolean;
|
|
14
|
+
/** A toggle is in flight. */
|
|
15
|
+
pending: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface StoreEvents {
|
|
18
|
+
/** A toggle was confirmed by the server. */
|
|
19
|
+
onChange?: (change: {
|
|
20
|
+
productId: string;
|
|
21
|
+
variantId?: string;
|
|
22
|
+
saved: boolean;
|
|
23
|
+
}) => void;
|
|
24
|
+
/** Any failed request. Toggles have already been rolled back. */
|
|
25
|
+
onError?: (error: unknown) => void;
|
|
26
|
+
}
|
|
27
|
+
type Listener = () => void;
|
|
28
|
+
/**
|
|
29
|
+
* Saved state for one shopper, shared by every component under a provider.
|
|
30
|
+
* Plain TypeScript, so it can be used without React (and tested in Node).
|
|
31
|
+
*
|
|
32
|
+
* - Lookups for products not seen before are batched: every component that
|
|
33
|
+
* mounts in the same tick shares one `/saved` request.
|
|
34
|
+
* - Toggles are optimistic and roll back on failure.
|
|
35
|
+
* - Server-rendered state can be seeded, so the first render is already right.
|
|
36
|
+
*/
|
|
37
|
+
declare class BagsyStore {
|
|
38
|
+
readonly client: Bagsy;
|
|
39
|
+
private events;
|
|
40
|
+
private saved;
|
|
41
|
+
private pendingToggles;
|
|
42
|
+
private queued;
|
|
43
|
+
private flushing?;
|
|
44
|
+
private productListeners;
|
|
45
|
+
private countListeners;
|
|
46
|
+
private snapshots;
|
|
47
|
+
private count?;
|
|
48
|
+
private countRequest?;
|
|
49
|
+
constructor(client: Bagsy, events?: StoreEvents);
|
|
50
|
+
setEvents(events: StoreEvents): void;
|
|
51
|
+
/**
|
|
52
|
+
* Server-rendered saved state, e.g. `shopper.saved(ids).byProduct`. Never
|
|
53
|
+
* overwrites state the client already has. Returns the products it filled
|
|
54
|
+
* in; pass `notify: false` while rendering and call `notify()` after.
|
|
55
|
+
*/
|
|
56
|
+
seed(byProduct: Readonly<Record<string, SavedEntry[]>> | undefined, { notify }?: {
|
|
57
|
+
notify?: boolean | undefined;
|
|
58
|
+
}): string[];
|
|
59
|
+
/**
|
|
60
|
+
* Server-rendered count. Later values (e.g. a root loader revalidating after
|
|
61
|
+
* navigation) replace it, unless a toggle is in flight.
|
|
62
|
+
*/
|
|
63
|
+
seedCount(count: number | undefined, { notify }?: {
|
|
64
|
+
notify?: boolean | undefined;
|
|
65
|
+
}): void;
|
|
66
|
+
/** Current state. Stable object identity until it changes, for useSyncExternalStore. */
|
|
67
|
+
getState(product: ProductRef, variant?: VariantRef): SavedState;
|
|
68
|
+
/** Subscribe to one product. Loads its state (batched) if unknown. */
|
|
69
|
+
subscribe(product: ProductRef, listener: Listener): () => void;
|
|
70
|
+
/**
|
|
71
|
+
* Save if unsaved, unsave if saved. The UI updates before the server answers.
|
|
72
|
+
*
|
|
73
|
+
* - Without a variant: saves the product; unsaving removes every saved
|
|
74
|
+
* variant of it.
|
|
75
|
+
* - With a variant: saves that variant; unsaving removes that variant and
|
|
76
|
+
* any product-level save (which covers all variants).
|
|
77
|
+
*
|
|
78
|
+
* A second tap while the first is in flight is ignored.
|
|
79
|
+
*/
|
|
80
|
+
toggle(product: ProductRef, variant?: VariantRef): Promise<boolean>;
|
|
81
|
+
private unsave;
|
|
82
|
+
/** Record an item removed elsewhere (e.g. the wishlist page), so hearts update without a request. */
|
|
83
|
+
forget(product: ProductRef, itemId: string): void;
|
|
84
|
+
getCount(): number | undefined;
|
|
85
|
+
subscribeCount(listener: Listener): () => void;
|
|
86
|
+
/** Loads the default list's item count once; later calls share it. */
|
|
87
|
+
ensureCount(): Promise<number>;
|
|
88
|
+
private refreshCount;
|
|
89
|
+
private loadCount;
|
|
90
|
+
private adjustCount;
|
|
91
|
+
private queue;
|
|
92
|
+
/** Tell subscribers of these products their state may have changed. */
|
|
93
|
+
notify(...ids: string[]): void;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface BagsyProviderProps extends StoreEvents {
|
|
97
|
+
/**
|
|
98
|
+
* Your server's relay endpoint (see `@umxr/bagsy/hydrogen`).
|
|
99
|
+
* @default '/api/bagsy'
|
|
100
|
+
*/
|
|
101
|
+
endpoint?: string;
|
|
102
|
+
/** Or bring your own client, e.g. `createBagsy({ mode: 'proxy', visitorId: getVisitorId() })` for React in a Liquid theme. */
|
|
103
|
+
client?: Bagsy;
|
|
104
|
+
/** Server-rendered saved state: `(await shopper.saved(ids)).byProduct`. */
|
|
105
|
+
initialSaved?: Readonly<Record<string, SavedEntry[]>>;
|
|
106
|
+
/** Server-rendered count: `await shopper.count()`. */
|
|
107
|
+
initialCount?: number;
|
|
108
|
+
children?: ReactNode;
|
|
109
|
+
}
|
|
110
|
+
/** One per app. Every Bagsy hook and component below it shares one store. */
|
|
111
|
+
declare function BagsyProvider({ endpoint, client, initialSaved, initialCount, onChange, onError, children, }: BagsyProviderProps): react.JSX.Element;
|
|
112
|
+
/** The provider's store and client, for anything the hooks don't cover. */
|
|
113
|
+
declare function useBagsy(): {
|
|
114
|
+
store: BagsyStore;
|
|
115
|
+
client: Bagsy;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* Seed saved state from a route's loader, e.g. a collection page that
|
|
119
|
+
* returns `saved: (await shopper.saved(productIds)).byProduct`.
|
|
120
|
+
*/
|
|
121
|
+
declare function useSeedSaved(byProduct: Readonly<Record<string, SavedEntry[]>> | undefined): void;
|
|
122
|
+
interface UseSavedResult extends SavedState {
|
|
123
|
+
/** Save if unsaved, unsave if saved. Resolves to the new state; rejects (after rolling back) on failure. */
|
|
124
|
+
toggle(): Promise<boolean>;
|
|
125
|
+
}
|
|
126
|
+
/** Saved state for one product, or one variant of it. */
|
|
127
|
+
declare function useSaved(product: ProductRef, options?: {
|
|
128
|
+
variantId?: VariantRef | undefined;
|
|
129
|
+
}): UseSavedResult;
|
|
130
|
+
/** Number of items in the shopper's default list, for a header badge. Undefined until known. */
|
|
131
|
+
declare function useWishlistCount(): number | undefined;
|
|
132
|
+
interface UseWishlistOptions {
|
|
133
|
+
/** @default 'default' */
|
|
134
|
+
listId?: ListRef;
|
|
135
|
+
/** Items per page. @default 24 */
|
|
136
|
+
pageSize?: number;
|
|
137
|
+
/** Server-rendered first page: `await (await bagsy.shopper(context)).items({ limit })`. */
|
|
138
|
+
initial?: {
|
|
139
|
+
data: Item[];
|
|
140
|
+
pageInfo: {
|
|
141
|
+
hasNextPage: boolean;
|
|
142
|
+
endCursor?: string | null;
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
interface UseWishlistResult {
|
|
147
|
+
items: Item[];
|
|
148
|
+
status: 'loading' | 'ready' | 'error';
|
|
149
|
+
error: unknown;
|
|
150
|
+
hasNextPage: boolean;
|
|
151
|
+
loadingMore: boolean;
|
|
152
|
+
loadMore(): Promise<void>;
|
|
153
|
+
/** Remove an item. Optimistic; restores it on failure. Hearts elsewhere update too. */
|
|
154
|
+
remove(item: Item): Promise<void>;
|
|
155
|
+
refresh(): Promise<void>;
|
|
156
|
+
}
|
|
157
|
+
/** The items in a list, newest first, page by page. */
|
|
158
|
+
declare function useWishlist({ listId, pageSize, initial }?: UseWishlistOptions): UseWishlistResult;
|
|
159
|
+
interface WishlistButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onToggle' | 'onError'> {
|
|
160
|
+
productId: ProductRef;
|
|
161
|
+
variantId?: VariantRef | undefined;
|
|
162
|
+
/** Content, or a function of the saved state. Defaults to "Save" / "Saved". */
|
|
163
|
+
children?: ReactNode | ((state: SavedState) => ReactNode);
|
|
164
|
+
onToggle?: (saved: boolean) => void;
|
|
165
|
+
onError?: (error: unknown) => void;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* An accessible save toggle. Unstyled: style it with `[data-saved]`,
|
|
169
|
+
* `[data-pending]` and `[aria-pressed]`. Safe inside product-card links.
|
|
170
|
+
*/
|
|
171
|
+
declare function WishlistButton({ productId, variantId, children, onToggle, onError, onClick, ...rest }: WishlistButtonProps): react.JSX.Element;
|
|
172
|
+
|
|
173
|
+
export { BagsyProvider, BagsyStore, WishlistButton, useBagsy, useSaved, useSeedSaved, useWishlist, useWishlistCount };
|
|
174
|
+
export type { BagsyProviderProps, SavedState, StoreEvents, UseSavedResult, UseWishlistOptions, UseWishlistResult, WishlistButtonProps };
|