@businessdash/sdk 0.9.60 → 0.9.80

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 (239) hide show
  1. package/README.md +991 -295
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +622 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +888 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/consent-core/index.cjs +107 -0
  47. package/dist/consent-core/index.d.ts +103 -0
  48. package/dist/consent-core/index.d.ts.map +1 -0
  49. package/dist/consent-core/index.js +157 -0
  50. package/dist/consent-core/index.js.map +1 -0
  51. package/dist/contracts.d.ts +3526 -767
  52. package/dist/contracts.d.ts.map +1 -1
  53. package/dist/contracts.js +571 -4
  54. package/dist/contracts.js.map +1 -1
  55. package/dist/data-model-schema.d.ts +1 -1
  56. package/dist/data-model-schema.d.ts.map +1 -1
  57. package/dist/element.d.ts +30 -0
  58. package/dist/element.d.ts.map +1 -1
  59. package/dist/element.js +56 -0
  60. package/dist/element.js.map +1 -1
  61. package/dist/embed-protocol.d.ts +86 -0
  62. package/dist/embed-protocol.d.ts.map +1 -0
  63. package/dist/embed-protocol.js +60 -0
  64. package/dist/embed-protocol.js.map +1 -0
  65. package/dist/framework-support.d.ts +122 -0
  66. package/dist/framework-support.d.ts.map +1 -0
  67. package/dist/framework-support.js +323 -0
  68. package/dist/framework-support.js.map +1 -0
  69. package/dist/index.cjs +1355 -24
  70. package/dist/legal-core/index.cjs +115 -0
  71. package/dist/legal-core/index.d.ts +160 -0
  72. package/dist/legal-core/index.d.ts.map +1 -0
  73. package/dist/legal-core/index.js +220 -0
  74. package/dist/legal-core/index.js.map +1 -0
  75. package/dist/marketing-core/index.d.ts +66 -0
  76. package/dist/marketing-core/index.d.ts.map +1 -0
  77. package/dist/marketing-core/index.js +154 -0
  78. package/dist/marketing-core/index.js.map +1 -0
  79. package/dist/marketing.d.ts +7 -0
  80. package/dist/marketing.d.ts.map +1 -0
  81. package/dist/marketing.js +7 -0
  82. package/dist/marketing.js.map +1 -0
  83. package/dist/mcp-adapters/index.d.ts +135 -0
  84. package/dist/mcp-adapters/index.d.ts.map +1 -0
  85. package/dist/mcp-adapters/index.js +187 -0
  86. package/dist/mcp-adapters/index.js.map +1 -0
  87. package/dist/mcp-adapters.d.ts +7 -0
  88. package/dist/mcp-adapters.d.ts.map +1 -0
  89. package/dist/mcp-adapters.js +7 -0
  90. package/dist/mcp-adapters.js.map +1 -0
  91. package/dist/model3d-core/index.cjs +95 -0
  92. package/dist/model3d-core/index.d.ts +167 -0
  93. package/dist/model3d-core/index.d.ts.map +1 -0
  94. package/dist/model3d-core/index.js +158 -0
  95. package/dist/model3d-core/index.js.map +1 -0
  96. package/dist/notifications-core/index.cjs +315 -0
  97. package/dist/notifications-core/index.d.ts +229 -0
  98. package/dist/notifications-core/index.d.ts.map +1 -0
  99. package/dist/notifications-core/index.js +445 -0
  100. package/dist/notifications-core/index.js.map +1 -0
  101. package/dist/portal-core/index.d.ts +451 -0
  102. package/dist/portal-core/index.d.ts.map +1 -0
  103. package/dist/portal-core/index.js +819 -0
  104. package/dist/portal-core/index.js.map +1 -0
  105. package/dist/portal.d.ts +7 -0
  106. package/dist/portal.d.ts.map +1 -0
  107. package/dist/portal.js +7 -0
  108. package/dist/portal.js.map +1 -0
  109. package/dist/push-core/index.cjs +171 -0
  110. package/dist/push-core/index.d.ts +149 -0
  111. package/dist/push-core/index.d.ts.map +1 -0
  112. package/dist/push-core/index.js +231 -0
  113. package/dist/push-core/index.js.map +1 -0
  114. package/dist/react-marketing.d.ts +1 -17
  115. package/dist/react-marketing.d.ts.map +1 -1
  116. package/dist/react-marketing.js +4 -0
  117. package/dist/react-marketing.js.map +1 -1
  118. package/dist/react.d.ts +294 -101
  119. package/dist/react.d.ts.map +1 -1
  120. package/dist/react.js +302 -246
  121. package/dist/react.js.map +1 -1
  122. package/dist/seed-core/index.d.ts +354 -0
  123. package/dist/seed-core/index.d.ts.map +1 -0
  124. package/dist/seed-core/index.js +420 -0
  125. package/dist/seed-core/index.js.map +1 -0
  126. package/dist/seed.d.ts +7 -0
  127. package/dist/seed.d.ts.map +1 -0
  128. package/dist/seed.js +7 -0
  129. package/dist/seed.js.map +1 -0
  130. package/dist/seo-core/blog.d.ts +94 -0
  131. package/dist/seo-core/blog.d.ts.map +1 -0
  132. package/dist/seo-core/blog.js +149 -0
  133. package/dist/seo-core/blog.js.map +1 -0
  134. package/dist/seo-core/commerce.d.ts +64 -0
  135. package/dist/seo-core/commerce.d.ts.map +1 -0
  136. package/dist/seo-core/commerce.js +87 -0
  137. package/dist/seo-core/commerce.js.map +1 -0
  138. package/dist/seo-core/index.cjs +751 -0
  139. package/dist/seo-core/index.d.ts +226 -0
  140. package/dist/seo-core/index.d.ts.map +1 -0
  141. package/dist/seo-core/index.js +455 -0
  142. package/dist/seo-core/index.js.map +1 -0
  143. package/dist/seo-core/local.d.ts +181 -0
  144. package/dist/seo-core/local.d.ts.map +1 -0
  145. package/dist/seo-core/local.js +193 -0
  146. package/dist/seo-core/local.js.map +1 -0
  147. package/dist/sitemap-core/index.cjs +167 -0
  148. package/dist/sitemap-core/index.d.ts +155 -0
  149. package/dist/sitemap-core/index.d.ts.map +1 -0
  150. package/dist/sitemap-core/index.js +195 -0
  151. package/dist/sitemap-core/index.js.map +1 -0
  152. package/dist/social-core/index.d.ts +74 -0
  153. package/dist/social-core/index.d.ts.map +1 -0
  154. package/dist/social-core/index.js +72 -0
  155. package/dist/social-core/index.js.map +1 -0
  156. package/dist/social.d.ts +7 -0
  157. package/dist/social.d.ts.map +1 -0
  158. package/dist/social.js +7 -0
  159. package/dist/social.js.map +1 -0
  160. package/dist/solid-compat.d.ts +204 -0
  161. package/dist/solid-compat.d.ts.map +1 -0
  162. package/dist/solid-compat.js +221 -0
  163. package/dist/solid-compat.js.map +1 -0
  164. package/dist/store-core/index.d.ts +141 -0
  165. package/dist/store-core/index.d.ts.map +1 -0
  166. package/dist/store-core/index.js +277 -0
  167. package/dist/store-core/index.js.map +1 -0
  168. package/dist/store.d.ts +12 -0
  169. package/dist/store.d.ts.map +1 -0
  170. package/dist/store.js +12 -0
  171. package/dist/store.js.map +1 -0
  172. package/dist/vanilla.d.ts +111 -0
  173. package/dist/vanilla.d.ts.map +1 -1
  174. package/dist/vanilla.js +206 -0
  175. package/dist/vanilla.js.map +1 -1
  176. package/dist/verify-core/index.d.ts +98 -0
  177. package/dist/verify-core/index.d.ts.map +1 -0
  178. package/dist/verify-core/index.js +186 -0
  179. package/dist/verify-core/index.js.map +1 -0
  180. package/dist/verify.d.ts +8 -0
  181. package/dist/verify.d.ts.map +1 -0
  182. package/dist/verify.js +8 -0
  183. package/dist/verify.js.map +1 -0
  184. package/openapi.json +4516 -1311
  185. package/package.json +128 -11
  186. package/src/account-core/index.ts +309 -0
  187. package/src/account.ts +11 -0
  188. package/src/angular/headless.ts +519 -0
  189. package/src/angular/index.ts +3 -0
  190. package/src/auth-links.ts +76 -0
  191. package/src/blog-core/index.ts +423 -0
  192. package/src/blog.ts +11 -0
  193. package/src/chat-core/index.ts +335 -0
  194. package/src/chat.ts +11 -0
  195. package/src/checkout-core/index.ts +225 -0
  196. package/src/checkout.ts +10 -0
  197. package/src/cli.ts +184 -28
  198. package/src/client.ts +1097 -0
  199. package/src/consent-core/index.ts +213 -0
  200. package/src/contracts.ts +768 -11
  201. package/src/data-model-schema.ts +1 -1
  202. package/src/element.ts +102 -0
  203. package/src/embed-protocol.ts +100 -0
  204. package/src/framework-support.ts +440 -0
  205. package/src/legal-core/index.ts +276 -0
  206. package/src/marketing-core/index.ts +209 -0
  207. package/src/marketing.ts +7 -0
  208. package/src/mcp-adapters/index.ts +264 -0
  209. package/src/mcp-adapters.ts +7 -0
  210. package/src/model3d-core/index.ts +248 -0
  211. package/src/notifications-core/index.ts +650 -0
  212. package/src/portal-core/index.ts +1564 -0
  213. package/src/portal.ts +7 -0
  214. package/src/push-core/index.ts +335 -0
  215. package/src/qwik.tsx +602 -2
  216. package/src/react-marketing.tsx +5 -0
  217. package/src/react.tsx +434 -319
  218. package/src/seed-core/index.ts +735 -0
  219. package/src/seed.ts +7 -0
  220. package/src/seo-core/blog.ts +216 -0
  221. package/src/seo-core/commerce.ts +138 -0
  222. package/src/seo-core/index.ts +605 -0
  223. package/src/seo-core/local.ts +333 -0
  224. package/src/sitemap-core/index.ts +296 -0
  225. package/src/social-core/index.ts +103 -0
  226. package/src/social.ts +7 -0
  227. package/src/solid-compat.tsx +347 -0
  228. package/src/solid-surfaces.tsx +1311 -0
  229. package/src/solid.tsx +82 -60
  230. package/src/store-core/index.ts +426 -0
  231. package/src/store.ts +12 -0
  232. package/src/svelte/SocialLinks.svelte +84 -0
  233. package/src/svelte/headless.ts +435 -0
  234. package/src/svelte/index.ts +9 -0
  235. package/src/vanilla.ts +367 -0
  236. package/src/verify-core/index.ts +288 -0
  237. package/src/verify.ts +8 -0
  238. package/src/vue/headless.ts +527 -0
  239. package/src/vue/index.ts +3 -0
@@ -0,0 +1,149 @@
1
+ /**
2
+ * SEO for blog posts and blog listings.
3
+ *
4
+ * ## Why a post needs more than a title
5
+ *
6
+ * A `BlogPosting` node is what turns a link in search results into a dated,
7
+ * attributed article card. Without it a post competes as a bare URL, and the
8
+ * publish date — the thing readers use to decide whether it is still true —
9
+ * never appears.
10
+ *
11
+ * ## Gated posts stay indexed, and say so
12
+ *
13
+ * A subscriber-only post should still be findable: being discoverable is how
14
+ * anyone subscribes. But serving a full article to a crawler and a paywall to
15
+ * a reader is **cloaking**, which is penalised.
16
+ *
17
+ * The correct answer is schema.org's paywall markup — `isAccessibleForFree:
18
+ * false` plus a `hasPart` naming the gated region. That tells a crawler the
19
+ * content is restricted, so the difference between what it sees and what a
20
+ * reader sees is declared rather than deceptive.
21
+ *
22
+ * @module
23
+ */
24
+ import { article, breadcrumb } from "../seo.js";
25
+ function isGated(accessLevel) {
26
+ const value = (accessLevel ?? "public").trim();
27
+ // Anything unrecognised counts as gated. Being wrong the other way means
28
+ // declaring open access for content that is actually restricted, which is
29
+ // the cloaking case.
30
+ return value !== "public" && value !== "";
31
+ }
32
+ /**
33
+ * SEO for one blog post.
34
+ *
35
+ * ```ts
36
+ * const seo = resolveSeo(layoutDefaults, blogPostSeo({ siteUrl, post }))
37
+ * ```
38
+ */
39
+ export function blogPostSeo(input) {
40
+ const { post } = input;
41
+ const path = input.path ?? `/blog/${post.slug}`;
42
+ const canonical = `${input.siteUrl.replace(/\/+$/, "")}${path}`;
43
+ const gated = isGated(post.accessLevel);
44
+ const description = post.excerpt?.trim() || undefined;
45
+ const node = article({
46
+ siteUrl: input.siteUrl,
47
+ headline: post.title,
48
+ description: description ?? post.title,
49
+ slug: post.slug,
50
+ pubDate: post.publishedAt ?? new Date(0).toISOString(),
51
+ ...(post.updatedAt ? { updatedDate: post.updatedAt } : {}),
52
+ ...(post.authorName ? { authorName: post.authorName } : {}),
53
+ ...(post.imageUrl ? { heroImageUrl: post.imageUrl } : {}),
54
+ });
55
+ // The post's real URL, which may not be `/blog/{slug}` if the consumer
56
+ // routes differently. `article()` assumes that shape; correct it here
57
+ // rather than shipping a mainEntityOfPage that 404s.
58
+ node["mainEntityOfPage"] = canonical;
59
+ node["url"] = canonical;
60
+ if (post.tags?.length)
61
+ node["keywords"] = post.tags.join(", ");
62
+ // Paywall markup — see the note at the top of this module.
63
+ node["isAccessibleForFree"] = !gated;
64
+ if (gated) {
65
+ node["hasPart"] = {
66
+ "@type": "WebPageElement",
67
+ isAccessibleForFree: false,
68
+ cssSelector: input.paywallSelector ?? ".biab-post-body",
69
+ };
70
+ }
71
+ const extraMeta = [];
72
+ if (post.publishedAt) {
73
+ extraMeta.push({
74
+ property: "article:published_time",
75
+ content: post.publishedAt,
76
+ });
77
+ }
78
+ if (post.updatedAt) {
79
+ extraMeta.push({
80
+ property: "article:modified_time",
81
+ content: post.updatedAt,
82
+ });
83
+ }
84
+ if (post.authorName) {
85
+ extraMeta.push({ property: "article:author", content: post.authorName });
86
+ }
87
+ for (const tag of post.tags ?? []) {
88
+ extraMeta.push({ property: "article:tag", content: tag });
89
+ }
90
+ return {
91
+ title: post.title,
92
+ ...(description ? { description } : {}),
93
+ canonical,
94
+ ...(post.imageUrl ? { ogImage: post.imageUrl } : {}),
95
+ // Not "website": social cards for articles show the date and byline.
96
+ ogType: "article",
97
+ ...(post.tags?.length ? { keywords: post.tags } : {}),
98
+ extraMeta,
99
+ jsonLd: [
100
+ node,
101
+ breadcrumb({
102
+ siteUrl: input.siteUrl,
103
+ items: [
104
+ { name: "Home", url: input.siteUrl },
105
+ { name: "Blog", url: `${input.siteUrl.replace(/\/+$/, "")}/blog` },
106
+ ...(input.breadcrumbs ?? []),
107
+ { name: post.title, url: canonical },
108
+ ],
109
+ }),
110
+ ],
111
+ };
112
+ }
113
+ /**
114
+ * SEO for a blog index or archive.
115
+ *
116
+ * Emits a `Blog` node with an `ItemList` of the posts on this page, so a
117
+ * crawler can see the structure without fetching every post.
118
+ */
119
+ export function blogListingSeo(input) {
120
+ const base = input.siteUrl.replace(/\/+$/, "");
121
+ const path = input.path ?? "/blog";
122
+ const canonical = `${base}${path}`;
123
+ const postPath = input.postPath ?? ((p) => `/blog/${p.slug}`);
124
+ const page = input.page ?? 1;
125
+ const node = {
126
+ "@context": "https://schema.org",
127
+ "@type": "Blog",
128
+ url: canonical,
129
+ ...(input.title ? { name: input.title } : {}),
130
+ ...(input.description ? { description: input.description } : {}),
131
+ blogPost: input.posts.map((post) => ({
132
+ "@type": "BlogPosting",
133
+ headline: post.title,
134
+ url: `${base}${postPath(post)}`,
135
+ ...(post.publishedAt ? { datePublished: post.publishedAt } : {}),
136
+ ...(post.imageUrl ? { image: post.imageUrl } : {}),
137
+ })),
138
+ };
139
+ return {
140
+ ...(input.title ? { title: input.title } : {}),
141
+ ...(input.description ? { description: input.description } : {}),
142
+ canonical,
143
+ // Thin archive pages competing with the posts themselves splits the
144
+ // signal; the posts are what should rank.
145
+ ...(page > 1 ? { noIndex: true } : {}),
146
+ jsonLd: [node],
147
+ };
148
+ }
149
+ //# sourceMappingURL=blog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.js","sourceRoot":"","sources":["../../src/seo-core/blog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * SEO for storefront listings — the category and collection pages.
3
+ *
4
+ * A product page has `productPageSeo`. The page ABOVE it needs different
5
+ * markup: `CollectionPage` with an `ItemList`, so a crawler can see what the
6
+ * category contains without fetching every product, and so the listing ranks
7
+ * for the category term instead of competing with its own products.
8
+ *
9
+ * @module
10
+ */
11
+ import type { SeoInput } from "./index.js";
12
+ /** A product as it appears in a listing. */
13
+ export type ListedProduct = {
14
+ name: string;
15
+ /** Path or absolute URL of the product page. */
16
+ url: string;
17
+ image?: string | null;
18
+ /** Integer cents. */
19
+ priceCents?: number | null;
20
+ currency?: string | null;
21
+ availability?: "InStock" | "OutOfStock" | "PreOrder";
22
+ };
23
+ export type ProductListingSeoInput = {
24
+ siteUrl: string;
25
+ /** Path of the listing, e.g. `/shop` or `/shop/tools`. */
26
+ path: string;
27
+ title?: string;
28
+ description?: string;
29
+ products: readonly ListedProduct[];
30
+ breadcrumbs?: Array<{
31
+ name: string;
32
+ url: string;
33
+ }>;
34
+ /**
35
+ * Page number for paginated listings.
36
+ *
37
+ * Pages 2+ are `noindex, follow`: the products are what should rank, and a
38
+ * thin page-7 of a category competes with them for the same term. Links are
39
+ * still followed so every product stays reachable.
40
+ */
41
+ page?: number;
42
+ /**
43
+ * True when the listing is filtered or sorted.
44
+ *
45
+ * Faceted URLs are the classic crawl trap — colour × size × sort multiplies
46
+ * into thousands of near-identical pages that eat crawl budget and split
47
+ * ranking. A filtered view should point its canonical at the unfiltered
48
+ * category, which is what this does.
49
+ */
50
+ filtered?: boolean;
51
+ /** Unfiltered category path, used as the canonical when `filtered`. */
52
+ unfilteredPath?: string;
53
+ };
54
+ /** SEO for a storefront category or collection page. */
55
+ export declare function productListingSeo(input: ProductListingSeoInput): SeoInput;
56
+ /**
57
+ * SEO for cart, checkout, account and order-confirmation pages.
58
+ *
59
+ * These must never be indexed. A cart URL in search results is at best useless
60
+ * and at worst a leak — order confirmations have been indexed with customer
61
+ * details in the query string more than once, by more than one large retailer.
62
+ */
63
+ export declare function transactionalPageSeo(title: string): SeoInput;
64
+ //# sourceMappingURL=commerce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce.d.ts","sourceRoot":"","sources":["../../src/seo-core/commerce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qBAAqB;IACrB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,UAAU,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACnD;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wDAAwD;AACxD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,QAAQ,CAqEzE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAE5D"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * SEO for storefront listings — the category and collection pages.
3
+ *
4
+ * A product page has `productPageSeo`. The page ABOVE it needs different
5
+ * markup: `CollectionPage` with an `ItemList`, so a crawler can see what the
6
+ * category contains without fetching every product, and so the listing ranks
7
+ * for the category term instead of competing with its own products.
8
+ *
9
+ * @module
10
+ */
11
+ import { breadcrumb } from "../seo.js";
12
+ /** SEO for a storefront category or collection page. */
13
+ export function productListingSeo(input) {
14
+ const base = input.siteUrl.replace(/\/+$/, "");
15
+ const page = input.page ?? 1;
16
+ // A filtered view canonicalises to the category it filters, so the facets
17
+ // consolidate rather than compete.
18
+ const canonicalPath = input.filtered && input.unfilteredPath ? input.unfilteredPath : input.path;
19
+ const canonical = `${base}${canonicalPath}`;
20
+ const absolute = (url) => /^[a-z][a-z0-9+.-]*:|^\/\//i.test(url)
21
+ ? url
22
+ : `${base}${url.startsWith("/") ? "" : "/"}${url}`;
23
+ const node = {
24
+ "@context": "https://schema.org",
25
+ "@type": "CollectionPage",
26
+ url: canonical,
27
+ ...(input.title ? { name: input.title } : {}),
28
+ ...(input.description ? { description: input.description } : {}),
29
+ mainEntity: {
30
+ "@type": "ItemList",
31
+ numberOfItems: input.products.length,
32
+ itemListElement: input.products.map((product, index) => ({
33
+ "@type": "ListItem",
34
+ position: index + 1,
35
+ item: {
36
+ "@type": "Product",
37
+ name: product.name,
38
+ url: absolute(product.url),
39
+ ...(product.image ? { image: absolute(product.image) } : {}),
40
+ ...(typeof product.priceCents === "number"
41
+ ? {
42
+ offers: {
43
+ "@type": "Offer",
44
+ price: (product.priceCents / 100).toFixed(2),
45
+ priceCurrency: product.currency ?? "USD",
46
+ ...(product.availability
47
+ ? {
48
+ availability: `https://schema.org/${product.availability}`,
49
+ }
50
+ : {}),
51
+ },
52
+ }
53
+ : {}),
54
+ },
55
+ })),
56
+ },
57
+ };
58
+ return {
59
+ ...(input.title ? { title: input.title } : {}),
60
+ ...(input.description ? { description: input.description } : {}),
61
+ canonical,
62
+ // Either reason is enough on its own to keep the page out of the index.
63
+ ...(page > 1 || input.filtered ? { noIndex: page > 1 } : {}),
64
+ jsonLd: [
65
+ node,
66
+ breadcrumb({
67
+ siteUrl: input.siteUrl,
68
+ items: [
69
+ { name: "Home", url: input.siteUrl },
70
+ ...(input.breadcrumbs ?? []),
71
+ ...(input.title ? [{ name: input.title, url: canonical }] : []),
72
+ ],
73
+ }),
74
+ ],
75
+ };
76
+ }
77
+ /**
78
+ * SEO for cart, checkout, account and order-confirmation pages.
79
+ *
80
+ * These must never be indexed. A cart URL in search results is at best useless
81
+ * and at worst a leak — order confirmations have been indexed with customer
82
+ * details in the query string more than once, by more than one large retailer.
83
+ */
84
+ export function transactionalPageSeo(title) {
85
+ return { title, noIndex: true, canonical: null };
86
+ }
87
+ //# sourceMappingURL=commerce.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commerce.js","sourceRoot":"","sources":["../../src/seo-core/commerce.ts"],"names":[],"mappings":"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"}