@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.
- package/LICENSE +21 -0
- package/README.md +899 -0
- package/dist/adapters-revalidate.d.ts +44 -0
- package/dist/adapters-revalidate.d.ts.map +1 -0
- package/dist/adapters-revalidate.js +68 -0
- package/dist/adapters-revalidate.js.map +1 -0
- package/dist/alpha.d.ts +21 -0
- package/dist/alpha.d.ts.map +1 -0
- package/dist/alpha.js +21 -0
- package/dist/alpha.js.map +1 -0
- package/dist/analytics-core.d.ts +34 -0
- package/dist/analytics-core.d.ts.map +1 -0
- package/dist/analytics-core.js +138 -0
- package/dist/analytics-core.js.map +1 -0
- package/dist/analytics-server.d.ts +54 -0
- package/dist/analytics-server.d.ts.map +1 -0
- package/dist/analytics-server.js +78 -0
- package/dist/analytics-server.js.map +1 -0
- package/dist/attribution-logo.d.ts +2 -0
- package/dist/attribution-logo.d.ts.map +1 -0
- package/dist/attribution-logo.js +5 -0
- package/dist/attribution-logo.js.map +1 -0
- package/dist/auth-handler.d.ts +92 -0
- package/dist/auth-handler.d.ts.map +1 -0
- package/dist/auth-handler.js +277 -0
- package/dist/auth-handler.js.map +1 -0
- package/dist/biab-forms.css +484 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1824 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +1681 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2845 -0
- package/dist/client.js.map +1 -0
- package/dist/collections.d.ts +13 -0
- package/dist/collections.d.ts.map +1 -0
- package/dist/collections.js +13 -0
- package/dist/collections.js.map +1 -0
- package/dist/contracts.d.ts +14609 -0
- package/dist/contracts.d.ts.map +1 -0
- package/dist/contracts.js +2428 -0
- package/dist/contracts.js.map +1 -0
- package/dist/data-model-schema.d.ts +962 -0
- package/dist/data-model-schema.d.ts.map +1 -0
- package/dist/data-model-schema.js +2295 -0
- package/dist/data-model-schema.js.map +1 -0
- package/dist/distribution.d.ts +38 -0
- package/dist/distribution.d.ts.map +1 -0
- package/dist/distribution.js +61 -0
- package/dist/distribution.js.map +1 -0
- package/dist/element.d.ts +121 -0
- package/dist/element.d.ts.map +1 -0
- package/dist/element.js +276 -0
- package/dist/element.js.map +1 -0
- package/dist/enums.d.ts +25 -0
- package/dist/enums.d.ts.map +1 -0
- package/dist/enums.js +38 -0
- package/dist/enums.js.map +1 -0
- package/dist/errors.d.ts +195 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +188 -0
- package/dist/errors.js.map +1 -0
- package/dist/forms-core/autocomplete.d.ts +37 -0
- package/dist/forms-core/autocomplete.d.ts.map +1 -0
- package/dist/forms-core/autocomplete.js +82 -0
- package/dist/forms-core/autocomplete.js.map +1 -0
- package/dist/forms-core/business-function.d.ts +61 -0
- package/dist/forms-core/business-function.d.ts.map +1 -0
- package/dist/forms-core/business-function.js +149 -0
- package/dist/forms-core/business-function.js.map +1 -0
- package/dist/forms-core/calculation.d.ts +72 -0
- package/dist/forms-core/calculation.d.ts.map +1 -0
- package/dist/forms-core/calculation.js +103 -0
- package/dist/forms-core/calculation.js.map +1 -0
- package/dist/forms-core/controller.d.ts +249 -0
- package/dist/forms-core/controller.d.ts.map +1 -0
- package/dist/forms-core/controller.js +590 -0
- package/dist/forms-core/controller.js.map +1 -0
- package/dist/forms-core/elements.d.ts +44 -0
- package/dist/forms-core/elements.d.ts.map +1 -0
- package/dist/forms-core/elements.js +68 -0
- package/dist/forms-core/elements.js.map +1 -0
- package/dist/forms-core/email-domains.d.ts +10 -0
- package/dist/forms-core/email-domains.d.ts.map +1 -0
- package/dist/forms-core/email-domains.js +69 -0
- package/dist/forms-core/email-domains.js.map +1 -0
- package/dist/forms-core/file-rules.d.ts +51 -0
- package/dist/forms-core/file-rules.d.ts.map +1 -0
- package/dist/forms-core/file-rules.js +105 -0
- package/dist/forms-core/file-rules.js.map +1 -0
- package/dist/forms-core/formula.d.ts +114 -0
- package/dist/forms-core/formula.d.ts.map +1 -0
- package/dist/forms-core/formula.js +1170 -0
- package/dist/forms-core/formula.js.map +1 -0
- package/dist/forms-core/index.d.ts +24 -0
- package/dist/forms-core/index.d.ts.map +1 -0
- package/dist/forms-core/index.js +28 -0
- package/dist/forms-core/index.js.map +1 -0
- package/dist/forms-core/units.d.ts +49 -0
- package/dist/forms-core/units.d.ts.map +1 -0
- package/dist/forms-core/units.js +255 -0
- package/dist/forms-core/units.js.map +1 -0
- package/dist/forms-core/us-states.d.ts +14 -0
- package/dist/forms-core/us-states.d.ts.map +1 -0
- package/dist/forms-core/us-states.js +66 -0
- package/dist/forms-core/us-states.js.map +1 -0
- package/dist/forms-core/validation.d.ts +266 -0
- package/dist/forms-core/validation.d.ts.map +1 -0
- package/dist/forms-core/validation.js +850 -0
- package/dist/forms-core/validation.js.map +1 -0
- package/dist/forms.d.ts +19 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +29 -0
- package/dist/forms.js.map +1 -0
- package/dist/gallery-types.d.ts +32 -0
- package/dist/gallery-types.d.ts.map +1 -0
- package/dist/gallery-types.js +2 -0
- package/dist/gallery-types.js.map +1 -0
- package/dist/graphql.d.ts +97 -0
- package/dist/graphql.d.ts.map +1 -0
- package/dist/graphql.js +212 -0
- package/dist/graphql.js.map +1 -0
- package/dist/index.cjs +19448 -0
- package/dist/index.d.cts +19 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/marketing-schema.d.ts +216 -0
- package/dist/marketing-schema.d.ts.map +1 -0
- package/dist/marketing-schema.js +200 -0
- package/dist/marketing-schema.js.map +1 -0
- package/dist/mcp.d.ts +42 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +87 -0
- package/dist/mcp.js.map +1 -0
- package/dist/native.d.ts +45 -0
- package/dist/native.d.ts.map +1 -0
- package/dist/native.js +43 -0
- package/dist/native.js.map +1 -0
- package/dist/next-revalidate.d.ts +43 -0
- package/dist/next-revalidate.d.ts.map +1 -0
- package/dist/next-revalidate.js +84 -0
- package/dist/next-revalidate.js.map +1 -0
- package/dist/parallel-pages.d.ts +102 -0
- package/dist/parallel-pages.d.ts.map +1 -0
- package/dist/parallel-pages.js +108 -0
- package/dist/parallel-pages.js.map +1 -0
- package/dist/payment-icons.d.ts +66 -0
- package/dist/payment-icons.d.ts.map +1 -0
- package/dist/payment-icons.js +160 -0
- package/dist/payment-icons.js.map +1 -0
- package/dist/proxy.d.ts +67 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +97 -0
- package/dist/proxy.js.map +1 -0
- package/dist/react-analytics.d.ts +39 -0
- package/dist/react-analytics.d.ts.map +1 -0
- package/dist/react-analytics.js +59 -0
- package/dist/react-analytics.js.map +1 -0
- package/dist/react-attribution.d.ts +99 -0
- package/dist/react-attribution.d.ts.map +1 -0
- package/dist/react-attribution.js +52 -0
- package/dist/react-attribution.js.map +1 -0
- package/dist/react-marketing.d.ts +181 -0
- package/dist/react-marketing.d.ts.map +1 -0
- package/dist/react-marketing.js +215 -0
- package/dist/react-marketing.js.map +1 -0
- package/dist/react.d.ts +727 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +2418 -0
- package/dist/react.js.map +1 -0
- package/dist/reserved-paths.d.ts +24 -0
- package/dist/reserved-paths.d.ts.map +1 -0
- package/dist/reserved-paths.js +60 -0
- package/dist/reserved-paths.js.map +1 -0
- package/dist/revalidate-verify.d.ts +30 -0
- package/dist/revalidate-verify.d.ts.map +1 -0
- package/dist/revalidate-verify.js +74 -0
- package/dist/revalidate-verify.js.map +1 -0
- package/dist/sdk.d.ts +678 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +801 -0
- package/dist/sdk.js.map +1 -0
- package/dist/seo.d.ts +218 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +309 -0
- package/dist/seo.js.map +1 -0
- package/dist/socials.d.ts +47 -0
- package/dist/socials.d.ts.map +1 -0
- package/dist/socials.js +172 -0
- package/dist/socials.js.map +1 -0
- package/dist/static.d.ts +14 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +14 -0
- package/dist/static.js.map +1 -0
- package/dist/url-pattern.d.ts +47 -0
- package/dist/url-pattern.d.ts.map +1 -0
- package/dist/url-pattern.js +124 -0
- package/dist/url-pattern.js.map +1 -0
- package/dist/vanilla.d.ts +77 -0
- package/dist/vanilla.d.ts.map +1 -0
- package/dist/vanilla.js +903 -0
- package/dist/vanilla.js.map +1 -0
- package/openapi.json +13779 -0
- package/package.json +204 -0
- package/src/adapters-revalidate.ts +88 -0
- package/src/alpha.ts +89 -0
- package/src/analytics-core.ts +175 -0
- package/src/analytics-server.ts +99 -0
- package/src/angular/biab-form.component.ts +1443 -0
- package/src/angular/index.ts +26 -0
- package/src/attribution-logo.ts +5 -0
- package/src/auth-handler.ts +352 -0
- package/src/cli.ts +2517 -0
- package/src/client.ts +3578 -0
- package/src/collections.ts +101 -0
- package/src/contracts.ts +3390 -0
- package/src/data-model-schema.ts +4042 -0
- package/src/distribution.ts +73 -0
- package/src/element.ts +321 -0
- package/src/enums.ts +65 -0
- package/src/errors.ts +338 -0
- package/src/forms-core/autocomplete.ts +97 -0
- package/src/forms-core/business-function.ts +209 -0
- package/src/forms-core/calculation.ts +167 -0
- package/src/forms-core/controller.ts +922 -0
- package/src/forms-core/elements.ts +86 -0
- package/src/forms-core/email-domains.ts +68 -0
- package/src/forms-core/file-rules.ts +123 -0
- package/src/forms-core/formula.ts +1339 -0
- package/src/forms-core/index.ts +122 -0
- package/src/forms-core/units.ts +308 -0
- package/src/forms-core/us-states.ts +68 -0
- package/src/forms-core/validation.ts +1037 -0
- package/src/forms.ts +63 -0
- package/src/gallery-types.ts +31 -0
- package/src/graphql.ts +295 -0
- package/src/index.ts +505 -0
- package/src/marketing-schema.ts +395 -0
- package/src/mcp.ts +108 -0
- package/src/native.ts +73 -0
- package/src/next-revalidate.ts +124 -0
- package/src/parallel-pages.ts +192 -0
- package/src/payment-icons.ts +205 -0
- package/src/proxy.ts +173 -0
- package/src/qwik.tsx +1234 -0
- package/src/react-analytics.tsx +76 -0
- package/src/react-attribution.tsx +192 -0
- package/src/react-marketing.tsx +338 -0
- package/src/react.tsx +5495 -0
- package/src/reserved-paths.ts +65 -0
- package/src/revalidate-verify.ts +98 -0
- package/src/sdk.ts +1671 -0
- package/src/seo.ts +488 -0
- package/src/socials.ts +230 -0
- package/src/solid.tsx +1466 -0
- package/src/static.ts +21 -0
- package/src/styles/biab-forms.css +484 -0
- package/src/svelte/BiabForm.svelte +1069 -0
- package/src/svelte/context.ts +57 -0
- package/src/svelte/index.ts +79 -0
- package/src/url-pattern.ts +153 -0
- package/src/vanilla.ts +1290 -0
- package/src/vue/BiabForm.vue +1538 -0
- package/src/vue/index.ts +119 -0
package/README.md
ADDED
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
# `@businessdash/sdk`
|
|
2
|
+
|
|
3
|
+
> Formerly published as `@biab-dev/sdk` (≤0.9.53) — same API, new name.
|
|
4
|
+
|
|
5
|
+
**Alpha — 0.9.53.** This package declares, validates, and orchestrates your application's database schema, form layout, and type-safe field rules in a single declarative pipeline — and ships the answer-engine surface (llms.txt + product feed, MCP connector proxy, AI-crawler analytics) that makes the site you build with it legible to AI assistants.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @businessdash/sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Imports
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
collection,
|
|
20
|
+
bd,
|
|
21
|
+
Enums,
|
|
22
|
+
createFormSchemaOrchestrator,
|
|
23
|
+
layoutContainers,
|
|
24
|
+
SchemaBoundary,
|
|
25
|
+
} from "@businessdash/sdk";
|
|
26
|
+
|
|
27
|
+
// Destructure visual layout wrappers for intuitive, HTML-like inline grouping
|
|
28
|
+
const { FormStep, ConditionalBlock, MultiPopulator, OrCondition } = layoutContainers;
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Core `bd` Primitive Modifiers
|
|
34
|
+
|
|
35
|
+
The compact `bd` factory namespace eliminates boilerplate. Each chain method implements fluent builders to define data rules, UI presentation hints, database constraints, and cryptographic security features.
|
|
36
|
+
|
|
37
|
+
### Core Field API Reference
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
bd.text().formElement("short_text") // Map raw DB type to a front-end input component
|
|
41
|
+
.validate({ minLength: 3, maxLength: 100 }) // Append structural validation parameters
|
|
42
|
+
.helper("The display name of the item") // Descriptive metadata for tooltips
|
|
43
|
+
.regex(/^[A-Z][a-z]+$/) // Absolute input string validation
|
|
44
|
+
.check("name != ''") // Low-level SQL CHECK constraint
|
|
45
|
+
.encrypted() // Per-user cryptographic isolation
|
|
46
|
+
.encryptedLight() // Master key encryption-at-rest
|
|
47
|
+
.private() // Strip from public log telemetry
|
|
48
|
+
.required();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Available `.validate()` rules:
|
|
52
|
+
|
|
53
|
+
| Rule | Applies to | Description |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| `minLength` | TEXT, ARRAY | Minimum characters or entries |
|
|
56
|
+
| `maxLength` | TEXT, ARRAY | Maximum characters or entries |
|
|
57
|
+
| `lessThan` | NUMBER, NUMERIC | Strictly less than |
|
|
58
|
+
| `greaterThan` | NUMBER, NUMERIC | Strictly greater than |
|
|
59
|
+
| `round` | NUMBER, NUMERIC | Decimal places to round to |
|
|
60
|
+
| `positive` | NUMBER, NUMERIC | Must be >= 0 |
|
|
61
|
+
| `notNull` | Any | Reject null/empty |
|
|
62
|
+
| `floor` | NUMBER, NUMERIC | Floor the value |
|
|
63
|
+
| `isDecimal` | NUMBER, NUMERIC | Must have decimal component |
|
|
64
|
+
| `regex` | TEXT | Pattern match |
|
|
65
|
+
| `checkExpression` | TEXT, NUMBER | SQL CHECK expression |
|
|
66
|
+
| `lessThanToday` | DATE, DATE_TIME | Must be historical |
|
|
67
|
+
| `greaterThanToday` | DATE, DATE_TIME | Must be future |
|
|
68
|
+
|
|
69
|
+
### Form Elements
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
.formElement("short_text") // TEXT → short/long/email/url/dropdown/radio/color/…
|
|
73
|
+
.formElement("currency") // NUMBER/NUMERIC → currency/range/star_rating/…
|
|
74
|
+
.formElement("yes_no_toggle") // BOOLEAN → yes_no_toggle / yes_no_checkbox
|
|
75
|
+
.formElement("dropdown") // SELECT → dropdown / radio_group
|
|
76
|
+
.formElement("measurement") // RAW_JSON → measurement/address_group/geo/object
|
|
77
|
+
.formElement("formFileValue") // FILES → file upload
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Complex Data Fields
|
|
81
|
+
|
|
82
|
+
**`bd.measurement(options)`** — Explicit physics metrics compiler block:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
memoryBusSpeed: bd.measurement({
|
|
86
|
+
category: "storageSpeed",
|
|
87
|
+
unitPicker: "fixed",
|
|
88
|
+
default_unit: "MHz",
|
|
89
|
+
resolve_to_your_unit: "MT/s",
|
|
90
|
+
allow_decimals: true,
|
|
91
|
+
}).required()
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Supported categories: `sound`, `pressure`, `torque`, `speed` (incl. `Mach`, `ly/yr`), `time`, `area`, `volume`, `power`, `energy`, `storage`, `storageSpeed`, `frequency`, `temperature`, `mass`, `length` ( incl. `µm`, `nm`), `currency`.
|
|
95
|
+
|
|
96
|
+
**`bd.object(properties)`** — Structural nested JSON with storage strategy:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
metadata: bd.object({
|
|
100
|
+
sku: bd.text().required(),
|
|
101
|
+
warehouse: bd.text(),
|
|
102
|
+
}).strategy("flattened") // or "jsonb" | "separateTable" (spawns 1:1 side-table)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
All `RAW_JSON` object elements support JSONB output, not only `bd.object()`:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
shippingAddress: bd.address().strategy("jsonb"),
|
|
109
|
+
availability: bd.json().formElement("availability").strategy("jsonb"),
|
|
110
|
+
bookingWindow: bd.json().formElement("date_range").strategy("jsonb"),
|
|
111
|
+
coordinates: bd.geo().strategy("jsonb"),
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`strategy` and `separateTableName` are included in `toJSONSchema()`.
|
|
115
|
+
|
|
116
|
+
**`bd.file(options)`** — File uploads:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
architectureSpecs: bd.file({
|
|
120
|
+
max_size_mb: 15,
|
|
121
|
+
allowed_mimetypes: ["application/pdf"],
|
|
122
|
+
}).optional()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Optional Schema Boundaries
|
|
128
|
+
|
|
129
|
+
`SchemaBoundary()` is feature metadata, not form layout. It accepts collections
|
|
130
|
+
or nested boundaries directly and does not require a step, wizard, repeater, or
|
|
131
|
+
group:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const projects = collection("projects", {
|
|
135
|
+
fields: { title: bd.text().required() },
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const direct = createFormSchemaOrchestrator([projects]);
|
|
139
|
+
|
|
140
|
+
const tagged = createFormSchemaOrchestrator([
|
|
141
|
+
SchemaBoundary(
|
|
142
|
+
{ featureId: "projects", name: "Projects" },
|
|
143
|
+
projects,
|
|
144
|
+
),
|
|
145
|
+
]);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Both declarations compile the same collection. The second additionally sets
|
|
149
|
+
`tagged.featureTags.project` to `"projects"`.
|
|
150
|
+
|
|
151
|
+
## High-Utility Form Layout Containers
|
|
152
|
+
|
|
153
|
+
Layout containers are optional semantic wrappers. Add them only when the UI
|
|
154
|
+
needs steps, conditional visibility, repetition, or branching.
|
|
155
|
+
|
|
156
|
+
### `FormStep(name, options, ...children)`
|
|
157
|
+
|
|
158
|
+
Wraps schema portions into sequential steps for multi-step wizards:
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
FormStep("taxonomy_setup", {
|
|
162
|
+
showTitle: true,
|
|
163
|
+
helperText: "Configure high-level project taxonomy.",
|
|
164
|
+
},
|
|
165
|
+
collection("categories", { fields: { name: bd.text().required() } }),
|
|
166
|
+
collection("feature_groups", { fields: { name: bd.text().required() } }),
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### `ConditionalBlock(config, ...children)`
|
|
171
|
+
|
|
172
|
+
Dynamically toggles visibility based on matching logic gates:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
ConditionalBlock(
|
|
176
|
+
{
|
|
177
|
+
matchStrategy: "AND",
|
|
178
|
+
showTitle: true,
|
|
179
|
+
rules: [
|
|
180
|
+
{ fieldRef: "project.isActive", op: "equals", values: [true] },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
collection("features", { fields: { name: bd.text().required() } }),
|
|
184
|
+
)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Supports `matchStrategy: 'AND' | 'OR'` and operators: `equals`, `not_equals`, `any_of`, `none_of`.
|
|
188
|
+
|
|
189
|
+
### `MultiPopulator(config, ...children)`
|
|
190
|
+
|
|
191
|
+
Generates an N+1 array row repeater grid. A single child collection is inferred
|
|
192
|
+
as a secondary-table output:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
MultiPopulator({ max_entries: 5, button_label: "➕ Register Node" },
|
|
196
|
+
collection("hardware_nodes", {
|
|
197
|
+
fields: {
|
|
198
|
+
nodeSerial: bd.text().required().private(),
|
|
199
|
+
memoryBusSpeed: bd.measurement({
|
|
200
|
+
category: "storageSpeed",
|
|
201
|
+
unitPicker: "fixed",
|
|
202
|
+
default_unit: "MHz",
|
|
203
|
+
resolve_to_your_unit: "MT/s",
|
|
204
|
+
allow_decimals: true,
|
|
205
|
+
}).required(),
|
|
206
|
+
},
|
|
207
|
+
}),
|
|
208
|
+
)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
To keep the repeated array on a parent record instead, bind the repeater to a
|
|
212
|
+
JSONB field and declare the shape of one entry:
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
const projects = collection("projects", {
|
|
216
|
+
fields: {
|
|
217
|
+
name: bd.text().required(),
|
|
218
|
+
teamMembers: bd.array().strategy("jsonb"),
|
|
219
|
+
},
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
createFormSchemaOrchestrator([
|
|
223
|
+
projects,
|
|
224
|
+
MultiPopulator({
|
|
225
|
+
max_entries: 10,
|
|
226
|
+
output: {
|
|
227
|
+
strategy: "jsonb",
|
|
228
|
+
fieldRef: "project.teamMembers",
|
|
229
|
+
},
|
|
230
|
+
entryFields: {
|
|
231
|
+
name: bd.text().required(),
|
|
232
|
+
role: bd.text().optional(),
|
|
233
|
+
},
|
|
234
|
+
}),
|
|
235
|
+
])
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The orchestrator rejects missing targets, scalar JSONB targets, fields without
|
|
239
|
+
`.strategy("jsonb")`, and ambiguous implicit secondary-table outputs.
|
|
240
|
+
|
|
241
|
+
### `OrCondition(options, branches)`
|
|
242
|
+
|
|
243
|
+
Multi-path router layout engine for custom branching (tabbed selections):
|
|
244
|
+
|
|
245
|
+
```ts
|
|
246
|
+
OrCondition({ showTitle: true, button_label: "Select Target Pathway" }, [
|
|
247
|
+
{
|
|
248
|
+
name: "Hardware Validation Path",
|
|
249
|
+
helperText: "Provisions system telemetry benchmarks.",
|
|
250
|
+
nodes: [
|
|
251
|
+
MultiPopulator({ max_entries: 5, button_label: "➕ Register Node" },
|
|
252
|
+
collection("hardware_nodes", { fields: { /* ... */ } }),
|
|
253
|
+
),
|
|
254
|
+
],
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: "Standard Baseline Deployment Only",
|
|
258
|
+
nodes: [ /* ... */ ],
|
|
259
|
+
},
|
|
260
|
+
])
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Compile-Time Type Safety & Form Alignment
|
|
266
|
+
|
|
267
|
+
The SDK leverages TypeScript type inference to turn common engineering mistakes into instant IDE compile errors.
|
|
268
|
+
|
|
269
|
+
### Invalid Field Refs
|
|
270
|
+
|
|
271
|
+
A typo inside a conditional block path (e.g. `projects.isActveeee`) fails validation at orchestration time:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
⚠️ [BusinessDash SDK] Conditional rule references field "isActveeee"
|
|
275
|
+
in collection "projects" which does not exist.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Data Type Mismatches
|
|
279
|
+
|
|
280
|
+
Conditional `fieldRef` values are validated when orchestration runs. The current
|
|
281
|
+
public type is `string`, so invalid collection or field paths are runtime
|
|
282
|
+
alignment errors rather than template-literal compile-time errors.
|
|
283
|
+
|
|
284
|
+
### Implicit Key Generation
|
|
285
|
+
|
|
286
|
+
Primary keys are optional. The orchestration engine pre-scans collections, automatically injecting type-safe sequential integer or secure random `uuid` primary identifiers.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Unified Schema & Form Orchestration Blueprint
|
|
291
|
+
|
|
292
|
+
The complete declaration — combining database layers, field constraints, RLS policies, and layout:
|
|
293
|
+
|
|
294
|
+
```ts
|
|
295
|
+
import {
|
|
296
|
+
collection, bd, Enums,
|
|
297
|
+
createFormSchemaOrchestrator,
|
|
298
|
+
layoutContainers,
|
|
299
|
+
} from "@businessdash/sdk";
|
|
300
|
+
|
|
301
|
+
const { FormStep, ConditionalBlock, MultiPopulator, OrCondition } = layoutContainers;
|
|
302
|
+
|
|
303
|
+
export const ProjectStatus = Enums(["planned", "in_progress", "beta", "completed"] as const);
|
|
304
|
+
|
|
305
|
+
export const applicationMasterContext = createFormSchemaOrchestrator([
|
|
306
|
+
|
|
307
|
+
FormStep("taxonomy_setup", {
|
|
308
|
+
showTitle: true,
|
|
309
|
+
helperText: "Configure high-level project taxonomy and categorization mappings.",
|
|
310
|
+
},
|
|
311
|
+
collection("categories", {
|
|
312
|
+
fields: {
|
|
313
|
+
name: bd.text().formElement("short_text").required()
|
|
314
|
+
.validate({ minLength: 3 }).helper("Category name."),
|
|
315
|
+
icon: bd.text().formElement("short_text").optional(),
|
|
316
|
+
},
|
|
317
|
+
}),
|
|
318
|
+
|
|
319
|
+
collection("feature_groups", {
|
|
320
|
+
fields: {
|
|
321
|
+
name: bd.text().formElement("short_text").required(),
|
|
322
|
+
category_id: bd.relation()
|
|
323
|
+
.references("categories").onDelete("CASCADE").required(),
|
|
324
|
+
},
|
|
325
|
+
}),
|
|
326
|
+
),
|
|
327
|
+
|
|
328
|
+
FormStep("feature_engineering", { showTitle: true },
|
|
329
|
+
|
|
330
|
+
collection("features", {
|
|
331
|
+
rlsPolicies: [
|
|
332
|
+
{ roles: ["authenticated"], actions: ["read"], expression: "owner_id = auth.uid()" },
|
|
333
|
+
],
|
|
334
|
+
fields: {
|
|
335
|
+
name: bd.text().formElement("short_text").required().regex(/^[A-Z][a-z]+$/),
|
|
336
|
+
status: bd.text().formElement("dropdown").required(),
|
|
337
|
+
|
|
338
|
+
estimatedBudget: bd.number().formElement("currency")
|
|
339
|
+
.check("estimated_budget >= 0").validate({ round: 2 }),
|
|
340
|
+
|
|
341
|
+
feature_group_id: bd.relation()
|
|
342
|
+
.references("feature_groups").onDelete("CASCADE").required(),
|
|
343
|
+
|
|
344
|
+
related_features: bd.relation().belongsToMany("features", {
|
|
345
|
+
joinCollectionName: "feature_references",
|
|
346
|
+
fields: {
|
|
347
|
+
isBidirectional: bd.boolean().formElement("yes_no_toggle").default(false),
|
|
348
|
+
},
|
|
349
|
+
}),
|
|
350
|
+
},
|
|
351
|
+
}),
|
|
352
|
+
),
|
|
353
|
+
|
|
354
|
+
FormStep("routing_and_sandbox_studio", { showTitle: true },
|
|
355
|
+
|
|
356
|
+
OrCondition({ showTitle: true, button_label: "Select Target Pathway" }, [
|
|
357
|
+
{
|
|
358
|
+
name: "Hardware Validation Path",
|
|
359
|
+
helperText: "Provisions system telemetry benchmarks using physical metrics components.",
|
|
360
|
+
nodes: [
|
|
361
|
+
MultiPopulator({ max_entries: 5, button_label: "➕ Register Network Hardware Node" },
|
|
362
|
+
collection("hardware_nodes", {
|
|
363
|
+
fields: {
|
|
364
|
+
nodeSerial: bd.text().formElement("short_text").required().private(),
|
|
365
|
+
memoryBusSpeed: bd.measurement({
|
|
366
|
+
category: "storageSpeed",
|
|
367
|
+
unitPicker: "fixed",
|
|
368
|
+
default_unit: "MHz",
|
|
369
|
+
resolve_to_your_unit: "MT/s",
|
|
370
|
+
allow_decimals: true,
|
|
371
|
+
}).required(),
|
|
372
|
+
architectureSpecs: bd.file({
|
|
373
|
+
max_size_mb: 15,
|
|
374
|
+
allowed_mimetypes: ["application/pdf"],
|
|
375
|
+
}).optional(),
|
|
376
|
+
},
|
|
377
|
+
}),
|
|
378
|
+
),
|
|
379
|
+
],
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: "Standard Baseline Deployment Only",
|
|
383
|
+
nodes: [],
|
|
384
|
+
},
|
|
385
|
+
]),
|
|
386
|
+
),
|
|
387
|
+
|
|
388
|
+
], {
|
|
389
|
+
defaultIdType: "uuid", // Auto-injects unique string identifiers for missing primary keys
|
|
390
|
+
consecutive: true, // Forces strict wizard sequence stepping rules
|
|
391
|
+
progress_style: "progress_bar", // Instructs the layout renderer to generate progress indicators
|
|
392
|
+
});
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Deployment Validation Lifecycle
|
|
398
|
+
|
|
399
|
+
When `createFormSchemaOrchestrator` compiles, it parses the layout tree and splits it into two isolated channels:
|
|
400
|
+
|
|
401
|
+
- **`databaseSchema`** — A pure array of raw collection models, stripped of UI components, ready to stream to Prisma, Drizzle, or raw storage pipelines.
|
|
402
|
+
- **`uiFormTree`** — The complete visual rendering graph mapping steps, multi-populators, validation constraints, and helper text fields for your form engine.
|
|
403
|
+
|
|
404
|
+
If a layout node points to an unmapped collection or field, orchestration stops
|
|
405
|
+
with a focused diagnostic:
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
[BusinessDash SDK] Conditional rule references field "isActive"
|
|
409
|
+
in collection "project" which does not exist.
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## AI Distribution — llms.txt & Product Feed on Your Domain
|
|
415
|
+
|
|
416
|
+
Orgs curate products/services for answer engines under **Dashboard → Marketing
|
|
417
|
+
→ AI Distribution**; the platform keeps two public artifacts current per site —
|
|
418
|
+
an OpenAI merchant-feed-shaped product feed and an `llms.txt`. The llms.txt
|
|
419
|
+
convention only works at the site's own root (an AI crawler asks for
|
|
420
|
+
`https://yoursite.com/llms.txt`), while the source of truth lives on BusinessDash where
|
|
421
|
+
the org curates it. `@businessdash/sdk/distribution` proxies the two together:
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
// app/llms.txt/route.ts
|
|
425
|
+
import { llmsTxtHandler } from '@businessdash/sdk/distribution'
|
|
426
|
+
|
|
427
|
+
export const GET = llmsTxtHandler({
|
|
428
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
429
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // e.g. https://www.biab.app
|
|
430
|
+
})
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- **`llmsTxtHandler(options)`** serves the curated llms.txt from your own
|
|
434
|
+
domain root, with a 5-minute `s-maxage` + `stale-while-revalidate`. An
|
|
435
|
+
upstream 404 (feed disabled or entitlement lapsed) passes through untouched;
|
|
436
|
+
network failure returns a plain 503 — it never throws into the framework.
|
|
437
|
+
- **`productFeedUrl(options)` / `llmsTxtUrl(options)`** build the public BIAB
|
|
438
|
+
feed URLs. The product feed needs no proxy — submit its BusinessDash URL directly to
|
|
439
|
+
merchant/feed programs; `productFeedUrl` builds it for display/submission.
|
|
440
|
+
|
|
441
|
+
Pair the feed with the `product()` JSON-LD builder from `@businessdash/sdk/seo`:
|
|
442
|
+
the feed gets your product INTO the engines' catalogs; the markup is what their
|
|
443
|
+
crawlers verify on the landing page itself.
|
|
444
|
+
|
|
445
|
+
## MCP Connector on Your Domain
|
|
446
|
+
|
|
447
|
+
The platform's host-resolved `/api/mcp` only exists on sites the platform
|
|
448
|
+
serves. If you host your own app with the SDK, mount the two
|
|
449
|
+
`@businessdash/sdk/mcp` handlers and your domain gains the same connector surface —
|
|
450
|
+
so the URL an org hands to Claude / ChatGPT / Gemini is their own site:
|
|
451
|
+
|
|
452
|
+
```ts
|
|
453
|
+
// app/api/mcp/route.ts
|
|
454
|
+
import { mcpHandler } from '@businessdash/sdk/mcp'
|
|
455
|
+
export const { POST, GET } = mcpHandler({
|
|
456
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
457
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // https://www.biab.app
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
// app/.well-known/mcp.json/route.ts
|
|
461
|
+
import { mcpManifestHandler } from '@businessdash/sdk/mcp'
|
|
462
|
+
export const GET = mcpManifestHandler({
|
|
463
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
464
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!,
|
|
465
|
+
})
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
- **`mcpHandler(options)`** — `POST` proxies JSON-RPC to the platform's public
|
|
469
|
+
per-site connector; `GET` mirrors the spec's 405. An unreachable upstream
|
|
470
|
+
answers with a proper JSON-RPC `-32603` error and a 502.
|
|
471
|
+
- **`mcpManifestHandler(options)`** — serves `.well-known/mcp.json` with the
|
|
472
|
+
endpoint URL rewritten to YOUR domain (the requesting origin).
|
|
473
|
+
- **Thin by design.** The body is forwarded verbatim and the platform still
|
|
474
|
+
enforces the org's MCP opt-in and per-tool write gates — a proxy can't widen
|
|
475
|
+
anything.
|
|
476
|
+
|
|
477
|
+
## AI-Crawler Visibility (AEO Analytics)
|
|
478
|
+
|
|
479
|
+
AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) fetch your HTML without
|
|
480
|
+
executing JavaScript, so `<BIABAnalytics />` never sees them. Your server does
|
|
481
|
+
— report them from middleware via `@businessdash/sdk/analytics-server`:
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
// middleware.ts
|
|
485
|
+
import { isAiCrawlerUserAgent, reportAiCrawlerHit } from "@businessdash/sdk/analytics-server";
|
|
486
|
+
|
|
487
|
+
export function middleware(request: Request) {
|
|
488
|
+
const ua = request.headers.get("user-agent");
|
|
489
|
+
if (isAiCrawlerUserAgent(ua)) {
|
|
490
|
+
// Fire-and-forget — never block or fail the crawler's request.
|
|
491
|
+
void reportAiCrawlerHit({
|
|
492
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
493
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_API_URL!,
|
|
494
|
+
apiKey: process.env.NEXT_PUBLIC_BIAB_PUBLISHABLE_KEY!,
|
|
495
|
+
userAgent: ua,
|
|
496
|
+
path: new URL(request.url).pathname,
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Read it back with `biab.site(siteId).analytics.aiVisibility({ days: 30 })` —
|
|
503
|
+
per-crawler fetch counts, distinct pages read, vendor, intent
|
|
504
|
+
(`training` / `search` / `user`), and last-seen timestamps. Branch on
|
|
505
|
+
`.available`; same plan gating as `pageViews()`.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Entrypoints
|
|
510
|
+
|
|
511
|
+
| Import path | Contents |
|
|
512
|
+
|---|---|
|
|
513
|
+
| `@businessdash/sdk` | Main barrel — all schema, layout, client, auth, SEO exports |
|
|
514
|
+
| `@businessdash/sdk/contracts` | Zod schemas + inferred types for every API surface |
|
|
515
|
+
| `@businessdash/sdk/enums` | Enum helpers — label formatting, enum entry utilities |
|
|
516
|
+
| `@businessdash/sdk/collections` | Schema primitives — `collection()`, `createSchema()`, `bd`, `Enums()`, `SchemaBoundary` |
|
|
517
|
+
| `@businessdash/sdk/forms` | Form layout + runtime — `layoutContainers`, `createFormSchemaOrchestrator()`, `createSingleResourceForm()`, controller, formula engine |
|
|
518
|
+
| `@businessdash/sdk/static` | Static data — `Enums()`, enum types |
|
|
519
|
+
| `@businessdash/sdk/react` | React bindings — form, auth, analytics, attribution |
|
|
520
|
+
| `@businessdash/sdk/vue` | Vue binding — `<BiabForm>` |
|
|
521
|
+
| `@businessdash/sdk/svelte` | Svelte binding — `<BiabForm>` |
|
|
522
|
+
| `@businessdash/sdk/solid` | Solid binding — `<BiabForm>` |
|
|
523
|
+
| `@businessdash/sdk/qwik` | Qwik binding — `<BiabForm>` |
|
|
524
|
+
| `@businessdash/sdk/angular` | Angular binding — `<biab-form>` component |
|
|
525
|
+
| `@businessdash/sdk/element` | `<biab-form>` custom element — drop-in web component |
|
|
526
|
+
| `@businessdash/sdk/vanilla` | Framework-free imperative DOM binding — `mountBiabForm()` |
|
|
527
|
+
| `@businessdash/sdk/react-analytics` | `<BIABAnalytics />` — browser page-view tracker for React |
|
|
528
|
+
| `@businessdash/sdk/react-attribution` | "Powered by BusinessDash" attribution badge |
|
|
529
|
+
| `@businessdash/sdk/analytics-core` | Framework-agnostic analytics core — `initBiabAnalytics()` |
|
|
530
|
+
| `@businessdash/sdk/analytics-server` | Server-side AEO reporting — `isAiCrawlerUserAgent()`, `reportAiCrawlerHit()` |
|
|
531
|
+
| `@businessdash/sdk/distribution` | llms.txt proxy + feed URLs — `llmsTxtHandler()`, `productFeedUrl()`, `llmsTxtUrl()` |
|
|
532
|
+
| `@businessdash/sdk/mcp` | MCP connector proxy — `mcpHandler()`, `mcpManifestHandler()` |
|
|
533
|
+
| `@businessdash/sdk/proxy` | `BiabDevClient` / customer-portal client re-exports for proxy setups |
|
|
534
|
+
| `@businessdash/sdk/seo` | JSON-LD builders — `localBusiness()`, `product()`, … |
|
|
535
|
+
| `@businessdash/sdk/data-model-schema` | `defineDataModel` — org custom database (tables + enums) as code |
|
|
536
|
+
| `@businessdash/sdk/alpha` | Legacy/deprecated pre-0.9 schema API, re-exported for compatibility |
|
|
537
|
+
| `@businessdash/sdk/marketing-schema` | `defineSiteMarketingSchema()` — schema-driven marketing-page flow |
|
|
538
|
+
| `@businessdash/sdk/next/revalidate` | One-line Next.js route handler for BusinessDash revalidation webhooks |
|
|
539
|
+
| `@businessdash/sdk/adapters/revalidate` | Framework-agnostic revalidation handler builder |
|
|
540
|
+
| `@businessdash/sdk/biab-forms.css` | Default form stylesheet |
|
|
541
|
+
|
|
542
|
+
## Releases
|
|
543
|
+
|
|
544
|
+
> **Versioning convention (0.9.50+):** Significant feature groups bump the minor
|
|
545
|
+
> field directly. Previous releases used the 36-band patch series (0.9.36, 0.9.35…).
|
|
546
|
+
> The 0.9.35 docs are archived at `docs-legacy/0.9.35/` — this is the first
|
|
547
|
+
> supported SDK baseline. 0.9.50 introduces the unified Schema, Validation, and
|
|
548
|
+
> Form orchestration pipeline with `createFormSchemaOrchestrator`, `layoutContainers`,
|
|
549
|
+
> and the full `bd` field builder namespace.
|
|
550
|
+
>
|
|
551
|
+
> **0.8.x = schema-driven marketing flow.** Major surface bump (still
|
|
552
|
+
> alpha). New entrypoints: `defineSiteMarketingSchema()`,
|
|
553
|
+
> `@businessdash/sdk/seo` for typed JSON-LD builders, and a `biab-dev` CLI.
|
|
554
|
+
>
|
|
555
|
+
> **0.9.x = full programmatic consumer surface.** The largest expansion
|
|
556
|
+
> yet: native storefront / cart / checkout / coupons / subscriptions,
|
|
557
|
+
> customer portal + tenant auth (`createAuthHandler`, `getTenantSession`,
|
|
558
|
+
> `<SignIn/>` `<SignUp/>` `<SignOut/>` `useUser`), blog, a paginated
|
|
559
|
+
> reviews wall, address autocomplete + shipping, programmatic local SEO
|
|
560
|
+
> (`defineParallelPage()`), the revalidation webhook channel
|
|
561
|
+
> (`@businessdash/sdk/next/revalidate` + `@businessdash/sdk/adapters/revalidate`),
|
|
562
|
+
> privacy-conscious analytics (`@businessdash/sdk/react-analytics` +
|
|
563
|
+
> `@businessdash/sdk/analytics-core`), and a three-state billing-lifecycle
|
|
564
|
+
> degradation contract. Detailed per-version notes below.
|
|
565
|
+
|
|
566
|
+
The 3 most recent releases are below. **For the complete release
|
|
567
|
+
history, see the [changelog](https://www.biab.app/docs/developer/changelog).**
|
|
568
|
+
|
|
569
|
+
### 0.9.53 — AI Distribution, AEO Visibility & the MCP Proxy
|
|
570
|
+
|
|
571
|
+
The release that makes a consumer-hosted site legible to answer engines. Three
|
|
572
|
+
new entrypoints — `@businessdash/sdk/distribution`, `@businessdash/sdk/mcp`, and
|
|
573
|
+
`@businessdash/sdk/analytics-server` — plus an AI-visibility read-back on the
|
|
574
|
+
analytics client, a `product()` JSON-LD builder, and two formula functions.
|
|
575
|
+
|
|
576
|
+
#### `@businessdash/sdk/distribution` — llms.txt + product feed on your domain
|
|
577
|
+
|
|
578
|
+
Orgs curate products/services (or custom entries) under **Dashboard →
|
|
579
|
+
Marketing → AI Distribution**, and the platform keeps two public artifacts
|
|
580
|
+
current per site: an OpenAI merchant-feed-shaped **product feed** (submittable
|
|
581
|
+
to ChatGPT's product-feed program as-is) and an **`llms.txt`**. The llms.txt
|
|
582
|
+
convention only works at the site's own root — an AI crawler asks for
|
|
583
|
+
`https://yoursite.com/llms.txt` — while the source of truth lives on BusinessDash where
|
|
584
|
+
the org curates it. The new entrypoint proxies the two together:
|
|
585
|
+
|
|
586
|
+
```ts
|
|
587
|
+
// app/llms.txt/route.ts
|
|
588
|
+
import { llmsTxtHandler } from '@businessdash/sdk/distribution'
|
|
589
|
+
|
|
590
|
+
export const GET = llmsTxtHandler({
|
|
591
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
592
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // e.g. https://www.biab.app
|
|
593
|
+
})
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
- **`llmsTxtHandler(options)`** — GET handler serving the curated llms.txt from
|
|
597
|
+
your own domain root. Upstream 404 passes through untouched (feed disabled or
|
|
598
|
+
entitlement lapsed), network failure returns a plain 503 — it never throws
|
|
599
|
+
into the framework. Responses carry a 5-minute `s-maxage` +
|
|
600
|
+
`stale-while-revalidate`.
|
|
601
|
+
- **`productFeedUrl(options)` / `llmsTxtUrl(options)`** — build the public BIAB
|
|
602
|
+
feed URLs. The product feed needs no proxy — you submit its BusinessDash URL directly
|
|
603
|
+
to merchant/feed programs; `productFeedUrl` builds it for display/submission.
|
|
604
|
+
- Gated by the org's **AI Distribution** feature (`marketing.ai_distribution`);
|
|
605
|
+
a lapsed entitlement surfaces as the pass-through 404.
|
|
606
|
+
|
|
607
|
+
#### `@businessdash/sdk/mcp` — the site's AI connector on ANY domain
|
|
608
|
+
|
|
609
|
+
The platform's host-resolved `/api/mcp` only exists on sites the platform
|
|
610
|
+
serves. If you host your own app with the SDK, mount these two handlers and
|
|
611
|
+
your domain gains the same connector surface — so the URL an org hands to
|
|
612
|
+
Claude / ChatGPT / Gemini is their own site, exactly like the docs promise:
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
// app/api/mcp/route.ts
|
|
616
|
+
import { mcpHandler } from '@businessdash/sdk/mcp'
|
|
617
|
+
export const { POST, GET } = mcpHandler({
|
|
618
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
619
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!, // https://www.biab.app
|
|
620
|
+
})
|
|
621
|
+
|
|
622
|
+
// app/.well-known/mcp.json/route.ts
|
|
623
|
+
import { mcpManifestHandler } from '@businessdash/sdk/mcp'
|
|
624
|
+
export const GET = mcpManifestHandler({
|
|
625
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
626
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_APP_URL!,
|
|
627
|
+
})
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
- **`mcpHandler(options)`** — `POST` proxies JSON-RPC to the platform's public
|
|
631
|
+
per-site connector; `GET` mirrors the spec's 405. An unreachable upstream
|
|
632
|
+
answers with a proper JSON-RPC `-32603` error and a 502.
|
|
633
|
+
- **`mcpManifestHandler(options)`** — serves `.well-known/mcp.json` with the
|
|
634
|
+
endpoint URL rewritten to YOUR domain (the requesting origin).
|
|
635
|
+
- **Thin by design.** The body is forwarded verbatim and the platform still
|
|
636
|
+
enforces the org's MCP opt-in and per-tool write gates — a proxy can't widen
|
|
637
|
+
anything.
|
|
638
|
+
|
|
639
|
+
#### `@businessdash/sdk/analytics-server` — first-party AEO visibility
|
|
640
|
+
|
|
641
|
+
AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) fetch your HTML without
|
|
642
|
+
executing JavaScript, so `<BIABAnalytics />` never sees them. Your server does:
|
|
643
|
+
|
|
644
|
+
```ts
|
|
645
|
+
// middleware.ts
|
|
646
|
+
import { isAiCrawlerUserAgent, reportAiCrawlerHit } from "@businessdash/sdk/analytics-server";
|
|
647
|
+
|
|
648
|
+
export function middleware(request: Request) {
|
|
649
|
+
const ua = request.headers.get("user-agent");
|
|
650
|
+
if (isAiCrawlerUserAgent(ua)) {
|
|
651
|
+
// Fire-and-forget — never block or fail the crawler's request.
|
|
652
|
+
void reportAiCrawlerHit({
|
|
653
|
+
siteId: process.env.BIAB_SITE_ID!,
|
|
654
|
+
baseUrl: process.env.NEXT_PUBLIC_BIAB_API_URL!,
|
|
655
|
+
apiKey: process.env.NEXT_PUBLIC_BIAB_PUBLISHABLE_KEY!,
|
|
656
|
+
userAgent: ua,
|
|
657
|
+
path: new URL(request.url).pathname,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
- **`isAiCrawlerUserAgent(ua)`** — cheap prefilter mirroring the platform's
|
|
664
|
+
AI-crawler classifier (GPTBot, OAI-SearchBot, ClaudeBot/Claude-User/
|
|
665
|
+
Claude-SearchBot, PerplexityBot, GrokBot/xAI-Crawler, Google-CloudVertexBot,
|
|
666
|
+
GoogleOther, Applebot-Extended, Bytespider, CCBot, Amazonbot, MistralAI-User,
|
|
667
|
+
DuckAssistBot, YouBot, AI2Bot, …). The platform's list is authoritative — a
|
|
668
|
+
lagging local list only under-reports, never records bad data.
|
|
669
|
+
- **`reportAiCrawlerHit(options)`** — reports one crawler fetch with the
|
|
670
|
+
publishable key. Resolves quietly on every outcome — analytics must never
|
|
671
|
+
break page serving. Abuse posture: the ingest endpoint keeps a forwarded
|
|
672
|
+
user-agent ONLY when it classifies as a known AI crawler, so this path can
|
|
673
|
+
record crawler visibility but can never inflate human visitor counts.
|
|
674
|
+
- **Read it back — `analytics.aiVisibility()`.** Which crawlers actually
|
|
675
|
+
fetched your pages: fetch counts, distinct pages read, vendor, intent
|
|
676
|
+
(`training` / `search` / `user` — corpus building vs. answer-engine indexing
|
|
677
|
+
vs. a live assistant reading on a person's behalf), and when each last came:
|
|
678
|
+
|
|
679
|
+
```ts
|
|
680
|
+
const ai = await biab.site(siteId).analytics.aiVisibility({ days: 30 });
|
|
681
|
+
if (ai.available) {
|
|
682
|
+
for (const c of ai.crawlers)
|
|
683
|
+
console.log(`${c.name}: ${c.fetches} fetches over ${c.distinctPages} pages`);
|
|
684
|
+
}
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
Ordered most-fetches-first; defaults to a 30-day window. Branch on
|
|
688
|
+
`.available` — same plan gating as `pageViews()`. Contracts export the
|
|
689
|
+
`aiVisibility*` schemas and `AiCrawlerVisibility` types.
|
|
690
|
+
|
|
691
|
+
#### `product()` JSON-LD builder
|
|
692
|
+
|
|
693
|
+
`@businessdash/sdk/seo` adds a schema.org **Product** (+ nested Offer) builder —
|
|
694
|
+
what shopping surfaces and answer engines read off a product page:
|
|
695
|
+
|
|
696
|
+
```ts
|
|
697
|
+
import { product } from "@businessdash/sdk/seo";
|
|
698
|
+
|
|
699
|
+
const jsonLd = product({
|
|
700
|
+
url: "https://acme.co/store/widget",
|
|
701
|
+
name: "Widget",
|
|
702
|
+
brand: "Acme",
|
|
703
|
+
offer: { priceCents: 12900, availability: "InStock" },
|
|
704
|
+
});
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
`priceCents` is integer cents, formatted to schema.org's decimal string here.
|
|
708
|
+
Pair it with the AI Distribution product feed: the feed gets your product INTO
|
|
709
|
+
the engines' catalogs; this markup is what their crawlers verify on the landing
|
|
710
|
+
page itself.
|
|
711
|
+
|
|
712
|
+
#### Formula library: `SUBSTITUTE` + `SLUGIFY`
|
|
713
|
+
|
|
714
|
+
Two additions to `FORMULA_FUNCTIONS` (the engine behind the Math/calculation
|
|
715
|
+
form element and Business Functions, via `@businessdash/sdk/forms`):
|
|
716
|
+
|
|
717
|
+
- **`SUBSTITUTE(text, find, replace, [find2, replace2, …])`** — multi-pair text
|
|
718
|
+
replacement applied in a **single pass**, so a replacement's output is never
|
|
719
|
+
itself re-replaced (pairs stay independent of their order; longest match wins
|
|
720
|
+
at each position).
|
|
721
|
+
- **`SLUGIFY(text)`** — the platform's canonical strict slug: lowercase, accents
|
|
722
|
+
stripped, non-alphanumerics collapsed to single hyphens.
|
|
723
|
+
`SLUGIFY("Solar Panel Install (2026)")` → `"solar-panel-install-2026"`.
|
|
724
|
+
|
|
725
|
+
#### Platform-side in this release window
|
|
726
|
+
|
|
727
|
+
Customer Loyalty (points + tiers) and a Customer Portal expansion (including a
|
|
728
|
+
staff preview-as-customer mode) shipped on the dashboard/portal side — no new
|
|
729
|
+
SDK exports yet. The org-grade OAuth MCP connector (the assistant acting as org
|
|
730
|
+
STAFF, with a consent screen and per-tool write gates) lives on the platform
|
|
731
|
+
host; the SDK's `mcp` entrypoint above fronts the public site-visitor
|
|
732
|
+
connector.
|
|
733
|
+
|
|
734
|
+
### 0.9.52 — Single-Resource Forms, Typed Join Tables & a Careers Surface
|
|
735
|
+
|
|
736
|
+
The schema pipeline learns to generate atomic Create/Edit/Delete forms,
|
|
737
|
+
many-to-many joins carry typed payloads, public job postings get a client, two
|
|
738
|
+
new form elements land, and a batch of parity fixes makes the dashboard and an
|
|
739
|
+
SDK-rendered form finally agree on conditional logic.
|
|
740
|
+
|
|
741
|
+
#### `createSingleResourceForm()` — Create / Edit / Delete instead of a wizard
|
|
742
|
+
|
|
743
|
+
```ts
|
|
744
|
+
import { createSingleResourceForm } from "@businessdash/sdk/forms";
|
|
745
|
+
|
|
746
|
+
const { create, edit, delete: remove, dependencies } =
|
|
747
|
+
createSingleResourceForm(object);
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
- Generates the **three atomic, single-record forms** for one collection —
|
|
751
|
+
Create, Edit and Delete — instead of one big multi-step wizard. Edit and
|
|
752
|
+
Delete lead with a record picker ("which row are we acting on?").
|
|
753
|
+
- **Relation fields become live collection pickers** automatically, single or
|
|
754
|
+
multi according to the relation's own cardinality, so a many-to-many is
|
|
755
|
+
filled in inline and the join table never surfaces as a concept.
|
|
756
|
+
- **Delete states its impact honestly** — read from the relations that actually
|
|
757
|
+
point AT the collection and their declared `onDelete`, rather than asserting
|
|
758
|
+
a generic "respects referential integrity".
|
|
759
|
+
- **Lineage-safe.** `create` keeps the base `data-model:<uid>` sourceKey the
|
|
760
|
+
one-form-per-collection generator has always used, so promoting a model that
|
|
761
|
+
previously produced a single form updates that draft rather than orphaning it
|
|
762
|
+
and creating a duplicate; `edit`/`delete` take suffixed keys off the same
|
|
763
|
+
stem. Types `SingleResourceForms` / `SingleResourceFormOptions` are exported
|
|
764
|
+
from `@businessdash/sdk` and `@businessdash/sdk/forms`.
|
|
765
|
+
|
|
766
|
+
#### Typed join tables — `belongsToMany({ fields })` end-to-end
|
|
767
|
+
|
|
768
|
+
- A join's columns — declared with the same `bd` field builders as a
|
|
769
|
+
collection's — now compile into typed **link-attribute definitions**
|
|
770
|
+
(`LinkAttributeDefinition`, mirroring the platform's link-attributes
|
|
771
|
+
contract), the typed payload each many-to-many link row may carry.
|
|
772
|
+
- A link is an EDGE, not a record: only a small set of value kinds makes sense
|
|
773
|
+
on one. Anything richer maps to TEXT rather than silently pretending to be
|
|
774
|
+
supported — richer shapes are asking for a real collection.
|
|
775
|
+
|
|
776
|
+
#### `client.hiring` — careers pages on your own domain
|
|
777
|
+
|
|
778
|
+
The org's open job postings and their categories, so a consumer can render a
|
|
779
|
+
careers page in their own design system:
|
|
780
|
+
|
|
781
|
+
```ts
|
|
782
|
+
const { items } = await client.hiring.list({ limit: 20 });
|
|
783
|
+
const posting = await client.hiring.get("senior-installer");
|
|
784
|
+
if (posting.applicationFormId) {
|
|
785
|
+
const form = await client.forms.schema(posting.applicationFormId);
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
- **`list({ limit, offset, category })`** — one page of open postings,
|
|
790
|
+
newest-first, offset-paged via `nextOffset`. Only postings that are
|
|
791
|
+
published, active, have seats left, and are under their application cap are
|
|
792
|
+
returned — a careers page never advertises a role that can no longer be
|
|
793
|
+
applied to.
|
|
794
|
+
- **`get(slug)`** — rejects with a 404 for drafts, closed, archived, filled, or
|
|
795
|
+
capped postings; treat that as "this role is gone", not a transient failure.
|
|
796
|
+
- **`categories()`** — job categories with a count of open postings in each,
|
|
797
|
+
enough for filter chips or grouped sections without fetching every posting.
|
|
798
|
+
- **Read-only by construction.** Internal hiring state (openings filled,
|
|
799
|
+
application caps, default permissions/roles, interview questions) never
|
|
800
|
+
crosses this boundary. To take applications, hand the posting's
|
|
801
|
+
`applicationFormId` to the forms surface — this client grants no submit path
|
|
802
|
+
of its own. Contracts export `jobPostingSchema`, `jobCategorySchema`, and
|
|
803
|
+
friends.
|
|
804
|
+
|
|
805
|
+
#### New form elements: `icon_select` and `image_input`
|
|
806
|
+
|
|
807
|
+
- **`icon_select`** — pick an icon by name from a searchable grid. The value is
|
|
808
|
+
the icon's kebab-case name; `iconConfig.allowedIcons` curates the choices (a
|
|
809
|
+
curated list skips the network entirely), otherwise the picker lazily fetches
|
|
810
|
+
the platform's icon-name index. Names render as
|
|
811
|
+
`<img src="…/api/icons/<name>.svg">` — the SDK deliberately bundles no icon
|
|
812
|
+
set (~1,600 icons, and three of the bindings can't import a React package),
|
|
813
|
+
so the platform stays the single source of truth for what a name means.
|
|
814
|
+
- **`image_input`** — one image as a single URL string, from an upload OR a
|
|
815
|
+
pasted URL (`imageInputConfig.sources: "both" | "upload" | "url"`).
|
|
816
|
+
- **Display variants** — `display` extends beyond `toggle`/`checkbox` to
|
|
817
|
+
`"dropdown"` and `"combobox"`. Display-only: never affects the submitted
|
|
818
|
+
value or its validation. Dropdowns self-upgrade to a searchable input past 10
|
|
819
|
+
options, or when the builder chose the ComboBox card
|
|
820
|
+
(`SEARCHABLE_THRESHOLD` / `shouldSearchOptions`).
|
|
821
|
+
- **Shared element helpers** exported from `@businessdash/sdk/forms`:
|
|
822
|
+
`BIAB_ICON_BASE`, `iconUrl`, `ICON_INDEX_URL`, `fetchIconNames`,
|
|
823
|
+
`filterIconNames`, `SEARCHABLE_THRESHOLD`, `shouldSearchOptions`. The new
|
|
824
|
+
elements render in the React, Vue, Svelte and Angular bindings.
|
|
825
|
+
|
|
826
|
+
#### Conditional-logic parity — the dashboard and your site now agree
|
|
827
|
+
|
|
828
|
+
- **`match: "any"` is honored.** The clause combinator was missing from the
|
|
829
|
+
SDK's rule type, so a rule the builder saved as "ANY of" silently evaluated
|
|
830
|
+
as "ALL of" once it rendered through the SDK — the dashboard and a consumer's
|
|
831
|
+
site disagreed about the same form. Fixed across every binding.
|
|
832
|
+
- **`matchField` on collection-picker rules.** Collection pickers store record
|
|
833
|
+
IDs — a rule can now compare a column of the chosen record instead
|
|
834
|
+
(`"__label__"` = the displayed label); unresolved falls back to the raw
|
|
835
|
+
value.
|
|
836
|
+
- **Conditional PAGES.** `input_group` now gates on its `conditionRule` (at the
|
|
837
|
+
root of a consecutive form a group IS a page, so a rule there makes the whole
|
|
838
|
+
page conditional). A page whose rule is currently false is DROPPED from the
|
|
839
|
+
step partition — "Next" skips straight past it and the progress indicator
|
|
840
|
+
loses its entry — in React, Svelte, Vue, Angular, Solid, Qwik and vanilla
|
|
841
|
+
alike. Its required fields no longer block Next/Submit, and stale values
|
|
842
|
+
behind a hidden group no longer decide an `or_condition` branch. New
|
|
843
|
+
`isNodeVisible` export.
|
|
844
|
+
|
|
845
|
+
#### CLI: `wipe-database`
|
|
846
|
+
|
|
847
|
+
- **`biab-dev wipe-database --collections a,b`** — developer shortcut that
|
|
848
|
+
wipes records AND removes the named collections from the schema: the
|
|
849
|
+
backup-first data-wipe flow plus a pruned schema-draft push. The schema
|
|
850
|
+
change still requires dashboard promotion.
|
|
851
|
+
- **`--all`** calls the data-model **reset** endpoint, which atomically
|
|
852
|
+
soft-deletes all metadata, clears records, and removes schema slots. The
|
|
853
|
+
server saves and verifies the backup before resetting any data.
|
|
854
|
+
- Accepts `--dry-run`, and `--yes` to collapse to a single confirmation
|
|
855
|
+
(unlike `wipe-collections`, which stays interactive-only by design).
|
|
856
|
+
|
|
857
|
+
#### Fixes
|
|
858
|
+
|
|
859
|
+
- **Node-ESM import specifiers.** Internal extensionless imports in the
|
|
860
|
+
validation and schema modules gained explicit `.js` specifiers
|
|
861
|
+
(`./enums.js`, `./email-domains.js`, `./units.js`, `./us-states.js`) — no
|
|
862
|
+
more resolution failures for Node ESM / `NodeNext` consumers.
|
|
863
|
+
- **`createFormSchemaOrchestrator`** throws a focused error when `boundaries`
|
|
864
|
+
isn't an array, instead of failing obscurely mid-walk.
|
|
865
|
+
|
|
866
|
+
### 0.9.51 — Pipeline Completion
|
|
867
|
+
|
|
868
|
+
The 0.9.50 notes below were written at 0.9.51 and describe the unified
|
|
869
|
+
Schema/Validation/Form pipeline **as completed in this release** — several
|
|
870
|
+
documented pieces landed between the two npm artifacts. If you installed the
|
|
871
|
+
0.9.50 artifact, upgrading gets you:
|
|
872
|
+
|
|
873
|
+
- **`biab-dev wipe-collections`** — the backup-first record wipe (two-stage
|
|
874
|
+
confirmation, verified Media backup, state-bound execution) documented under
|
|
875
|
+
0.9.50.
|
|
876
|
+
- **Generated-form schema sync** — `sync-data-model` accepts the full
|
|
877
|
+
orchestrator result, offers the interactive candidate multi-select, and
|
|
878
|
+
supports `--forms`, `--forms slug-a,slug-b`, and `--no-forms` for CI.
|
|
879
|
+
- **The compile-time `Infer*` family** — `InferFieldValue`,
|
|
880
|
+
`InferFieldsValues`, `InferCollectionValues`, `InferFormValues`,
|
|
881
|
+
`InferSchemaValues` (+ the type-inference compile tests behind them).
|
|
882
|
+
- **Multi-location business details** — `companyAddressSchema` /
|
|
883
|
+
`companyLocationSchema`, `BusinessAddress` / `BusinessLocation`,
|
|
884
|
+
`localBusinessLocations()` + `biabSchemas.localBusinessLocations()`, and the
|
|
885
|
+
extended `localBusiness()` builder.
|
|
886
|
+
- **Deterministic fluent identities** — collection, field, injected
|
|
887
|
+
primary-key, junction, and generated-form IDs converge across CLI
|
|
888
|
+
evaluations; camelCase junction fields and distinct `source…Id` /
|
|
889
|
+
`target…Id` self-relation fields.
|
|
890
|
+
- **Correct published artifacts** — root declarations generated from the
|
|
891
|
+
TypeScript build (replacing the handwritten ambient declaration) and the
|
|
892
|
+
`dist/index.cjs` runtime file the `require` export map had been promising.
|
|
893
|
+
- **Legacy docs archive** — the 0.9.35 README/CHANGELOG moved to
|
|
894
|
+
`docs-legacy/0.9.35/`, and the package README was rebuilt around the 0.9.50
|
|
895
|
+
pipeline.
|
|
896
|
+
|
|
897
|
+
## Legacy Docs
|
|
898
|
+
|
|
899
|
+
The 0.9.35 docs are archived at [`docs-legacy/0.9.35/`](./docs-legacy/0.9.35/).
|