@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.
- package/README.md +991 -295
- package/assets/biab-sw.js +92 -0
- package/dist/account-core/index.d.ts +126 -0
- package/dist/account-core/index.d.ts.map +1 -0
- package/dist/account-core/index.js +191 -0
- package/dist/account-core/index.js.map +1 -0
- package/dist/account.d.ts +11 -0
- package/dist/account.d.ts.map +1 -0
- package/dist/account.js +11 -0
- package/dist/account.js.map +1 -0
- package/dist/auth-handler.d.ts +1 -1
- package/dist/auth-links.d.ts +33 -0
- package/dist/auth-links.d.ts.map +1 -0
- package/dist/auth-links.js +64 -0
- package/dist/auth-links.js.map +1 -0
- package/dist/blog-core/index.d.ts +112 -0
- package/dist/blog-core/index.d.ts.map +1 -0
- package/dist/blog-core/index.js +330 -0
- package/dist/blog-core/index.js.map +1 -0
- package/dist/blog.d.ts +11 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +11 -0
- package/dist/blog.js.map +1 -0
- package/dist/chat-core/index.d.ts +97 -0
- package/dist/chat-core/index.d.ts.map +1 -0
- package/dist/chat-core/index.js +237 -0
- package/dist/chat-core/index.js.map +1 -0
- package/dist/chat.d.ts +11 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +11 -0
- package/dist/chat.js.map +1 -0
- package/dist/checkout-core/index.d.ts +65 -0
- package/dist/checkout-core/index.d.ts.map +1 -0
- package/dist/checkout-core/index.js +159 -0
- package/dist/checkout-core/index.js.map +1 -0
- package/dist/checkout.d.ts +10 -0
- package/dist/checkout.d.ts.map +1 -0
- package/dist/checkout.js +10 -0
- package/dist/checkout.js.map +1 -0
- package/dist/cli.js +133 -25
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +622 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +888 -1
- package/dist/client.js.map +1 -1
- package/dist/consent-core/index.cjs +107 -0
- package/dist/consent-core/index.d.ts +103 -0
- package/dist/consent-core/index.d.ts.map +1 -0
- package/dist/consent-core/index.js +157 -0
- package/dist/consent-core/index.js.map +1 -0
- package/dist/contracts.d.ts +3526 -767
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +571 -4
- package/dist/contracts.js.map +1 -1
- package/dist/data-model-schema.d.ts +1 -1
- package/dist/data-model-schema.d.ts.map +1 -1
- package/dist/element.d.ts +30 -0
- package/dist/element.d.ts.map +1 -1
- package/dist/element.js +56 -0
- package/dist/element.js.map +1 -1
- package/dist/embed-protocol.d.ts +86 -0
- package/dist/embed-protocol.d.ts.map +1 -0
- package/dist/embed-protocol.js +60 -0
- package/dist/embed-protocol.js.map +1 -0
- package/dist/framework-support.d.ts +122 -0
- package/dist/framework-support.d.ts.map +1 -0
- package/dist/framework-support.js +323 -0
- package/dist/framework-support.js.map +1 -0
- package/dist/index.cjs +1355 -24
- package/dist/legal-core/index.cjs +115 -0
- package/dist/legal-core/index.d.ts +160 -0
- package/dist/legal-core/index.d.ts.map +1 -0
- package/dist/legal-core/index.js +220 -0
- package/dist/legal-core/index.js.map +1 -0
- package/dist/marketing-core/index.d.ts +66 -0
- package/dist/marketing-core/index.d.ts.map +1 -0
- package/dist/marketing-core/index.js +154 -0
- package/dist/marketing-core/index.js.map +1 -0
- package/dist/marketing.d.ts +7 -0
- package/dist/marketing.d.ts.map +1 -0
- package/dist/marketing.js +7 -0
- package/dist/marketing.js.map +1 -0
- package/dist/mcp-adapters/index.d.ts +135 -0
- package/dist/mcp-adapters/index.d.ts.map +1 -0
- package/dist/mcp-adapters/index.js +187 -0
- package/dist/mcp-adapters/index.js.map +1 -0
- package/dist/mcp-adapters.d.ts +7 -0
- package/dist/mcp-adapters.d.ts.map +1 -0
- package/dist/mcp-adapters.js +7 -0
- package/dist/mcp-adapters.js.map +1 -0
- package/dist/model3d-core/index.cjs +95 -0
- package/dist/model3d-core/index.d.ts +167 -0
- package/dist/model3d-core/index.d.ts.map +1 -0
- package/dist/model3d-core/index.js +158 -0
- package/dist/model3d-core/index.js.map +1 -0
- package/dist/notifications-core/index.cjs +315 -0
- package/dist/notifications-core/index.d.ts +229 -0
- package/dist/notifications-core/index.d.ts.map +1 -0
- package/dist/notifications-core/index.js +445 -0
- package/dist/notifications-core/index.js.map +1 -0
- package/dist/portal-core/index.d.ts +451 -0
- package/dist/portal-core/index.d.ts.map +1 -0
- package/dist/portal-core/index.js +819 -0
- package/dist/portal-core/index.js.map +1 -0
- package/dist/portal.d.ts +7 -0
- package/dist/portal.d.ts.map +1 -0
- package/dist/portal.js +7 -0
- package/dist/portal.js.map +1 -0
- package/dist/push-core/index.cjs +171 -0
- package/dist/push-core/index.d.ts +149 -0
- package/dist/push-core/index.d.ts.map +1 -0
- package/dist/push-core/index.js +231 -0
- package/dist/push-core/index.js.map +1 -0
- package/dist/react-marketing.d.ts +1 -17
- package/dist/react-marketing.d.ts.map +1 -1
- package/dist/react-marketing.js +4 -0
- package/dist/react-marketing.js.map +1 -1
- package/dist/react.d.ts +294 -101
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +302 -246
- package/dist/react.js.map +1 -1
- package/dist/seed-core/index.d.ts +354 -0
- package/dist/seed-core/index.d.ts.map +1 -0
- package/dist/seed-core/index.js +420 -0
- package/dist/seed-core/index.js.map +1 -0
- package/dist/seed.d.ts +7 -0
- package/dist/seed.d.ts.map +1 -0
- package/dist/seed.js +7 -0
- package/dist/seed.js.map +1 -0
- package/dist/seo-core/blog.d.ts +94 -0
- package/dist/seo-core/blog.d.ts.map +1 -0
- package/dist/seo-core/blog.js +149 -0
- package/dist/seo-core/blog.js.map +1 -0
- package/dist/seo-core/commerce.d.ts +64 -0
- package/dist/seo-core/commerce.d.ts.map +1 -0
- package/dist/seo-core/commerce.js +87 -0
- package/dist/seo-core/commerce.js.map +1 -0
- package/dist/seo-core/index.cjs +751 -0
- package/dist/seo-core/index.d.ts +226 -0
- package/dist/seo-core/index.d.ts.map +1 -0
- package/dist/seo-core/index.js +455 -0
- package/dist/seo-core/index.js.map +1 -0
- package/dist/seo-core/local.d.ts +181 -0
- package/dist/seo-core/local.d.ts.map +1 -0
- package/dist/seo-core/local.js +193 -0
- package/dist/seo-core/local.js.map +1 -0
- package/dist/sitemap-core/index.cjs +167 -0
- package/dist/sitemap-core/index.d.ts +155 -0
- package/dist/sitemap-core/index.d.ts.map +1 -0
- package/dist/sitemap-core/index.js +195 -0
- package/dist/sitemap-core/index.js.map +1 -0
- package/dist/social-core/index.d.ts +74 -0
- package/dist/social-core/index.d.ts.map +1 -0
- package/dist/social-core/index.js +72 -0
- package/dist/social-core/index.js.map +1 -0
- package/dist/social.d.ts +7 -0
- package/dist/social.d.ts.map +1 -0
- package/dist/social.js +7 -0
- package/dist/social.js.map +1 -0
- package/dist/solid-compat.d.ts +204 -0
- package/dist/solid-compat.d.ts.map +1 -0
- package/dist/solid-compat.js +221 -0
- package/dist/solid-compat.js.map +1 -0
- package/dist/store-core/index.d.ts +141 -0
- package/dist/store-core/index.d.ts.map +1 -0
- package/dist/store-core/index.js +277 -0
- package/dist/store-core/index.js.map +1 -0
- package/dist/store.d.ts +12 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +12 -0
- package/dist/store.js.map +1 -0
- package/dist/vanilla.d.ts +111 -0
- package/dist/vanilla.d.ts.map +1 -1
- package/dist/vanilla.js +206 -0
- package/dist/vanilla.js.map +1 -1
- package/dist/verify-core/index.d.ts +98 -0
- package/dist/verify-core/index.d.ts.map +1 -0
- package/dist/verify-core/index.js +186 -0
- package/dist/verify-core/index.js.map +1 -0
- package/dist/verify.d.ts +8 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +8 -0
- package/dist/verify.js.map +1 -0
- package/openapi.json +4516 -1311
- package/package.json +128 -11
- package/src/account-core/index.ts +309 -0
- package/src/account.ts +11 -0
- package/src/angular/headless.ts +519 -0
- package/src/angular/index.ts +3 -0
- package/src/auth-links.ts +76 -0
- package/src/blog-core/index.ts +423 -0
- package/src/blog.ts +11 -0
- package/src/chat-core/index.ts +335 -0
- package/src/chat.ts +11 -0
- package/src/checkout-core/index.ts +225 -0
- package/src/checkout.ts +10 -0
- package/src/cli.ts +184 -28
- package/src/client.ts +1097 -0
- package/src/consent-core/index.ts +213 -0
- package/src/contracts.ts +768 -11
- package/src/data-model-schema.ts +1 -1
- package/src/element.ts +102 -0
- package/src/embed-protocol.ts +100 -0
- package/src/framework-support.ts +440 -0
- package/src/legal-core/index.ts +276 -0
- package/src/marketing-core/index.ts +209 -0
- package/src/marketing.ts +7 -0
- package/src/mcp-adapters/index.ts +264 -0
- package/src/mcp-adapters.ts +7 -0
- package/src/model3d-core/index.ts +248 -0
- package/src/notifications-core/index.ts +650 -0
- package/src/portal-core/index.ts +1564 -0
- package/src/portal.ts +7 -0
- package/src/push-core/index.ts +335 -0
- package/src/qwik.tsx +602 -2
- package/src/react-marketing.tsx +5 -0
- package/src/react.tsx +434 -319
- package/src/seed-core/index.ts +735 -0
- package/src/seed.ts +7 -0
- package/src/seo-core/blog.ts +216 -0
- package/src/seo-core/commerce.ts +138 -0
- package/src/seo-core/index.ts +605 -0
- package/src/seo-core/local.ts +333 -0
- package/src/sitemap-core/index.ts +296 -0
- package/src/social-core/index.ts +103 -0
- package/src/social.ts +7 -0
- package/src/solid-compat.tsx +347 -0
- package/src/solid-surfaces.tsx +1311 -0
- package/src/solid.tsx +82 -60
- package/src/store-core/index.ts +426 -0
- package/src/store.ts +12 -0
- package/src/svelte/SocialLinks.svelte +84 -0
- package/src/svelte/headless.ts +435 -0
- package/src/svelte/index.ts +9 -0
- package/src/vanilla.ts +367 -0
- package/src/verify-core/index.ts +288 -0
- package/src/verify.ts +8 -0
- package/src/vue/headless.ts +527 -0
- 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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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, "&")
|
|
110
|
+
.replace(/</g, "<")
|
|
111
|
+
.replace(/>/g, ">")
|
|
112
|
+
.replace(/"/g, """);
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAiBH;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAoC;IACjE,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9D,OAAO,MAAM;SACX,IAAI,EAAE;SACN,WAAW,EAAE;SACb,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC;SACzB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAC3B,CAAC;AAQD;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,OAAgC;IAEhC,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,IAAI,CAAC;QACJ,OAAO,MAAM,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC;IACb,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC/B,IAAgD,EAChD,UAAiC,EAAE;IAEnC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;IACtD,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC1D,OAAO,KAAK;SACV,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,YAAY,CAAC;SACjC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED,wEAAwE;AACxE,SAAS,UAAU,CAAC,KAAa;IAChC,OAAO,KAAK;SACV,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,gBAAgB,CAC/B,QAA8C,EAC9C,MAAe;IAEf,IAAI,CAAC,QAAQ,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IACzC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE;QACtC,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,MAAM;QACb,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,KAAK;KACf,CAAC,CAAC;AACJ,CAAC;AAWD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAClC,QAAuB,EACvB,UAAkC,EAAE;IAEpC,MAAM,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC;IAC3C,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,YAAY,CAAC;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,GAAG,CAAC;IACzC,MAAM,OAAO,GAAG,gBAAgB,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAE3D,MAAM,IAAI,GAAG,CAAC,KAA0B,EAAE,EAAE;QAC3C,MAAM,IAAI,GACT,KAAK,KAAK,QAAQ;YACjB,CAAC,CAAC,YAAY,CAAC,gBAAgB;YAC/B,CAAC,CAAC,YAAY,CAAC,gBAAgB,CAAC;QAClC,IAAI,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO;YAAE,OAAO,EAAE,CAAC;QAC9C,OAAO,CACN,YAAY,UAAU,CAAC,QAAQ,CAAC,8BAA8B;YAC9D,aAAa,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,kCAAkC,KAAK,MAAM;YAC1F,MAAM,CACN,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG;QACb,mBAAmB,UAAU,CAAC,SAAS,CAAC,oBAAoB;QAC5D,iCAAiC;QACjC,IAAI,CAAC,QAAQ,CAAC;QACd,YAAY,CAAC,UAAU,IAAI,EAAE;QAC7B,6BAA6B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO;QAC9D,OAAO;YACN,CAAC,CAAC,4CAA4C,UAAU,CAAC,OAAO,CAAC,MAAM;YACvE,CAAC,CAAC,EAAE;QACL,WAAW;QACX,6BAA6B,QAAQ,CAAC,WAAW,QAAQ;QACzD,iCAAiC;QACjC,YAAY,CAAC,UAAU,IAAI,EAAE;QAC7B,IAAI,CAAC,QAAQ,CAAC;QACd,WAAW;QACX,YAAY;KACZ,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAC3B,QAAuB,EACvB,UAAqD,EAAE;IAOvD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ;QAC7B,CAAC,CAAC,GAAG,QAAQ,CAAC,KAAK,MAAM,OAAO,CAAC,QAAQ,EAAE;QAC3C,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;IAClB,OAAO;QACN,KAAK;QACL,qEAAqE;QACrE,wEAAwE;QACxE,8DAA8D;QAC9D,WAAW,EAAE,OAAO,CAAC,QAAQ;YAC5B,CAAC,CAAC,OAAO,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,OAAO,CAAC,QAAQ,GAAG;YAChE,CAAC,CAAC,YAAY,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG;QAC9C,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS;YAClC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;QACpC,SAAS,EAAE;YACV,KAAK;YACL,IAAI,EAAE,SAAS;YACf,GAAG,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC;SACtE;KACD,CAAC;AACH,CAAC"}
|
|
@@ -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"}
|