@shopbb/helium 0.5.10 → 0.6.1

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 +146 -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 +183 -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,121 @@
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
+
25
+ export interface SitemapResource {
26
+ url: string;
27
+ lastmod?: string;
28
+ changefreq?: 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' | 'always' | 'hourly';
29
+ priority?: number;
30
+ }
31
+
32
+ export interface GetSitemapOptions {
33
+ storefront: { query: (q: string, opts?: any) => Promise<any> };
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: { type: string; baseUrl: string; handle: string }) => string;
41
+ /** 强制 base URL(不传则从 request 推导) */
42
+ baseUrl?: string;
43
+ }
44
+
45
+ const Q_PRODUCTS = `query SitemapProducts($first:Int!,$after:String){products(first:$first,after:$after){edges{cursor node{handle updatedAt}} pageInfo{hasNextPage endCursor}}}`;
46
+ const Q_COLLECTIONS = `query SitemapCollections($first:Int!,$after:String){collections(first:$first,after:$after){edges{cursor node{handle updatedAt}} pageInfo{hasNextPage endCursor}}}`;
47
+
48
+ function escapeXml(s: string): string {
49
+ return s.replace(/[<>&'"]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' }[c]!));
50
+ }
51
+
52
+ function urlEntry(r: SitemapResource): string {
53
+ const parts: string[] = [`<url>`, ` <loc>${escapeXml(r.url)}</loc>`];
54
+ if (r.lastmod) parts.push(` <lastmod>${r.lastmod}</lastmod>`);
55
+ if (r.changefreq) parts.push(` <changefreq>${r.changefreq}</changefreq>`);
56
+ if (r.priority != null) parts.push(` <priority>${r.priority}</priority>`);
57
+ parts.push('</url>');
58
+ return parts.join('\n');
59
+ }
60
+
61
+ export async function getSitemap(options: GetSitemapOptions): Promise<string> {
62
+ const { storefront, request, types, pageSize = 250, getLink, baseUrl: baseUrlOverride } = options;
63
+ const url = new URL(request.url);
64
+ const baseUrl = (baseUrlOverride || `${url.protocol}//${url.host}`).replace(/\/$/, '');
65
+
66
+ const linker = getLink || (({ type, baseUrl, handle }) => `${baseUrl}/${type}/${handle}`);
67
+
68
+ const entries: SitemapResource[] = [];
69
+
70
+ for (const type of types) {
71
+ let cursor: string | null = null;
72
+ let hasNext = true;
73
+ const query = type === 'products' ? Q_PRODUCTS : type === 'collections' ? Q_COLLECTIONS : null;
74
+ if (!query) continue;
75
+ while (hasNext) {
76
+ const data: any = await storefront.query(query, { variables: { first: pageSize, after: cursor } });
77
+ const conn = (data as any)[type];
78
+ if (!conn) break;
79
+ for (const e of conn.edges) {
80
+ const node = e.node;
81
+ entries.push({
82
+ url: linker({ type, baseUrl, handle: node.handle }),
83
+ lastmod: node.updatedAt,
84
+ changefreq: 'daily',
85
+ });
86
+ }
87
+ hasNext = conn.pageInfo.hasNextPage;
88
+ cursor = conn.pageInfo.endCursor;
89
+ // safety break — 单次请求最多 50 page
90
+ if (entries.length > pageSize * 50) break;
91
+ }
92
+ }
93
+
94
+ return [
95
+ `<?xml version="1.0" encoding="UTF-8"?>`,
96
+ `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
97
+ ...entries.map(urlEntry),
98
+ `</urlset>`,
99
+ ].join('\n');
100
+ }
101
+
102
+ export interface GetSitemapIndexOptions {
103
+ baseUrl: string;
104
+ sitemaps: string[]; // 子 sitemap 路径或完整 URL
105
+ lastmod?: string;
106
+ }
107
+
108
+ export function getSitemapIndex(options: GetSitemapIndexOptions): string {
109
+ const { baseUrl, sitemaps, lastmod } = options;
110
+ const base = baseUrl.replace(/\/$/, '');
111
+ const items = sitemaps.map((s) => {
112
+ const loc = /^https?:\/\//.test(s) ? s : `${base}/${s.replace(/^\//, '')}`;
113
+ return `<sitemap><loc>${escapeXml(loc)}</loc>${lastmod ? `<lastmod>${lastmod}</lastmod>` : ''}</sitemap>`;
114
+ });
115
+ return [
116
+ `<?xml version="1.0" encoding="UTF-8"?>`,
117
+ `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">`,
118
+ ...items,
119
+ `</sitemapindex>`,
120
+ ].join('\n');
121
+ }
package/src/types.ts CHANGED
@@ -96,10 +96,21 @@ export interface CartHandler<TCart = any> {
96
96
  get(): Promise<TCart | null>;
97
97
  getCartId(): string | null;
98
98
  setCartId(cartId: string): void;
99
- create(input?: { lines?: CartLineInput[] }): Promise<CartResult<TCart>>;
99
+ create(input?: { lines?: CartLineInput[]; discountCodes?: string[] }): Promise<CartResult<TCart>>;
100
100
  addLines(lines: CartLineInput[]): Promise<CartResult<TCart>>;
101
101
  updateLines(lines: CartLineUpdateInput[]): Promise<CartResult<TCart>>;
102
102
  removeLines(lineIds: string[]): Promise<CartResult<TCart>>;
103
+ /** 替换 cart 上的优惠码列表(对齐 Hydrogen cart.updateDiscountCodes) */
104
+ updateDiscountCodes(discountCodes: string[]): Promise<CartResult<TCart>>;
105
+ /**
106
+ * shopbb-extension:买家从已领的 claim 中选一张(不是输入 code)。
107
+ * 服务端把 selected_claim_id 写到 cart DO;下次 resolveCart 时优先用该 claim。
108
+ *
109
+ * Hydrogen 没这个 API;这是 shopbb W5 的优惠券模型(claim-based)专属能力。
110
+ */
111
+ selectDiscount(claimId: string): Promise<CartResult<TCart>>;
112
+ /** 清掉显式选择,回到"服务端自动选最佳"模式 */
113
+ clearDiscount(): Promise<CartResult<TCart>>;
103
114
  }
104
115
 
105
116
  // ============================================================
@@ -0,0 +1,33 @@
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
+
15
+ interface ConnectionWithEdges<T> {
16
+ edges: Array<{ node: T }>;
17
+ }
18
+ interface ConnectionWithNodes<T> {
19
+ nodes: T[];
20
+ }
21
+
22
+ export type Connection<T> = ConnectionWithEdges<T> | ConnectionWithNodes<T> | null | undefined;
23
+
24
+ export function flattenConnection<T = any>(connection: Connection<T>): T[] {
25
+ if (!connection) return [];
26
+ if ('nodes' in connection && Array.isArray(connection.nodes)) {
27
+ return connection.nodes;
28
+ }
29
+ if ('edges' in connection && Array.isArray(connection.edges)) {
30
+ return connection.edges.map((e) => e.node);
31
+ }
32
+ return [];
33
+ }
@@ -0,0 +1,25 @@
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
+
11
+ export interface ParsedGid {
12
+ id: string;
13
+ resource: string | null;
14
+ /** 完整原始 gid,便于回填 */
15
+ raw: string;
16
+ }
17
+
18
+ const GID_PATTERN = /^gid:\/\/[^/]+\/([^/]+)\/(.+)$/;
19
+
20
+ export function parseGid(gid: string | null | undefined): ParsedGid {
21
+ if (!gid) return { id: '', resource: null, raw: '' };
22
+ const m = gid.match(GID_PATTERN);
23
+ if (!m) return { id: gid, resource: null, raw: gid };
24
+ return { id: m[2], resource: m[1], raw: gid };
25
+ }