@shopbb/helium 0.1.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/README.md +50 -0
- package/dist/cache.d.ts +16 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +26 -0
- package/dist/cache.js.map +1 -0
- package/dist/cart-id.d.ts +33 -0
- package/dist/cart-id.d.ts.map +1 -0
- package/dist/cart-id.js +42 -0
- package/dist/cart-id.js.map +1 -0
- package/dist/createCartHandler.d.ts +17 -0
- package/dist/createCartHandler.d.ts.map +1 -0
- package/dist/createCartHandler.js +156 -0
- package/dist/createCartHandler.js.map +1 -0
- package/dist/createHeliumContext.d.ts +23 -0
- package/dist/createHeliumContext.d.ts.map +1 -0
- package/dist/createHeliumContext.js +66 -0
- package/dist/createHeliumContext.js.map +1 -0
- package/dist/createStorefrontClient.d.ts +19 -0
- package/dist/createStorefrontClient.d.ts.map +1 -0
- package/dist/createStorefrontClient.js +131 -0
- package/dist/createStorefrontClient.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +106 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
- package/src/cache.ts +31 -0
- package/src/cart-id.ts +63 -0
- package/src/createCartHandler.ts +189 -0
- package/src/createHeliumContext.ts +72 -0
- package/src/createStorefrontClient.ts +164 -0
- package/src/index.ts +40 -0
- package/src/types.ts +133 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC/D,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAGxE,YAAY,EACV,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EACvB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @shopbb/helium
|
|
3
|
+
*
|
|
4
|
+
* shopbb's storefront framework. Hydrogen-equivalent for the shopbb platform.
|
|
5
|
+
*
|
|
6
|
+
* Public API:
|
|
7
|
+
*
|
|
8
|
+
* - createHeliumContext Top-level factory
|
|
9
|
+
* - createStorefrontClient GraphQL client (used by createHeliumContext)
|
|
10
|
+
* - createCartHandler Cart operations (used by createHeliumContext)
|
|
11
|
+
* - cartGetIdDefault Default cart ID getter (reads cookie)
|
|
12
|
+
* - cartSetIdDefault Default cart ID setter (writes Set-Cookie)
|
|
13
|
+
* - CacheNone / CacheShort / CacheLong / CacheCustom
|
|
14
|
+
*/
|
|
15
|
+
export { createHeliumContext } from './createHeliumContext';
|
|
16
|
+
export { createStorefrontClient } from './createStorefrontClient';
|
|
17
|
+
export { createCartHandler, DEFAULT_CART_FRAGMENT } from './createCartHandler';
|
|
18
|
+
export { cartGetIdDefault, cartSetIdDefault } from './cart-id';
|
|
19
|
+
export { CacheNone, CacheShort, CacheLong, CacheCustom } from './cache';
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helium shared types.
|
|
3
|
+
*
|
|
4
|
+
* 这些类型在 storefront client / cart handler / context 之间共享。
|
|
5
|
+
* 对应 shopbb Storefront API GraphQL schema 的核心类型。
|
|
6
|
+
*/
|
|
7
|
+
export interface CacheStrategy {
|
|
8
|
+
mode: 'NONE' | 'PUBLIC';
|
|
9
|
+
/** CDN/edge cache TTL in seconds */
|
|
10
|
+
maxAge: number;
|
|
11
|
+
/** SWR window in seconds */
|
|
12
|
+
staleWhileRevalidate?: number;
|
|
13
|
+
}
|
|
14
|
+
export interface StorefrontClientOptions {
|
|
15
|
+
/** GraphQL endpoint URL */
|
|
16
|
+
apiUrl: string;
|
|
17
|
+
/** Public Storefront API access token */
|
|
18
|
+
publicAccessToken: string;
|
|
19
|
+
/** Private Storefront API access token (preferred when available) */
|
|
20
|
+
privateAccessToken?: string;
|
|
21
|
+
/** Store ID */
|
|
22
|
+
storeId: string;
|
|
23
|
+
/** Original request, used to forward identifying headers if needed */
|
|
24
|
+
request?: Request;
|
|
25
|
+
/** Cache instance (e.g. caches.default or caches.open(...)) */
|
|
26
|
+
cache?: Cache;
|
|
27
|
+
/** waitUntil from ExecutionContext for SWR */
|
|
28
|
+
waitUntil?: (promise: Promise<any>) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface QueryOptions {
|
|
31
|
+
variables?: Record<string, any>;
|
|
32
|
+
cache?: CacheStrategy;
|
|
33
|
+
}
|
|
34
|
+
export interface MutateOptions {
|
|
35
|
+
variables?: Record<string, any>;
|
|
36
|
+
}
|
|
37
|
+
export interface StorefrontClient {
|
|
38
|
+
query<TData = any>(query: string, options?: QueryOptions): Promise<TData>;
|
|
39
|
+
mutate<TData = any>(mutation: string, options?: MutateOptions): Promise<TData>;
|
|
40
|
+
CacheNone(): CacheStrategy;
|
|
41
|
+
CacheShort(): CacheStrategy;
|
|
42
|
+
CacheLong(): CacheStrategy;
|
|
43
|
+
CacheCustom(seconds: number): CacheStrategy;
|
|
44
|
+
}
|
|
45
|
+
export type CartIdGetter = () => string | null;
|
|
46
|
+
export type CartIdSetter = (cartId: string, responseHeaders: Headers) => void;
|
|
47
|
+
export interface CartHandlerOptions {
|
|
48
|
+
storefront: StorefrontClient;
|
|
49
|
+
getCartId: CartIdGetter;
|
|
50
|
+
/** setCartId 会写入 responseHeaders(注入 Set-Cookie) */
|
|
51
|
+
setCartId: CartIdSetter;
|
|
52
|
+
responseHeaders: Headers;
|
|
53
|
+
}
|
|
54
|
+
export interface CartLineInput {
|
|
55
|
+
merchandiseId: string;
|
|
56
|
+
quantity: number;
|
|
57
|
+
}
|
|
58
|
+
export interface CartLineUpdateInput {
|
|
59
|
+
id: string;
|
|
60
|
+
quantity: number;
|
|
61
|
+
}
|
|
62
|
+
export interface CartUserError {
|
|
63
|
+
field?: string[] | null;
|
|
64
|
+
message: string;
|
|
65
|
+
code?: string | null;
|
|
66
|
+
}
|
|
67
|
+
export interface CartResult<TCart = any> {
|
|
68
|
+
cart: TCart | null;
|
|
69
|
+
userErrors: CartUserError[];
|
|
70
|
+
}
|
|
71
|
+
export interface CartHandler<TCart = any> {
|
|
72
|
+
get(): Promise<TCart | null>;
|
|
73
|
+
getCartId(): string | null;
|
|
74
|
+
setCartId(cartId: string): void;
|
|
75
|
+
create(input?: {
|
|
76
|
+
lines?: CartLineInput[];
|
|
77
|
+
}): Promise<CartResult<TCart>>;
|
|
78
|
+
addLines(lines: CartLineInput[]): Promise<CartResult<TCart>>;
|
|
79
|
+
updateLines(lines: CartLineUpdateInput[]): Promise<CartResult<TCart>>;
|
|
80
|
+
removeLines(lineIds: string[]): Promise<CartResult<TCart>>;
|
|
81
|
+
}
|
|
82
|
+
export interface HeliumContextOptions {
|
|
83
|
+
request: Request;
|
|
84
|
+
env: Record<string, any>;
|
|
85
|
+
executionContext: ExecutionContext;
|
|
86
|
+
storefront: Omit<StorefrontClientOptions, 'request' | 'cache' | 'waitUntil'> & {
|
|
87
|
+
request?: Request;
|
|
88
|
+
cache?: Cache;
|
|
89
|
+
waitUntil?: (p: Promise<any>) => void;
|
|
90
|
+
};
|
|
91
|
+
cart?: {
|
|
92
|
+
getId: CartIdGetter;
|
|
93
|
+
setId: (cartId: string, headers: Headers) => void;
|
|
94
|
+
/** Optional custom GraphQL fragment for the Cart return type */
|
|
95
|
+
cartFragment?: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export interface HeliumContext {
|
|
99
|
+
env: Record<string, any>;
|
|
100
|
+
request: Request;
|
|
101
|
+
storefront: StorefrontClient;
|
|
102
|
+
cart: CartHandler;
|
|
103
|
+
/** Headers that should be merged into the outgoing response (e.g. Set-Cookie) */
|
|
104
|
+
responseHeaders: Headers;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shopbb/helium",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "shopbb storefront framework — GraphQL client, cart handler, and cache helpers for Cloudflare Workers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"src",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.build.json",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"shopbb",
|
|
29
|
+
"storefront",
|
|
30
|
+
"ecommerce",
|
|
31
|
+
"cloudflare",
|
|
32
|
+
"workers",
|
|
33
|
+
"graphql"
|
|
34
|
+
],
|
|
35
|
+
"repository": "https://github.com/shopbb/shopbb",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@cloudflare/workers-types": "^4.20240117.0",
|
|
38
|
+
"typescript": "^5.3.3"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache strategies.
|
|
3
|
+
*
|
|
4
|
+
* Use with storefront.query({ cache: CacheLong() }).
|
|
5
|
+
*
|
|
6
|
+
* Mirrors Shopify Hydrogen's cache strategies API.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { CacheStrategy } from './types';
|
|
10
|
+
|
|
11
|
+
export function CacheNone(): CacheStrategy {
|
|
12
|
+
return { mode: 'NONE', maxAge: 0 };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function CacheShort(): CacheStrategy {
|
|
16
|
+
return { mode: 'PUBLIC', maxAge: 60, staleWhileRevalidate: 600 };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CacheLong(): CacheStrategy {
|
|
20
|
+
return { mode: 'PUBLIC', maxAge: 3600, staleWhileRevalidate: 82800 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Custom cache TTL in seconds.
|
|
25
|
+
*/
|
|
26
|
+
export function CacheCustom(seconds: number): CacheStrategy {
|
|
27
|
+
return {
|
|
28
|
+
mode: seconds > 0 ? 'PUBLIC' : 'NONE',
|
|
29
|
+
maxAge: Math.max(0, seconds),
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/cart-id.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cart ID helpers.
|
|
3
|
+
*
|
|
4
|
+
* Cookie name: "cart"
|
|
5
|
+
* - cartGetIdDefault: parse "cart=<id>" from request cookie
|
|
6
|
+
* - cartSetIdDefault: returns a function that appends Set-Cookie to response headers
|
|
7
|
+
*
|
|
8
|
+
* 命名/行为对齐 Shopify Hydrogen (`cartGetIdDefault` / `cartSetIdDefault`).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { CartIdGetter, CartIdSetter } from './types';
|
|
12
|
+
|
|
13
|
+
export interface CartSetIdOptions {
|
|
14
|
+
/** Cookie path (default '/') */
|
|
15
|
+
path?: string;
|
|
16
|
+
/** Cookie max-age in seconds (default 1 year) */
|
|
17
|
+
maxage?: number;
|
|
18
|
+
/** SameSite policy (default 'Lax') */
|
|
19
|
+
sameSite?: 'Lax' | 'Strict' | 'None';
|
|
20
|
+
/** Secure flag (default true) */
|
|
21
|
+
secure?: boolean;
|
|
22
|
+
/** HttpOnly flag (default true) */
|
|
23
|
+
httpOnly?: boolean;
|
|
24
|
+
/** Optional domain */
|
|
25
|
+
domain?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const COOKIE_NAME = 'cart';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns a getter that reads `cart=<id>` from request cookies.
|
|
32
|
+
*/
|
|
33
|
+
export function cartGetIdDefault(requestHeaders: Headers): CartIdGetter {
|
|
34
|
+
return () => {
|
|
35
|
+
const cookie = requestHeaders.get('cookie') || '';
|
|
36
|
+
const match = cookie.match(/(?:^|; )cart=([^;]+)/);
|
|
37
|
+
return match ? decodeURIComponent(match[1]) : null;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns a setter that appends `Set-Cookie: cart=<id>; ...` to response headers.
|
|
43
|
+
*/
|
|
44
|
+
export function cartSetIdDefault(options: CartSetIdOptions = {}): CartIdSetter {
|
|
45
|
+
const path = options.path ?? '/';
|
|
46
|
+
const maxage = options.maxage ?? 60 * 60 * 24 * 365;
|
|
47
|
+
const sameSite = options.sameSite ?? 'Lax';
|
|
48
|
+
const secure = options.secure !== false;
|
|
49
|
+
const httpOnly = options.httpOnly !== false;
|
|
50
|
+
const domainPart = options.domain ? `; Domain=${options.domain}` : '';
|
|
51
|
+
|
|
52
|
+
return (cartId: string, headers: Headers) => {
|
|
53
|
+
const cookie =
|
|
54
|
+
`${COOKIE_NAME}=${encodeURIComponent(cartId)}` +
|
|
55
|
+
`; Path=${path}` +
|
|
56
|
+
`; Max-Age=${maxage}` +
|
|
57
|
+
`; SameSite=${sameSite}` +
|
|
58
|
+
(secure ? '; Secure' : '') +
|
|
59
|
+
(httpOnly ? '; HttpOnly' : '') +
|
|
60
|
+
domainPart;
|
|
61
|
+
headers.append('Set-Cookie', cookie);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createCartHandler
|
|
3
|
+
*
|
|
4
|
+
* High-level cart API that wraps the storefront GraphQL mutations.
|
|
5
|
+
* Behavior aligned with Shopify Hydrogen:
|
|
6
|
+
* - get(): returns null if no cart cookie or cart doesn't exist
|
|
7
|
+
* - addLines(): if no cart exists, calls cartCreate first
|
|
8
|
+
* - All mutations: auto setCookie when a new cart is created
|
|
9
|
+
*
|
|
10
|
+
* The handler operates on Cart GIDs internally; callers pass merchandiseIds
|
|
11
|
+
* (ProductVariant GIDs) and line IDs (cart line IDs).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
CartHandler,
|
|
16
|
+
CartHandlerOptions,
|
|
17
|
+
CartLineInput,
|
|
18
|
+
CartLineUpdateInput,
|
|
19
|
+
CartResult,
|
|
20
|
+
} from './types';
|
|
21
|
+
|
|
22
|
+
/** Default cart return fragment. Can be overridden by caller. */
|
|
23
|
+
export const DEFAULT_CART_FRAGMENT = /* GraphQL */ `
|
|
24
|
+
fragment CartReturn on Cart {
|
|
25
|
+
id
|
|
26
|
+
createdAt
|
|
27
|
+
updatedAt
|
|
28
|
+
totalQuantity
|
|
29
|
+
cost {
|
|
30
|
+
subtotalAmount { amount currencyCode }
|
|
31
|
+
totalAmount { amount currencyCode }
|
|
32
|
+
}
|
|
33
|
+
lines(first: 50) {
|
|
34
|
+
nodes {
|
|
35
|
+
id
|
|
36
|
+
quantity
|
|
37
|
+
cost {
|
|
38
|
+
totalAmount { amount currencyCode }
|
|
39
|
+
amountPerQuantity { amount currencyCode }
|
|
40
|
+
}
|
|
41
|
+
merchandise {
|
|
42
|
+
... on ProductVariant {
|
|
43
|
+
id
|
|
44
|
+
title
|
|
45
|
+
sku
|
|
46
|
+
availableForSale
|
|
47
|
+
price { amount currencyCode }
|
|
48
|
+
image { url altText }
|
|
49
|
+
product { id handle title }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
checkoutUrl
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
export function createCartHandler(options: CartHandlerOptions): CartHandler {
|
|
59
|
+
const { storefront, getCartId, setCartId: rawSetId, responseHeaders } = options;
|
|
60
|
+
|
|
61
|
+
// Set the cart ID (delegate to options.setCartId, which writes Set-Cookie)
|
|
62
|
+
function setCartId(cartId: string): void {
|
|
63
|
+
rawSetId(cartId, responseHeaders);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ----- GraphQL operations -----
|
|
67
|
+
|
|
68
|
+
const CART_QUERY = /* GraphQL */ `
|
|
69
|
+
${DEFAULT_CART_FRAGMENT}
|
|
70
|
+
query Cart($id: ID!) {
|
|
71
|
+
cart(id: $id) { ...CartReturn }
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
const CART_CREATE = /* GraphQL */ `
|
|
76
|
+
${DEFAULT_CART_FRAGMENT}
|
|
77
|
+
mutation CartCreate($input: CartInput) {
|
|
78
|
+
cartCreate(input: $input) {
|
|
79
|
+
cart { ...CartReturn }
|
|
80
|
+
userErrors { field message code }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const CART_LINES_ADD = /* GraphQL */ `
|
|
86
|
+
${DEFAULT_CART_FRAGMENT}
|
|
87
|
+
mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
|
|
88
|
+
cartLinesAdd(cartId: $cartId, lines: $lines) {
|
|
89
|
+
cart { ...CartReturn }
|
|
90
|
+
userErrors { field message code }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
const CART_LINES_UPDATE = /* GraphQL */ `
|
|
96
|
+
${DEFAULT_CART_FRAGMENT}
|
|
97
|
+
mutation CartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) {
|
|
98
|
+
cartLinesUpdate(cartId: $cartId, lines: $lines) {
|
|
99
|
+
cart { ...CartReturn }
|
|
100
|
+
userErrors { field message code }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
const CART_LINES_REMOVE = /* GraphQL */ `
|
|
106
|
+
${DEFAULT_CART_FRAGMENT}
|
|
107
|
+
mutation CartLinesRemove($cartId: ID!, $lineIds: [ID!]!) {
|
|
108
|
+
cartLinesRemove(cartId: $cartId, lineIds: $lineIds) {
|
|
109
|
+
cart { ...CartReturn }
|
|
110
|
+
userErrors { field message code }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
// ----- Handler methods -----
|
|
116
|
+
|
|
117
|
+
async function get(): Promise<any | null> {
|
|
118
|
+
const cartId = getCartId();
|
|
119
|
+
if (!cartId) return null;
|
|
120
|
+
const data = await storefront.query<{ cart: any }>(CART_QUERY, {
|
|
121
|
+
variables: { id: cartId },
|
|
122
|
+
cache: storefront.CacheNone(),
|
|
123
|
+
});
|
|
124
|
+
return data.cart ?? null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function create(input?: { lines?: CartLineInput[] }): Promise<CartResult> {
|
|
128
|
+
const data = await storefront.mutate<{ cartCreate: { cart: any; userErrors: any[] } }>(
|
|
129
|
+
CART_CREATE,
|
|
130
|
+
{ variables: { input: input ?? {} } },
|
|
131
|
+
);
|
|
132
|
+
const result = data.cartCreate;
|
|
133
|
+
if (result.cart) setCartId(result.cart.id);
|
|
134
|
+
return { cart: result.cart, userErrors: result.userErrors };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function addLines(lines: CartLineInput[]): Promise<CartResult> {
|
|
138
|
+
const cartId = getCartId();
|
|
139
|
+
if (!cartId) {
|
|
140
|
+
// No existing cart → create with these lines
|
|
141
|
+
return create({ lines });
|
|
142
|
+
}
|
|
143
|
+
const data = await storefront.mutate<{ cartLinesAdd: { cart: any; userErrors: any[] } }>(
|
|
144
|
+
CART_LINES_ADD,
|
|
145
|
+
{ variables: { cartId, lines } },
|
|
146
|
+
);
|
|
147
|
+
return { cart: data.cartLinesAdd.cart, userErrors: data.cartLinesAdd.userErrors };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function updateLines(lines: CartLineUpdateInput[]): Promise<CartResult> {
|
|
151
|
+
const cartId = getCartId();
|
|
152
|
+
if (!cartId) {
|
|
153
|
+
return {
|
|
154
|
+
cart: null,
|
|
155
|
+
userErrors: [{ message: 'No cart exists', code: 'NO_CART' }],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
const data = await storefront.mutate<{ cartLinesUpdate: { cart: any; userErrors: any[] } }>(
|
|
159
|
+
CART_LINES_UPDATE,
|
|
160
|
+
{ variables: { cartId, lines } },
|
|
161
|
+
);
|
|
162
|
+
return { cart: data.cartLinesUpdate.cart, userErrors: data.cartLinesUpdate.userErrors };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function removeLines(lineIds: string[]): Promise<CartResult> {
|
|
166
|
+
const cartId = getCartId();
|
|
167
|
+
if (!cartId) {
|
|
168
|
+
return {
|
|
169
|
+
cart: null,
|
|
170
|
+
userErrors: [{ message: 'No cart exists', code: 'NO_CART' }],
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const data = await storefront.mutate<{ cartLinesRemove: { cart: any; userErrors: any[] } }>(
|
|
174
|
+
CART_LINES_REMOVE,
|
|
175
|
+
{ variables: { cartId, lineIds } },
|
|
176
|
+
);
|
|
177
|
+
return { cart: data.cartLinesRemove.cart, userErrors: data.cartLinesRemove.userErrors };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
get,
|
|
182
|
+
getCartId,
|
|
183
|
+
setCartId,
|
|
184
|
+
create,
|
|
185
|
+
addLines,
|
|
186
|
+
updateLines,
|
|
187
|
+
removeLines,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* createHeliumContext
|
|
3
|
+
*
|
|
4
|
+
* Entry-point factory that assembles a storefront client and a cart handler
|
|
5
|
+
* into a single context object that loaders/actions can consume.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* const context = createHeliumContext({ request, env, executionContext,
|
|
9
|
+
* storefront: { apiUrl, publicAccessToken, storeId },
|
|
10
|
+
* cart: { getId: cartGetIdDefault(request.headers),
|
|
11
|
+
* setId: cartSetIdDefault({ maxage: ... }) }
|
|
12
|
+
* });
|
|
13
|
+
*
|
|
14
|
+
* // Then:
|
|
15
|
+
* const data = await context.storefront.query(QUERY);
|
|
16
|
+
* const result = await context.cart.addLines([...]);
|
|
17
|
+
*
|
|
18
|
+
* // When responding:
|
|
19
|
+
* return new Response(html, { headers: context.responseHeaders });
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { HeliumContext, HeliumContextOptions } from './types';
|
|
23
|
+
import { createStorefrontClient } from './createStorefrontClient';
|
|
24
|
+
import { createCartHandler } from './createCartHandler';
|
|
25
|
+
|
|
26
|
+
export function createHeliumContext(options: HeliumContextOptions): HeliumContext {
|
|
27
|
+
const { request, env, executionContext, storefront: storefrontOpts, cart: cartOpts } = options;
|
|
28
|
+
|
|
29
|
+
// Mutable headers that will be merged into the eventual response
|
|
30
|
+
// (used for Set-Cookie when cart cookie needs to be written).
|
|
31
|
+
const responseHeaders = new Headers();
|
|
32
|
+
|
|
33
|
+
// 1. Build storefront client
|
|
34
|
+
const storefront = createStorefrontClient({
|
|
35
|
+
apiUrl: storefrontOpts.apiUrl,
|
|
36
|
+
publicAccessToken: storefrontOpts.publicAccessToken,
|
|
37
|
+
privateAccessToken: storefrontOpts.privateAccessToken,
|
|
38
|
+
storeId: storefrontOpts.storeId,
|
|
39
|
+
request,
|
|
40
|
+
cache: storefrontOpts.cache,
|
|
41
|
+
waitUntil:
|
|
42
|
+
storefrontOpts.waitUntil ??
|
|
43
|
+
executionContext.waitUntil.bind(executionContext),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 2. Build cart handler (if cart options provided)
|
|
47
|
+
let cart;
|
|
48
|
+
if (cartOpts) {
|
|
49
|
+
cart = createCartHandler({
|
|
50
|
+
storefront,
|
|
51
|
+
getCartId: cartOpts.getId,
|
|
52
|
+
setCartId: cartOpts.setId,
|
|
53
|
+
responseHeaders,
|
|
54
|
+
});
|
|
55
|
+
} else {
|
|
56
|
+
// No cart options provided → no-op cart handler
|
|
57
|
+
cart = createCartHandler({
|
|
58
|
+
storefront,
|
|
59
|
+
getCartId: () => null,
|
|
60
|
+
setCartId: () => {},
|
|
61
|
+
responseHeaders,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
env,
|
|
67
|
+
request,
|
|
68
|
+
storefront,
|
|
69
|
+
cart,
|
|
70
|
+
responseHeaders,
|
|
71
|
+
};
|
|
72
|
+
}
|