@shopbb/helium 0.5.10 → 0.6.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.
Files changed (132) hide show
  1. package/dist/cache/withCache.d.ts +49 -0
  2. package/dist/cache/withCache.d.ts.map +1 -0
  3. package/dist/cache/withCache.js +117 -0
  4. package/dist/cache/withCache.js.map +1 -0
  5. package/dist/components/AddToCartButton.d.ts +28 -22
  6. package/dist/components/AddToCartButton.d.ts.map +1 -1
  7. package/dist/components/AddToCartButton.js +36 -47
  8. package/dist/components/AddToCartButton.js.map +1 -1
  9. package/dist/components/BuyNowButton.d.ts +45 -0
  10. package/dist/components/BuyNowButton.d.ts.map +1 -0
  11. package/dist/components/BuyNowButton.js +49 -0
  12. package/dist/components/BuyNowButton.js.map +1 -0
  13. package/dist/components/CartCheckoutButton.d.ts +39 -0
  14. package/dist/components/CartCheckoutButton.d.ts.map +1 -0
  15. package/dist/components/CartCheckoutButton.js +32 -0
  16. package/dist/components/CartCheckoutButton.js.map +1 -0
  17. package/dist/components/CartCost.d.ts +43 -0
  18. package/dist/components/CartCost.d.ts.map +1 -0
  19. package/dist/components/CartCost.js +34 -0
  20. package/dist/components/CartCost.js.map +1 -0
  21. package/dist/components/CartForm.d.ts +201 -0
  22. package/dist/components/CartForm.d.ts.map +1 -0
  23. package/dist/components/CartForm.js +213 -0
  24. package/dist/components/CartForm.js.map +1 -0
  25. package/dist/components/CartLineProvider.d.ts +78 -0
  26. package/dist/components/CartLineProvider.d.ts.map +1 -0
  27. package/dist/components/CartLineProvider.js +46 -0
  28. package/dist/components/CartLineProvider.js.map +1 -0
  29. package/dist/components/CartLineQuantity.d.ts +24 -0
  30. package/dist/components/CartLineQuantity.d.ts.map +1 -0
  31. package/dist/components/CartLineQuantity.js +9 -0
  32. package/dist/components/CartLineQuantity.js.map +1 -0
  33. package/dist/components/DiscountSelector.d.ts.map +1 -1
  34. package/dist/components/DiscountSelector.js +8 -19
  35. package/dist/components/DiscountSelector.js.map +1 -1
  36. package/dist/components/Image.d.ts +18 -0
  37. package/dist/components/Image.d.ts.map +1 -1
  38. package/dist/components/Image.js +26 -0
  39. package/dist/components/Image.js.map +1 -1
  40. package/dist/components/Pagination.d.ts +82 -0
  41. package/dist/components/Pagination.d.ts.map +1 -0
  42. package/dist/components/Pagination.js +84 -0
  43. package/dist/components/Pagination.js.map +1 -0
  44. package/dist/components/RichText.d.ts +78 -0
  45. package/dist/components/RichText.d.ts.map +1 -0
  46. package/dist/components/RichText.js +93 -0
  47. package/dist/components/RichText.js.map +1 -0
  48. package/dist/components/Seo.d.ts +25 -0
  49. package/dist/components/Seo.d.ts.map +1 -0
  50. package/dist/components/Seo.js +54 -0
  51. package/dist/components/Seo.js.map +1 -0
  52. package/dist/components/hooks/useMoney.d.ts +40 -0
  53. package/dist/components/hooks/useMoney.d.ts.map +1 -0
  54. package/dist/components/hooks/useMoney.js +60 -0
  55. package/dist/components/hooks/useMoney.js.map +1 -0
  56. package/dist/components/hooks/useOptimisticCart.d.ts +50 -0
  57. package/dist/components/hooks/useOptimisticCart.d.ts.map +1 -0
  58. package/dist/components/hooks/useOptimisticCart.js +138 -0
  59. package/dist/components/hooks/useOptimisticCart.js.map +1 -0
  60. package/dist/components/index.d.ts +28 -0
  61. package/dist/components/index.d.ts.map +1 -1
  62. package/dist/components/index.js +21 -0
  63. package/dist/components/index.js.map +1 -1
  64. package/dist/createCartHandler.d.ts.map +1 -1
  65. package/dist/createCartHandler.js +57 -0
  66. package/dist/createCartHandler.js.map +1 -1
  67. package/dist/csp/csp.d.ts +57 -0
  68. package/dist/csp/csp.d.ts.map +1 -0
  69. package/dist/csp/csp.js +73 -0
  70. package/dist/csp/csp.js.map +1 -0
  71. package/dist/customer/createCustomerAccountClient.d.ts +43 -0
  72. package/dist/customer/createCustomerAccountClient.d.ts.map +1 -0
  73. package/dist/customer/createCustomerAccountClient.js +68 -0
  74. package/dist/customer/createCustomerAccountClient.js.map +1 -0
  75. package/dist/handleCartFormAction.d.ts +39 -0
  76. package/dist/handleCartFormAction.d.ts.map +1 -0
  77. package/dist/handleCartFormAction.js +103 -0
  78. package/dist/handleCartFormAction.js.map +1 -0
  79. package/dist/index.d.ts +18 -0
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +11 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/routing/storefrontRedirect.d.ts +37 -0
  84. package/dist/routing/storefrontRedirect.d.ts.map +1 -0
  85. package/dist/routing/storefrontRedirect.js +64 -0
  86. package/dist/routing/storefrontRedirect.js.map +1 -0
  87. package/dist/seo/getSeoMeta.d.ts +68 -0
  88. package/dist/seo/getSeoMeta.d.ts.map +1 -0
  89. package/dist/seo/getSeoMeta.js +89 -0
  90. package/dist/seo/getSeoMeta.js.map +1 -0
  91. package/dist/sitemap/sitemap.d.ts +55 -0
  92. package/dist/sitemap/sitemap.d.ts.map +1 -0
  93. package/dist/sitemap/sitemap.js +93 -0
  94. package/dist/sitemap/sitemap.js.map +1 -0
  95. package/dist/types.d.ts +12 -0
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/utils/flattenConnection.d.ts +25 -0
  98. package/dist/utils/flattenConnection.d.ts.map +1 -0
  99. package/dist/utils/flattenConnection.js +25 -0
  100. package/dist/utils/flattenConnection.js.map +1 -0
  101. package/dist/utils/parseGid.d.ts +17 -0
  102. package/dist/utils/parseGid.d.ts.map +1 -0
  103. package/dist/utils/parseGid.js +19 -0
  104. package/dist/utils/parseGid.js.map +1 -0
  105. package/package.json +1 -1
  106. package/src/cache/withCache.ts +144 -0
  107. package/src/components/AddToCartButton.tsx +94 -56
  108. package/src/components/BuyNowButton.tsx +135 -0
  109. package/src/components/CartCheckoutButton.tsx +97 -0
  110. package/src/components/CartCost.tsx +65 -0
  111. package/src/components/CartForm.tsx +311 -0
  112. package/src/components/CartLineProvider.tsx +77 -0
  113. package/src/components/CartLineQuantity.tsx +37 -0
  114. package/src/components/DiscountSelector.tsx +34 -45
  115. package/src/components/Image.tsx +27 -0
  116. package/src/components/Pagination.tsx +139 -0
  117. package/src/components/RichText.tsx +122 -0
  118. package/src/components/Seo.tsx +61 -0
  119. package/src/components/hooks/useMoney.ts +87 -0
  120. package/src/components/hooks/useOptimisticCart.ts +173 -0
  121. package/src/components/index.ts +44 -0
  122. package/src/createCartHandler.ts +71 -0
  123. package/src/csp/csp.tsx +119 -0
  124. package/src/customer/createCustomerAccountClient.ts +89 -0
  125. package/src/handleCartFormAction.ts +129 -0
  126. package/src/index.ts +24 -0
  127. package/src/routing/storefrontRedirect.ts +86 -0
  128. package/src/seo/getSeoMeta.ts +125 -0
  129. package/src/sitemap/sitemap.ts +121 -0
  130. package/src/types.ts +12 -1
  131. package/src/utils/flattenConnection.ts +33 -0
  132. package/src/utils/parseGid.ts +25 -0
@@ -0,0 +1,89 @@
1
+ /**
2
+ * getSeoMeta + <Seo> — 对齐 Hydrogen 的 SEO 工具
3
+ *
4
+ * Hydrogen 通过 Remix `meta` export 返回 SeoConfig;我们这里因不绑 Remix,
5
+ * 提供一个 `getSeoMeta(config)` 函数返回 HTML `<meta>` 字符串,供 SSR
6
+ * 服务端拼到 `<head>` 里。也提供 `<Seo data={config} />` 客户端组件
7
+ * (hydrate 阶段更新 document title / meta),用作 SPA 路由切换。
8
+ *
9
+ * 用法(服务端):
10
+ *
11
+ * const seo = getSeoMeta({
12
+ * title: product.title,
13
+ * description: product.description,
14
+ * image: product.featuredImage?.url,
15
+ * url: 'https://shop/products/xxx',
16
+ * type: 'product',
17
+ * });
18
+ * // seo = { title, htmlTags: '<title>...</title><meta ...>' }
19
+ * const html = `<head>${seo.htmlTags}...</head>`;
20
+ */
21
+ function escapeHtml(s) {
22
+ return String(s).replace(/[<>&"']/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;', "'": '&#39;' }[c]));
23
+ }
24
+ /**
25
+ * 根据 config 生成 <head> 该有的全部 SEO 标签字符串。
26
+ */
27
+ export function getSeoMeta(config) {
28
+ const { title, titleTemplate, description = '', url, image, type = 'website', twitterCard = 'summary_large_image', og = {}, robots, jsonLd, } = config;
29
+ const fullTitle = title
30
+ ? (titleTemplate ? `${title} · ${titleTemplate}` : title)
31
+ : (titleTemplate || '');
32
+ const imgUrl = typeof image === 'string' ? image : image?.url;
33
+ const imgAlt = typeof image === 'object' ? image?.altText : undefined;
34
+ const imgW = typeof image === 'object' ? image?.width : undefined;
35
+ const imgH = typeof image === 'object' ? image?.height : undefined;
36
+ const meta = [];
37
+ if (description)
38
+ meta.push({ name: 'description', content: description });
39
+ if (robots)
40
+ meta.push({ name: 'robots', content: robots });
41
+ if (fullTitle)
42
+ meta.push({ property: 'og:title', content: fullTitle });
43
+ if (description)
44
+ meta.push({ property: 'og:description', content: description });
45
+ if (url)
46
+ meta.push({ property: 'og:url', content: url });
47
+ meta.push({ property: 'og:type', content: type });
48
+ if (imgUrl)
49
+ meta.push({ property: 'og:image', content: imgUrl });
50
+ if (imgAlt)
51
+ meta.push({ property: 'og:image:alt', content: imgAlt });
52
+ if (imgW)
53
+ meta.push({ property: 'og:image:width', content: String(imgW) });
54
+ if (imgH)
55
+ meta.push({ property: 'og:image:height', content: String(imgH) });
56
+ for (const [k, v] of Object.entries(og)) {
57
+ meta.push({ property: k.startsWith('og:') ? k : `og:${k}`, content: v });
58
+ }
59
+ meta.push({ name: 'twitter:card', content: twitterCard });
60
+ if (fullTitle)
61
+ meta.push({ name: 'twitter:title', content: fullTitle });
62
+ if (description)
63
+ meta.push({ name: 'twitter:description', content: description });
64
+ if (imgUrl)
65
+ meta.push({ name: 'twitter:image', content: imgUrl });
66
+ const parts = [];
67
+ if (fullTitle)
68
+ parts.push(`<title>${escapeHtml(fullTitle)}</title>`);
69
+ if (url)
70
+ parts.push(`<link rel="canonical" href="${escapeHtml(url)}" />`);
71
+ for (const m of meta) {
72
+ const attr = m.name ? `name="${m.name}"` : `property="${m.property}"`;
73
+ parts.push(`<meta ${attr} content="${escapeHtml(m.content)}" />`);
74
+ }
75
+ if (jsonLd) {
76
+ const data = Array.isArray(jsonLd) ? jsonLd : [jsonLd];
77
+ for (const d of data) {
78
+ parts.push(`<script type="application/ld+json">${JSON.stringify(d).replace(/</g, '\\u003c')}</script>`);
79
+ }
80
+ }
81
+ return {
82
+ title: fullTitle,
83
+ description,
84
+ meta,
85
+ fullTitle,
86
+ htmlTags: parts.join('\n'),
87
+ };
88
+ }
89
+ //# sourceMappingURL=getSeoMeta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getSeoMeta.js","sourceRoot":"","sources":["../../src/seo/getSeoMeta.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * getSitemap + getSitemapIndex — 对齐 Hydrogen
3
+ *
4
+ * 生成 XML sitemap,让搜索引擎能爬到商品 / 集合 / 页面。
5
+ *
6
+ * 用法(服务端 GET /sitemap.xml):
7
+ *
8
+ * const xml = await getSitemap({
9
+ * storefront,
10
+ * request,
11
+ * types: ['products', 'collections', 'pages'],
12
+ * getLink: ({ type, baseUrl, handle }) => `${baseUrl}/${type}/${handle}`,
13
+ * });
14
+ * return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
15
+ *
16
+ * 大店铺:一个 sitemap 装不下时分多个:
17
+ *
18
+ * // GET /sitemap.xml
19
+ * const indexXml = await getSitemapIndex({
20
+ * baseUrl: 'https://shop',
21
+ * sitemaps: ['products-1.xml', 'collections.xml'],
22
+ * });
23
+ */
24
+ export interface SitemapResource {
25
+ url: string;
26
+ lastmod?: string;
27
+ changefreq?: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' | 'always' | 'hourly';
28
+ priority?: number;
29
+ }
30
+ export interface GetSitemapOptions {
31
+ storefront: {
32
+ query: (q: string, opts?: any) => Promise<any>;
33
+ };
34
+ request: Request;
35
+ /** 拉哪些类型 */
36
+ types: Array<'products' | 'collections' | 'pages' | 'articles' | 'blogs'>;
37
+ /** 每页最多多少(GraphQL connection pageSize),默认 250 */
38
+ pageSize?: number;
39
+ /** 自定义 link 构造:({type, baseUrl, handle}) => url。默认 baseUrl + /type + /handle */
40
+ getLink?: (args: {
41
+ type: string;
42
+ baseUrl: string;
43
+ handle: string;
44
+ }) => string;
45
+ /** 强制 base URL(不传则从 request 推导) */
46
+ baseUrl?: string;
47
+ }
48
+ export declare function getSitemap(options: GetSitemapOptions): Promise<string>;
49
+ export interface GetSitemapIndexOptions {
50
+ baseUrl: string;
51
+ sitemaps: string[];
52
+ lastmod?: string;
53
+ }
54
+ export declare function getSitemapIndex(options: GetSitemapIndexOptions): string;
55
+ //# sourceMappingURL=sitemap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.d.ts","sourceRoot":"","sources":["../../src/sitemap/sitemap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE;QAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;KAAE,CAAC;IAC/D,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY;IACZ,KAAK,EAAE,KAAK,CAAC,UAAU,GAAG,aAAa,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,CAAC,CAAC;IAC1E,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IAC9E,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAkBD,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuC5E;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAavE"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * getSitemap + getSitemapIndex — 对齐 Hydrogen
3
+ *
4
+ * 生成 XML sitemap,让搜索引擎能爬到商品 / 集合 / 页面。
5
+ *
6
+ * 用法(服务端 GET /sitemap.xml):
7
+ *
8
+ * const xml = await getSitemap({
9
+ * storefront,
10
+ * request,
11
+ * types: ['products', 'collections', 'pages'],
12
+ * getLink: ({ type, baseUrl, handle }) => `${baseUrl}/${type}/${handle}`,
13
+ * });
14
+ * return new Response(xml, { headers: { 'Content-Type': 'application/xml' } });
15
+ *
16
+ * 大店铺:一个 sitemap 装不下时分多个:
17
+ *
18
+ * // GET /sitemap.xml
19
+ * const indexXml = await getSitemapIndex({
20
+ * baseUrl: 'https://shop',
21
+ * sitemaps: ['products-1.xml', 'collections.xml'],
22
+ * });
23
+ */
24
+ const Q_PRODUCTS = `query SitemapProducts($first:Int!,$after:String){products(first:$first,after:$after){edges{cursor node{handle updatedAt}} pageInfo{hasNextPage endCursor}}}`;
25
+ const Q_COLLECTIONS = `query SitemapCollections($first:Int!,$after:String){collections(first:$first,after:$after){edges{cursor node{handle updatedAt}} pageInfo{hasNextPage endCursor}}}`;
26
+ function escapeXml(s) {
27
+ return s.replace(/[<>&'"]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' }[c]));
28
+ }
29
+ function urlEntry(r) {
30
+ const parts = [`<url>`, ` <loc>${escapeXml(r.url)}</loc>`];
31
+ if (r.lastmod)
32
+ parts.push(` <lastmod>${r.lastmod}</lastmod>`);
33
+ if (r.changefreq)
34
+ parts.push(` <changefreq>${r.changefreq}</changefreq>`);
35
+ if (r.priority != null)
36
+ parts.push(` <priority>${r.priority}</priority>`);
37
+ parts.push('</url>');
38
+ return parts.join('\n');
39
+ }
40
+ export async function getSitemap(options) {
41
+ const { storefront, request, types, pageSize = 250, getLink, baseUrl: baseUrlOverride } = options;
42
+ const url = new URL(request.url);
43
+ const baseUrl = (baseUrlOverride || `${url.protocol}//${url.host}`).replace(/\/$/, '');
44
+ const linker = getLink || (({ type, baseUrl, handle }) => `${baseUrl}/${type}/${handle}`);
45
+ const entries = [];
46
+ for (const type of types) {
47
+ let cursor = null;
48
+ let hasNext = true;
49
+ const query = type === 'products' ? Q_PRODUCTS : type === 'collections' ? Q_COLLECTIONS : null;
50
+ if (!query)
51
+ continue;
52
+ while (hasNext) {
53
+ const data = await storefront.query(query, { variables: { first: pageSize, after: cursor } });
54
+ const conn = data[type];
55
+ if (!conn)
56
+ break;
57
+ for (const e of conn.edges) {
58
+ const node = e.node;
59
+ entries.push({
60
+ url: linker({ type, baseUrl, handle: node.handle }),
61
+ lastmod: node.updatedAt,
62
+ changefreq: 'daily',
63
+ });
64
+ }
65
+ hasNext = conn.pageInfo.hasNextPage;
66
+ cursor = conn.pageInfo.endCursor;
67
+ // safety break — 单次请求最多 50 page
68
+ if (entries.length > pageSize * 50)
69
+ break;
70
+ }
71
+ }
72
+ return [
73
+ `<?xml version="1.0" encoding="UTF-8"?>`,
74
+ `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
75
+ ...entries.map(urlEntry),
76
+ `</urlset>`,
77
+ ].join('\n');
78
+ }
79
+ export function getSitemapIndex(options) {
80
+ const { baseUrl, sitemaps, lastmod } = options;
81
+ const base = baseUrl.replace(/\/$/, '');
82
+ const items = sitemaps.map((s) => {
83
+ const loc = /^https?:\/\//.test(s) ? s : `${base}/${s.replace(/^\//, '')}`;
84
+ return `<sitemap><loc>${escapeXml(loc)}</loc>${lastmod ? `<lastmod>${lastmod}</lastmod>` : ''}</sitemap>`;
85
+ });
86
+ return [
87
+ `<?xml version="1.0" encoding="UTF-8"?>`,
88
+ `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
89
+ ...items,
90
+ `</sitemapindex>`,
91
+ ].join('\n');
92
+ }
93
+ //# sourceMappingURL=sitemap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/sitemap/sitemap.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -74,10 +74,22 @@ export interface CartHandler<TCart = any> {
74
74
  setCartId(cartId: string): void;
75
75
  create(input?: {
76
76
  lines?: CartLineInput[];
77
+ discountCodes?: string[];
77
78
  }): Promise<CartResult<TCart>>;
78
79
  addLines(lines: CartLineInput[]): Promise<CartResult<TCart>>;
79
80
  updateLines(lines: CartLineUpdateInput[]): Promise<CartResult<TCart>>;
80
81
  removeLines(lineIds: string[]): Promise<CartResult<TCart>>;
82
+ /** 替换 cart 上的优惠码列表(对齐 Hydrogen cart.updateDiscountCodes) */
83
+ updateDiscountCodes(discountCodes: string[]): Promise<CartResult<TCart>>;
84
+ /**
85
+ * shopbb-extension:买家从已领的 claim 中选一张(不是输入 code)。
86
+ * 服务端把 selected_claim_id 写到 cart DO;下次 resolveCart 时优先用该 claim。
87
+ *
88
+ * Hydrogen 没这个 API;这是 shopbb W5 的优惠券模型(claim-based)专属能力。
89
+ */
90
+ selectDiscount(claimId: string): Promise<CartResult<TCart>>;
91
+ /** 清掉显式选择,回到"服务端自动选最佳"模式 */
92
+ clearDiscount(): Promise<CartResult<TCart>>;
81
93
  }
82
94
  export interface HeliumContextOptions {
83
95
  request: Request;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * flattenConnection — 对齐 Hydrogen React 同名工具
3
+ *
4
+ * GraphQL Connection 类型常见两种形态:
5
+ * { edges: [{node: T}, ...] }
6
+ * { nodes: [T, ...] }
7
+ *
8
+ * 此函数把两种统一拍平成 T[],让消费方写法一致。
9
+ *
10
+ * 用法:
11
+ * const products = flattenConnection(data.products);
12
+ * products.forEach(p => ...);
13
+ */
14
+ interface ConnectionWithEdges<T> {
15
+ edges: Array<{
16
+ node: T;
17
+ }>;
18
+ }
19
+ interface ConnectionWithNodes<T> {
20
+ nodes: T[];
21
+ }
22
+ export type Connection<T> = ConnectionWithEdges<T> | ConnectionWithNodes<T> | null | undefined;
23
+ export declare function flattenConnection<T = any>(connection: Connection<T>): T[];
24
+ export {};
25
+ //# sourceMappingURL=flattenConnection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenConnection.d.ts","sourceRoot":"","sources":["../../src/utils/flattenConnection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,UAAU,mBAAmB,CAAC,CAAC;IAC7B,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC;CAC3B;AACD,UAAU,mBAAmB,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAC;CACZ;AAED,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;AAE/F,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,GAAG,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CASzE"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * flattenConnection — 对齐 Hydrogen React 同名工具
3
+ *
4
+ * GraphQL Connection 类型常见两种形态:
5
+ * { edges: [{node: T}, ...] }
6
+ * { nodes: [T, ...] }
7
+ *
8
+ * 此函数把两种统一拍平成 T[],让消费方写法一致。
9
+ *
10
+ * 用法:
11
+ * const products = flattenConnection(data.products);
12
+ * products.forEach(p => ...);
13
+ */
14
+ export function flattenConnection(connection) {
15
+ if (!connection)
16
+ return [];
17
+ if ('nodes' in connection && Array.isArray(connection.nodes)) {
18
+ return connection.nodes;
19
+ }
20
+ if ('edges' in connection && Array.isArray(connection.edges)) {
21
+ return connection.edges.map((e) => e.node);
22
+ }
23
+ return [];
24
+ }
25
+ //# sourceMappingURL=flattenConnection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flattenConnection.js","sourceRoot":"","sources":["../../src/utils/flattenConnection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAWH,MAAM,UAAU,iBAAiB,CAAU,UAAyB;IAClE,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IAC3B,IAAI,OAAO,IAAI,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7D,OAAO,UAAU,CAAC,KAAK,CAAC;IAC1B,CAAC;IACD,IAAI,OAAO,IAAI,UAAU,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7D,OAAO,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * parseGid — 对齐 Hydrogen React 同名工具
3
+ *
4
+ * 把 GraphQL global ID 拆成 { id, resource }:
5
+ * "gid://shopbb/Product/prod_123" → { id: "prod_123", resource: "Product" }
6
+ * "gid://shopify/CartLine/abc" → { id: "abc", resource: "CartLine" }
7
+ *
8
+ * 找不到合法格式 → 返回 { id: gid, resource: null }(保留原值,便于兜底渲染)
9
+ */
10
+ export interface ParsedGid {
11
+ id: string;
12
+ resource: string | null;
13
+ /** 完整原始 gid,便于回填 */
14
+ raw: string;
15
+ }
16
+ export declare function parseGid(gid: string | null | undefined): ParsedGid;
17
+ //# sourceMappingURL=parseGid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseGid.d.ts","sourceRoot":"","sources":["../../src/utils/parseGid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oBAAoB;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAID,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,SAAS,CAKlE"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * parseGid — 对齐 Hydrogen React 同名工具
3
+ *
4
+ * 把 GraphQL global ID 拆成 { id, resource }:
5
+ * "gid://shopbb/Product/prod_123" → { id: "prod_123", resource: "Product" }
6
+ * "gid://shopify/CartLine/abc" → { id: "abc", resource: "CartLine" }
7
+ *
8
+ * 找不到合法格式 → 返回 { id: gid, resource: null }(保留原值,便于兜底渲染)
9
+ */
10
+ const GID_PATTERN = /^gid:\/\/[^/]+\/([^/]+)\/(.+)$/;
11
+ export function parseGid(gid) {
12
+ if (!gid)
13
+ return { id: '', resource: null, raw: '' };
14
+ const m = gid.match(GID_PATTERN);
15
+ if (!m)
16
+ return { id: gid, resource: null, raw: gid };
17
+ return { id: m[2], resource: m[1], raw: gid };
18
+ }
19
+ //# sourceMappingURL=parseGid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseGid.js","sourceRoot":"","sources":["../../src/utils/parseGid.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,MAAM,WAAW,GAAG,gCAAgC,CAAC;AAErD,MAAM,UAAU,QAAQ,CAAC,GAA8B;IACrD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;IACrD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACjC,IAAI,CAAC,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACrD,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAChD,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopbb/helium",
3
- "version": "0.5.10",
3
+ "version": "0.6.0",
4
4
  "description": "shopbb storefront framework — components, React SSR, GraphQL client, cart handler, cache for Cloudflare Workers",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,144 @@
1
+ /**
2
+ * createWithCache + InMemoryCache — 对齐 Hydrogen
3
+ *
4
+ * Hydrogen 的 `createWithCache({cache, waitUntil})` 返回一个高阶函数,
5
+ * 用 `withCache.run(strategy, cacheKey, fn)` 包裹任意异步函数,自动加 cache。
6
+ *
7
+ * 在 Cloudflare Workers 上 cache 可以是:
8
+ * - `caches.default` / `caches.open(...)` (Cache API — 真正的 edge cache)
9
+ * - `InMemoryCache` (本 worker 实例内存,per-instance;轻量缓存)
10
+ *
11
+ * 用法:
12
+ *
13
+ * const withCache = createWithCache({ cache: await caches.open('helium'), waitUntil });
14
+ *
15
+ * const products = await withCache.run(
16
+ * CacheLong(),
17
+ * ['products', 'page', '1'],
18
+ * async () => storefront.query(PRODUCTS_QUERY, { variables }),
19
+ * );
20
+ *
21
+ * 简化点(vs Hydrogen):
22
+ * - 不实现 stale-while-revalidate 后台 revalidate(Workers 内复杂,先做基础)
23
+ * - 不实现 stale-if-error
24
+ * - 提供 InMemoryCache 兜底(test / 没 caches.open 的环境)
25
+ */
26
+
27
+ import type { CacheStrategy } from '../types';
28
+
29
+ // 对齐 Hydrogen 命名 (CachingStrategy)
30
+ export type CachingStrategy = CacheStrategy;
31
+
32
+ export type CacheKey = string | (string | number)[];
33
+
34
+ export interface WithCacheOptions {
35
+ cache: Cache | InMemoryCache;
36
+ waitUntil?: (p: Promise<any>) => void;
37
+ }
38
+
39
+ export interface WithCache {
40
+ /** 用 cache 包裹异步函数。命中 cache 直接返回;未命中执行 fn 写入 */
41
+ run<T>(strategy: CachingStrategy, key: CacheKey, fn: () => Promise<T>): Promise<T>;
42
+ /** 直接读 cache(不执行 fn) */
43
+ get<T>(key: CacheKey): Promise<T | null>;
44
+ /** 写 cache */
45
+ set<T>(strategy: CachingStrategy, key: CacheKey, value: T): Promise<void>;
46
+ }
47
+
48
+ function toCacheKey(key: CacheKey): string {
49
+ if (typeof key === 'string') return key;
50
+ return key.map(String).join(':');
51
+ }
52
+
53
+ function buildCacheControl(strategy: CachingStrategy): string {
54
+ const dirs: string[] = [];
55
+ if (strategy.mode === 'NONE') return 'no-store';
56
+ if (strategy.mode === 'PUBLIC') dirs.push('public');
57
+ if (strategy.maxAge != null) dirs.push(`max-age=${strategy.maxAge}`);
58
+ if (strategy.staleWhileRevalidate != null) dirs.push(`stale-while-revalidate=${strategy.staleWhileRevalidate}`);
59
+ return dirs.join(', ');
60
+ }
61
+
62
+ export function createWithCache(opts: WithCacheOptions): WithCache {
63
+ const { cache, waitUntil } = opts;
64
+
65
+ async function get<T>(key: CacheKey): Promise<T | null> {
66
+ const cacheKey = toCacheKey(key);
67
+ if (cache instanceof InMemoryCache) {
68
+ return cache.get<T>(cacheKey);
69
+ }
70
+ const req = new Request(`https://helium.cache/${encodeURIComponent(cacheKey)}`);
71
+ const cached = await cache.match(req);
72
+ if (!cached) return null;
73
+ try {
74
+ return (await cached.json()) as T;
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ async function set<T>(strategy: CachingStrategy, key: CacheKey, value: T): Promise<void> {
81
+ if (strategy.mode === 'NONE') return;
82
+ const cacheKey = toCacheKey(key);
83
+ if (cache instanceof InMemoryCache) {
84
+ cache.set(cacheKey, value, strategy.maxAge ?? 0);
85
+ return;
86
+ }
87
+ const req = new Request(`https://helium.cache/${encodeURIComponent(cacheKey)}`);
88
+ const res = new Response(JSON.stringify(value), {
89
+ headers: {
90
+ 'Content-Type': 'application/json',
91
+ 'Cache-Control': buildCacheControl(strategy),
92
+ },
93
+ });
94
+ const p = cache.put(req, res);
95
+ if (waitUntil) waitUntil(p); else await p;
96
+ }
97
+
98
+ async function run<T>(strategy: CachingStrategy, key: CacheKey, fn: () => Promise<T>): Promise<T> {
99
+ if (strategy.mode === 'NONE') return fn();
100
+ const cached = await get<T>(key);
101
+ if (cached !== null) return cached;
102
+ const value = await fn();
103
+ await set(strategy, key, value);
104
+ return value;
105
+ }
106
+
107
+ return { run, get, set };
108
+ }
109
+
110
+ // ============================================================
111
+ // InMemoryCache —— 进程内 cache(轻量)
112
+ // ============================================================
113
+
114
+ interface MemEntry<T = any> {
115
+ value: T;
116
+ expiresAt: number;
117
+ }
118
+
119
+ export class InMemoryCache {
120
+ private store = new Map<string, MemEntry>();
121
+
122
+ get<T = any>(key: string): T | null {
123
+ const e = this.store.get(key);
124
+ if (!e) return null;
125
+ if (e.expiresAt > 0 && Date.now() > e.expiresAt) {
126
+ this.store.delete(key);
127
+ return null;
128
+ }
129
+ return e.value as T;
130
+ }
131
+
132
+ set<T = any>(key: string, value: T, maxAgeSeconds: number): void {
133
+ const expiresAt = maxAgeSeconds > 0 ? Date.now() + maxAgeSeconds * 1000 : 0;
134
+ this.store.set(key, { value, expiresAt });
135
+ }
136
+
137
+ delete(key: string): boolean {
138
+ return this.store.delete(key);
139
+ }
140
+
141
+ clear(): void {
142
+ this.store.clear();
143
+ }
144
+ }