@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,115 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/legal-core/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ legalFooterLinks: () => legalFooterLinks,
24
+ legalLastUpdated: () => legalLastUpdated,
25
+ legalPageSeo: () => legalPageSeo,
26
+ legalSlugFrom: () => legalSlugFrom,
27
+ renderLegalPageHtml: () => renderLegalPageHtml,
28
+ resolveLegalPage: () => resolveLegalPage
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ function legalSlugFrom(input) {
32
+ if (!input) return "";
33
+ const joined = Array.isArray(input) ? input.join("/") : input;
34
+ return joined.trim().toLowerCase().replace(/^\/+|\/+$/g, "").replace(/\/{2,}/g, "/");
35
+ }
36
+ async function resolveLegalPage(options) {
37
+ const slug = legalSlugFrom(options.slug);
38
+ if (!slug) return null;
39
+ try {
40
+ return await options.client.legal.get(slug);
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+ function legalFooterLinks(list, options = {}) {
46
+ const items = Array.isArray(list) ? list : list.items;
47
+ const base = (options.basePath ?? "").replace(/\/+$/, "");
48
+ return items.filter((doc) => doc.showInFooter).map((doc) => ({ href: `${base}/${doc.slug}`, label: doc.title }));
49
+ }
50
+ function escapeHtml(value) {
51
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
52
+ }
53
+ function legalLastUpdated(document, locale) {
54
+ if (!document.effectiveDate) return null;
55
+ const date = new Date(document.effectiveDate);
56
+ if (Number.isNaN(date.getTime())) return null;
57
+ return date.toLocaleDateString(locale, {
58
+ year: "numeric",
59
+ month: "long",
60
+ day: "numeric",
61
+ timeZone: "UTC"
62
+ });
63
+ }
64
+ function renderLegalPageHtml(document, options = {}) {
65
+ const presentation = document.presentation;
66
+ const className = options.className ?? "biab-legal";
67
+ const homeHref = options.homeHref ?? "/";
68
+ const updated = legalLastUpdated(document, options.locale);
69
+ const logo = (where) => {
70
+ const show = where === "header" ? presentation.showLogoInHeader : presentation.showLogoInFooter;
71
+ if (!show || !presentation.logoUrl) return "";
72
+ return `<a href="${escapeHtml(homeHref)}" data-biab-legal-logo-link><img src="${escapeHtml(presentation.logoUrl)}" alt="" data-biab-legal-logo="${where}" /></a>`;
73
+ };
74
+ const parts = [
75
+ `<article class="${escapeHtml(className)}" data-biab-legal>`,
76
+ "<header data-biab-legal-header>",
77
+ logo("header"),
78
+ presentation.headerHtml ?? "",
79
+ `<h1 data-biab-legal-title>${escapeHtml(document.title)}</h1>`,
80
+ updated ? `<p data-biab-legal-updated>Last updated: ${escapeHtml(updated)}</p>` : "",
81
+ "</header>",
82
+ `<div data-biab-legal-body>${document.contentHtml}</div>`,
83
+ "<footer data-biab-legal-footer>",
84
+ presentation.footerHtml ?? "",
85
+ logo("footer"),
86
+ "</footer>",
87
+ "</article>"
88
+ ];
89
+ return parts.filter(Boolean).join("");
90
+ }
91
+ function legalPageSeo(document, options = {}) {
92
+ const title = options.siteName ? `${document.title} \u2014 ${options.siteName}` : document.title;
93
+ return {
94
+ title,
95
+ // Built from the title rather than the body: the first sentence of a
96
+ // privacy policy is boilerplate ("This policy explains how…") and makes
97
+ // a description indistinguishable from every other company's.
98
+ description: options.siteName ? `The ${document.title.toLowerCase()} for ${options.siteName}.` : `Read our ${document.title.toLowerCase()}.`,
99
+ ...options.canonical === void 0 ? {} : { canonical: options.canonical },
100
+ openGraph: {
101
+ title,
102
+ type: "article",
103
+ ...options.canonical === void 0 ? {} : { url: options.canonical }
104
+ }
105
+ };
106
+ }
107
+ // Annotate the CommonJS export names for ESM import in node:
108
+ 0 && (module.exports = {
109
+ legalFooterLinks,
110
+ legalLastUpdated,
111
+ legalPageSeo,
112
+ legalSlugFrom,
113
+ renderLegalPageHtml,
114
+ resolveLegalPage
115
+ });
@@ -0,0 +1,160 @@
1
+ /**
2
+ * `@businessdash/sdk/legal` — the org's legal pages, rendered on their site.
3
+ *
4
+ * Privacy policy, terms, refund policy: written once in the BusinessDash
5
+ * dashboard, and appearing on the org's own domain without anyone authoring a
6
+ * page for them.
7
+ *
8
+ * ## The design constraint that decides everything: your page must win
9
+ *
10
+ * A consumer who writes their own `/privacy` must keep it, and their build must
11
+ * not fail because we also wanted that path. There is a tempting wrong answer —
12
+ * generate `app/privacy/page.tsx` at install time — and it fails badly: two
13
+ * files claiming one route is a hard build error in Next, Remix, Nuxt, Astro,
14
+ * SvelteKit and TanStack Start alike. It turns "I wrote my own privacy page"
15
+ * into a broken deploy.
16
+ *
17
+ * So **this SDK never claims a literal path.** It gives you a resolver to mount
18
+ * in a CATCH-ALL route, and every one of those frameworks resolves a static
19
+ * segment before a catch-all:
20
+ *
21
+ * | Framework | Your page | The catch-all |
22
+ * |--------------|------------------------|----------------------------|
23
+ * | Next (app) | `app/privacy/page.tsx` | `app/[...slug]/page.tsx` |
24
+ * | Remix / RR7 | `routes/privacy.tsx` | `routes/$.tsx` |
25
+ * | Astro | `pages/privacy.astro` | `pages/[...slug].astro` |
26
+ * | Nuxt | `pages/privacy.vue` | `pages/[...slug].vue` |
27
+ * | SvelteKit | `routes/privacy/+page` | `routes/[...rest]/+page` |
28
+ * | TanStack | `routes/privacy.tsx` | `routes/$.tsx` |
29
+ *
30
+ * Write your own page and it simply shadows ours. No conflict, no build error,
31
+ * no configuration, and nothing to remove — the override is the framework's own
32
+ * routing precedence rather than anything we invented.
33
+ *
34
+ * The corollary: `resolveLegalPage()` returns `null` for a slug with no
35
+ * document, and your catch-all must then fall through to its normal 404. It is
36
+ * a resolver, not a router, precisely so it cannot swallow paths it has no
37
+ * business answering.
38
+ *
39
+ * ## What you render
40
+ *
41
+ * `contentHtml` is sanitised server-side when the org saves it, so it is safe
42
+ * to inject. The page is deliberately unstyled markup: legal pages should look
43
+ * like the rest of the site, and an SDK that shipped its own typography would
44
+ * be one more thing to override. `renderLegalPageHtml()` produces semantic HTML
45
+ * with stable `data-biab-legal-*` hooks you can style, or ignore entirely and
46
+ * build your own from the same data.
47
+ *
48
+ * @module
49
+ */
50
+ import type { LegalDocument, LegalDocumentSummary, LegalListResponse, LegalPresentation } from "../contracts.js";
51
+ /** The subset of the SDK client this module needs. */
52
+ export type LegalClientLike = {
53
+ legal: {
54
+ list(): Promise<LegalListResponse>;
55
+ get(slug: string): Promise<LegalDocument>;
56
+ };
57
+ };
58
+ /**
59
+ * Turn a catch-all's path segments into a slug.
60
+ *
61
+ * Accepts what each framework actually hands you — an array of segments, a
62
+ * joined path, with or without leading slashes — because normalising this at
63
+ * every call site is how the two sides end up disagreeing about `/privacy/`
64
+ * versus `privacy`.
65
+ */
66
+ export declare function legalSlugFrom(input: string | string[] | undefined): string;
67
+ export type ResolveLegalPageOptions = {
68
+ client: LegalClientLike;
69
+ /** Whatever the catch-all captured. */
70
+ slug: string | string[] | undefined;
71
+ };
72
+ /**
73
+ * The document at this path, or null if there isn't one.
74
+ *
75
+ * **Null means fall through to your own 404.** Returning an "empty legal page"
76
+ * would put a bare heading reading "Privacy Policy" on every unmatched URL on
77
+ * the site, which is worse than a 404 in every way that matters — it is wrong
78
+ * for users, wrong for crawlers, and hides real routing mistakes.
79
+ *
80
+ * A network failure also returns null rather than throwing. A catch-all runs on
81
+ * every unmatched request, and an exception there takes down paths that have
82
+ * nothing to do with legal pages; a 404 during an outage is recoverable, a
83
+ * 500 on every unknown URL is not.
84
+ */
85
+ export declare function resolveLegalPage(options: ResolveLegalPageOptions): Promise<LegalDocument | null>;
86
+ /**
87
+ * The links an org wants in their footer.
88
+ *
89
+ * Filtered on `showInFooter`, which is separate from published on purpose: an
90
+ * org may need a document to exist at a URL they hand to a processor without
91
+ * putting it in front of every visitor.
92
+ */
93
+ export declare function legalFooterLinks(list: LegalListResponse | LegalDocumentSummary[], options?: {
94
+ basePath?: string;
95
+ }): Array<{
96
+ href: string;
97
+ label: string;
98
+ }>;
99
+ /**
100
+ * "Last updated" text, or null when the org never set a date.
101
+ *
102
+ * Uses `effectiveDate` in preference to `updatedAt`: a typo fix bumps
103
+ * `updatedAt` and should not tell every reader the terms changed. The org
104
+ * decides when a change was material by setting the effective date.
105
+ *
106
+ * ## Formatted in UTC, deliberately
107
+ *
108
+ * The dashboard stores a calendar date as midnight UTC, so formatting in the
109
+ * READER's zone shifts it a day for everyone west of UTC — an effective date of
110
+ * 1 August renders as "July 31" in New York. On a marketing page that is a
111
+ * cosmetic bug; on a document whose whole purpose is to say when terms changed,
112
+ * it is a wrong date shown to roughly half the audience.
113
+ */
114
+ export declare function legalLastUpdated(document: Pick<LegalDocument, "effectiveDate">, locale?: string): string | null;
115
+ export type RenderLegalPageOptions = {
116
+ /** Wrapped around everything. Defaults to `biab-legal`. */
117
+ className?: string;
118
+ /** Locale for the "Last updated" line. */
119
+ locale?: string;
120
+ /** Where the logo links to. Defaults to `/`. */
121
+ homeHref?: string;
122
+ };
123
+ /**
124
+ * Semantic markup for one document: header, title, body, footer.
125
+ *
126
+ * Unstyled by design. Every element carries a stable `data-biab-legal-*`
127
+ * attribute so a stylesheet can reach it, and none carries a class you would
128
+ * have to fight. If you want a different structure, read the same fields off
129
+ * the document and build your own — this is a convenience, not a requirement.
130
+ *
131
+ * `contentHtml`, `headerHtml` and `footerHtml` are inserted as-is because they
132
+ * were sanitised when the org saved them. The title and logo URL are escaped
133
+ * here, since they are plain values that have never been through a sanitiser.
134
+ */
135
+ export declare function renderLegalPageHtml(document: LegalDocument, options?: RenderLegalPageOptions): string;
136
+ /**
137
+ * Page metadata for a legal document.
138
+ *
139
+ * `noindex` is deliberately NOT set. Legal pages should be indexable: people
140
+ * search for "acme refund policy", and app stores and payment processors check
141
+ * that the URL you gave them resolves to something a crawler can see.
142
+ *
143
+ * Feed this to `@businessdash/sdk/seo-core`'s adapters, or spread it into your
144
+ * framework's own metadata export.
145
+ */
146
+ export declare function legalPageSeo(document: LegalDocument, options?: {
147
+ siteName?: string;
148
+ canonical?: string;
149
+ }): {
150
+ title: string;
151
+ description: string;
152
+ canonical?: string;
153
+ openGraph: {
154
+ title: string;
155
+ type: "article";
156
+ url?: string;
157
+ };
158
+ };
159
+ export type { LegalDocument, LegalDocumentSummary, LegalPresentation };
160
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/legal-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAEH,OAAO,KAAK,EACX,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,MAAM,iBAAiB,CAAC;AAEzB,sDAAsD;AACtD,MAAM,MAAM,eAAe,GAAG;IAC7B,KAAK,EAAE;QACN,IAAI,IAAI,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACnC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;KAC1C,CAAC;CACF,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,CAQ1E;AAED,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,eAAe,CAAC;IACxB,uCAAuC;IACvC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACrC,OAAO,EAAE,uBAAuB,GAC9B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAQ/B;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,iBAAiB,GAAG,oBAAoB,EAAE,EAChD,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAMxC;AAWD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC/B,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,EAC9C,MAAM,CAAC,EAAE,MAAM,GACb,MAAM,GAAG,IAAI,CAUf;AAED,MAAM,MAAM,sBAAsB,GAAG;IACpC,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE,sBAA2B,GAClC,MAAM,CAqCR;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,aAAa,EACvB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACrD;IACF,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,CAqBA;AAED,YAAY,EAAE,aAAa,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,CAAC"}
@@ -0,0 +1,220 @@
1
+ /**
2
+ * `@businessdash/sdk/legal` — the org's legal pages, rendered on their site.
3
+ *
4
+ * Privacy policy, terms, refund policy: written once in the BusinessDash
5
+ * dashboard, and appearing on the org's own domain without anyone authoring a
6
+ * page for them.
7
+ *
8
+ * ## The design constraint that decides everything: your page must win
9
+ *
10
+ * A consumer who writes their own `/privacy` must keep it, and their build must
11
+ * not fail because we also wanted that path. There is a tempting wrong answer —
12
+ * generate `app/privacy/page.tsx` at install time — and it fails badly: two
13
+ * files claiming one route is a hard build error in Next, Remix, Nuxt, Astro,
14
+ * SvelteKit and TanStack Start alike. It turns "I wrote my own privacy page"
15
+ * into a broken deploy.
16
+ *
17
+ * So **this SDK never claims a literal path.** It gives you a resolver to mount
18
+ * in a CATCH-ALL route, and every one of those frameworks resolves a static
19
+ * segment before a catch-all:
20
+ *
21
+ * | Framework | Your page | The catch-all |
22
+ * |--------------|------------------------|----------------------------|
23
+ * | Next (app) | `app/privacy/page.tsx` | `app/[...slug]/page.tsx` |
24
+ * | Remix / RR7 | `routes/privacy.tsx` | `routes/$.tsx` |
25
+ * | Astro | `pages/privacy.astro` | `pages/[...slug].astro` |
26
+ * | Nuxt | `pages/privacy.vue` | `pages/[...slug].vue` |
27
+ * | SvelteKit | `routes/privacy/+page` | `routes/[...rest]/+page` |
28
+ * | TanStack | `routes/privacy.tsx` | `routes/$.tsx` |
29
+ *
30
+ * Write your own page and it simply shadows ours. No conflict, no build error,
31
+ * no configuration, and nothing to remove — the override is the framework's own
32
+ * routing precedence rather than anything we invented.
33
+ *
34
+ * The corollary: `resolveLegalPage()` returns `null` for a slug with no
35
+ * document, and your catch-all must then fall through to its normal 404. It is
36
+ * a resolver, not a router, precisely so it cannot swallow paths it has no
37
+ * business answering.
38
+ *
39
+ * ## What you render
40
+ *
41
+ * `contentHtml` is sanitised server-side when the org saves it, so it is safe
42
+ * to inject. The page is deliberately unstyled markup: legal pages should look
43
+ * like the rest of the site, and an SDK that shipped its own typography would
44
+ * be one more thing to override. `renderLegalPageHtml()` produces semantic HTML
45
+ * with stable `data-biab-legal-*` hooks you can style, or ignore entirely and
46
+ * build your own from the same data.
47
+ *
48
+ * @module
49
+ */
50
+ /**
51
+ * Turn a catch-all's path segments into a slug.
52
+ *
53
+ * Accepts what each framework actually hands you — an array of segments, a
54
+ * joined path, with or without leading slashes — because normalising this at
55
+ * every call site is how the two sides end up disagreeing about `/privacy/`
56
+ * versus `privacy`.
57
+ */
58
+ export function legalSlugFrom(input) {
59
+ if (!input)
60
+ return "";
61
+ const joined = Array.isArray(input) ? input.join("/") : input;
62
+ return joined
63
+ .trim()
64
+ .toLowerCase()
65
+ .replace(/^\/+|\/+$/g, "")
66
+ .replace(/\/{2,}/g, "/");
67
+ }
68
+ /**
69
+ * The document at this path, or null if there isn't one.
70
+ *
71
+ * **Null means fall through to your own 404.** Returning an "empty legal page"
72
+ * would put a bare heading reading "Privacy Policy" on every unmatched URL on
73
+ * the site, which is worse than a 404 in every way that matters — it is wrong
74
+ * for users, wrong for crawlers, and hides real routing mistakes.
75
+ *
76
+ * A network failure also returns null rather than throwing. A catch-all runs on
77
+ * every unmatched request, and an exception there takes down paths that have
78
+ * nothing to do with legal pages; a 404 during an outage is recoverable, a
79
+ * 500 on every unknown URL is not.
80
+ */
81
+ export async function resolveLegalPage(options) {
82
+ const slug = legalSlugFrom(options.slug);
83
+ if (!slug)
84
+ return null;
85
+ try {
86
+ return await options.client.legal.get(slug);
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ /**
93
+ * The links an org wants in their footer.
94
+ *
95
+ * Filtered on `showInFooter`, which is separate from published on purpose: an
96
+ * org may need a document to exist at a URL they hand to a processor without
97
+ * putting it in front of every visitor.
98
+ */
99
+ export function legalFooterLinks(list, options = {}) {
100
+ const items = Array.isArray(list) ? list : list.items;
101
+ const base = (options.basePath ?? "").replace(/\/+$/, "");
102
+ return items
103
+ .filter((doc) => doc.showInFooter)
104
+ .map((doc) => ({ href: `${base}/${doc.slug}`, label: doc.title }));
105
+ }
106
+ /** Escape text destined for markup. Titles are plain text, not HTML. */
107
+ function escapeHtml(value) {
108
+ return value
109
+ .replace(/&/g, "&amp;")
110
+ .replace(/</g, "&lt;")
111
+ .replace(/>/g, "&gt;")
112
+ .replace(/"/g, "&quot;");
113
+ }
114
+ /**
115
+ * "Last updated" text, or null when the org never set a date.
116
+ *
117
+ * Uses `effectiveDate` in preference to `updatedAt`: a typo fix bumps
118
+ * `updatedAt` and should not tell every reader the terms changed. The org
119
+ * decides when a change was material by setting the effective date.
120
+ *
121
+ * ## Formatted in UTC, deliberately
122
+ *
123
+ * The dashboard stores a calendar date as midnight UTC, so formatting in the
124
+ * READER's zone shifts it a day for everyone west of UTC — an effective date of
125
+ * 1 August renders as "July 31" in New York. On a marketing page that is a
126
+ * cosmetic bug; on a document whose whole purpose is to say when terms changed,
127
+ * it is a wrong date shown to roughly half the audience.
128
+ */
129
+ export function legalLastUpdated(document, locale) {
130
+ if (!document.effectiveDate)
131
+ return null;
132
+ const date = new Date(document.effectiveDate);
133
+ if (Number.isNaN(date.getTime()))
134
+ return null;
135
+ return date.toLocaleDateString(locale, {
136
+ year: "numeric",
137
+ month: "long",
138
+ day: "numeric",
139
+ timeZone: "UTC",
140
+ });
141
+ }
142
+ /**
143
+ * Semantic markup for one document: header, title, body, footer.
144
+ *
145
+ * Unstyled by design. Every element carries a stable `data-biab-legal-*`
146
+ * attribute so a stylesheet can reach it, and none carries a class you would
147
+ * have to fight. If you want a different structure, read the same fields off
148
+ * the document and build your own — this is a convenience, not a requirement.
149
+ *
150
+ * `contentHtml`, `headerHtml` and `footerHtml` are inserted as-is because they
151
+ * were sanitised when the org saved them. The title and logo URL are escaped
152
+ * here, since they are plain values that have never been through a sanitiser.
153
+ */
154
+ export function renderLegalPageHtml(document, options = {}) {
155
+ const presentation = document.presentation;
156
+ const className = options.className ?? "biab-legal";
157
+ const homeHref = options.homeHref ?? "/";
158
+ const updated = legalLastUpdated(document, options.locale);
159
+ const logo = (where) => {
160
+ const show = where === "header"
161
+ ? presentation.showLogoInHeader
162
+ : presentation.showLogoInFooter;
163
+ if (!show || !presentation.logoUrl)
164
+ return "";
165
+ return (`<a href="${escapeHtml(homeHref)}" data-biab-legal-logo-link>` +
166
+ `<img src="${escapeHtml(presentation.logoUrl)}" alt="" data-biab-legal-logo="${where}" />` +
167
+ "</a>");
168
+ };
169
+ const parts = [
170
+ `<article class="${escapeHtml(className)}" data-biab-legal>`,
171
+ "<header data-biab-legal-header>",
172
+ logo("header"),
173
+ presentation.headerHtml ?? "",
174
+ `<h1 data-biab-legal-title>${escapeHtml(document.title)}</h1>`,
175
+ updated
176
+ ? `<p data-biab-legal-updated>Last updated: ${escapeHtml(updated)}</p>`
177
+ : "",
178
+ "</header>",
179
+ `<div data-biab-legal-body>${document.contentHtml}</div>`,
180
+ "<footer data-biab-legal-footer>",
181
+ presentation.footerHtml ?? "",
182
+ logo("footer"),
183
+ "</footer>",
184
+ "</article>",
185
+ ];
186
+ return parts.filter(Boolean).join("");
187
+ }
188
+ /**
189
+ * Page metadata for a legal document.
190
+ *
191
+ * `noindex` is deliberately NOT set. Legal pages should be indexable: people
192
+ * search for "acme refund policy", and app stores and payment processors check
193
+ * that the URL you gave them resolves to something a crawler can see.
194
+ *
195
+ * Feed this to `@businessdash/sdk/seo-core`'s adapters, or spread it into your
196
+ * framework's own metadata export.
197
+ */
198
+ export function legalPageSeo(document, options = {}) {
199
+ const title = options.siteName
200
+ ? `${document.title} — ${options.siteName}`
201
+ : document.title;
202
+ return {
203
+ title,
204
+ // Built from the title rather than the body: the first sentence of a
205
+ // privacy policy is boilerplate ("This policy explains how…") and makes
206
+ // a description indistinguishable from every other company's.
207
+ description: options.siteName
208
+ ? `The ${document.title.toLowerCase()} for ${options.siteName}.`
209
+ : `Read our ${document.title.toLowerCase()}.`,
210
+ ...(options.canonical === undefined
211
+ ? {}
212
+ : { canonical: options.canonical }),
213
+ openGraph: {
214
+ title,
215
+ type: "article",
216
+ ...(options.canonical === undefined ? {} : { url: options.canonical }),
217
+ },
218
+ };
219
+ }
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/legal-core/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * `@businessdash/sdk/marketing` — the headless marketing-page controller.
3
+ *
4
+ * Marketing pages were React-only, and the reason was accidental: the logic is
5
+ * a cache, a fetch and a status flag. This lifts it out so every binding can
6
+ * render a schema-driven page.
7
+ *
8
+ * ## Why the cache matters here specifically
9
+ *
10
+ * A site can have many marketing pages, and a visitor reads one. Fetching all
11
+ * of them to render one is the obvious mistake, so `open()` pulls a single page
12
+ * on demand, and `prefetch()` exists for the case where you know what is coming
13
+ * next (a nav hover, a likely next step) and want it warm before the click.
14
+ *
15
+ * The SDK client keeps its own cache; this controller does not duplicate it. It
16
+ * tracks WHICH page is showing and whether a request is in flight — the cache
17
+ * itself stays one layer down, where every framework already shares it.
18
+ *
19
+ * @module
20
+ */
21
+ import type { GetSiteMarketingPageResponse, ListSiteMarketingPagesResponse } from "../contracts.js";
22
+ export type MarketingCoreClient = {
23
+ marketingPages: {
24
+ list(): Promise<ListSiteMarketingPagesResponse>;
25
+ get(pageKey?: string, options?: {
26
+ skipCache?: boolean;
27
+ }): Promise<GetSiteMarketingPageResponse>;
28
+ prefetch(pageKey?: string): Promise<GetSiteMarketingPageResponse>;
29
+ };
30
+ };
31
+ export type MarketingStatus = "idle" | "loading" | "ready" | "error";
32
+ /** Field names are STABLE and load-bearing for every framework binding. */
33
+ export type MarketingSnapshot = {
34
+ status: MarketingStatus;
35
+ /** Every page on the site — keys and titles, not bodies. */
36
+ pages: ListSiteMarketingPagesResponse["pages"];
37
+ /** The page currently open, or `null`. */
38
+ page: GetSiteMarketingPageResponse | null;
39
+ /** Which key `page` belongs to. */
40
+ pageKey: string | null;
41
+ error: Error | null;
42
+ pending: {
43
+ list: boolean;
44
+ page: boolean;
45
+ };
46
+ };
47
+ export type MarketingController = {
48
+ snapshot(): MarketingSnapshot;
49
+ subscribe(callback: (snapshot: MarketingSnapshot) => void): () => void;
50
+ /** Fetch the page index. */
51
+ load(): Promise<void>;
52
+ /** Fetch and show one page. */
53
+ open(pageKey?: string, options?: {
54
+ skipCache?: boolean;
55
+ }): Promise<void>;
56
+ /** Warm a page's cache without showing it. Failures are swallowed. */
57
+ prefetch(pageKey?: string): Promise<void>;
58
+ close(): void;
59
+ destroy(): void;
60
+ };
61
+ export type CreateMarketingControllerOptions = {
62
+ client: MarketingCoreClient;
63
+ onError?: (error: Error) => void;
64
+ };
65
+ export declare function createMarketingController(options: CreateMarketingControllerOptions): MarketingController;
66
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/marketing-core/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EACX,4BAA4B,EAC5B,8BAA8B,EAC9B,MAAM,iBAAiB,CAAC;AAEzB,MAAM,MAAM,mBAAmB,GAAG;IACjC,cAAc,EAAE;QACf,IAAI,IAAI,OAAO,CAAC,8BAA8B,CAAC,CAAC;QAChD,GAAG,CACF,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;YAAE,SAAS,CAAC,EAAE,OAAO,CAAA;SAAE,GAC/B,OAAO,CAAC,4BAA4B,CAAC,CAAC;QACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,CAAC,CAAC;KAClE,CAAC;CACF,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAErE,2EAA2E;AAC3E,MAAM,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,eAAe,CAAC;IACxB,4DAA4D;IAC5D,KAAK,EAAE,8BAA8B,CAAC,OAAO,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,IAAI,EAAE,4BAA4B,GAAG,IAAI,CAAC;IAC1C,mCAAmC;IACnC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,QAAQ,IAAI,iBAAiB,CAAC;IAC9B,SAAS,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IACvE,4BAA4B;IAC5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,+BAA+B;IAC/B,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzE,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC9C,MAAM,EAAE,mBAAmB,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACjC,CAAC;AAMF,wBAAgB,yBAAyB,CACxC,OAAO,EAAE,gCAAgC,GACvC,mBAAmB,CAoIrB"}