@businessdash/sdk 0.9.60

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 (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +899 -0
  3. package/dist/adapters-revalidate.d.ts +44 -0
  4. package/dist/adapters-revalidate.d.ts.map +1 -0
  5. package/dist/adapters-revalidate.js +68 -0
  6. package/dist/adapters-revalidate.js.map +1 -0
  7. package/dist/alpha.d.ts +21 -0
  8. package/dist/alpha.d.ts.map +1 -0
  9. package/dist/alpha.js +21 -0
  10. package/dist/alpha.js.map +1 -0
  11. package/dist/analytics-core.d.ts +34 -0
  12. package/dist/analytics-core.d.ts.map +1 -0
  13. package/dist/analytics-core.js +138 -0
  14. package/dist/analytics-core.js.map +1 -0
  15. package/dist/analytics-server.d.ts +54 -0
  16. package/dist/analytics-server.d.ts.map +1 -0
  17. package/dist/analytics-server.js +78 -0
  18. package/dist/analytics-server.js.map +1 -0
  19. package/dist/attribution-logo.d.ts +2 -0
  20. package/dist/attribution-logo.d.ts.map +1 -0
  21. package/dist/attribution-logo.js +5 -0
  22. package/dist/attribution-logo.js.map +1 -0
  23. package/dist/auth-handler.d.ts +92 -0
  24. package/dist/auth-handler.d.ts.map +1 -0
  25. package/dist/auth-handler.js +277 -0
  26. package/dist/auth-handler.js.map +1 -0
  27. package/dist/biab-forms.css +484 -0
  28. package/dist/cli.d.ts +46 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +1824 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/client.d.ts +1681 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +2845 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/collections.d.ts +13 -0
  37. package/dist/collections.d.ts.map +1 -0
  38. package/dist/collections.js +13 -0
  39. package/dist/collections.js.map +1 -0
  40. package/dist/contracts.d.ts +14609 -0
  41. package/dist/contracts.d.ts.map +1 -0
  42. package/dist/contracts.js +2428 -0
  43. package/dist/contracts.js.map +1 -0
  44. package/dist/data-model-schema.d.ts +962 -0
  45. package/dist/data-model-schema.d.ts.map +1 -0
  46. package/dist/data-model-schema.js +2295 -0
  47. package/dist/data-model-schema.js.map +1 -0
  48. package/dist/distribution.d.ts +38 -0
  49. package/dist/distribution.d.ts.map +1 -0
  50. package/dist/distribution.js +61 -0
  51. package/dist/distribution.js.map +1 -0
  52. package/dist/element.d.ts +121 -0
  53. package/dist/element.d.ts.map +1 -0
  54. package/dist/element.js +276 -0
  55. package/dist/element.js.map +1 -0
  56. package/dist/enums.d.ts +25 -0
  57. package/dist/enums.d.ts.map +1 -0
  58. package/dist/enums.js +38 -0
  59. package/dist/enums.js.map +1 -0
  60. package/dist/errors.d.ts +195 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +188 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/forms-core/autocomplete.d.ts +37 -0
  65. package/dist/forms-core/autocomplete.d.ts.map +1 -0
  66. package/dist/forms-core/autocomplete.js +82 -0
  67. package/dist/forms-core/autocomplete.js.map +1 -0
  68. package/dist/forms-core/business-function.d.ts +61 -0
  69. package/dist/forms-core/business-function.d.ts.map +1 -0
  70. package/dist/forms-core/business-function.js +149 -0
  71. package/dist/forms-core/business-function.js.map +1 -0
  72. package/dist/forms-core/calculation.d.ts +72 -0
  73. package/dist/forms-core/calculation.d.ts.map +1 -0
  74. package/dist/forms-core/calculation.js +103 -0
  75. package/dist/forms-core/calculation.js.map +1 -0
  76. package/dist/forms-core/controller.d.ts +249 -0
  77. package/dist/forms-core/controller.d.ts.map +1 -0
  78. package/dist/forms-core/controller.js +590 -0
  79. package/dist/forms-core/controller.js.map +1 -0
  80. package/dist/forms-core/elements.d.ts +44 -0
  81. package/dist/forms-core/elements.d.ts.map +1 -0
  82. package/dist/forms-core/elements.js +68 -0
  83. package/dist/forms-core/elements.js.map +1 -0
  84. package/dist/forms-core/email-domains.d.ts +10 -0
  85. package/dist/forms-core/email-domains.d.ts.map +1 -0
  86. package/dist/forms-core/email-domains.js +69 -0
  87. package/dist/forms-core/email-domains.js.map +1 -0
  88. package/dist/forms-core/file-rules.d.ts +51 -0
  89. package/dist/forms-core/file-rules.d.ts.map +1 -0
  90. package/dist/forms-core/file-rules.js +105 -0
  91. package/dist/forms-core/file-rules.js.map +1 -0
  92. package/dist/forms-core/formula.d.ts +114 -0
  93. package/dist/forms-core/formula.d.ts.map +1 -0
  94. package/dist/forms-core/formula.js +1170 -0
  95. package/dist/forms-core/formula.js.map +1 -0
  96. package/dist/forms-core/index.d.ts +24 -0
  97. package/dist/forms-core/index.d.ts.map +1 -0
  98. package/dist/forms-core/index.js +28 -0
  99. package/dist/forms-core/index.js.map +1 -0
  100. package/dist/forms-core/units.d.ts +49 -0
  101. package/dist/forms-core/units.d.ts.map +1 -0
  102. package/dist/forms-core/units.js +255 -0
  103. package/dist/forms-core/units.js.map +1 -0
  104. package/dist/forms-core/us-states.d.ts +14 -0
  105. package/dist/forms-core/us-states.d.ts.map +1 -0
  106. package/dist/forms-core/us-states.js +66 -0
  107. package/dist/forms-core/us-states.js.map +1 -0
  108. package/dist/forms-core/validation.d.ts +266 -0
  109. package/dist/forms-core/validation.d.ts.map +1 -0
  110. package/dist/forms-core/validation.js +850 -0
  111. package/dist/forms-core/validation.js.map +1 -0
  112. package/dist/forms.d.ts +19 -0
  113. package/dist/forms.d.ts.map +1 -0
  114. package/dist/forms.js +29 -0
  115. package/dist/forms.js.map +1 -0
  116. package/dist/gallery-types.d.ts +32 -0
  117. package/dist/gallery-types.d.ts.map +1 -0
  118. package/dist/gallery-types.js +2 -0
  119. package/dist/gallery-types.js.map +1 -0
  120. package/dist/graphql.d.ts +97 -0
  121. package/dist/graphql.d.ts.map +1 -0
  122. package/dist/graphql.js +212 -0
  123. package/dist/graphql.js.map +1 -0
  124. package/dist/index.cjs +19448 -0
  125. package/dist/index.d.cts +19 -0
  126. package/dist/index.d.mts +19 -0
  127. package/dist/index.d.ts +19 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +20 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/marketing-schema.d.ts +216 -0
  132. package/dist/marketing-schema.d.ts.map +1 -0
  133. package/dist/marketing-schema.js +200 -0
  134. package/dist/marketing-schema.js.map +1 -0
  135. package/dist/mcp.d.ts +42 -0
  136. package/dist/mcp.d.ts.map +1 -0
  137. package/dist/mcp.js +87 -0
  138. package/dist/mcp.js.map +1 -0
  139. package/dist/native.d.ts +45 -0
  140. package/dist/native.d.ts.map +1 -0
  141. package/dist/native.js +43 -0
  142. package/dist/native.js.map +1 -0
  143. package/dist/next-revalidate.d.ts +43 -0
  144. package/dist/next-revalidate.d.ts.map +1 -0
  145. package/dist/next-revalidate.js +84 -0
  146. package/dist/next-revalidate.js.map +1 -0
  147. package/dist/parallel-pages.d.ts +102 -0
  148. package/dist/parallel-pages.d.ts.map +1 -0
  149. package/dist/parallel-pages.js +108 -0
  150. package/dist/parallel-pages.js.map +1 -0
  151. package/dist/payment-icons.d.ts +66 -0
  152. package/dist/payment-icons.d.ts.map +1 -0
  153. package/dist/payment-icons.js +160 -0
  154. package/dist/payment-icons.js.map +1 -0
  155. package/dist/proxy.d.ts +67 -0
  156. package/dist/proxy.d.ts.map +1 -0
  157. package/dist/proxy.js +97 -0
  158. package/dist/proxy.js.map +1 -0
  159. package/dist/react-analytics.d.ts +39 -0
  160. package/dist/react-analytics.d.ts.map +1 -0
  161. package/dist/react-analytics.js +59 -0
  162. package/dist/react-analytics.js.map +1 -0
  163. package/dist/react-attribution.d.ts +99 -0
  164. package/dist/react-attribution.d.ts.map +1 -0
  165. package/dist/react-attribution.js +52 -0
  166. package/dist/react-attribution.js.map +1 -0
  167. package/dist/react-marketing.d.ts +181 -0
  168. package/dist/react-marketing.d.ts.map +1 -0
  169. package/dist/react-marketing.js +215 -0
  170. package/dist/react-marketing.js.map +1 -0
  171. package/dist/react.d.ts +727 -0
  172. package/dist/react.d.ts.map +1 -0
  173. package/dist/react.js +2418 -0
  174. package/dist/react.js.map +1 -0
  175. package/dist/reserved-paths.d.ts +24 -0
  176. package/dist/reserved-paths.d.ts.map +1 -0
  177. package/dist/reserved-paths.js +60 -0
  178. package/dist/reserved-paths.js.map +1 -0
  179. package/dist/revalidate-verify.d.ts +30 -0
  180. package/dist/revalidate-verify.d.ts.map +1 -0
  181. package/dist/revalidate-verify.js +74 -0
  182. package/dist/revalidate-verify.js.map +1 -0
  183. package/dist/sdk.d.ts +678 -0
  184. package/dist/sdk.d.ts.map +1 -0
  185. package/dist/sdk.js +801 -0
  186. package/dist/sdk.js.map +1 -0
  187. package/dist/seo.d.ts +218 -0
  188. package/dist/seo.d.ts.map +1 -0
  189. package/dist/seo.js +309 -0
  190. package/dist/seo.js.map +1 -0
  191. package/dist/socials.d.ts +47 -0
  192. package/dist/socials.d.ts.map +1 -0
  193. package/dist/socials.js +172 -0
  194. package/dist/socials.js.map +1 -0
  195. package/dist/static.d.ts +14 -0
  196. package/dist/static.d.ts.map +1 -0
  197. package/dist/static.js +14 -0
  198. package/dist/static.js.map +1 -0
  199. package/dist/url-pattern.d.ts +47 -0
  200. package/dist/url-pattern.d.ts.map +1 -0
  201. package/dist/url-pattern.js +124 -0
  202. package/dist/url-pattern.js.map +1 -0
  203. package/dist/vanilla.d.ts +77 -0
  204. package/dist/vanilla.d.ts.map +1 -0
  205. package/dist/vanilla.js +903 -0
  206. package/dist/vanilla.js.map +1 -0
  207. package/openapi.json +13779 -0
  208. package/package.json +204 -0
  209. package/src/adapters-revalidate.ts +88 -0
  210. package/src/alpha.ts +89 -0
  211. package/src/analytics-core.ts +175 -0
  212. package/src/analytics-server.ts +99 -0
  213. package/src/angular/biab-form.component.ts +1443 -0
  214. package/src/angular/index.ts +26 -0
  215. package/src/attribution-logo.ts +5 -0
  216. package/src/auth-handler.ts +352 -0
  217. package/src/cli.ts +2517 -0
  218. package/src/client.ts +3578 -0
  219. package/src/collections.ts +101 -0
  220. package/src/contracts.ts +3390 -0
  221. package/src/data-model-schema.ts +4042 -0
  222. package/src/distribution.ts +73 -0
  223. package/src/element.ts +321 -0
  224. package/src/enums.ts +65 -0
  225. package/src/errors.ts +338 -0
  226. package/src/forms-core/autocomplete.ts +97 -0
  227. package/src/forms-core/business-function.ts +209 -0
  228. package/src/forms-core/calculation.ts +167 -0
  229. package/src/forms-core/controller.ts +922 -0
  230. package/src/forms-core/elements.ts +86 -0
  231. package/src/forms-core/email-domains.ts +68 -0
  232. package/src/forms-core/file-rules.ts +123 -0
  233. package/src/forms-core/formula.ts +1339 -0
  234. package/src/forms-core/index.ts +122 -0
  235. package/src/forms-core/units.ts +308 -0
  236. package/src/forms-core/us-states.ts +68 -0
  237. package/src/forms-core/validation.ts +1037 -0
  238. package/src/forms.ts +63 -0
  239. package/src/gallery-types.ts +31 -0
  240. package/src/graphql.ts +295 -0
  241. package/src/index.ts +505 -0
  242. package/src/marketing-schema.ts +395 -0
  243. package/src/mcp.ts +108 -0
  244. package/src/native.ts +73 -0
  245. package/src/next-revalidate.ts +124 -0
  246. package/src/parallel-pages.ts +192 -0
  247. package/src/payment-icons.ts +205 -0
  248. package/src/proxy.ts +173 -0
  249. package/src/qwik.tsx +1234 -0
  250. package/src/react-analytics.tsx +76 -0
  251. package/src/react-attribution.tsx +192 -0
  252. package/src/react-marketing.tsx +338 -0
  253. package/src/react.tsx +5495 -0
  254. package/src/reserved-paths.ts +65 -0
  255. package/src/revalidate-verify.ts +98 -0
  256. package/src/sdk.ts +1671 -0
  257. package/src/seo.ts +488 -0
  258. package/src/socials.ts +230 -0
  259. package/src/solid.tsx +1466 -0
  260. package/src/static.ts +21 -0
  261. package/src/styles/biab-forms.css +484 -0
  262. package/src/svelte/BiabForm.svelte +1069 -0
  263. package/src/svelte/context.ts +57 -0
  264. package/src/svelte/index.ts +79 -0
  265. package/src/url-pattern.ts +153 -0
  266. package/src/vanilla.ts +1290 -0
  267. package/src/vue/BiabForm.vue +1538 -0
  268. package/src/vue/index.ts +119 -0
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Consumer-facing builder for the schema-driven marketing-page flow.
3
+ *
4
+ * The consumer's codebase is the source of truth for the content model.
5
+ * `defineSiteMarketingSchema()` lets them declare each section as a typed
6
+ * zod schema + UI metadata, and produces an artifact the SDK CLI uploads
7
+ * to BIAB:
8
+ *
9
+ * const marketing = defineSiteMarketingSchema({
10
+ * brandTokens: ["company.name", "company.phone", "company.email"],
11
+ * sections: {
12
+ * hero: section({
13
+ * label: "Hero",
14
+ * schema: z.object({
15
+ * eyebrow: z.string().optional(),
16
+ * title: z.string(),
17
+ * body: z.string().optional(),
18
+ * primaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
19
+ * }),
20
+ * defaults: { title: "Built right, the first time." },
21
+ * ui: {
22
+ * body: { widget: "richtext" },
23
+ * primaryCta: { collapsedByDefault: false },
24
+ * },
25
+ * }),
26
+ *
27
+ * gallery: section({
28
+ * label: "Gallery",
29
+ * schema: z.object({
30
+ * title: z.string().optional(),
31
+ * layout: z.enum(["grid", "masonry", "carousel"]).default("grid"),
32
+ * maxItems: z.number().int().positive().default(12),
33
+ * }),
34
+ * dataSource: { kind: "project-media", configureHref: "/dashboard/projects" },
35
+ * }),
36
+ * },
37
+ * });
38
+ *
39
+ * Then in the consumer's pages:
40
+ *
41
+ * const { sections } = await marketing.client.getPageBundle({ pageKey, locale });
42
+ * const hero = marketing.parseSection("hero", sections.hero); // fully typed
43
+ *
44
+ * `marketing.toJSONSchema()` produces the body the CLI uploads. The same
45
+ * SHA-256 canonicalization the server uses lives here too, so the CLI's
46
+ * claimed checksum matches what the server recomputes.
47
+ */
48
+
49
+ import type { ZodTypeAny, z } from "zod";
50
+ import { zodToJsonSchema } from "zod-to-json-schema";
51
+
52
+ // ── UI hint types ────────────────────────────────────────────────────
53
+
54
+ /**
55
+ * The fixed allowlist of widget kinds. The dashboard form renderer + the
56
+ * server's schema validator only accept these — anything else is rejected
57
+ * at upload time. Keep this in lockstep with the server's
58
+ * `ALLOWED_FIELD_KINDS` constant in `src/server/site-data/marketing-schema.ts`.
59
+ */
60
+ export const SITE_MARKETING_FIELD_WIDGETS = [
61
+ "text",
62
+ "textarea",
63
+ "richtext",
64
+ "url",
65
+ "email",
66
+ "image",
67
+ "number",
68
+ "integer",
69
+ "boolean",
70
+ "select",
71
+ "multiselect",
72
+ "color",
73
+ "date",
74
+ "markdown",
75
+ "array",
76
+ "object",
77
+ "dataSource",
78
+ ] as const;
79
+
80
+ export type SiteMarketingFieldWidget =
81
+ (typeof SITE_MARKETING_FIELD_WIDGETS)[number];
82
+
83
+ export const SITE_MARKETING_DATA_SOURCE_KINDS = [
84
+ "project-media",
85
+ "blog-posts",
86
+ "services-products",
87
+ ] as const;
88
+
89
+ export type SiteMarketingDataSourceKind =
90
+ (typeof SITE_MARKETING_DATA_SOURCE_KINDS)[number];
91
+
92
+ /**
93
+ * UI hints overlaid on a section's zod schema. Each key is a dotted
94
+ * path into the schema (e.g. `"primaryCta.label"`); the corresponding
95
+ * value tells the dashboard renderer which widget / options to use.
96
+ *
97
+ * `widget` is optional — the renderer infers a sensible default from
98
+ * the zod type (string → "text", `z.string().url()` → "url",
99
+ * `z.array(...)` → "array", etc.). Override only when the inferred
100
+ * default isn't right (e.g. a string that should render as "richtext").
101
+ */
102
+ export type SiteMarketingFieldUI = {
103
+ widget?: SiteMarketingFieldWidget;
104
+ label?: string;
105
+ helpText?: string;
106
+ placeholder?: string;
107
+ enum?: readonly string[];
108
+ collapsedByDefault?: boolean;
109
+ rows?: number;
110
+ min?: number;
111
+ max?: number;
112
+ };
113
+
114
+ export type SiteMarketingDataSourceMarker = {
115
+ kind: SiteMarketingDataSourceKind;
116
+ /** Where the dashboard's "Configure data source →" button links to. */
117
+ configureHref: string;
118
+ /** Optional human-readable note shown beside the configure CTA. */
119
+ helpText?: string;
120
+ };
121
+
122
+ // ── Section + schema builders ────────────────────────────────────────
123
+
124
+ export type SiteMarketingSectionDef<S extends ZodTypeAny = ZodTypeAny> = {
125
+ label: string;
126
+ description?: string;
127
+ icon?: string;
128
+ schema: S;
129
+ defaults?: Partial<z.input<S>>;
130
+ ui?: Record<string, SiteMarketingFieldUI>;
131
+ /** When set, the dashboard renders a "Configure data source →" tile
132
+ * instead of editing the row as a regular form. Items come from the
133
+ * named BIAB module at render time. */
134
+ dataSource?: SiteMarketingDataSourceMarker;
135
+ };
136
+
137
+ /**
138
+ * Helper that exists solely so consumers can write
139
+ * section({ ... })
140
+ * instead of having to specify the zod-typed generic. Inference is
141
+ * preserved end-to-end.
142
+ */
143
+ export function section<S extends ZodTypeAny>(
144
+ def: SiteMarketingSectionDef<S>,
145
+ ): SiteMarketingSectionDef<S> {
146
+ return def;
147
+ }
148
+
149
+ /**
150
+ * Optional `contentSync` configuration consumed by the
151
+ * `biab-dev sync-content` CLI command. Lets the consumer declare:
152
+ * - Where local JSON content lives on disk
153
+ * - Which directories map to which BIAB pageKeys (explicit) or to
154
+ * follow a glob convention (auto-derived)
155
+ * - Which filename → sectionKey aliases are needed (e.g.
156
+ * `marquee.json` actually means `trustMarquee`)
157
+ * - Which locales to sync (defaults to ["en"])
158
+ *
159
+ * The default layout is `<rootDir>/<locale>/<page>/<section>.json`,
160
+ * which matches the convention emitted by the `@businessdash/sdk` content
161
+ * loader (`getContent`). For trees that wrap pages in an intermediate
162
+ * segment (e.g. `<rootDir>/<locale>/pages/<page>/`) or that keep
163
+ * cross-page content in a sibling dir (e.g. `<locale>/shared/`),
164
+ * supply an explicit `pages` array.
165
+ */
166
+ export type SiteMarketingContentSyncConfig = {
167
+ /** Directory (relative to `biab.config.*`) where JSON content trees live. */
168
+ rootDir: string;
169
+ /**
170
+ * Explicit page directories. Each entry maps a filesystem path
171
+ * (relative to `rootDir`, with optional `{locale}` placeholder) to
172
+ * a `pageKey` in BIAB. When omitted, the CLI auto-discovers using
173
+ * `<rootDir>/<locale>/*` and uses the leaf dir name as the pageKey.
174
+ *
175
+ * Example for a `<locale>/pages/<name>/` + `<locale>/shared/` layout:
176
+ * pages: [
177
+ * { dir: "{locale}/shared", pageKey: "shared" },
178
+ * { dir: "{locale}/pages/home", pageKey: "home" },
179
+ * { dir: "{locale}/pages/about", pageKey: "about" },
180
+ * ]
181
+ */
182
+ pages?: ReadonlyArray<{ dir: string; pageKey: string }>;
183
+ /**
184
+ * Maps a file basename (without `.json`) → schema sectionKey.
185
+ * Supports both:
186
+ * - `"<basename>"` — global alias (e.g. `"marquee": "trustMarquee"`)
187
+ * - `"<pageKey>/<basename>"` — scoped alias (e.g.
188
+ * `"about/page": "aboutPage"`). Scoped takes precedence.
189
+ */
190
+ sectionAliases?: Record<string, string>;
191
+ /**
192
+ * Locales to sync. Each substitutes for `{locale}` in `pages[].dir`.
193
+ * Defaults to `["en"]`.
194
+ */
195
+ locales?: readonly string[];
196
+ /**
197
+ * Section keys to skip — typically the ones whose data comes from
198
+ * managed sources (gallery from media uploads, blogFeed from the
199
+ * blog editor, pricing from services/products). The CLI will still
200
+ * warn if it sees a file matching one of these, but won't upload.
201
+ */
202
+ skipSections?: readonly string[];
203
+ };
204
+
205
+ export type SiteMarketingSchemaInput<
206
+ Sections extends Record<string, SiteMarketingSectionDef> = Record<
207
+ string,
208
+ SiteMarketingSectionDef
209
+ >,
210
+ > = {
211
+ sections: Sections;
212
+ /**
213
+ * Declared brand-token keys (e.g. `["company.name", "company.phone"]`).
214
+ * The dashboard renders a token-resolution preview that highlights
215
+ * any token referenced in a section value but missing from the
216
+ * site's `brandTokens` map.
217
+ */
218
+ brandTokens?: readonly string[];
219
+ /** Free-form metadata shipped alongside the schema (your build SHA, source repo URL, etc.). */
220
+ metadata?: Record<string, string | number | boolean>;
221
+ /** Optional config for `biab-dev sync-content`. */
222
+ contentSync?: SiteMarketingContentSyncConfig;
223
+ };
224
+
225
+ // ── Canonicalization + checksum ──────────────────────────────────────
226
+
227
+ /**
228
+ * Sort keys recursively before serialization so two functionally
229
+ * identical schemas hash to the same SHA-256. Mirrors the server's
230
+ * canonicalization byte-for-byte — the CLI's claimed checksum has to
231
+ * match what the server recomputes.
232
+ */
233
+ function canonicalize(value: unknown): unknown {
234
+ if (value === null || typeof value !== "object") return value;
235
+ if (Array.isArray(value)) return value.map(canonicalize);
236
+ const out: Record<string, unknown> = {};
237
+ for (const k of Object.keys(value as Record<string, unknown>).sort()) {
238
+ if (k === "__proto__" || k === "constructor" || k === "prototype") {
239
+ continue;
240
+ }
241
+ out[k] = canonicalize((value as Record<string, unknown>)[k]);
242
+ }
243
+ return out;
244
+ }
245
+
246
+ /**
247
+ * Web Crypto `crypto.subtle.digest` is async, so this returns a promise.
248
+ * Node ≥ 18 ships `globalThis.crypto.subtle`. The CLI awaits this once
249
+ * before POSTing — no per-request cost.
250
+ */
251
+ async function sha256Hex(input: string): Promise<string> {
252
+ const buf = new TextEncoder().encode(input);
253
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", buf);
254
+ return Array.from(new Uint8Array(digest))
255
+ .map((b) => b.toString(16).padStart(2, "0"))
256
+ .join("");
257
+ }
258
+
259
+ export async function computeSiteMarketingSchemaChecksum(
260
+ schemaBody: Record<string, unknown>,
261
+ ): Promise<string> {
262
+ return sha256Hex(JSON.stringify(canonicalize(schemaBody)));
263
+ }
264
+
265
+ // ── Build the JSON-Schema artifact ──────────────────────────────────
266
+
267
+ function uiTreeFromZod(
268
+ uiOverrides: Record<string, SiteMarketingFieldUI> | undefined,
269
+ ): Record<string, SiteMarketingFieldUI> {
270
+ if (!uiOverrides) return {};
271
+ const out: Record<string, SiteMarketingFieldUI> = {};
272
+ for (const [path, hint] of Object.entries(uiOverrides)) {
273
+ if (
274
+ path === "__proto__" ||
275
+ path === "constructor" ||
276
+ path === "prototype"
277
+ ) {
278
+ continue;
279
+ }
280
+ out[path] = hint;
281
+ }
282
+ return out;
283
+ }
284
+
285
+ export type SiteMarketingSchemaArtifact = {
286
+ $schema: "https://biab.app/schemas/marketing/v1";
287
+ version: 1;
288
+ brandTokens: readonly string[];
289
+ metadata: Record<string, string | number | boolean>;
290
+ sections: Record<
291
+ string,
292
+ {
293
+ label: string;
294
+ description?: string;
295
+ icon?: string;
296
+ schema: Record<string, unknown>;
297
+ defaults?: unknown;
298
+ ui?: Record<string, SiteMarketingFieldUI>;
299
+ dataSource?: SiteMarketingDataSourceMarker;
300
+ }
301
+ >;
302
+ };
303
+
304
+ export type SiteMarketingClientShape<
305
+ Sections extends Record<string, SiteMarketingSectionDef>,
306
+ > = {
307
+ /** Reusable section schemas keyed by section key — handy for runtime parsing. */
308
+ readonly sections: Sections;
309
+ /** Optional file-tree → BIAB mapping for the `sync-content` CLI. */
310
+ readonly contentSync: SiteMarketingContentSyncConfig | undefined;
311
+ /** JSON-Schema artifact ready to upload to BIAB. */
312
+ toJSONSchema(): SiteMarketingSchemaArtifact;
313
+ /** SHA-256 hex digest of the canonicalized JSON-Schema artifact. */
314
+ checksum(): Promise<string>;
315
+ /**
316
+ * Type-safe parser for a value returned by `getPageBundle`'s
317
+ * `sections[key]` payload. Returns `null` when the section is
318
+ * absent or its `ok: false`.
319
+ */
320
+ parseSection<K extends keyof Sections>(
321
+ key: K,
322
+ entry: { ok: true; data: unknown } | { ok: false } | null | undefined,
323
+ ): z.output<Sections[K]["schema"]> | null;
324
+ };
325
+
326
+ export function defineSiteMarketingSchema<
327
+ Sections extends Record<string, SiteMarketingSectionDef>,
328
+ >(
329
+ input: SiteMarketingSchemaInput<Sections>,
330
+ ): SiteMarketingClientShape<Sections> {
331
+ const { sections, brandTokens = [], metadata = {}, contentSync } = input;
332
+
333
+ let cachedArtifact: SiteMarketingSchemaArtifact | null = null;
334
+
335
+ function build(): SiteMarketingSchemaArtifact {
336
+ if (cachedArtifact) return cachedArtifact;
337
+ const out: SiteMarketingSchemaArtifact = {
338
+ $schema: "https://biab.app/schemas/marketing/v1",
339
+ version: 1,
340
+ brandTokens: [...brandTokens],
341
+ metadata: { ...metadata },
342
+ sections: {},
343
+ };
344
+ for (const [key, def] of Object.entries(sections) as [
345
+ string,
346
+ SiteMarketingSectionDef,
347
+ ][]) {
348
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
349
+ continue;
350
+ }
351
+ const jsonSchema = zodToJsonSchema(def.schema, {
352
+ $refStrategy: "none",
353
+ target: "jsonSchema7",
354
+ }) as Record<string, unknown>;
355
+ const sectionEntry: SiteMarketingSchemaArtifact["sections"][string] = {
356
+ label: def.label,
357
+ schema: jsonSchema,
358
+ ui: uiTreeFromZod(def.ui),
359
+ };
360
+ if (def.description !== undefined)
361
+ sectionEntry.description = def.description;
362
+ if (def.icon !== undefined) sectionEntry.icon = def.icon;
363
+ if (def.defaults !== undefined) sectionEntry.defaults = def.defaults;
364
+ if (def.dataSource !== undefined)
365
+ sectionEntry.dataSource = def.dataSource;
366
+ out.sections[key] = sectionEntry;
367
+ }
368
+ cachedArtifact = out;
369
+ return out;
370
+ }
371
+
372
+ return {
373
+ sections,
374
+ contentSync,
375
+ toJSONSchema: build,
376
+ async checksum() {
377
+ const artifact = build();
378
+ return computeSiteMarketingSchemaChecksum(
379
+ artifact as unknown as Record<string, unknown>,
380
+ );
381
+ },
382
+ parseSection<K extends keyof Sections>(
383
+ key: K,
384
+ entry: { ok: true; data: unknown } | { ok: false } | null | undefined,
385
+ ): z.output<Sections[K]["schema"]> | null {
386
+ if (!entry || entry.ok === false) return null;
387
+ const def = sections[key];
388
+ if (!def) return null;
389
+ const parsed = def.schema.safeParse(entry.data);
390
+ return parsed.success
391
+ ? (parsed.data as z.output<Sections[K]["schema"]>)
392
+ : null;
393
+ },
394
+ };
395
+ }
package/src/mcp.ts ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Serve your business's MCP connector from YOUR domain.
3
+ *
4
+ * The platform's host-resolved `/api/mcp` only exists on sites the platform
5
+ * serves. If you host your own app with the SDK, mount these two handlers
6
+ * and your domain gains the same connector surface — so the URL an org
7
+ * hands to Claude / ChatGPT / Gemini is their own site, exactly like the
8
+ * docs promise:
9
+ *
10
+ * ```ts
11
+ * // app/api/mcp/route.ts
12
+ * import { mcpHandler } from '@businessdash/sdk/mcp'
13
+ * export const { POST, GET } = mcpHandler({
14
+ * siteId: process.env.BIAB_SITE_ID!,
15
+ * baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // https://www.biab.app
16
+ * })
17
+ *
18
+ * // app/.well-known/mcp.json/route.ts
19
+ * import { mcpManifestHandler } from '@businessdash/sdk/mcp'
20
+ * export const GET = mcpManifestHandler({
21
+ * siteId: process.env.BIAB_SITE_ID!,
22
+ * baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!,
23
+ * })
24
+ * ```
25
+ *
26
+ * Thin by design: the body is forwarded verbatim and the platform still
27
+ * enforces the org's MCP opt-in and per-tool write gates — a proxy can't
28
+ * widen anything.
29
+ */
30
+
31
+ export type McpProxyOptions = {
32
+ siteId: string;
33
+ /** Platform origin, e.g. "https://www.biab.app". */
34
+ baseUrl: string;
35
+ };
36
+
37
+ function upstream(options: McpProxyOptions): string {
38
+ return `${options.baseUrl.replace(/\/$/, "")}/api/public/mcp/${encodeURIComponent(
39
+ options.siteId,
40
+ )}`;
41
+ }
42
+
43
+ /** POST proxies JSON-RPC to the platform; GET mirrors the spec's 405. */
44
+ export function mcpHandler(options: McpProxyOptions): {
45
+ POST: (request: Request) => Promise<Response>;
46
+ GET: () => Promise<Response>;
47
+ } {
48
+ const target = upstream(options);
49
+ return {
50
+ POST: async (request: Request) => {
51
+ try {
52
+ const res = await fetch(target, {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ Accept: request.headers.get("accept") ?? "application/json",
57
+ },
58
+ body: await request.text(),
59
+ });
60
+ const text = await res.text();
61
+ return new Response(text.length > 0 ? text : null, {
62
+ status: res.status,
63
+ headers: {
64
+ "Content-Type":
65
+ res.headers.get("content-type") ?? "application/json",
66
+ "Cache-Control": "no-store",
67
+ },
68
+ });
69
+ } catch {
70
+ return Response.json(
71
+ {
72
+ jsonrpc: "2.0",
73
+ id: null,
74
+ error: { code: -32603, message: "Upstream unavailable" },
75
+ },
76
+ { status: 502 },
77
+ );
78
+ }
79
+ },
80
+ GET: async () =>
81
+ new Response(null, { status: 405, headers: { Allow: "POST" } }),
82
+ };
83
+ }
84
+
85
+ /** Serves `.well-known/mcp.json` with the endpoint URL pointing at YOUR domain. */
86
+ export function mcpManifestHandler(
87
+ options: McpProxyOptions,
88
+ ): (request: Request) => Promise<Response> {
89
+ const target = `${upstream(options)}/manifest`;
90
+ return async (request: Request) => {
91
+ try {
92
+ const origin = new URL(request.url).origin;
93
+ const res = await fetch(
94
+ `${target}?origin=${encodeURIComponent(origin)}`,
95
+ { next: { revalidate: 300 } } as RequestInit,
96
+ );
97
+ return new Response(await res.text(), {
98
+ status: res.status,
99
+ headers: {
100
+ "Content-Type": "application/json",
101
+ "Cache-Control": "public, max-age=300",
102
+ },
103
+ });
104
+ } catch {
105
+ return Response.json({ error: "upstream_unavailable" }, { status: 502 });
106
+ }
107
+ };
108
+ }
package/src/native.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * `@businessdash/sdk/native` — the entry point for React Native / Expo.
3
+ *
4
+ * ## Why this exists
5
+ *
6
+ * The root export (`.`) re-exports `createAuthHandler` and `getTenantSession`
7
+ * from `auth-handler.ts`, which calls `Buffer.from(state, "base64url")`.
8
+ * `Buffer` is a Node global that React Native does not provide, and Metro does
9
+ * not polyfill it. The call sits inside a function rather than at module
10
+ * scope, so importing the root *appears* to work — right up until a sign-in
11
+ * callback is decoded and it throws `ReferenceError: Buffer is not defined` in
12
+ * production.
13
+ *
14
+ * That whole module is server-side by design anyway: an auth handler is a
15
+ * thing a tenant's SERVER mounts to receive an OAuth callback and set a
16
+ * cookie. An app has neither. Same for `proxy.ts` (a same-origin proxy route)
17
+ * and `revalidate-verify.ts` (`node:crypto`).
18
+ *
19
+ * So this entry is the data layer with the server-only surface removed. It is
20
+ * additive — nothing here changes what `.` exports.
21
+ *
22
+ * ## What a native app does instead
23
+ *
24
+ * - **Auth** — drive the hosted flow directly: `POST auth/start`, open the URL
25
+ * in a web-auth session, then `POST auth/exchange` with the `code` and
26
+ * `state` from your deep-link callback. `client.auth` has all three.
27
+ * - **Forms** — `<biab-form>` is a DOM custom element with no native
28
+ * counterpart. Read the schema and render it with your own components.
29
+ * - **Revalidation** — irrelevant. The webhook targets a server that holds a
30
+ * cache; an app just re-fetches.
31
+ *
32
+ * ## Metro
33
+ *
34
+ * Subpath exports (`@businessdash/sdk/native`) need Metro's package-exports
35
+ * support. Expo SDK 51+ enables it by default; on bare React Native set
36
+ * `unstable_enablePackageExports = true` in `metro.config.js`. Without it the
37
+ * import fails to resolve at all, with an error that looks like a missing
38
+ * dependency.
39
+ */
40
+
41
+ export type { BiabDevClientOptions, FetchLike } from "./client.js";
42
+ export {
43
+ BiabDevClient,
44
+ createBiabDevClient,
45
+ } from "./client.js";
46
+ export {
47
+ accessRejectionFromBody,
48
+ BiabAccessRejectedError,
49
+ BiabApiError,
50
+ BiabFeatureNotEntitledError,
51
+ BiabPaymentLapsedError,
52
+ type BiabPlanGateBody,
53
+ BiabServiceSuspendedError,
54
+ isBiabPlanGateBody,
55
+ type UnwrappedAccess,
56
+ type UnwrappedEntitlement,
57
+ } from "./errors.js";
58
+ export type {
59
+ BiabClient,
60
+ CreateBiabClientOptions,
61
+ FormFieldDef,
62
+ FormRenderSettings,
63
+ FormSchema,
64
+ FormSubmissionData,
65
+ FormSubmitResult,
66
+ FormValidationIssue,
67
+ GalleryResource,
68
+ SchedulingBookingResult,
69
+ SchedulingEventType,
70
+ SchedulingResource,
71
+ SchedulingSlot,
72
+ } from "./sdk.js";
73
+ export { createBiabClient } from "./sdk.js";
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Drop-in Next.js route handler for BIAB revalidation webhooks.
3
+ *
4
+ * Consumer mounts this with literally one line:
5
+ *
6
+ * // app/api/biab/revalidate/route.ts
7
+ * export { POST } from "@businessdash/sdk/next/revalidate";
8
+ *
9
+ * That's it. The handler:
10
+ *
11
+ * 1. Reads the shared secret from `BIAB_REVALIDATION_SECRET`
12
+ * (override via `createRevalidateHandler({ secret })` if env
13
+ * isn't available — see below).
14
+ * 2. Verifies the `X-Biab-Signature: t=<unix>,v1=<hex>` header.
15
+ * Constant-time hash compare, ±5 minute replay window.
16
+ * 3. Calls `revalidateTag(tag, { expire: 0 })` for each tag in
17
+ * the payload.
18
+ * 4. Returns `{ ok: true, revalidated: string[] }`.
19
+ *
20
+ * For non-Next consumers, import `verifyAndExtractTags` directly
21
+ * from `@businessdash/sdk/adapters/revalidate` and call your framework's
22
+ * invalidation primitive (CDN purge, KV delete, static rebuild
23
+ * trigger). The verification logic is framework-agnostic and lives
24
+ * in a separate file so non-Next bundlers don't transitively pick
25
+ * up `next/cache`.
26
+ */
27
+
28
+ import {
29
+ type BiabPublishPayload,
30
+ type VerifyOptions,
31
+ type VerifyResult,
32
+ verifyAndExtractTags,
33
+ } from "./revalidate-verify.js";
34
+
35
+ // Re-export for backwards compatibility — callers that previously
36
+ // imported these from `@businessdash/sdk/next/revalidate` still work.
37
+ export {
38
+ type BiabPublishPayload,
39
+ type VerifyOptions,
40
+ type VerifyResult,
41
+ verifyAndExtractTags,
42
+ };
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Next.js route handler
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export interface CreateRevalidateHandlerOptions {
49
+ /**
50
+ * Override the secret instead of reading from
51
+ * `BIAB_REVALIDATION_SECRET`. Useful in tests or when the
52
+ * consumer manages secrets in a non-standard place.
53
+ */
54
+ secret?: string;
55
+ }
56
+
57
+ type NextRevalidateTagFn = (
58
+ tag: string,
59
+ profile: string | { expire?: number },
60
+ ) => void;
61
+
62
+ async function callRevalidateTag(tags: string[]): Promise<string[]> {
63
+ // Lazy-import so the SDK is usable outside Next without dragging
64
+ // `next/cache` into the bundle. The module name lives in a variable
65
+ // so TypeScript doesn't try to type-check the missing dep at build
66
+ // time — the import is purely runtime.
67
+ const modName = "next/cache";
68
+ const mod: { revalidateTag?: NextRevalidateTagFn } | null = await import(
69
+ /* @vite-ignore */ /* webpackIgnore: true */ modName
70
+ ).catch(() => null);
71
+ if (!mod || typeof mod.revalidateTag !== "function") {
72
+ return [];
73
+ }
74
+ const fn = mod.revalidateTag;
75
+ const revalidated: string[] = [];
76
+ for (const tag of tags) {
77
+ try {
78
+ fn(tag, { expire: 0 });
79
+ revalidated.push(tag);
80
+ } catch {
81
+ /* outside a Next request scope — caller may retry */
82
+ }
83
+ }
84
+ return revalidated;
85
+ }
86
+
87
+ export function createRevalidateHandler(
88
+ opts: CreateRevalidateHandlerOptions = {},
89
+ ) {
90
+ return async function POST(request: Request): Promise<Response> {
91
+ const secret = opts.secret ?? process.env["BIAB_REVALIDATION_SECRET"];
92
+ if (!secret) {
93
+ return Response.json(
94
+ { ok: false, reason: "missing_revalidation_secret_env" },
95
+ { status: 500 },
96
+ );
97
+ }
98
+ const rawBody = await request.text();
99
+ const sig = request.headers.get("x-biab-signature");
100
+ const verified = verifyAndExtractTags({
101
+ secret,
102
+ rawBody,
103
+ signatureHeader: sig,
104
+ });
105
+ if (!verified.ok || !verified.payload) {
106
+ return Response.json(
107
+ { ok: false, reason: verified.reason ?? "verify_failed" },
108
+ { status: 401 },
109
+ );
110
+ }
111
+ const revalidated = await callRevalidateTag(verified.payload.tags);
112
+ return Response.json({
113
+ ok: true,
114
+ revalidated,
115
+ orgId: verified.payload.orgId,
116
+ });
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Default `POST` handler the consumer re-exports. Reads the secret
122
+ * from `BIAB_REVALIDATION_SECRET` env automatically.
123
+ */
124
+ export const POST = createRevalidateHandler();