@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/src/errors.ts
ADDED
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
export class BiabApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly body: unknown;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
|
|
6
|
+
constructor(input: {
|
|
7
|
+
status: number;
|
|
8
|
+
path: string;
|
|
9
|
+
message: string;
|
|
10
|
+
body: unknown;
|
|
11
|
+
}) {
|
|
12
|
+
super(input.message);
|
|
13
|
+
this.name = "BiabApiError";
|
|
14
|
+
this.status = input.status;
|
|
15
|
+
this.body = input.body;
|
|
16
|
+
this.path = input.path;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Discriminated body BIAB returns when the package API refuses to
|
|
22
|
+
* run a handler — three reasons:
|
|
23
|
+
*
|
|
24
|
+
* - `plan_required` — feature not in the org's plan (customer can
|
|
25
|
+
* still use site; upsell card is the right UI).
|
|
26
|
+
* - `payment_required` — org's billing is 0-60 days past-due.
|
|
27
|
+
* Static-data routes still serve; interactive ones return this
|
|
28
|
+
* shape and the org's customer-facing site keeps working.
|
|
29
|
+
* - `service_suspended` — 60+ days past-due. Everything returns
|
|
30
|
+
* this shape. The SDK consumer should render a 503 fallback page
|
|
31
|
+
* so search engines treat the outage as temporary, not
|
|
32
|
+
* permanent.
|
|
33
|
+
*
|
|
34
|
+
* READ endpoints return 200 with this body so consumers can branch
|
|
35
|
+
* cleanly without try/catch; WRITE endpoints return 402 so
|
|
36
|
+
* accidental mutations surface as exceptions.
|
|
37
|
+
*/
|
|
38
|
+
export type BiabPlanGateBody =
|
|
39
|
+
| {
|
|
40
|
+
available: false;
|
|
41
|
+
reason: "plan_required";
|
|
42
|
+
requiredFeature: string;
|
|
43
|
+
requiredPlan: string;
|
|
44
|
+
planDisplayName: string;
|
|
45
|
+
upgradeUrl: string;
|
|
46
|
+
message: string;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
available: false;
|
|
50
|
+
reason: "payment_required";
|
|
51
|
+
daysLapsed: number;
|
|
52
|
+
daysUntilSuspension: number;
|
|
53
|
+
upgradeUrl: string;
|
|
54
|
+
message: string;
|
|
55
|
+
}
|
|
56
|
+
| {
|
|
57
|
+
available: false;
|
|
58
|
+
reason: "service_suspended";
|
|
59
|
+
daysLapsed: number;
|
|
60
|
+
upgradeUrl: string;
|
|
61
|
+
message: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export function isBiabPlanGateBody(value: unknown): value is BiabPlanGateBody {
|
|
65
|
+
if (!value || typeof value !== "object") return false;
|
|
66
|
+
const v = value as Record<string, unknown>;
|
|
67
|
+
if (v["available"] !== false) return false;
|
|
68
|
+
if (
|
|
69
|
+
v["reason"] !== "plan_required" &&
|
|
70
|
+
v["reason"] !== "payment_required" &&
|
|
71
|
+
v["reason"] !== "service_suspended"
|
|
72
|
+
) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return (
|
|
76
|
+
typeof v["upgradeUrl"] === "string" && typeof v["message"] === "string"
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Base class for every billing/entitlement rejection. Catch this to
|
|
82
|
+
* handle "the SDK refused to serve" uniformly; catch one of the three
|
|
83
|
+
* subclasses (or `instanceof`-narrow on this class) to branch on the
|
|
84
|
+
* specific reason.
|
|
85
|
+
*/
|
|
86
|
+
export class BiabAccessRejectedError extends Error {
|
|
87
|
+
readonly reason: "plan_required" | "payment_required" | "service_suspended";
|
|
88
|
+
readonly upgradeUrl: string;
|
|
89
|
+
readonly featureMessage: string;
|
|
90
|
+
readonly path: string;
|
|
91
|
+
readonly status: number;
|
|
92
|
+
|
|
93
|
+
constructor(input: {
|
|
94
|
+
body: BiabPlanGateBody;
|
|
95
|
+
path: string;
|
|
96
|
+
status: number;
|
|
97
|
+
}) {
|
|
98
|
+
super(input.body.message);
|
|
99
|
+
this.name = "BiabAccessRejectedError";
|
|
100
|
+
this.reason = input.body.reason;
|
|
101
|
+
this.upgradeUrl = input.body.upgradeUrl;
|
|
102
|
+
this.featureMessage = input.body.message;
|
|
103
|
+
this.path = input.path;
|
|
104
|
+
this.status = input.status;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Thrown when the org's plan doesn't include the feature the call
|
|
110
|
+
* needs. The org can resolve this by upgrading (or by adding the
|
|
111
|
+
* specific feature à la carte once that surface ships).
|
|
112
|
+
*/
|
|
113
|
+
export class BiabFeatureNotEntitledError extends BiabAccessRejectedError {
|
|
114
|
+
readonly feature: string;
|
|
115
|
+
readonly requiredPlan: string;
|
|
116
|
+
readonly planDisplayName: string;
|
|
117
|
+
|
|
118
|
+
constructor(input: {
|
|
119
|
+
body: Extract<BiabPlanGateBody, { reason: "plan_required" }>;
|
|
120
|
+
path: string;
|
|
121
|
+
status: number;
|
|
122
|
+
}) {
|
|
123
|
+
super(input);
|
|
124
|
+
this.name = "BiabFeatureNotEntitledError";
|
|
125
|
+
this.feature = input.body.requiredFeature;
|
|
126
|
+
this.requiredPlan = input.body.requiredPlan;
|
|
127
|
+
this.planDisplayName = input.body.planDisplayName;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Thrown when the org's billing is 0-60 days past-due. Static-data
|
|
133
|
+
* routes (marketing bundle, sitemap, robots, parallel-page reads,
|
|
134
|
+
* gallery/blog reads) continue serving normally — interactive ones
|
|
135
|
+
* (forms, cart, scheduling bookings, chatbot writes) throw this.
|
|
136
|
+
*/
|
|
137
|
+
export class BiabPaymentLapsedError extends BiabAccessRejectedError {
|
|
138
|
+
readonly daysLapsed: number;
|
|
139
|
+
readonly daysUntilSuspension: number;
|
|
140
|
+
|
|
141
|
+
constructor(input: {
|
|
142
|
+
body: Extract<BiabPlanGateBody, { reason: "payment_required" }>;
|
|
143
|
+
path: string;
|
|
144
|
+
status: number;
|
|
145
|
+
}) {
|
|
146
|
+
super(input);
|
|
147
|
+
this.name = "BiabPaymentLapsedError";
|
|
148
|
+
this.daysLapsed = input.body.daysLapsed;
|
|
149
|
+
this.daysUntilSuspension = input.body.daysUntilSuspension;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Thrown when the org's billing has been past-due for more than 60
|
|
155
|
+
* days. Every SDK call throws this; the consumer's framework-level
|
|
156
|
+
* page handler should catch it and return HTTP 503 with a "site
|
|
157
|
+
* temporarily unavailable" page so search engines treat the outage
|
|
158
|
+
* as temporary. `robots.txt` and `sitemap.xml` separately switch to
|
|
159
|
+
* suspended-mode bodies (`Disallow: /` / empty) for the same reason.
|
|
160
|
+
*/
|
|
161
|
+
export class BiabServiceSuspendedError extends BiabAccessRejectedError {
|
|
162
|
+
readonly daysLapsed: number;
|
|
163
|
+
|
|
164
|
+
constructor(input: {
|
|
165
|
+
body: Extract<BiabPlanGateBody, { reason: "service_suspended" }>;
|
|
166
|
+
path: string;
|
|
167
|
+
status: number;
|
|
168
|
+
}) {
|
|
169
|
+
super(input);
|
|
170
|
+
this.name = "BiabServiceSuspendedError";
|
|
171
|
+
this.daysLapsed = input.body.daysLapsed;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Factory the transport uses to throw the right subclass.
|
|
177
|
+
*/
|
|
178
|
+
export function accessRejectionFromBody(input: {
|
|
179
|
+
body: BiabPlanGateBody;
|
|
180
|
+
path: string;
|
|
181
|
+
status: number;
|
|
182
|
+
}): BiabAccessRejectedError {
|
|
183
|
+
switch (input.body.reason) {
|
|
184
|
+
case "plan_required":
|
|
185
|
+
return new BiabFeatureNotEntitledError({
|
|
186
|
+
body: input.body,
|
|
187
|
+
path: input.path,
|
|
188
|
+
status: input.status,
|
|
189
|
+
});
|
|
190
|
+
case "payment_required":
|
|
191
|
+
return new BiabPaymentLapsedError({
|
|
192
|
+
body: input.body,
|
|
193
|
+
path: input.path,
|
|
194
|
+
status: input.status,
|
|
195
|
+
});
|
|
196
|
+
case "service_suspended":
|
|
197
|
+
return new BiabServiceSuspendedError({
|
|
198
|
+
body: input.body,
|
|
199
|
+
path: input.path,
|
|
200
|
+
status: input.status,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Result shape returned by `unwrapAccess` — either the data the
|
|
207
|
+
* inner promise resolved to, OR a `rejected` descriptor (one of the
|
|
208
|
+
* three reasons) so the consumer can render the appropriate card.
|
|
209
|
+
* Errors that aren't access rejections continue to throw.
|
|
210
|
+
*
|
|
211
|
+
* ```ts
|
|
212
|
+
* const result = await unwrapAccess(() => client.gallery.list());
|
|
213
|
+
* if (result.rejected) {
|
|
214
|
+
* switch (result.rejected.reason) {
|
|
215
|
+
* case "plan_required": return <UpsellCard {...result.rejected} />;
|
|
216
|
+
* case "payment_required": return <BillingLapsedCard {...result.rejected} />;
|
|
217
|
+
* case "service_suspended": return <SiteUnavailableCard {...result.rejected} />;
|
|
218
|
+
* }
|
|
219
|
+
* }
|
|
220
|
+
* return <Gallery items={result.data} />;
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
export type UnwrappedAccess<T> =
|
|
224
|
+
| { data: T; rejected: null }
|
|
225
|
+
| {
|
|
226
|
+
data: null;
|
|
227
|
+
rejected:
|
|
228
|
+
| {
|
|
229
|
+
reason: "plan_required";
|
|
230
|
+
feature: string;
|
|
231
|
+
requiredPlan: string;
|
|
232
|
+
planDisplayName: string;
|
|
233
|
+
upgradeUrl: string;
|
|
234
|
+
message: string;
|
|
235
|
+
}
|
|
236
|
+
| {
|
|
237
|
+
reason: "payment_required";
|
|
238
|
+
daysLapsed: number;
|
|
239
|
+
daysUntilSuspension: number;
|
|
240
|
+
upgradeUrl: string;
|
|
241
|
+
message: string;
|
|
242
|
+
}
|
|
243
|
+
| {
|
|
244
|
+
reason: "service_suspended";
|
|
245
|
+
daysLapsed: number;
|
|
246
|
+
upgradeUrl: string;
|
|
247
|
+
message: string;
|
|
248
|
+
};
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export async function unwrapAccess<T>(
|
|
252
|
+
fn: () => Promise<T>,
|
|
253
|
+
): Promise<UnwrappedAccess<T>> {
|
|
254
|
+
try {
|
|
255
|
+
const data = await fn();
|
|
256
|
+
return { data, rejected: null };
|
|
257
|
+
} catch (err) {
|
|
258
|
+
if (err instanceof BiabFeatureNotEntitledError) {
|
|
259
|
+
return {
|
|
260
|
+
data: null,
|
|
261
|
+
rejected: {
|
|
262
|
+
reason: "plan_required",
|
|
263
|
+
feature: err.feature,
|
|
264
|
+
requiredPlan: err.requiredPlan,
|
|
265
|
+
planDisplayName: err.planDisplayName,
|
|
266
|
+
upgradeUrl: err.upgradeUrl,
|
|
267
|
+
message: err.featureMessage,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
if (err instanceof BiabPaymentLapsedError) {
|
|
272
|
+
return {
|
|
273
|
+
data: null,
|
|
274
|
+
rejected: {
|
|
275
|
+
reason: "payment_required",
|
|
276
|
+
daysLapsed: err.daysLapsed,
|
|
277
|
+
daysUntilSuspension: err.daysUntilSuspension,
|
|
278
|
+
upgradeUrl: err.upgradeUrl,
|
|
279
|
+
message: err.featureMessage,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (err instanceof BiabServiceSuspendedError) {
|
|
284
|
+
return {
|
|
285
|
+
data: null,
|
|
286
|
+
rejected: {
|
|
287
|
+
reason: "service_suspended",
|
|
288
|
+
daysLapsed: err.daysLapsed,
|
|
289
|
+
upgradeUrl: err.upgradeUrl,
|
|
290
|
+
message: err.featureMessage,
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
throw err;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Back-compat alias for the plan-only escape hatch. Same shape as
|
|
300
|
+
* `unwrapAccess` but only catches `BiabFeatureNotEntitledError` —
|
|
301
|
+
* other access rejections (payment lapse, suspension) still throw.
|
|
302
|
+
* Prefer `unwrapAccess` for new code.
|
|
303
|
+
*/
|
|
304
|
+
export type UnwrappedEntitlement<T> =
|
|
305
|
+
| { data: T; notEntitled: null }
|
|
306
|
+
| {
|
|
307
|
+
data: null;
|
|
308
|
+
notEntitled: {
|
|
309
|
+
feature: string;
|
|
310
|
+
requiredPlan: string;
|
|
311
|
+
planDisplayName: string;
|
|
312
|
+
upgradeUrl: string;
|
|
313
|
+
featureMessage: string | null;
|
|
314
|
+
};
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export async function unwrapEntitlement<T>(
|
|
318
|
+
fn: () => Promise<T>,
|
|
319
|
+
): Promise<UnwrappedEntitlement<T>> {
|
|
320
|
+
try {
|
|
321
|
+
const data = await fn();
|
|
322
|
+
return { data, notEntitled: null };
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (err instanceof BiabFeatureNotEntitledError) {
|
|
325
|
+
return {
|
|
326
|
+
data: null,
|
|
327
|
+
notEntitled: {
|
|
328
|
+
feature: err.feature,
|
|
329
|
+
requiredPlan: err.requiredPlan,
|
|
330
|
+
planDisplayName: err.planDisplayName,
|
|
331
|
+
upgradeUrl: err.upgradeUrl,
|
|
332
|
+
featureMessage: err.featureMessage,
|
|
333
|
+
},
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
throw err;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML autofill (`autocomplete`) resolution for `<BiabForm>` inputs.
|
|
3
|
+
*
|
|
4
|
+
* Browsers autofill an input when it carries a recognized `autocomplete` token
|
|
5
|
+
* (https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill).
|
|
6
|
+
* The SDK infers a sensible token per field (so a "Full name" text field gets
|
|
7
|
+
* `name`, an email field gets `email`, etc.), and lets the consumer:
|
|
8
|
+
*
|
|
9
|
+
* - turn it off globally (`<BiabForm autoComplete={false}>` → every input
|
|
10
|
+
* gets `autocomplete="off"`), and
|
|
11
|
+
* - override per field (`fieldAutoComplete={{ [fieldId]: "off" | true | token }}`),
|
|
12
|
+
*
|
|
13
|
+
* Precedence (highest first): per-field override → schema token (`field.autocomplete`)
|
|
14
|
+
* → global off → inferred token.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { FormCoreField } from "./validation.js";
|
|
18
|
+
|
|
19
|
+
/** Global + per-field autofill configuration, threaded from `<BiabForm>`. */
|
|
20
|
+
export type AutocompleteConfig = {
|
|
21
|
+
/** Global default. `false` emits `autocomplete="off"` on every input.
|
|
22
|
+
* Defaults to `true` (infer tokens). */
|
|
23
|
+
enabled?: boolean;
|
|
24
|
+
/** Per-field overrides keyed by field id: a literal token, `true` (infer),
|
|
25
|
+
* or `false` (off). Wins over everything else. */
|
|
26
|
+
overrides?: Record<string, string | boolean>;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Label/key → token heuristics, in priority order (first match wins). */
|
|
30
|
+
const KEY_TOKEN_MAP: ReadonlyArray<readonly [RegExp, string]> = [
|
|
31
|
+
[/first[\s_-]?name|given[\s_-]?name/i, "given-name"],
|
|
32
|
+
[/last[\s_-]?name|family[\s_-]?name|surname/i, "family-name"],
|
|
33
|
+
[/full[\s_-]?name|your[\s_-]?name|^name$|contact[\s_-]?name/i, "name"],
|
|
34
|
+
[/company|organi[sz]ation|business[\s_-]?name|employer/i, "organization"],
|
|
35
|
+
[/job[\s_-]?title|position|occupation/i, "organization-title"],
|
|
36
|
+
[/web[\s_-]?site|homepage|\burl\b/i, "url"],
|
|
37
|
+
[
|
|
38
|
+
/address[\s_-]?line[\s_-]?2|apartment|\bapt\b|\bsuite\b|\bunit\b/i,
|
|
39
|
+
"address-line2",
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
/street|address[\s_-]?line[\s_-]?1|^address1?$|street[\s_-]?address/i,
|
|
43
|
+
"street-address",
|
|
44
|
+
],
|
|
45
|
+
[/\bcity\b|\btown\b/i, "address-level2"],
|
|
46
|
+
[/\bstate\b|province|region/i, "address-level1"],
|
|
47
|
+
[/\bzip\b|postal|post[\s_-]?code/i, "postal-code"],
|
|
48
|
+
[/\bcountry\b/i, "country-name"],
|
|
49
|
+
[/birth|\bdob\b/i, "bday"],
|
|
50
|
+
[
|
|
51
|
+
/work[\s_-]?phone|office[\s_-]?phone|company[\s_-]?phone|mobile|cell/i,
|
|
52
|
+
"tel",
|
|
53
|
+
],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Best-guess `autocomplete` token for a field from its type, then its key/label.
|
|
58
|
+
* Returns `undefined` when nothing sensible applies (e.g. a freeform number) so
|
|
59
|
+
* the attribute can be omitted entirely.
|
|
60
|
+
*/
|
|
61
|
+
export function inferAutocompleteToken(
|
|
62
|
+
field: FormCoreField,
|
|
63
|
+
): string | undefined {
|
|
64
|
+
switch (field.type) {
|
|
65
|
+
case "email":
|
|
66
|
+
return "email";
|
|
67
|
+
case "phone":
|
|
68
|
+
return "tel";
|
|
69
|
+
case "url":
|
|
70
|
+
return "url";
|
|
71
|
+
}
|
|
72
|
+
const haystack = `${field.key ?? ""} ${field.label ?? ""}`;
|
|
73
|
+
for (const [pattern, token] of KEY_TOKEN_MAP) {
|
|
74
|
+
if (pattern.test(haystack)) return token;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the final `autocomplete` attribute value for a field, or `undefined`
|
|
81
|
+
* to omit it. Applies the precedence documented above.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveFieldAutocomplete(
|
|
84
|
+
field: FormCoreField,
|
|
85
|
+
config: AutocompleteConfig,
|
|
86
|
+
): string | undefined {
|
|
87
|
+
const override = config.overrides?.[field.id];
|
|
88
|
+
if (typeof override === "string") return override;
|
|
89
|
+
if (override === false) return "off";
|
|
90
|
+
if (override === true) return inferAutocompleteToken(field) ?? "on";
|
|
91
|
+
|
|
92
|
+
if (typeof field.autocomplete === "string" && field.autocomplete) {
|
|
93
|
+
return field.autocomplete;
|
|
94
|
+
}
|
|
95
|
+
if (config.enabled === false) return "off";
|
|
96
|
+
return inferAutocompleteToken(field);
|
|
97
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Business-function runtime — the typed layer over the formula engine.
|
|
3
|
+
*
|
|
4
|
+
* A business function has typed **inputs** (with required/default) and one or
|
|
5
|
+
* more typed **outputs** ("exported variables"), each its own expression. This
|
|
6
|
+
* module runs a function definition end-to-end and *self-corrects*: a missing
|
|
7
|
+
* optional input falls back to its default; a missing required input or a
|
|
8
|
+
* bad/null/wrong-type output is flagged per-output (with an optional fallback)
|
|
9
|
+
* while the other outputs still resolve — it never throws out. Outputs may
|
|
10
|
+
* reference inputs, prior outputs (declaration order = a mini-pipeline), and
|
|
11
|
+
* other business functions (composition).
|
|
12
|
+
*
|
|
13
|
+
* Pure + isomorphic (no DB, no React). The registry/UI and the form runner build
|
|
14
|
+
* on this; the SDK mirrors it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
type EvalOptions,
|
|
19
|
+
type FormulaObject,
|
|
20
|
+
type FormulaValue,
|
|
21
|
+
type FormulaVars,
|
|
22
|
+
isFormulaObjectValue,
|
|
23
|
+
parseFormula,
|
|
24
|
+
runFormula,
|
|
25
|
+
} from "./formula.js";
|
|
26
|
+
|
|
27
|
+
export type FnValueType =
|
|
28
|
+
| "number"
|
|
29
|
+
| "text"
|
|
30
|
+
| "boolean"
|
|
31
|
+
| "date"
|
|
32
|
+
| "object"
|
|
33
|
+
| "any";
|
|
34
|
+
|
|
35
|
+
export interface FnInput {
|
|
36
|
+
key: string;
|
|
37
|
+
label?: string;
|
|
38
|
+
type: FnValueType;
|
|
39
|
+
required?: boolean;
|
|
40
|
+
default?: FormulaValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface FnOutput {
|
|
44
|
+
key: string;
|
|
45
|
+
label?: string;
|
|
46
|
+
type: FnValueType;
|
|
47
|
+
/** Expression over inputs / prior outputs / other functions. */
|
|
48
|
+
formula: string;
|
|
49
|
+
/** Used when the computed value is missing/invalid for the declared type. */
|
|
50
|
+
fallback?: FormulaValue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface BusinessFunctionDef {
|
|
54
|
+
slug?: string;
|
|
55
|
+
inputs: FnInput[];
|
|
56
|
+
outputs: FnOutput[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FnIssue {
|
|
60
|
+
scope: "input" | "output";
|
|
61
|
+
key: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface FnRunResult {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
/** The exported object — successfully computed outputs (incl. applied fallbacks). */
|
|
68
|
+
outputs: FormulaObject;
|
|
69
|
+
issues: FnIssue[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}([T ].*)?$/;
|
|
73
|
+
|
|
74
|
+
/** Coerce a raw value to a declared type, or return an error reason. */
|
|
75
|
+
function coerce(
|
|
76
|
+
value: unknown,
|
|
77
|
+
type: FnValueType,
|
|
78
|
+
): { ok: true; value: FormulaValue } | { ok: false; reason: string } {
|
|
79
|
+
if (type === "any") return { ok: true, value: value as FormulaValue };
|
|
80
|
+
if (type === "object") {
|
|
81
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
82
|
+
return { ok: true, value: value as FormulaObject };
|
|
83
|
+
return { ok: false, reason: "expected an object" };
|
|
84
|
+
}
|
|
85
|
+
if (type === "number") {
|
|
86
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
87
|
+
return Number.isNaN(n)
|
|
88
|
+
? { ok: false, reason: "not a number" }
|
|
89
|
+
: { ok: true, value: n };
|
|
90
|
+
}
|
|
91
|
+
if (type === "boolean") {
|
|
92
|
+
if (typeof value === "boolean") return { ok: true, value };
|
|
93
|
+
if (value === "true" || value === 1) return { ok: true, value: true };
|
|
94
|
+
if (value === "false" || value === 0) return { ok: true, value: false };
|
|
95
|
+
return { ok: false, reason: "not a boolean" };
|
|
96
|
+
}
|
|
97
|
+
if (type === "date") {
|
|
98
|
+
const s = String(value);
|
|
99
|
+
if (ISO_DATE.test(s) && !Number.isNaN(new Date(s).getTime()))
|
|
100
|
+
return { ok: true, value: s };
|
|
101
|
+
return { ok: false, reason: "not a date" };
|
|
102
|
+
}
|
|
103
|
+
// text
|
|
104
|
+
return { ok: true, value: typeof value === "string" ? value : String(value) };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Validate an already-computed value against a declared output type. */
|
|
108
|
+
function matchesType(value: FormulaValue, type: FnValueType): boolean {
|
|
109
|
+
switch (type) {
|
|
110
|
+
case "any":
|
|
111
|
+
return true;
|
|
112
|
+
case "number":
|
|
113
|
+
return typeof value === "number" && !Number.isNaN(value);
|
|
114
|
+
case "text":
|
|
115
|
+
return typeof value === "string";
|
|
116
|
+
case "boolean":
|
|
117
|
+
return typeof value === "boolean";
|
|
118
|
+
case "object":
|
|
119
|
+
return isFormulaObjectValue(value);
|
|
120
|
+
case "date":
|
|
121
|
+
return typeof value === "string" && ISO_DATE.test(value);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Run a business function. `rawInputs` is keyed by input `key`. `options.functions`
|
|
127
|
+
* supplies other business functions for composition. Never throws.
|
|
128
|
+
*/
|
|
129
|
+
export function runBusinessFunction(
|
|
130
|
+
def: BusinessFunctionDef,
|
|
131
|
+
rawInputs: Record<string, unknown>,
|
|
132
|
+
options?: EvalOptions,
|
|
133
|
+
): FnRunResult {
|
|
134
|
+
const issues: FnIssue[] = [];
|
|
135
|
+
const vars: FormulaVars = {};
|
|
136
|
+
|
|
137
|
+
// 1. Coerce inputs (missing optional → default; missing required → issue).
|
|
138
|
+
for (const input of def.inputs) {
|
|
139
|
+
const raw = rawInputs[input.key];
|
|
140
|
+
const missing = raw === undefined || raw === null || raw === "";
|
|
141
|
+
if (missing) {
|
|
142
|
+
if (input.default !== undefined) {
|
|
143
|
+
vars[input.key] = input.default;
|
|
144
|
+
} else if (input.required) {
|
|
145
|
+
issues.push({
|
|
146
|
+
scope: "input",
|
|
147
|
+
key: input.key,
|
|
148
|
+
reason: "required value is missing",
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const c = coerce(raw, input.type);
|
|
154
|
+
if (c.ok) vars[input.key] = c.value;
|
|
155
|
+
else issues.push({ scope: "input", key: input.key, reason: c.reason });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 2. Evaluate each output in order; later outputs can read earlier ones.
|
|
159
|
+
const outputs: FormulaObject = {};
|
|
160
|
+
for (const out of def.outputs) {
|
|
161
|
+
const r = runFormula(out.formula, vars, options);
|
|
162
|
+
if (!r.ok) {
|
|
163
|
+
if (out.fallback !== undefined) {
|
|
164
|
+
outputs[out.key] = out.fallback;
|
|
165
|
+
vars[out.key] = out.fallback;
|
|
166
|
+
} else {
|
|
167
|
+
issues.push({ scope: "output", key: out.key, reason: r.error });
|
|
168
|
+
}
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (!matchesType(r.value, out.type)) {
|
|
172
|
+
if (out.fallback !== undefined) {
|
|
173
|
+
outputs[out.key] = out.fallback;
|
|
174
|
+
vars[out.key] = out.fallback;
|
|
175
|
+
} else {
|
|
176
|
+
issues.push({
|
|
177
|
+
scope: "output",
|
|
178
|
+
key: out.key,
|
|
179
|
+
reason: `expected ${out.type}`,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
outputs[out.key] = r.value;
|
|
185
|
+
vars[out.key] = r.value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { ok: issues.length === 0, outputs, issues };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Build the {@link EvalOptions} function map from a set of business functions,
|
|
192
|
+
* so formulas can call them by name. Params are the function's input keys (in
|
|
193
|
+
* order); a call resolves to the function's FIRST output (single-output
|
|
194
|
+
* convenience) — multi-output composition is wired via input bindings, not
|
|
195
|
+
* in-formula calls. */
|
|
196
|
+
export function functionsToEvalOptions(
|
|
197
|
+
defs: Record<string, BusinessFunctionDef>,
|
|
198
|
+
): EvalOptions {
|
|
199
|
+
const functions: NonNullable<EvalOptions["functions"]> = {};
|
|
200
|
+
for (const [name, def] of Object.entries(defs)) {
|
|
201
|
+
const first = def.outputs[0];
|
|
202
|
+
if (!first) continue;
|
|
203
|
+
functions[name] = {
|
|
204
|
+
params: def.inputs.map((i) => i.key),
|
|
205
|
+
formula: parseFormula(first.formula),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return { functions };
|
|
209
|
+
}
|