@businessdash/sdk 0.9.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +899 -0
  3. package/dist/adapters-revalidate.d.ts +44 -0
  4. package/dist/adapters-revalidate.d.ts.map +1 -0
  5. package/dist/adapters-revalidate.js +68 -0
  6. package/dist/adapters-revalidate.js.map +1 -0
  7. package/dist/alpha.d.ts +21 -0
  8. package/dist/alpha.d.ts.map +1 -0
  9. package/dist/alpha.js +21 -0
  10. package/dist/alpha.js.map +1 -0
  11. package/dist/analytics-core.d.ts +34 -0
  12. package/dist/analytics-core.d.ts.map +1 -0
  13. package/dist/analytics-core.js +138 -0
  14. package/dist/analytics-core.js.map +1 -0
  15. package/dist/analytics-server.d.ts +54 -0
  16. package/dist/analytics-server.d.ts.map +1 -0
  17. package/dist/analytics-server.js +78 -0
  18. package/dist/analytics-server.js.map +1 -0
  19. package/dist/attribution-logo.d.ts +2 -0
  20. package/dist/attribution-logo.d.ts.map +1 -0
  21. package/dist/attribution-logo.js +5 -0
  22. package/dist/attribution-logo.js.map +1 -0
  23. package/dist/auth-handler.d.ts +92 -0
  24. package/dist/auth-handler.d.ts.map +1 -0
  25. package/dist/auth-handler.js +277 -0
  26. package/dist/auth-handler.js.map +1 -0
  27. package/dist/biab-forms.css +484 -0
  28. package/dist/cli.d.ts +46 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +1824 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/client.d.ts +1681 -0
  33. package/dist/client.d.ts.map +1 -0
  34. package/dist/client.js +2845 -0
  35. package/dist/client.js.map +1 -0
  36. package/dist/collections.d.ts +13 -0
  37. package/dist/collections.d.ts.map +1 -0
  38. package/dist/collections.js +13 -0
  39. package/dist/collections.js.map +1 -0
  40. package/dist/contracts.d.ts +14609 -0
  41. package/dist/contracts.d.ts.map +1 -0
  42. package/dist/contracts.js +2428 -0
  43. package/dist/contracts.js.map +1 -0
  44. package/dist/data-model-schema.d.ts +962 -0
  45. package/dist/data-model-schema.d.ts.map +1 -0
  46. package/dist/data-model-schema.js +2295 -0
  47. package/dist/data-model-schema.js.map +1 -0
  48. package/dist/distribution.d.ts +38 -0
  49. package/dist/distribution.d.ts.map +1 -0
  50. package/dist/distribution.js +61 -0
  51. package/dist/distribution.js.map +1 -0
  52. package/dist/element.d.ts +121 -0
  53. package/dist/element.d.ts.map +1 -0
  54. package/dist/element.js +276 -0
  55. package/dist/element.js.map +1 -0
  56. package/dist/enums.d.ts +25 -0
  57. package/dist/enums.d.ts.map +1 -0
  58. package/dist/enums.js +38 -0
  59. package/dist/enums.js.map +1 -0
  60. package/dist/errors.d.ts +195 -0
  61. package/dist/errors.d.ts.map +1 -0
  62. package/dist/errors.js +188 -0
  63. package/dist/errors.js.map +1 -0
  64. package/dist/forms-core/autocomplete.d.ts +37 -0
  65. package/dist/forms-core/autocomplete.d.ts.map +1 -0
  66. package/dist/forms-core/autocomplete.js +82 -0
  67. package/dist/forms-core/autocomplete.js.map +1 -0
  68. package/dist/forms-core/business-function.d.ts +61 -0
  69. package/dist/forms-core/business-function.d.ts.map +1 -0
  70. package/dist/forms-core/business-function.js +149 -0
  71. package/dist/forms-core/business-function.js.map +1 -0
  72. package/dist/forms-core/calculation.d.ts +72 -0
  73. package/dist/forms-core/calculation.d.ts.map +1 -0
  74. package/dist/forms-core/calculation.js +103 -0
  75. package/dist/forms-core/calculation.js.map +1 -0
  76. package/dist/forms-core/controller.d.ts +249 -0
  77. package/dist/forms-core/controller.d.ts.map +1 -0
  78. package/dist/forms-core/controller.js +590 -0
  79. package/dist/forms-core/controller.js.map +1 -0
  80. package/dist/forms-core/elements.d.ts +44 -0
  81. package/dist/forms-core/elements.d.ts.map +1 -0
  82. package/dist/forms-core/elements.js +68 -0
  83. package/dist/forms-core/elements.js.map +1 -0
  84. package/dist/forms-core/email-domains.d.ts +10 -0
  85. package/dist/forms-core/email-domains.d.ts.map +1 -0
  86. package/dist/forms-core/email-domains.js +69 -0
  87. package/dist/forms-core/email-domains.js.map +1 -0
  88. package/dist/forms-core/file-rules.d.ts +51 -0
  89. package/dist/forms-core/file-rules.d.ts.map +1 -0
  90. package/dist/forms-core/file-rules.js +105 -0
  91. package/dist/forms-core/file-rules.js.map +1 -0
  92. package/dist/forms-core/formula.d.ts +114 -0
  93. package/dist/forms-core/formula.d.ts.map +1 -0
  94. package/dist/forms-core/formula.js +1170 -0
  95. package/dist/forms-core/formula.js.map +1 -0
  96. package/dist/forms-core/index.d.ts +24 -0
  97. package/dist/forms-core/index.d.ts.map +1 -0
  98. package/dist/forms-core/index.js +28 -0
  99. package/dist/forms-core/index.js.map +1 -0
  100. package/dist/forms-core/units.d.ts +49 -0
  101. package/dist/forms-core/units.d.ts.map +1 -0
  102. package/dist/forms-core/units.js +255 -0
  103. package/dist/forms-core/units.js.map +1 -0
  104. package/dist/forms-core/us-states.d.ts +14 -0
  105. package/dist/forms-core/us-states.d.ts.map +1 -0
  106. package/dist/forms-core/us-states.js +66 -0
  107. package/dist/forms-core/us-states.js.map +1 -0
  108. package/dist/forms-core/validation.d.ts +266 -0
  109. package/dist/forms-core/validation.d.ts.map +1 -0
  110. package/dist/forms-core/validation.js +850 -0
  111. package/dist/forms-core/validation.js.map +1 -0
  112. package/dist/forms.d.ts +19 -0
  113. package/dist/forms.d.ts.map +1 -0
  114. package/dist/forms.js +29 -0
  115. package/dist/forms.js.map +1 -0
  116. package/dist/gallery-types.d.ts +32 -0
  117. package/dist/gallery-types.d.ts.map +1 -0
  118. package/dist/gallery-types.js +2 -0
  119. package/dist/gallery-types.js.map +1 -0
  120. package/dist/graphql.d.ts +97 -0
  121. package/dist/graphql.d.ts.map +1 -0
  122. package/dist/graphql.js +212 -0
  123. package/dist/graphql.js.map +1 -0
  124. package/dist/index.cjs +19448 -0
  125. package/dist/index.d.cts +19 -0
  126. package/dist/index.d.mts +19 -0
  127. package/dist/index.d.ts +19 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/index.js +20 -0
  130. package/dist/index.js.map +1 -0
  131. package/dist/marketing-schema.d.ts +216 -0
  132. package/dist/marketing-schema.d.ts.map +1 -0
  133. package/dist/marketing-schema.js +200 -0
  134. package/dist/marketing-schema.js.map +1 -0
  135. package/dist/mcp.d.ts +42 -0
  136. package/dist/mcp.d.ts.map +1 -0
  137. package/dist/mcp.js +87 -0
  138. package/dist/mcp.js.map +1 -0
  139. package/dist/native.d.ts +45 -0
  140. package/dist/native.d.ts.map +1 -0
  141. package/dist/native.js +43 -0
  142. package/dist/native.js.map +1 -0
  143. package/dist/next-revalidate.d.ts +43 -0
  144. package/dist/next-revalidate.d.ts.map +1 -0
  145. package/dist/next-revalidate.js +84 -0
  146. package/dist/next-revalidate.js.map +1 -0
  147. package/dist/parallel-pages.d.ts +102 -0
  148. package/dist/parallel-pages.d.ts.map +1 -0
  149. package/dist/parallel-pages.js +108 -0
  150. package/dist/parallel-pages.js.map +1 -0
  151. package/dist/payment-icons.d.ts +66 -0
  152. package/dist/payment-icons.d.ts.map +1 -0
  153. package/dist/payment-icons.js +160 -0
  154. package/dist/payment-icons.js.map +1 -0
  155. package/dist/proxy.d.ts +67 -0
  156. package/dist/proxy.d.ts.map +1 -0
  157. package/dist/proxy.js +97 -0
  158. package/dist/proxy.js.map +1 -0
  159. package/dist/react-analytics.d.ts +39 -0
  160. package/dist/react-analytics.d.ts.map +1 -0
  161. package/dist/react-analytics.js +59 -0
  162. package/dist/react-analytics.js.map +1 -0
  163. package/dist/react-attribution.d.ts +99 -0
  164. package/dist/react-attribution.d.ts.map +1 -0
  165. package/dist/react-attribution.js +52 -0
  166. package/dist/react-attribution.js.map +1 -0
  167. package/dist/react-marketing.d.ts +181 -0
  168. package/dist/react-marketing.d.ts.map +1 -0
  169. package/dist/react-marketing.js +215 -0
  170. package/dist/react-marketing.js.map +1 -0
  171. package/dist/react.d.ts +727 -0
  172. package/dist/react.d.ts.map +1 -0
  173. package/dist/react.js +2418 -0
  174. package/dist/react.js.map +1 -0
  175. package/dist/reserved-paths.d.ts +24 -0
  176. package/dist/reserved-paths.d.ts.map +1 -0
  177. package/dist/reserved-paths.js +60 -0
  178. package/dist/reserved-paths.js.map +1 -0
  179. package/dist/revalidate-verify.d.ts +30 -0
  180. package/dist/revalidate-verify.d.ts.map +1 -0
  181. package/dist/revalidate-verify.js +74 -0
  182. package/dist/revalidate-verify.js.map +1 -0
  183. package/dist/sdk.d.ts +678 -0
  184. package/dist/sdk.d.ts.map +1 -0
  185. package/dist/sdk.js +801 -0
  186. package/dist/sdk.js.map +1 -0
  187. package/dist/seo.d.ts +218 -0
  188. package/dist/seo.d.ts.map +1 -0
  189. package/dist/seo.js +309 -0
  190. package/dist/seo.js.map +1 -0
  191. package/dist/socials.d.ts +47 -0
  192. package/dist/socials.d.ts.map +1 -0
  193. package/dist/socials.js +172 -0
  194. package/dist/socials.js.map +1 -0
  195. package/dist/static.d.ts +14 -0
  196. package/dist/static.d.ts.map +1 -0
  197. package/dist/static.js +14 -0
  198. package/dist/static.js.map +1 -0
  199. package/dist/url-pattern.d.ts +47 -0
  200. package/dist/url-pattern.d.ts.map +1 -0
  201. package/dist/url-pattern.js +124 -0
  202. package/dist/url-pattern.js.map +1 -0
  203. package/dist/vanilla.d.ts +77 -0
  204. package/dist/vanilla.d.ts.map +1 -0
  205. package/dist/vanilla.js +903 -0
  206. package/dist/vanilla.js.map +1 -0
  207. package/openapi.json +13779 -0
  208. package/package.json +204 -0
  209. package/src/adapters-revalidate.ts +88 -0
  210. package/src/alpha.ts +89 -0
  211. package/src/analytics-core.ts +175 -0
  212. package/src/analytics-server.ts +99 -0
  213. package/src/angular/biab-form.component.ts +1443 -0
  214. package/src/angular/index.ts +26 -0
  215. package/src/attribution-logo.ts +5 -0
  216. package/src/auth-handler.ts +352 -0
  217. package/src/cli.ts +2517 -0
  218. package/src/client.ts +3578 -0
  219. package/src/collections.ts +101 -0
  220. package/src/contracts.ts +3390 -0
  221. package/src/data-model-schema.ts +4042 -0
  222. package/src/distribution.ts +73 -0
  223. package/src/element.ts +321 -0
  224. package/src/enums.ts +65 -0
  225. package/src/errors.ts +338 -0
  226. package/src/forms-core/autocomplete.ts +97 -0
  227. package/src/forms-core/business-function.ts +209 -0
  228. package/src/forms-core/calculation.ts +167 -0
  229. package/src/forms-core/controller.ts +922 -0
  230. package/src/forms-core/elements.ts +86 -0
  231. package/src/forms-core/email-domains.ts +68 -0
  232. package/src/forms-core/file-rules.ts +123 -0
  233. package/src/forms-core/formula.ts +1339 -0
  234. package/src/forms-core/index.ts +122 -0
  235. package/src/forms-core/units.ts +308 -0
  236. package/src/forms-core/us-states.ts +68 -0
  237. package/src/forms-core/validation.ts +1037 -0
  238. package/src/forms.ts +63 -0
  239. package/src/gallery-types.ts +31 -0
  240. package/src/graphql.ts +295 -0
  241. package/src/index.ts +505 -0
  242. package/src/marketing-schema.ts +395 -0
  243. package/src/mcp.ts +108 -0
  244. package/src/native.ts +73 -0
  245. package/src/next-revalidate.ts +124 -0
  246. package/src/parallel-pages.ts +192 -0
  247. package/src/payment-icons.ts +205 -0
  248. package/src/proxy.ts +173 -0
  249. package/src/qwik.tsx +1234 -0
  250. package/src/react-analytics.tsx +76 -0
  251. package/src/react-attribution.tsx +192 -0
  252. package/src/react-marketing.tsx +338 -0
  253. package/src/react.tsx +5495 -0
  254. package/src/reserved-paths.ts +65 -0
  255. package/src/revalidate-verify.ts +98 -0
  256. package/src/sdk.ts +1671 -0
  257. package/src/seo.ts +488 -0
  258. package/src/socials.ts +230 -0
  259. package/src/solid.tsx +1466 -0
  260. package/src/static.ts +21 -0
  261. package/src/styles/biab-forms.css +484 -0
  262. package/src/svelte/BiabForm.svelte +1069 -0
  263. package/src/svelte/context.ts +57 -0
  264. package/src/svelte/index.ts +79 -0
  265. package/src/url-pattern.ts +153 -0
  266. package/src/vanilla.ts +1290 -0
  267. package/src/vue/BiabForm.vue +1538 -0
  268. package/src/vue/index.ts +119 -0
package/src/client.ts ADDED
@@ -0,0 +1,3578 @@
1
+ import { z } from "zod";
2
+
3
+ import {
4
+ type AddressDetailsResponse,
5
+ type AddressFormValues,
6
+ type AddressSearchInput,
7
+ type AddressSearchResponse,
8
+ type AddressVerifyResponse,
9
+ type AiVisibilityInput,
10
+ type AiVisibilityResponse,
11
+ type AttributionHeartbeatInput,
12
+ type AttributionHeartbeatResponse,
13
+ type AttributionPolicy,
14
+ type AuthIntrospectResponse,
15
+ addressDetailsResponseSchema,
16
+ addressFormValuesSchema,
17
+ addressSearchInputSchema,
18
+ addressSearchResponseSchema,
19
+ addressVerifyResponseSchema,
20
+ aiVisibilityInputSchema,
21
+ aiVisibilityResponseSchema,
22
+ attributionHeartbeatInputSchema,
23
+ attributionHeartbeatResponseSchema,
24
+ attributionPolicySchema,
25
+ authIntrospectResponseSchema,
26
+ type BlogCategoryListResponse,
27
+ type BlogCommentListResponse,
28
+ type BlogCommentPostInput,
29
+ type BlogCommentPostResponse,
30
+ type BlogLikeResponse,
31
+ type BlogListPostsResponse,
32
+ type BlogPostBySlugResponse,
33
+ type BlogSessionInput,
34
+ type BlogSessionResponse,
35
+ type BlogTagListResponse,
36
+ blogCategoryListResponseSchema,
37
+ blogCommentListResponseSchema,
38
+ blogCommentPostInputSchema,
39
+ blogCommentPostResponseSchema,
40
+ blogLikeResponseSchema,
41
+ blogListPostsResponseSchema,
42
+ blogPostBySlugResponseSchema,
43
+ blogSessionInputSchema,
44
+ blogSessionResponseSchema,
45
+ blogTagListResponseSchema,
46
+ type CartAddItemInput,
47
+ type CartCouponInput,
48
+ type CartSessionResponse,
49
+ type CartSnapshot,
50
+ type CartUpdateItemInput,
51
+ type ChatbotAppendMessageInput,
52
+ type ChatbotAvailability,
53
+ type ChatbotChatInput,
54
+ type ChatbotChatResponse,
55
+ type ChatbotConfig,
56
+ type ChatbotConfigUpdateInput,
57
+ type ChatbotFormDefinition,
58
+ type ChatbotFormSubmitInput,
59
+ type ChatbotFormSubmitResponse,
60
+ type ChatbotMessagesResponse,
61
+ type ChatbotPersistedSessionInput,
62
+ type ChatbotPersistedSessionResponse,
63
+ type ChatbotRequestHumanInput,
64
+ type ChatbotRequestHumanResponse,
65
+ type ChatbotSessionResponse,
66
+ type CheckoutSessionInput,
67
+ type CheckoutSessionResponse,
68
+ type CheckoutStartInput,
69
+ type CheckoutStartResponse,
70
+ type CheckoutStatusResponse,
71
+ type ConfirmVerificationInput,
72
+ type ConfirmVerificationResponse,
73
+ type CouponValidateResponse,
74
+ type CreateCollectionInput,
75
+ type CreateCollectionResponse,
76
+ type CreateEmailTemplateInput,
77
+ type CreateEmailTemplateResponse,
78
+ type CreateEmailTemplateVersionInput,
79
+ type CreateEmailTemplateVersionResponse,
80
+ type CustomerContactCompanyInput,
81
+ type CustomerContactCompanyResponse,
82
+ type CustomerContractDetail,
83
+ type CustomerContractSignResponse,
84
+ type CustomerDownloadsResponse,
85
+ type CustomerInvoiceDetail,
86
+ type CustomerInvoicePayInput,
87
+ type CustomerInvoicePayResponse,
88
+ type CustomerInvoicePdf,
89
+ type CustomerJobActivityResponse,
90
+ type CustomerJobComment,
91
+ type CustomerJobCommentPostInput,
92
+ type CustomerJobCommentsListResponse,
93
+ type CustomerJobDetail,
94
+ type CustomerJobEtaResponse,
95
+ type CustomerNotificationPreferencesResponse,
96
+ type CustomerNotificationPreferencesUpdateInput,
97
+ type CustomerNotificationPreferencesUpdateResponse,
98
+ type CustomerOrderDetail,
99
+ type CustomerOrderListResponse,
100
+ type CustomerPortalContextResponse,
101
+ type CustomerPortalOtherOrgsResponse,
102
+ type CustomerPortalProfile,
103
+ type CustomerPortalUpdateProfileInput,
104
+ type CustomerPortalUpdateProfileResponse,
105
+ type CustomerQuoteActionResponse,
106
+ type CustomerQuoteDetail,
107
+ type CustomerReferralHistoryResponse,
108
+ type CustomerReferralPayoutsResponse,
109
+ type CustomerReferralStats,
110
+ type CustomerReturnRequestCreated,
111
+ type CustomerReturnRequestInput,
112
+ type CustomerReviewListResponse,
113
+ type CustomerReviewSubmitInput,
114
+ type CustomerReviewSubmitResponse,
115
+ type CustomerShipmentTracking,
116
+ type CustomerWorkBundle,
117
+ cartAddItemInputSchema,
118
+ cartCouponInputSchema,
119
+ cartSessionResponseSchema,
120
+ cartSnapshotSchema,
121
+ cartUpdateItemInputSchema,
122
+ chatbotAppendMessageInputSchema,
123
+ chatbotAvailabilitySchema,
124
+ chatbotChatInputSchema,
125
+ chatbotChatResponseSchema,
126
+ chatbotConfigSchema,
127
+ chatbotConfigUpdateInputSchema,
128
+ chatbotFormDefinitionSchema,
129
+ chatbotFormSubmitInputSchema,
130
+ chatbotFormSubmitResponseSchema,
131
+ chatbotMessagesResponseSchema,
132
+ chatbotPersistedSessionInputSchema,
133
+ chatbotPersistedSessionResponseSchema,
134
+ chatbotRequestHumanInputSchema,
135
+ chatbotRequestHumanResponseSchema,
136
+ chatbotSessionResponseSchema,
137
+ chatbotUploadUrlResponseSchema,
138
+ checkoutSessionInputSchema,
139
+ checkoutSessionResponseSchema,
140
+ checkoutStartInputSchema,
141
+ checkoutStartResponseSchema,
142
+ checkoutStatusResponseSchema,
143
+ confirmVerificationInputSchema,
144
+ confirmVerificationResponseSchema,
145
+ couponValidateInputSchema,
146
+ couponValidateResponseSchema,
147
+ createCollectionInputSchema,
148
+ createCollectionResponseSchema,
149
+ createEmailTemplateInputSchema,
150
+ createEmailTemplateResponseSchema,
151
+ createEmailTemplateVersionInputSchema,
152
+ createEmailTemplateVersionResponseSchema,
153
+ customerContactCompanyInputSchema,
154
+ customerContactCompanyResponseSchema,
155
+ customerContractDetailSchema,
156
+ customerContractSignResponseSchema,
157
+ customerDownloadsResponseSchema,
158
+ customerInvoiceDetailSchema,
159
+ customerInvoicePayInputSchema,
160
+ customerInvoicePayResponseSchema,
161
+ customerInvoicePdfSchema,
162
+ customerJobActivityResponseSchema,
163
+ customerJobCommentPostInputSchema,
164
+ customerJobCommentSchema,
165
+ customerJobCommentsListResponseSchema,
166
+ customerJobDetailSchema,
167
+ customerJobEtaResponseSchema,
168
+ customerNotificationPreferencesResponseSchema,
169
+ customerNotificationPreferencesUpdateInputSchema,
170
+ customerNotificationPreferencesUpdateResponseSchema,
171
+ customerOrderDetailSchema,
172
+ customerOrderListResponseSchema,
173
+ customerPaymentHistoryResponseSchema,
174
+ customerPortalContextResponseSchema,
175
+ customerPortalOtherOrgsResponseSchema,
176
+ customerPortalProfileSchema,
177
+ customerPortalUpdateProfileInputSchema,
178
+ customerPortalUpdateProfileResponseSchema,
179
+ customerQuoteActionResponseSchema,
180
+ customerQuoteDetailSchema,
181
+ customerReferralHistoryResponseSchema,
182
+ customerReferralPayoutsResponseSchema,
183
+ customerReferralStatsSchema,
184
+ customerReturnRequestCreatedSchema,
185
+ customerReturnRequestInputSchema,
186
+ customerReviewListResponseSchema,
187
+ customerReviewSubmitInputSchema,
188
+ customerReviewSubmitResponseSchema,
189
+ customerShipmentTrackingSchema,
190
+ customerWorkBundleSchema,
191
+ type DashboardSessionResponse,
192
+ type DataModelRecord,
193
+ dashboardSessionResponseSchema,
194
+ type ExecuteActionInput,
195
+ type ExecuteActionResponse,
196
+ executeActionInputSchema,
197
+ executeActionResponseSchema,
198
+ type FollowerEditInput,
199
+ type FollowerEditResponse,
200
+ type FollowerJoinInput,
201
+ type FollowerJoinResponse,
202
+ type FollowerLeaveInput,
203
+ type FollowerLeaveResponse,
204
+ type FollowerMeInput,
205
+ type FollowerMeResponse,
206
+ followerEditInputSchema,
207
+ followerEditResponseSchema,
208
+ followerJoinInputSchema,
209
+ followerJoinResponseSchema,
210
+ followerLeaveInputSchema,
211
+ followerLeaveResponseSchema,
212
+ followerMeInputSchema,
213
+ followerMeResponseSchema,
214
+ type GetCollectionResponse,
215
+ type GetEmailTemplateResponse,
216
+ type GetMarketingLocalesResponse,
217
+ type GetMarketingPageBundleResponse,
218
+ type GetPublishedMarketingSchemaResponse,
219
+ type GetSiteMarketingPageResponse,
220
+ type GoogleAddress,
221
+ getCollectionResponseSchema,
222
+ getEmailTemplateResponseSchema,
223
+ getMarketingLocalesResponseSchema,
224
+ getMarketingPageBundleResponseSchema,
225
+ getPublishedMarketingSchemaResponseSchema,
226
+ getSiteMarketingPageResponseSchema,
227
+ type JobCategoryListResponse,
228
+ type JobPosting,
229
+ type JobPostingListResponse,
230
+ jobCategoryListResponseSchema,
231
+ jobPostingListResponseSchema,
232
+ jobPostingSchema,
233
+ type ListCollectionsResponse,
234
+ type ListDataModelRecordsInput,
235
+ type ListDataModelRecordsResponse,
236
+ type ListEmailTemplatesInput,
237
+ type ListEmailTemplatesResponse,
238
+ type ListRowsInput,
239
+ type ListRowsResponse,
240
+ type ListSiteMarketingPagesResponse,
241
+ listCollectionsResponseSchema,
242
+ listDataModelRecordsInputSchema,
243
+ listDataModelRecordsResponseSchema,
244
+ listEmailTemplatesInputSchema,
245
+ listEmailTemplatesResponseSchema,
246
+ listRowsInputSchema,
247
+ listRowsResponseSchema,
248
+ listSiteMarketingPagesResponseSchema,
249
+ type NotificationPreferencesResponse,
250
+ type NotificationPreferencesUpdateInput,
251
+ notificationPreferencesResponseSchema,
252
+ notificationPreferencesUpdateInputSchema,
253
+ type PageViewsInput,
254
+ type PageViewsResponse,
255
+ type PublishMarketingSectionsInput,
256
+ type PublishMarketingSectionsResponse,
257
+ pageViewsInputSchema,
258
+ pageViewsResponseSchema,
259
+ publishMarketingSectionsInputSchema,
260
+ publishMarketingSectionsResponseSchema,
261
+ type QueryRowsInput,
262
+ type QueryRowsResponse,
263
+ queryRowsInputSchema,
264
+ queryRowsResponseSchema,
265
+ type ReviewWallListResponse,
266
+ reviewWallListResponseSchema,
267
+ type ShippingRatesInput,
268
+ type ShippingRatesResponse,
269
+ type ShippingSettingsResponse,
270
+ type ShippingTrackResponse,
271
+ type SiteAssetInput,
272
+ type SiteBranding,
273
+ type StartVerificationInput,
274
+ type StartVerificationResponse,
275
+ type StorefrontAddonsResponse,
276
+ type StorefrontCategoriesResponse,
277
+ type StorefrontListProductsResponse,
278
+ type StorefrontProductDetail,
279
+ type StorefrontProductReviewsResponse,
280
+ type StorefrontProductsWithMetaResponse,
281
+ type StorefrontRelatedProductsResponse,
282
+ type StorefrontSessionInput,
283
+ type StorefrontSessionResponse,
284
+ type StorefrontSort,
285
+ type SubscriptionCheckoutInput,
286
+ type SubscriptionCheckoutResponse,
287
+ type SubscriptionOffering,
288
+ type SubscriptionOfferingsListResponse,
289
+ shippingRatesInputSchema,
290
+ shippingRatesResponseSchema,
291
+ shippingSettingsResponseSchema,
292
+ shippingTrackResponseSchema,
293
+ siteAssetInputSchema,
294
+ siteBrandingSchema,
295
+ startVerificationInputSchema,
296
+ startVerificationResponseSchema,
297
+ storefrontAddonsResponseSchema,
298
+ storefrontCategoriesResponseSchema,
299
+ storefrontListProductsResponseSchema,
300
+ storefrontProductDetailSchema,
301
+ storefrontProductReviewsResponseSchema,
302
+ storefrontProductsWithMetaResponseSchema,
303
+ storefrontRelatedProductsResponseSchema,
304
+ storefrontSessionInputSchema,
305
+ storefrontSessionResponseSchema,
306
+ subscriptionCheckoutInputSchema,
307
+ subscriptionCheckoutResponseSchema,
308
+ subscriptionOfferingSchema,
309
+ subscriptionOfferingsListResponseSchema,
310
+ type TenantAuthExchangeInput,
311
+ type TenantAuthExchangeResponse,
312
+ type TenantAuthMeResponse,
313
+ type TenantAuthPasswordResetInput,
314
+ type TenantAuthPasswordResetResponse,
315
+ type TenantAuthSignOutResponse,
316
+ type TenantAuthStartInput,
317
+ type TenantAuthStartResponse,
318
+ tenantAuthExchangeInputSchema,
319
+ tenantAuthExchangeResponseSchema,
320
+ tenantAuthMeResponseSchema,
321
+ tenantAuthPasswordResetInputSchema,
322
+ tenantAuthPasswordResetResponseSchema,
323
+ tenantAuthSignOutResponseSchema,
324
+ tenantAuthStartInputSchema,
325
+ tenantAuthStartResponseSchema,
326
+ type UploadSiteAssetResponse,
327
+ type UpsertDraftMarketingSchemaInput,
328
+ type UpsertDraftMarketingSchemaResponse,
329
+ type UpsertRowInput,
330
+ type UpsertRowResponse,
331
+ uploadSiteAssetResponseSchema,
332
+ upsertDraftMarketingSchemaInputSchema,
333
+ upsertDraftMarketingSchemaResponseSchema,
334
+ upsertRowInputSchema,
335
+ upsertRowResponseSchema,
336
+ } from "./contracts.js";
337
+ import {
338
+ accessRejectionFromBody,
339
+ BiabApiError,
340
+ isBiabPlanGateBody,
341
+ } from "./errors.js";
342
+
343
+ export type FetchLike = typeof fetch;
344
+
345
+ /** Options for {@link BiabDevClient} / {@link createBiabDevClient}. */
346
+ export type BiabDevClientOptions = {
347
+ baseUrl: string;
348
+ apiKey: string;
349
+ fetch?: FetchLike;
350
+ defaultHeaders?: HeadersInit;
351
+ /**
352
+ * Origin to send to the platform on every request. Required when the API
353
+ * key has an Allowed Host set on the host (server-to-server `fetch` does
354
+ * not send `Origin` automatically — without it the platform replies 403
355
+ * `origin_required`). Accepts a full URL (`https://my-site.com`) or a
356
+ * bare hostname (`my-site.com`); only the origin is sent on the wire.
357
+ */
358
+ siteOrigin?: string;
359
+ };
360
+
361
+ /** An array value becomes a repeated param: `?path=/a&path=/b`. */
362
+ type QueryValue =
363
+ | string
364
+ | number
365
+ | boolean
366
+ | Array<string | number>
367
+ | undefined;
368
+
369
+ type RequestOptions<T> = {
370
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
371
+ path: string;
372
+ query?: Record<string, QueryValue>;
373
+ body?: unknown;
374
+ responseSchema: z.ZodType<T, z.ZodTypeDef, unknown>;
375
+ headers?: HeadersInit;
376
+ };
377
+
378
+ function buildUrl(
379
+ baseUrl: string,
380
+ path: string,
381
+ query?: Record<string, QueryValue>,
382
+ ) {
383
+ /**
384
+ * Strip any leading slash from `path` so it is treated as a relative path and
385
+ * appended to `baseUrl`'s pathname (e.g. `/api/package/v1`). Adding a leading
386
+ * slash here would make `path` absolute under the URL spec and silently drop
387
+ * the base URL's path component — see https://url.spec.whatwg.org/#concept-basic-url-parser.
388
+ */
389
+ const relativePath = path.startsWith("/") ? path.slice(1) : path;
390
+ const url = new URL(relativePath, baseUrl);
391
+
392
+ for (const [key, value] of Object.entries(query ?? {})) {
393
+ if (value == null) continue;
394
+ if (Array.isArray(value)) {
395
+ // Repeat the param once per element so the server reads getAll(key).
396
+ for (const item of value) url.searchParams.append(key, String(item));
397
+ } else {
398
+ url.searchParams.set(key, String(value));
399
+ }
400
+ }
401
+
402
+ return url;
403
+ }
404
+
405
+ async function parseResponseBody(response: Response) {
406
+ const text = await response.text();
407
+ if (!text) return null;
408
+
409
+ try {
410
+ return JSON.parse(text) as unknown;
411
+ } catch {
412
+ return text;
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Coerce a user-provided origin (URL or bare hostname) into a wire-safe
418
+ * `scheme://host` value. Returns `undefined` if input is empty.
419
+ */
420
+ function normalizeSiteOrigin(raw: string | undefined): string | undefined {
421
+ if (!raw) return undefined;
422
+ const trimmed = raw.trim();
423
+ if (!trimmed) return undefined;
424
+ const withScheme = /^https?:\/\//i.test(trimmed)
425
+ ? trimmed
426
+ : `https://${trimmed}`;
427
+ try {
428
+ const url = new URL(withScheme);
429
+ return `${url.protocol}//${url.host}`;
430
+ } catch {
431
+ return undefined;
432
+ }
433
+ }
434
+
435
+ function extractErrorMessage(body: unknown, fallback: string) {
436
+ if (body && typeof body === "object" && "message" in body) {
437
+ const message = body.message;
438
+ if (typeof message === "string" && message.trim()) {
439
+ return message;
440
+ }
441
+ }
442
+
443
+ return fallback;
444
+ }
445
+
446
+ /**
447
+ * Typed client for the BIAB package HTTP API.
448
+ *
449
+ * @remarks
450
+ * **Alpha:** This SDK is in alpha. Surfaces and behavior may change without semver guarantees.
451
+ * Do not rely on it for production workloads—use development, staging, or experiments only.
452
+ */
453
+ export class BiabDevClient {
454
+ private readonly baseUrl: string;
455
+ private readonly apiKey: string;
456
+ private readonly fetchImpl: FetchLike;
457
+ private readonly defaultHeaders: HeadersInit | undefined;
458
+
459
+ constructor(options: BiabDevClientOptions) {
460
+ // Trailing slash is required by the WHATWG URL parser so the relative
461
+ // `path` segments are appended to (rather than replacing) the base path.
462
+ this.baseUrl = options.baseUrl.endsWith("/")
463
+ ? options.baseUrl
464
+ : `${options.baseUrl}/`;
465
+ this.apiKey = options.apiKey;
466
+ // Bind `fetch` to its global so that calling it as a method on
467
+ // `this` (`this.fetchImpl(url, init)`) doesn't trigger the
468
+ // "Illegal invocation" error in browsers — `Window.fetch` requires
469
+ // `this === window`. `globalThis` works in Node 18+ and the browser.
470
+ this.fetchImpl =
471
+ options.fetch ??
472
+ ((input: RequestInfo | URL, init?: RequestInit) =>
473
+ globalThis.fetch(input, init));
474
+ this.defaultHeaders = options.defaultHeaders;
475
+ this.siteOrigin = normalizeSiteOrigin(options.siteOrigin);
476
+ }
477
+
478
+ private readonly siteOrigin: string | undefined;
479
+
480
+ site(siteId: string) {
481
+ return new BiabDevSiteClient(this, siteId);
482
+ }
483
+
484
+ get emailTemplates() {
485
+ return new BiabDevEmailTemplatesClient(this);
486
+ }
487
+
488
+ get dashboard() {
489
+ return new BiabDevDashboardClient(this);
490
+ }
491
+
492
+ get storefront() {
493
+ return new BiabDevStorefrontClient(this);
494
+ }
495
+
496
+ get cart() {
497
+ return new BiabDevCartClient(this);
498
+ }
499
+
500
+ get checkout() {
501
+ return new BiabDevCheckoutClient(this);
502
+ }
503
+
504
+ get coupons() {
505
+ return new BiabDevCouponsClient(this);
506
+ }
507
+
508
+ get subscriptions() {
509
+ return new BiabDevSubscriptionsClient(this);
510
+ }
511
+
512
+ get shipping() {
513
+ return new BiabDevShippingClient(this);
514
+ }
515
+
516
+ get notifications() {
517
+ return new BiabDevNotificationsClient(this);
518
+ }
519
+
520
+ get address() {
521
+ return new BiabDevAddressClient(this);
522
+ }
523
+
524
+ get blog() {
525
+ return new BiabDevBlogClient(this);
526
+ }
527
+
528
+ get reviews() {
529
+ return new BiabDevReviewsClient(this);
530
+ }
531
+
532
+ /** Public job postings — build a careers page on your own domain. */
533
+ get hiring() {
534
+ return new BiabDevHiringClient(this);
535
+ }
536
+
537
+ get chatbot() {
538
+ return new BiabDevChatbotClient(this);
539
+ }
540
+
541
+ get auth() {
542
+ return new BiabDevAuthClient(this);
543
+ }
544
+
545
+ /**
546
+ * Customer-portal client. Pin to a specific BIAB organization id so the
547
+ * caller never sees data from any other org (recommended). Omit
548
+ * `organizationId` to fall back to the user's currently-active session
549
+ * org — only use that on first-party hosts where the host already pins
550
+ * the org.
551
+ */
552
+ customerPortal(organizationId?: string) {
553
+ return new BiabDevCustomerPortalClient(this, organizationId ?? null);
554
+ }
555
+
556
+ async introspect(): Promise<AuthIntrospectResponse> {
557
+ return this.request({
558
+ method: "POST",
559
+ path: "auth/introspect",
560
+ responseSchema: authIntrospectResponseSchema,
561
+ });
562
+ }
563
+
564
+ async request<T>(options: RequestOptions<T>): Promise<T> {
565
+ const url = buildUrl(this.baseUrl, options.path, options.query);
566
+ const init: RequestInit = {
567
+ method: options.method ?? "GET",
568
+ headers: {
569
+ Accept: "application/json",
570
+ Authorization: `Bearer ${this.apiKey}`,
571
+ ...(options.body === undefined
572
+ ? {}
573
+ : { "Content-Type": "application/json" }),
574
+ ...(this.siteOrigin ? { Origin: this.siteOrigin } : {}),
575
+ ...(this.defaultHeaders ?? {}),
576
+ ...(options.headers ?? {}),
577
+ },
578
+ };
579
+
580
+ if (options.body !== undefined) {
581
+ init.body = JSON.stringify(options.body);
582
+ }
583
+
584
+ const response = await this.fetchImpl(url, init);
585
+
586
+ const body = await parseResponseBody(response);
587
+
588
+ // Access gate — 200 (reads, body-only signal) and 402 (writes)
589
+ // both surface as a typed error so consumer code is uniform.
590
+ // Three flavours: plan-required (BiabFeatureNotEntitledError),
591
+ // payment-required (BiabPaymentLapsedError), service-suspended
592
+ // (BiabServiceSuspendedError) — all extend BiabAccessRejectedError
593
+ // for "catch any" patterns. `unwrapAccess()` is the opt-in
594
+ // Result-shape escape hatch.
595
+ if (isBiabPlanGateBody(body)) {
596
+ throw accessRejectionFromBody({
597
+ body,
598
+ path: url.pathname,
599
+ status: response.status,
600
+ });
601
+ }
602
+
603
+ if (!response.ok) {
604
+ throw new BiabApiError({
605
+ status: response.status,
606
+ path: url.pathname,
607
+ message: extractErrorMessage(
608
+ body,
609
+ `BIAB API request failed with status ${response.status}.`,
610
+ ),
611
+ body,
612
+ });
613
+ }
614
+
615
+ return options.responseSchema.parse(body);
616
+ }
617
+ }
618
+
619
+ export class BiabDevSiteClient {
620
+ readonly collections: BiabDevCollectionsClient;
621
+ readonly rows: BiabDevRowsClient;
622
+ /**
623
+ * The org's custom database — the tables from `defineDataModel()`.
624
+ * Distinct from `collections`/`rows`, which are Site Data. See
625
+ * {@link BiabDevDataModelClient}.
626
+ */
627
+ readonly dataModel: BiabDevDataModelClient;
628
+ /**
629
+ * Read your site's visitor counts back (the counterpart to the
630
+ * `<BIABAnalytics/>` tracker, which only records). See
631
+ * {@link BiabDevAnalyticsClient}.
632
+ */
633
+ readonly analytics: BiabDevAnalyticsClient;
634
+ /**
635
+ * The "Powered by BusinessDash" badge's policy + heartbeat. See
636
+ * {@link BiabDevAttributionClient}.
637
+ */
638
+ readonly attribution: BiabDevAttributionClient;
639
+ readonly assets: BiabDevAssetsClient;
640
+ readonly actions: BiabDevActionsClient;
641
+ readonly followers: BiabDevFollowersClient;
642
+ readonly marketingPages: BiabDevMarketingPagesClient;
643
+ /**
644
+ * Schema-driven marketing flow (Phase 2). Pair with the SDK's
645
+ * `defineSiteMarketingSchema()` builder + the `biab-dev sync-schema`
646
+ * CLI for the authoring side; this client is the live-site read
647
+ * path (`getPageBundle`) and the upload entry point for the CLI.
648
+ */
649
+ readonly marketing: BiabDevMarketingClient;
650
+ readonly parallelPages: BiabDevParallelPagesClient;
651
+
652
+ constructor(
653
+ readonly client: BiabDevClient,
654
+ readonly siteId: string,
655
+ ) {
656
+ this.collections = new BiabDevCollectionsClient(client, siteId);
657
+ this.rows = new BiabDevRowsClient(client, siteId);
658
+ this.dataModel = new BiabDevDataModelClient(client, siteId);
659
+ this.analytics = new BiabDevAnalyticsClient(client, siteId);
660
+ this.attribution = new BiabDevAttributionClient(client, siteId);
661
+ this.assets = new BiabDevAssetsClient(client, siteId);
662
+ this.actions = new BiabDevActionsClient(client, siteId);
663
+ this.followers = new BiabDevFollowersClient(client, siteId);
664
+ this.marketingPages = new BiabDevMarketingPagesClient(client, siteId);
665
+ this.marketing = new BiabDevMarketingClient(client, siteId);
666
+ this.parallelPages = new BiabDevParallelPagesClient(client, siteId);
667
+ }
668
+
669
+ /**
670
+ * The org's storefront branding — logo + favicon (site static media) plus
671
+ * the org name + icon. Render the masthead/favicon/og without the org
672
+ * re-supplying assets. Available even when billing has lapsed.
673
+ *
674
+ * @example
675
+ * ```ts
676
+ * const b = await client.site(SITE_ID).branding();
677
+ * <img src={b.logoUrl ?? b.orgIcon ?? ""} alt={b.orgName ?? ""} />
678
+ * ```
679
+ */
680
+ async branding(): Promise<SiteBranding> {
681
+ return this.client.request({
682
+ path: `sites/${encodeURIComponent(this.siteId)}/branding`,
683
+ responseSchema: siteBrandingSchema,
684
+ });
685
+ }
686
+ }
687
+
688
+ const parallelPageMetaSchema = z.object({
689
+ title: z.string(),
690
+ description: z.string(),
691
+ canonical: z.string().nullable(),
692
+ ogImage: z.string().nullable(),
693
+ });
694
+
695
+ export const parallelPagesListResponseSchema = z.object({
696
+ items: z.array(
697
+ z.object({
698
+ id: z.string(),
699
+ key: z.string(),
700
+ routePattern: z.string(),
701
+ variables: z.array(
702
+ z.object({
703
+ name: z.string(),
704
+ source: z.object({
705
+ section: z.string(),
706
+ path: z.string().optional(),
707
+ }),
708
+ slugField: z.string(),
709
+ tokenFields: z.array(z.string()),
710
+ }),
711
+ ),
712
+ meta: z.object({
713
+ title: z.string(),
714
+ description: z.string(),
715
+ canonical: z.string().optional(),
716
+ ogImage: z.string().optional(),
717
+ }),
718
+ templateRef: z.string(),
719
+ source: z.enum(["sdk", "site_builder"]),
720
+ crawlRule: z.enum(["inherit", "index", "noindex"]),
721
+ isActive: z.boolean(),
722
+ }),
723
+ ),
724
+ });
725
+
726
+ export const parallelPagesVariantsResponseSchema = z.object({
727
+ variants: z.array(z.record(z.string(), z.string())),
728
+ });
729
+
730
+ export const parallelPagesRenderResponseSchema = z.object({
731
+ key: z.string(),
732
+ routePattern: z.string(),
733
+ slugs: z.record(z.string(), z.string()),
734
+ meta: parallelPageMetaSchema,
735
+ body: z.unknown().nullable(),
736
+ });
737
+
738
+ export type ParallelPagesListResponse = z.infer<
739
+ typeof parallelPagesListResponseSchema
740
+ >;
741
+ export type ParallelPagesVariantsResponse = z.infer<
742
+ typeof parallelPagesVariantsResponseSchema
743
+ >;
744
+ export type ParallelPagesRenderResponse = z.infer<
745
+ typeof parallelPagesRenderResponseSchema
746
+ >;
747
+
748
+ /**
749
+ * Programmatic SEO ("parallel pages") client. Same shape across
750
+ * SDK-defined and site-builder-defined pages — both share the
751
+ * `org_parallel_pages` table on the server.
752
+ *
753
+ * Typical wiring in Next.js:
754
+ *
755
+ * export async function generateStaticParams() {
756
+ * const { variants } = await client
757
+ * .site(SITE_ID)
758
+ * .parallelPages
759
+ * .listVariants("repair-area");
760
+ * return variants;
761
+ * }
762
+ *
763
+ * export default async function Page({ params }) {
764
+ * const bundle = await client
765
+ * .site(SITE_ID)
766
+ * .parallelPages
767
+ * .render("repair-area", await params);
768
+ * return <RenderTemplate bundle={bundle} />;
769
+ * }
770
+ */
771
+ export class BiabDevParallelPagesClient {
772
+ constructor(
773
+ private readonly client: BiabDevClient,
774
+ private readonly siteId: string,
775
+ ) {}
776
+
777
+ async list(): Promise<ParallelPagesListResponse> {
778
+ return this.client.request({
779
+ path: `sites/${encodeURIComponent(this.siteId)}/parallel-pages`,
780
+ responseSchema: parallelPagesListResponseSchema,
781
+ });
782
+ }
783
+
784
+ async listVariants(
785
+ key: string,
786
+ opts: { locale?: string } = {},
787
+ ): Promise<ParallelPagesVariantsResponse> {
788
+ return this.client.request({
789
+ path: `sites/${encodeURIComponent(this.siteId)}/parallel-pages/${encodeURIComponent(key)}/variants`,
790
+ ...(opts.locale ? { query: { locale: opts.locale } } : {}),
791
+ responseSchema: parallelPagesVariantsResponseSchema,
792
+ });
793
+ }
794
+
795
+ async render(
796
+ key: string,
797
+ slugs: Record<string, string>,
798
+ opts: { locale?: string } = {},
799
+ ): Promise<ParallelPagesRenderResponse> {
800
+ return this.client.request({
801
+ path: `sites/${encodeURIComponent(this.siteId)}/parallel-pages/${encodeURIComponent(key)}/render`,
802
+ query: {
803
+ ...slugs,
804
+ ...(opts.locale ? { locale: opts.locale } : {}),
805
+ },
806
+ responseSchema: parallelPagesRenderResponseSchema,
807
+ });
808
+ }
809
+
810
+ /**
811
+ * URL of the auto-generated sitemap. Consumer's `/sitemap.xml` route
812
+ * proxies straight through. Returns null when the client isn't
813
+ * configured with a base URL.
814
+ */
815
+ sitemapUrl(): string {
816
+ return `sites/${encodeURIComponent(this.siteId)}/sitemap.xml`;
817
+ }
818
+
819
+ /**
820
+ * URL of the auto-generated robots.txt. Consumer's `/robots.txt` route
821
+ * proxies straight through.
822
+ */
823
+ robotsUrl(): string {
824
+ return `sites/${encodeURIComponent(this.siteId)}/robots.txt`;
825
+ }
826
+ }
827
+
828
+ /**
829
+ * Process-wide cache for the schema-driven marketing reads, keyed by
830
+ * the response's content-version fingerprint so cache hits are safe
831
+ * across SDK consumer re-renders. Bundle entries are short-lived (the
832
+ * server already sets `Cache-Control: s-maxage=60` so an upstream CDN
833
+ * does most of the work); the cache here is the SDK-process tier
834
+ * that prerendered Next builds reuse across all generated pages.
835
+ */
836
+ const marketingBundleCache: Map<string, GetMarketingPageBundleResponse> =
837
+ new Map();
838
+ const marketingSchemaCache: Map<string, GetPublishedMarketingSchemaResponse> =
839
+ new Map();
840
+ const marketingLocalesCache: Map<string, GetMarketingLocalesResponse> =
841
+ new Map();
842
+
843
+ function bundleCacheKey(
844
+ siteId: string,
845
+ pageKey: string,
846
+ locale: string,
847
+ ): string {
848
+ return `${siteId}/${pageKey}/${locale}`;
849
+ }
850
+
851
+ /**
852
+ * Manually invalidate marketing caches scoped to a site. Useful after a
853
+ * server-side mutation the SDK didn't initiate (e.g. an operator saved a
854
+ * section in the dashboard while the consumer's Next process is still
855
+ * warm).
856
+ */
857
+ export function clearMarketingCache(siteId: string): void {
858
+ const prefix = `${siteId}/`;
859
+ for (const k of Array.from(marketingBundleCache.keys())) {
860
+ if (k.startsWith(prefix)) marketingBundleCache.delete(k);
861
+ }
862
+ marketingSchemaCache.delete(siteId);
863
+ marketingLocalesCache.delete(siteId);
864
+ }
865
+
866
+ /**
867
+ * Live-site read client for the schema-driven marketing flow.
868
+ *
869
+ * Surfaces:
870
+ * - `getPageBundle({ pageKey, locale })` — every section for a page,
871
+ * tagged ok|error per section, plus SEO + availableLocales.
872
+ * - `getPublishedSchema()` — the live schema (returns `{ schema: null }`
873
+ * until the consumer's CLI has published one).
874
+ * - `getDraftSchema()` — what's currently staged (CLI-only path).
875
+ * - `publishDraftSchema(input)` — CLI upload. Idempotent (same
876
+ * checksum = `noop: true`).
877
+ * - `getLocales()` — enabled locales + brand-token map.
878
+ */
879
+ export class BiabDevMarketingClient {
880
+ constructor(
881
+ private readonly client: BiabDevClient,
882
+ private readonly siteId: string,
883
+ ) {}
884
+
885
+ async getPageBundle(
886
+ input: { pageKey?: string; locale?: string; reviewsLimit?: number } = {},
887
+ options: { skipCache?: boolean } = {},
888
+ ): Promise<GetMarketingPageBundleResponse> {
889
+ const pageKey = input.pageKey ?? "home";
890
+ const locale = input.locale ?? "en";
891
+ // reviewsLimit sizes bundle.reviews.items; different sizes are different
892
+ // payloads, so it participates in the cache key.
893
+ const cacheKey =
894
+ bundleCacheKey(this.siteId, pageKey, locale) +
895
+ (input.reviewsLimit != null ? `/r${input.reviewsLimit}` : "");
896
+ if (!options.skipCache) {
897
+ const hit = marketingBundleCache.get(cacheKey);
898
+ if (hit) return hit;
899
+ }
900
+ const response = await this.client.request({
901
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/bundle`,
902
+ query: { pageKey, locale, reviewsLimit: input.reviewsLimit },
903
+ responseSchema: getMarketingPageBundleResponseSchema,
904
+ });
905
+ marketingBundleCache.set(cacheKey, response);
906
+ return response;
907
+ }
908
+
909
+ getPageBundleCached(
910
+ input: { pageKey?: string; locale?: string } = {},
911
+ ): GetMarketingPageBundleResponse | null {
912
+ const pageKey = input.pageKey ?? "home";
913
+ const locale = input.locale ?? "en";
914
+ return (
915
+ marketingBundleCache.get(bundleCacheKey(this.siteId, pageKey, locale)) ??
916
+ null
917
+ );
918
+ }
919
+
920
+ async prefetchPageBundle(
921
+ input: { pageKey?: string; locale?: string } = {},
922
+ ): Promise<GetMarketingPageBundleResponse> {
923
+ return this.getPageBundle(input);
924
+ }
925
+
926
+ async getPublishedSchema(
927
+ options: { skipCache?: boolean } = {},
928
+ ): Promise<GetPublishedMarketingSchemaResponse> {
929
+ if (!options.skipCache) {
930
+ const hit = marketingSchemaCache.get(this.siteId);
931
+ if (hit) return hit;
932
+ }
933
+ const response = await this.client.request({
934
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/published-schema`,
935
+ responseSchema: getPublishedMarketingSchemaResponseSchema,
936
+ });
937
+ marketingSchemaCache.set(this.siteId, response);
938
+ return response;
939
+ }
940
+
941
+ async getDraftSchema(): Promise<GetPublishedMarketingSchemaResponse> {
942
+ // Same response shape as published-schema, just from the other slot.
943
+ // Not cached — the CLI calls this once per upload cycle.
944
+ return this.client.request({
945
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/draft-schema`,
946
+ responseSchema: getPublishedMarketingSchemaResponseSchema,
947
+ });
948
+ }
949
+
950
+ async publishDraftSchema(
951
+ input: UpsertDraftMarketingSchemaInput,
952
+ ): Promise<UpsertDraftMarketingSchemaResponse> {
953
+ const body = upsertDraftMarketingSchemaInputSchema.parse(input);
954
+ return this.client.request({
955
+ method: "POST",
956
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/draft-schema`,
957
+ body,
958
+ responseSchema: upsertDraftMarketingSchemaResponseSchema,
959
+ });
960
+ }
961
+
962
+ /**
963
+ * Bulk-upsert section values. Used by `biab-dev sync-content` to push
964
+ * local JSON content to BIAB. Server caps each request at 256 items;
965
+ * larger imports are batched by the CLI. Per-item failures are
966
+ * reported in the response (`results[].ok === false`) without aborting
967
+ * the rest of the batch.
968
+ *
969
+ * The CLI's `--lax` flag sets `validate: false` for first-time imports
970
+ * of legacy content that doesn't yet match the published schema —
971
+ * useful so writers can pull existing content into BIAB and fix
972
+ * validation errors in the dashboard incrementally.
973
+ */
974
+ async publishSections(
975
+ input: PublishMarketingSectionsInput,
976
+ ): Promise<PublishMarketingSectionsResponse> {
977
+ const body = publishMarketingSectionsInputSchema.parse(input);
978
+ return this.client.request({
979
+ method: "POST",
980
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/sections`,
981
+ body,
982
+ responseSchema: publishMarketingSectionsResponseSchema,
983
+ });
984
+ }
985
+
986
+ async getLocales(
987
+ options: { skipCache?: boolean } = {},
988
+ ): Promise<GetMarketingLocalesResponse> {
989
+ if (!options.skipCache) {
990
+ const hit = marketingLocalesCache.get(this.siteId);
991
+ if (hit) return hit;
992
+ }
993
+ const response = await this.client.request({
994
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing/locales`,
995
+ responseSchema: getMarketingLocalesResponseSchema,
996
+ });
997
+ marketingLocalesCache.set(this.siteId, response);
998
+ return response;
999
+ }
1000
+ }
1001
+
1002
+ /**
1003
+ * Process-wide cache for marketing-page payloads, keyed by `(siteId, pageKey)`.
1004
+ * Shared across `BiabDevMarketingPagesClient` instances so that
1005
+ * `prefetch(pageKey)` warms the same store `get(pageKey)` reads on demand.
1006
+ *
1007
+ * Lives on a module-level `Map` rather than instance state because consumers
1008
+ * commonly create transient `client.site(siteId)` proxies inside React
1009
+ * components — instance-level caching would evict on every re-render.
1010
+ */
1011
+ const marketingPageCache: Map<string, GetSiteMarketingPageResponse> = new Map();
1012
+
1013
+ function marketingPageCacheKey(siteId: string, pageKey: string): string {
1014
+ return `${siteId} ${pageKey}`;
1015
+ }
1016
+
1017
+ /**
1018
+ * Manually clear the SDK's cache for `(siteId, pageKey)` (or the whole
1019
+ * site when `pageKey` is omitted). Useful after a server-side mutation
1020
+ * that the SDK didn't initiate, or in tests.
1021
+ */
1022
+ export function clearMarketingPageCache(
1023
+ siteId: string,
1024
+ pageKey?: string,
1025
+ ): void {
1026
+ if (pageKey) {
1027
+ marketingPageCache.delete(marketingPageCacheKey(siteId, pageKey));
1028
+ return;
1029
+ }
1030
+ const prefix = `${siteId} `;
1031
+ for (const key of Array.from(marketingPageCache.keys())) {
1032
+ if (key.startsWith(prefix)) marketingPageCache.delete(key);
1033
+ }
1034
+ }
1035
+
1036
+ /**
1037
+ * Read-only access to the host's `org_site_marketing_pages` table — the JSON
1038
+ * documents the Site Builder writes for marketing landing pages (e.g. the
1039
+ * `home` page rendered by `custom-demo`).
1040
+ *
1041
+ * Requires the `collections:read` scope on the API key (the same scope used
1042
+ * for collection reads, so existing keys created from the host's Site Builder
1043
+ * → Developer panel work without re-issuing).
1044
+ *
1045
+ * The client transparently caches per-page responses across instances; pair
1046
+ * `list()` (no payloads) with `get(pageKey)` / `prefetch(pageKey)` to load
1047
+ * pages on demand instead of paying the full-content cost up front.
1048
+ */
1049
+ export class BiabDevMarketingPagesClient {
1050
+ constructor(
1051
+ private readonly client: BiabDevClient,
1052
+ private readonly siteId: string,
1053
+ ) {}
1054
+
1055
+ /**
1056
+ * Payload-free directory of every marketing page for the site. Use this
1057
+ * to render a navigation index without loading any content; call
1058
+ * {@link get} or {@link prefetch} when the visitor selects a page.
1059
+ */
1060
+ async list(): Promise<ListSiteMarketingPagesResponse> {
1061
+ return this.client.request({
1062
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing-pages`,
1063
+ responseSchema: listSiteMarketingPagesResponseSchema,
1064
+ });
1065
+ }
1066
+
1067
+ /**
1068
+ * Fetches a single marketing page payload by `pageKey` (defaults to `"home"`).
1069
+ * Returns `{ page: null }` when the host has not published one yet — callers
1070
+ * typically render a static fallback in that case.
1071
+ *
1072
+ * Reads from the shared cache when present (warmed by {@link prefetch} or
1073
+ * a previous `get`). Pass `{ skipCache: true }` to force a network round-trip
1074
+ * (e.g. after a save the SDK didn't make itself).
1075
+ */
1076
+ async get(
1077
+ pageKey = "home",
1078
+ options: { skipCache?: boolean } = {},
1079
+ ): Promise<GetSiteMarketingPageResponse> {
1080
+ const cacheKey = marketingPageCacheKey(this.siteId, pageKey);
1081
+ if (!options.skipCache) {
1082
+ const hit = marketingPageCache.get(cacheKey);
1083
+ if (hit) return hit;
1084
+ }
1085
+ const response = await this.client.request({
1086
+ path: `sites/${encodeURIComponent(this.siteId)}/marketing-pages/${encodeURIComponent(pageKey)}`,
1087
+ responseSchema: getSiteMarketingPageResponseSchema,
1088
+ });
1089
+ marketingPageCache.set(cacheKey, response);
1090
+ return response;
1091
+ }
1092
+
1093
+ /**
1094
+ * Read the cached value for `pageKey` without making a network call.
1095
+ * Returns `null` when the entry hasn't been fetched yet — callers can
1096
+ * fall back to a synchronous static default while {@link prefetch}
1097
+ * warms the cache in the background.
1098
+ */
1099
+ getCached(pageKey: string): GetSiteMarketingPageResponse | null {
1100
+ return (
1101
+ marketingPageCache.get(marketingPageCacheKey(this.siteId, pageKey)) ??
1102
+ null
1103
+ );
1104
+ }
1105
+
1106
+ /**
1107
+ * Warm the SDK cache for `pageKey` without rendering anything yet.
1108
+ * Common pattern: call from a `<Link onPointerEnter>` / `onFocus` so
1109
+ * the next page is already loaded by the time the visitor clicks.
1110
+ * Subsequent `get(pageKey)` calls resolve from cache.
1111
+ */
1112
+ async prefetch(pageKey = "home"): Promise<GetSiteMarketingPageResponse> {
1113
+ return this.get(pageKey);
1114
+ }
1115
+ }
1116
+
1117
+ export class BiabDevCollectionsClient {
1118
+ constructor(
1119
+ private readonly client: BiabDevClient,
1120
+ private readonly siteId: string,
1121
+ ) {}
1122
+
1123
+ async list(): Promise<ListCollectionsResponse> {
1124
+ return this.client.request({
1125
+ path: `sites/${encodeURIComponent(this.siteId)}/collections`,
1126
+ responseSchema: listCollectionsResponseSchema,
1127
+ });
1128
+ }
1129
+
1130
+ async create(
1131
+ input: CreateCollectionInput,
1132
+ ): Promise<CreateCollectionResponse> {
1133
+ const body = createCollectionInputSchema.parse(input);
1134
+
1135
+ return this.client.request({
1136
+ method: "POST",
1137
+ path: `sites/${encodeURIComponent(this.siteId)}/collections`,
1138
+ body,
1139
+ responseSchema: createCollectionResponseSchema,
1140
+ });
1141
+ }
1142
+
1143
+ async get(slug: string): Promise<GetCollectionResponse> {
1144
+ return this.client.request({
1145
+ path: `sites/${encodeURIComponent(this.siteId)}/collections/${encodeURIComponent(slug)}`,
1146
+ responseSchema: getCollectionResponseSchema,
1147
+ });
1148
+ }
1149
+ }
1150
+
1151
+ export class BiabDevRowsClient {
1152
+ constructor(
1153
+ private readonly client: BiabDevClient,
1154
+ private readonly siteId: string,
1155
+ ) {}
1156
+
1157
+ async list(
1158
+ collectionSlug: string,
1159
+ input: ListRowsInput = {},
1160
+ ): Promise<ListRowsResponse> {
1161
+ const query = listRowsInputSchema.parse(input);
1162
+
1163
+ return this.client.request({
1164
+ path: `sites/${encodeURIComponent(this.siteId)}/collections/${encodeURIComponent(collectionSlug)}/rows`,
1165
+ query,
1166
+ responseSchema: listRowsResponseSchema,
1167
+ });
1168
+ }
1169
+
1170
+ async upsert(
1171
+ collectionSlug: string,
1172
+ input: UpsertRowInput,
1173
+ ): Promise<UpsertRowResponse> {
1174
+ const body = upsertRowInputSchema.parse(input);
1175
+
1176
+ return this.client.request({
1177
+ method: "POST",
1178
+ path: `sites/${encodeURIComponent(this.siteId)}/collections/${encodeURIComponent(collectionSlug)}/rows`,
1179
+ body,
1180
+ responseSchema: upsertRowResponseSchema,
1181
+ });
1182
+ }
1183
+
1184
+ async query(
1185
+ collectionSlug: string,
1186
+ input: QueryRowsInput,
1187
+ ): Promise<QueryRowsResponse> {
1188
+ const body = queryRowsInputSchema.parse(input);
1189
+
1190
+ return this.client.request({
1191
+ method: "POST",
1192
+ path: `sites/${encodeURIComponent(this.siteId)}/collections/${encodeURIComponent(collectionSlug)}/query`,
1193
+ body,
1194
+ responseSchema: queryRowsResponseSchema,
1195
+ });
1196
+ }
1197
+ }
1198
+
1199
+ /**
1200
+ * Read rows out of the org's CUSTOM DATABASE — the tables you declare with
1201
+ * `defineDataModel()` and push with `biab-dev sync-data-model`.
1202
+ *
1203
+ * ⚠️ Not the same thing as `.collections` / `.rows`. Those read Site Data (the
1204
+ * site builder's tables, `site_data_rows`). Rows seeded with
1205
+ * `biab-dev sync-records` live in `org_object_records` and are reachable only
1206
+ * through here — no `collections:*` call will ever return one.
1207
+ *
1208
+ * Requires the `metadata:read_records` scope. What a key can actually read is
1209
+ * decided per object by its `visibility`:
1210
+ *
1211
+ * - `private` (default) — a SECRET key only. Call from a server component, a
1212
+ * route handler, or at build time; a `pk_…` key gets "not found".
1213
+ * - `authenticated` — a secret key, or a publishable key carrying a signed-in
1214
+ * tenant session.
1215
+ * - `public` — any key, including a publishable (`pk_…`) one in page JS.
1216
+ *
1217
+ * So a table you declared `public` is readable straight from the browser; a
1218
+ * private one never is. Open a table by declaring `visibility` in your model.
1219
+ *
1220
+ * ```ts
1221
+ * const result = await biab.site(siteId).dataModel.listRecords({
1222
+ * object: TECH_OBJECT_ID, // the universalIdentifier from your config
1223
+ * });
1224
+ * if (!result.available) return <UpgradeCard feature={result.requiredFeature} />;
1225
+ * for (const tech of result.records) {
1226
+ * tech.fields.name; // scalar values, keyed by field name
1227
+ * tech.relations.icon; // [{ recordId, object }] — links, not embedded rows
1228
+ * }
1229
+ * ```
1230
+ */
1231
+ export class BiabDevDataModelClient {
1232
+ constructor(
1233
+ private readonly client: BiabDevClient,
1234
+ private readonly siteId: string,
1235
+ ) {}
1236
+
1237
+ /**
1238
+ * One page of an object's rows, newest first, with relations expanded.
1239
+ *
1240
+ * Relations come back as LINKS (`{ recordId, object }`), not embedded rows —
1241
+ * fetch the targets with a follow-up call for the object they point at.
1242
+ * Every declared relation field is present; `[]` means "no links yet", which
1243
+ * is deliberately distinguishable from a field that wasn't returned.
1244
+ *
1245
+ * Paginate by passing the previous response's `nextCursor` back as `cursor`
1246
+ * until it comes back `null`. The cursor is opaque — don't build one.
1247
+ */
1248
+ async listRecords(
1249
+ input: ListDataModelRecordsInput,
1250
+ ): Promise<ListDataModelRecordsResponse> {
1251
+ const query = listDataModelRecordsInputSchema.parse(input);
1252
+
1253
+ return this.client.request({
1254
+ path: `sites/${encodeURIComponent(this.siteId)}/data-model/records`,
1255
+ query,
1256
+ responseSchema: listDataModelRecordsResponseSchema,
1257
+ });
1258
+ }
1259
+
1260
+ /**
1261
+ * Every row of an object, walking `nextCursor` to exhaustion.
1262
+ *
1263
+ * For build-time rendering of a table you own and whose size you know. It is
1264
+ * an unbounded loop over your data by definition — for anything visitor-facing
1265
+ * or open-ended, page with {@link listRecords} instead.
1266
+ *
1267
+ * Returns the plan-gate shape unchanged if the org lacks the entitlement, so
1268
+ * the same `available` branch works for both.
1269
+ */
1270
+ async listAllRecords(
1271
+ input: Omit<ListDataModelRecordsInput, "cursor">,
1272
+ ): Promise<ListDataModelRecordsResponse> {
1273
+ const records: DataModelRecord[] = [];
1274
+ let cursor: string | undefined;
1275
+
1276
+ do {
1277
+ const page = await this.listRecords({ ...input, cursor });
1278
+ if (!page.available) return page;
1279
+ records.push(...page.records);
1280
+ cursor = page.nextCursor ?? undefined;
1281
+ } while (cursor);
1282
+
1283
+ return { available: true, records, nextCursor: null };
1284
+ }
1285
+ }
1286
+
1287
+ /**
1288
+ * Read your site's visitor counts back.
1289
+ *
1290
+ * The `<BIABAnalytics/>` tracker (`@businessdash/sdk/react-analytics`) RECORDS
1291
+ * pageviews; this READS the totals, so you can put a number on the page instead
1292
+ * of only feeding the dashboard.
1293
+ *
1294
+ * ```ts
1295
+ * // Site-wide, all time
1296
+ * const all = await biab.site(siteId).analytics.pageViews();
1297
+ * if (all.available) console.log(`${all.total.views} views`);
1298
+ *
1299
+ * // One page, last 30 days
1300
+ * const home = await biab.site(siteId).analytics.pageViews({ paths: ['/'], days: 30 });
1301
+ *
1302
+ * // A batch of pages in one round-trip
1303
+ * const list = await biab.site(siteId).analytics.pageViews({
1304
+ * paths: ['/tech/typescript', '/tech/rust'],
1305
+ * });
1306
+ * // list.paths → [{ path, views, visitors }, …], every requested path present
1307
+ * ```
1308
+ *
1309
+ * `visitors` approximates unique visitors (distinct daily-rotating anon id).
1310
+ * Requires the `analytics.web` entitlement — an org without it gets
1311
+ * `{ available: false }`, so branch on `.available`.
1312
+ */
1313
+ export class BiabDevAnalyticsClient {
1314
+ constructor(
1315
+ private readonly client: BiabDevClient,
1316
+ private readonly siteId: string,
1317
+ ) {}
1318
+
1319
+ /**
1320
+ * Pageview + visitor counts. Pass `paths` for a per-path breakdown (plus a
1321
+ * combined `total`), or omit them for a site-wide `total`. `days` windows the
1322
+ * count; omit for all-time.
1323
+ */
1324
+ async pageViews(input: PageViewsInput = {}): Promise<PageViewsResponse> {
1325
+ const { paths, days } = pageViewsInputSchema.parse(input);
1326
+ return this.client.request({
1327
+ path: `sites/${encodeURIComponent(this.siteId)}/analytics/views`,
1328
+ query: {
1329
+ // `path` repeats once per requested path; the route reads getAll.
1330
+ ...(paths && paths.length > 0 ? { path: paths } : {}),
1331
+ ...(days !== undefined ? { days } : {}),
1332
+ },
1333
+ responseSchema: pageViewsResponseSchema,
1334
+ });
1335
+ }
1336
+
1337
+ /**
1338
+ * Which AI crawlers (GPTBot, ClaudeBot, PerplexityBot, …) actually fetched
1339
+ * this site's pages: fetch counts, distinct pages read, vendor, intent
1340
+ * (training / search / live-assistant), and when each last came.
1341
+ *
1342
+ * First-party AEO visibility — recorded when crawlers hit the tracker (the
1343
+ * assistant browsing agents run JS) and when your server reports the rest
1344
+ * via `reportAiCrawlerHit` from `@businessdash/sdk/analytics-server`. Defaults
1345
+ * to a 30-day window.
1346
+ *
1347
+ * ```ts
1348
+ * const ai = await biab.site(siteId).analytics.aiVisibility({ days: 30 });
1349
+ * if (ai.available) {
1350
+ * for (const c of ai.crawlers)
1351
+ * console.log(`${c.name}: ${c.fetches} fetches over ${c.distinctPages} pages`);
1352
+ * }
1353
+ * ```
1354
+ */
1355
+ async aiVisibility(
1356
+ input: AiVisibilityInput = {},
1357
+ ): Promise<AiVisibilityResponse> {
1358
+ const { days } = aiVisibilityInputSchema.parse(input);
1359
+ return this.client.request({
1360
+ path: `sites/${encodeURIComponent(this.siteId)}/analytics/ai-crawlers`,
1361
+ query: { ...(days !== undefined ? { days } : {}) },
1362
+ responseSchema: aiVisibilityResponseSchema,
1363
+ });
1364
+ }
1365
+ }
1366
+
1367
+ /**
1368
+ * The "Powered by BusinessDash" attribution badge's one narrow function.
1369
+ *
1370
+ * `<BusinessDashFooter/>` uses this to (a) read whether the badge is required on
1371
+ * this plan and (b) optionally report that it rendered. Both are scoped to your
1372
+ * own site with a publishable key.
1373
+ *
1374
+ * ⚠️ The heartbeat is a courtesy signal, not proof of anything — BusinessDash
1375
+ * verifies the badge by fetching your live domain, not by trusting this call. So
1376
+ * don't build logic on the heartbeat; do honor `policy().required`.
1377
+ */
1378
+ export class BiabDevAttributionClient {
1379
+ constructor(
1380
+ private readonly client: BiabDevClient,
1381
+ private readonly siteId: string,
1382
+ ) {}
1383
+
1384
+ /**
1385
+ * This site's badge policy: `{ siteId, required }`. `required` is `true` on
1386
+ * plans that can't remove the badge (launch), `false` otherwise. Build the
1387
+ * badge's `?ref=<siteId>` backlink from `siteId`.
1388
+ */
1389
+ async policy(): Promise<AttributionPolicy> {
1390
+ return this.client.request({
1391
+ path: `sites/${encodeURIComponent(this.siteId)}/attribution`,
1392
+ responseSchema: attributionPolicySchema,
1393
+ });
1394
+ }
1395
+
1396
+ /**
1397
+ * Report that the badge rendered on `url`. Best-effort and untrusted — it
1398
+ * never throws for a bad URL (you get `{ recorded: false }`), and it's safe to
1399
+ * fire-and-forget. Don't await it in a render path.
1400
+ */
1401
+ async heartbeat(
1402
+ input: AttributionHeartbeatInput,
1403
+ ): Promise<AttributionHeartbeatResponse> {
1404
+ const body = attributionHeartbeatInputSchema.parse(input);
1405
+ return this.client.request({
1406
+ method: "POST",
1407
+ path: `sites/${encodeURIComponent(this.siteId)}/attribution/heartbeat`,
1408
+ body,
1409
+ responseSchema: attributionHeartbeatResponseSchema,
1410
+ });
1411
+ }
1412
+ }
1413
+
1414
+ export class BiabDevAssetsClient {
1415
+ constructor(
1416
+ private readonly client: BiabDevClient,
1417
+ private readonly siteId: string,
1418
+ ) {}
1419
+
1420
+ async create(input: SiteAssetInput): Promise<UploadSiteAssetResponse> {
1421
+ const body = siteAssetInputSchema.parse(input);
1422
+
1423
+ return this.client.request({
1424
+ method: "POST",
1425
+ path: `sites/${encodeURIComponent(this.siteId)}/assets`,
1426
+ body,
1427
+ responseSchema: uploadSiteAssetResponseSchema,
1428
+ });
1429
+ }
1430
+ }
1431
+
1432
+ export class BiabDevActionsClient {
1433
+ constructor(
1434
+ private readonly client: BiabDevClient,
1435
+ private readonly siteId: string,
1436
+ ) {}
1437
+
1438
+ async run(
1439
+ actionName: string,
1440
+ input: ExecuteActionInput = { payload: {} },
1441
+ ): Promise<ExecuteActionResponse> {
1442
+ const body = executeActionInputSchema.parse(input);
1443
+
1444
+ return this.client.request({
1445
+ method: "POST",
1446
+ path: `sites/${encodeURIComponent(this.siteId)}/actions/${encodeURIComponent(actionName)}`,
1447
+ body,
1448
+ responseSchema: executeActionResponseSchema,
1449
+ });
1450
+ }
1451
+ }
1452
+
1453
+ export class BiabDevEmailTemplatesClient {
1454
+ constructor(private readonly client: BiabDevClient) {}
1455
+
1456
+ async list(
1457
+ input: ListEmailTemplatesInput = {},
1458
+ ): Promise<ListEmailTemplatesResponse> {
1459
+ const query = listEmailTemplatesInputSchema.parse(input);
1460
+
1461
+ return this.client.request({
1462
+ path: "email/templates",
1463
+ query,
1464
+ responseSchema: listEmailTemplatesResponseSchema,
1465
+ });
1466
+ }
1467
+
1468
+ async get(templateId: number): Promise<GetEmailTemplateResponse> {
1469
+ return this.client.request({
1470
+ path: `email/templates/${templateId}`,
1471
+ responseSchema: getEmailTemplateResponseSchema,
1472
+ });
1473
+ }
1474
+
1475
+ async create(
1476
+ input: CreateEmailTemplateInput,
1477
+ ): Promise<CreateEmailTemplateResponse> {
1478
+ const body = createEmailTemplateInputSchema.parse(input);
1479
+
1480
+ return this.client.request({
1481
+ method: "POST",
1482
+ path: "email/templates",
1483
+ body,
1484
+ responseSchema: createEmailTemplateResponseSchema,
1485
+ });
1486
+ }
1487
+
1488
+ async createVersion(
1489
+ templateId: number,
1490
+ input: CreateEmailTemplateVersionInput,
1491
+ ): Promise<CreateEmailTemplateVersionResponse> {
1492
+ const body = createEmailTemplateVersionInputSchema.parse(input);
1493
+
1494
+ return this.client.request({
1495
+ method: "POST",
1496
+ path: `email/templates/${templateId}/versions`,
1497
+ body,
1498
+ responseSchema: createEmailTemplateVersionResponseSchema,
1499
+ });
1500
+ }
1501
+ }
1502
+
1503
+ export class BiabDevFollowersClient {
1504
+ constructor(
1505
+ private readonly client: BiabDevClient,
1506
+ private readonly siteId: string,
1507
+ ) {}
1508
+
1509
+ private actionPath(action: string) {
1510
+ return `sites/${encodeURIComponent(this.siteId)}/actions/${encodeURIComponent(action)}`;
1511
+ }
1512
+
1513
+ async join(input: FollowerJoinInput): Promise<FollowerJoinResponse> {
1514
+ const payload = followerJoinInputSchema.parse(input);
1515
+ const body = executeActionInputSchema.parse({ payload });
1516
+ const response = await this.client.request({
1517
+ method: "POST",
1518
+ path: this.actionPath("followers.join"),
1519
+ body,
1520
+ responseSchema: executeActionResponseSchema,
1521
+ });
1522
+ return followerJoinResponseSchema.parse(response.result);
1523
+ }
1524
+
1525
+ async me(input: FollowerMeInput): Promise<FollowerMeResponse> {
1526
+ const payload = followerMeInputSchema.parse(input);
1527
+ const body = executeActionInputSchema.parse({ payload });
1528
+ const response = await this.client.request({
1529
+ method: "POST",
1530
+ path: this.actionPath("followers.me"),
1531
+ body,
1532
+ responseSchema: executeActionResponseSchema,
1533
+ });
1534
+ return followerMeResponseSchema.parse(response.result);
1535
+ }
1536
+
1537
+ async edit(input: FollowerEditInput): Promise<FollowerEditResponse> {
1538
+ const payload = followerEditInputSchema.parse(input);
1539
+ const body = executeActionInputSchema.parse({ payload });
1540
+ const response = await this.client.request({
1541
+ method: "POST",
1542
+ path: this.actionPath("followers.edit"),
1543
+ body,
1544
+ responseSchema: executeActionResponseSchema,
1545
+ });
1546
+ return followerEditResponseSchema.parse(response.result);
1547
+ }
1548
+
1549
+ async leave(input: FollowerLeaveInput): Promise<FollowerLeaveResponse> {
1550
+ const payload = followerLeaveInputSchema.parse(input);
1551
+ const body = executeActionInputSchema.parse({ payload });
1552
+ const response = await this.client.request({
1553
+ method: "POST",
1554
+ path: this.actionPath("followers.leave"),
1555
+ body,
1556
+ responseSchema: executeActionResponseSchema,
1557
+ });
1558
+ return followerLeaveResponseSchema.parse(response.result);
1559
+ }
1560
+ }
1561
+
1562
+ export class BiabDevDashboardClient {
1563
+ constructor(private readonly client: BiabDevClient) {}
1564
+
1565
+ async createSession(): Promise<DashboardSessionResponse> {
1566
+ return this.client.request({
1567
+ method: "POST",
1568
+ path: "dashboard/session",
1569
+ responseSchema: dashboardSessionResponseSchema,
1570
+ });
1571
+ }
1572
+ }
1573
+
1574
+ export class BiabDevStorefrontClient {
1575
+ constructor(private readonly client: BiabDevClient) {}
1576
+
1577
+ /**
1578
+ * Legacy embed-session mint (kept for iframe-widget consumers).
1579
+ * The programmatic methods below let consumer sites render their
1580
+ * own native product catalog using BIAB as the data layer.
1581
+ */
1582
+ async createSession(
1583
+ input?: StorefrontSessionInput,
1584
+ ): Promise<StorefrontSessionResponse> {
1585
+ const body = storefrontSessionInputSchema.parse(input ?? undefined);
1586
+ return this.client.request({
1587
+ method: "POST",
1588
+ path: "storefront/session",
1589
+ body: body ?? {},
1590
+ responseSchema: storefrontSessionResponseSchema,
1591
+ });
1592
+ }
1593
+
1594
+ /**
1595
+ * Paginated list of live products for the calling org's storefront.
1596
+ * Cursor is an opaque integer offset — pass back the response's
1597
+ * `nextCursor` to fetch the next page. Filter by category via the
1598
+ * `categoryId` option.
1599
+ */
1600
+ async listProducts(input?: {
1601
+ limit?: number;
1602
+ cursor?: number | null;
1603
+ categoryId?: string;
1604
+ }): Promise<StorefrontListProductsResponse> {
1605
+ const query: Record<string, string | number | undefined> = {};
1606
+ if (input?.limit !== undefined) query["limit"] = input.limit;
1607
+ if (input?.cursor != null) query["cursor"] = input.cursor;
1608
+ if (input?.categoryId) query["categoryId"] = input.categoryId;
1609
+ return this.client.request({
1610
+ path: "storefront/products",
1611
+ query,
1612
+ responseSchema: storefrontListProductsResponseSchema,
1613
+ });
1614
+ }
1615
+
1616
+ /**
1617
+ * Filterable listing grid with facets — the data behind a full shop page.
1618
+ * Returns enriched per-card price/ratings/badges plus `categoryCounts` and
1619
+ * the catalog-wide `priceRange` for a sidebar. Supports `search`,
1620
+ * `categoryId`, `minPriceCents`/`maxPriceCents`, `minRating` (1–5), and
1621
+ * `sort` (featured | newest | price-asc | price-desc | rating-desc).
1622
+ */
1623
+ async listProductsWithMeta(input?: {
1624
+ search?: string;
1625
+ categoryId?: string;
1626
+ minPriceCents?: number;
1627
+ maxPriceCents?: number;
1628
+ minRating?: number;
1629
+ sort?: StorefrontSort;
1630
+ limit?: number;
1631
+ /** Dev-only: also surface pending (non-live) products. Caller-gated. */
1632
+ preview?: boolean;
1633
+ }): Promise<StorefrontProductsWithMetaResponse> {
1634
+ const query: Record<string, string | number | undefined> = { meta: "1" };
1635
+ if (input?.search) query["search"] = input.search;
1636
+ if (input?.categoryId) query["categoryId"] = input.categoryId;
1637
+ if (input?.minPriceCents != null)
1638
+ query["minPriceCents"] = input.minPriceCents;
1639
+ if (input?.maxPriceCents != null)
1640
+ query["maxPriceCents"] = input.maxPriceCents;
1641
+ if (input?.minRating != null) query["minRating"] = input.minRating;
1642
+ if (input?.sort) query["sort"] = input.sort;
1643
+ if (input?.limit != null) query["limit"] = input.limit;
1644
+ if (input?.preview) query["preview"] = "1";
1645
+ return this.client.request({
1646
+ path: "storefront/products",
1647
+ query,
1648
+ responseSchema: storefrontProductsWithMetaResponseSchema,
1649
+ });
1650
+ }
1651
+
1652
+ /** Org-scoped product categories for a storefront sidebar. */
1653
+ async listCategories(): Promise<StorefrontCategoriesResponse> {
1654
+ return this.client.request({
1655
+ path: "storefront/categories",
1656
+ responseSchema: storefrontCategoriesResponseSchema,
1657
+ });
1658
+ }
1659
+
1660
+ /**
1661
+ * Full product detail with variants + per-image rows. Returns 404
1662
+ * via `BiabApiError` if the product isn't live or doesn't belong
1663
+ * to the org bound to the API key.
1664
+ */
1665
+ async getProduct(productId: string): Promise<StorefrontProductDetail> {
1666
+ return this.client.request({
1667
+ path: `storefront/products/${encodeURIComponent(productId)}`,
1668
+ responseSchema: storefrontProductDetailSchema,
1669
+ });
1670
+ }
1671
+
1672
+ /** Approved reviews for a product + aggregate (`avgRating`, `totalCount`).
1673
+ * Cursor-paginated — pass back `nextCursor` for the next page. */
1674
+ async getProductReviews(
1675
+ productId: string,
1676
+ input?: { limit?: number; cursor?: number | null },
1677
+ ): Promise<StorefrontProductReviewsResponse> {
1678
+ const query: Record<string, string | number | undefined> = {};
1679
+ if (input?.limit !== undefined) query["limit"] = input.limit;
1680
+ if (input?.cursor != null) query["cursor"] = input.cursor;
1681
+ return this.client.request({
1682
+ path: `storefront/products/${encodeURIComponent(productId)}/reviews`,
1683
+ query,
1684
+ responseSchema: storefrontProductReviewsResponseSchema,
1685
+ });
1686
+ }
1687
+
1688
+ /** "You may also like" — same-category / paired recommendations. */
1689
+ async getRelatedProducts(
1690
+ productId: string,
1691
+ input?: { limit?: number },
1692
+ ): Promise<StorefrontRelatedProductsResponse> {
1693
+ const query: Record<string, string | number | undefined> = {};
1694
+ if (input?.limit !== undefined) query["limit"] = input.limit;
1695
+ return this.client.request({
1696
+ path: `storefront/products/${encodeURIComponent(productId)}/related`,
1697
+ query,
1698
+ responseSchema: storefrontRelatedProductsResponseSchema,
1699
+ });
1700
+ }
1701
+
1702
+ /** Companion / exclusive cross-sell addons for the "complete your X" rail. */
1703
+ async getProductAddons(productId: string): Promise<StorefrontAddonsResponse> {
1704
+ return this.client.request({
1705
+ path: `storefront/products/${encodeURIComponent(productId)}/addons`,
1706
+ responseSchema: storefrontAddonsResponseSchema,
1707
+ });
1708
+ }
1709
+ }
1710
+
1711
+ export class BiabDevCartClient {
1712
+ constructor(
1713
+ private readonly client: BiabDevClient,
1714
+ private readonly visitorToken: string | null = null,
1715
+ private readonly sessionToken: string | null = null,
1716
+ ) {}
1717
+
1718
+ /**
1719
+ * Use an anonymous visitor token (UUID stored in the consumer's
1720
+ * own httpOnly cookie). Same shopper across page loads, no
1721
+ * sign-in required.
1722
+ */
1723
+ forVisitor(visitorToken: string): BiabDevCartClient {
1724
+ return new BiabDevCartClient(this.client, visitorToken, null);
1725
+ }
1726
+
1727
+ /**
1728
+ * Use a tenantAuth session token instead of a visitor token.
1729
+ * Carts are now keyed to the signed-in customer; persists
1730
+ * across devices.
1731
+ */
1732
+ withSession(sessionToken: string): BiabDevCartClient {
1733
+ return new BiabDevCartClient(this.client, null, sessionToken);
1734
+ }
1735
+
1736
+ private headers(): HeadersInit {
1737
+ const out: Record<string, string> = {};
1738
+ if (this.visitorToken) out["X-BIAB-Cart-Visitor"] = this.visitorToken;
1739
+ if (this.sessionToken) out["X-BIAB-Session-Token"] = this.sessionToken;
1740
+ return out;
1741
+ }
1742
+
1743
+ /**
1744
+ * Legacy embed-session mint (kept for iframe-widget consumers).
1745
+ * The programmatic methods below let the consumer site render
1746
+ * its own native cart UI.
1747
+ */
1748
+ async createSession(): Promise<CartSessionResponse> {
1749
+ return this.client.request({
1750
+ method: "POST",
1751
+ path: "cart/session",
1752
+ responseSchema: cartSessionResponseSchema,
1753
+ });
1754
+ }
1755
+
1756
+ /**
1757
+ * Read the current cart snapshot — item count, items with
1758
+ * resolved product detail (name, image, variant title, unit
1759
+ * price), subtotal, currency. Auto-creates an empty cart row
1760
+ * the first time it's called for a new visitor/session, so
1761
+ * the consumer always gets a valid snapshot.
1762
+ */
1763
+ async get(): Promise<CartSnapshot> {
1764
+ return this.client.request({
1765
+ path: "cart",
1766
+ responseSchema: cartSnapshotSchema,
1767
+ headers: this.headers(),
1768
+ });
1769
+ }
1770
+
1771
+ /**
1772
+ * Add a product (optionally with a variant) to the cart. If the
1773
+ * (productId, variantId) already exists, increments its quantity
1774
+ * rather than creating a duplicate line.
1775
+ */
1776
+ async addItem(input: CartAddItemInput): Promise<CartSnapshot> {
1777
+ const body = cartAddItemInputSchema.parse(input);
1778
+ return this.client.request({
1779
+ method: "POST",
1780
+ path: "cart/items",
1781
+ body,
1782
+ responseSchema: cartSnapshotSchema,
1783
+ headers: this.headers(),
1784
+ });
1785
+ }
1786
+
1787
+ /**
1788
+ * Set a line's quantity. Pass quantity=0 to delete the line.
1789
+ */
1790
+ async updateItem(
1791
+ itemId: string,
1792
+ input: CartUpdateItemInput,
1793
+ ): Promise<CartSnapshot> {
1794
+ const body = cartUpdateItemInputSchema.parse(input);
1795
+ return this.client.request({
1796
+ method: "PATCH",
1797
+ path: `cart/items/${encodeURIComponent(itemId)}`,
1798
+ body,
1799
+ responseSchema: cartSnapshotSchema,
1800
+ headers: this.headers(),
1801
+ });
1802
+ }
1803
+
1804
+ async removeItem(itemId: string): Promise<CartSnapshot> {
1805
+ return this.client.request({
1806
+ method: "DELETE",
1807
+ path: `cart/items/${encodeURIComponent(itemId)}`,
1808
+ responseSchema: cartSnapshotSchema,
1809
+ headers: this.headers(),
1810
+ });
1811
+ }
1812
+
1813
+ /**
1814
+ * Stamp a coupon code on the cart. Validation runs at checkout —
1815
+ * stamping here is a no-op when invalid; the consumer learns
1816
+ * via the Stripe-checkout-session response whether the code
1817
+ * actually applies.
1818
+ */
1819
+ async applyCoupon(input: CartCouponInput): Promise<CartSnapshot> {
1820
+ const body = cartCouponInputSchema.parse(input);
1821
+ return this.client.request({
1822
+ method: "POST",
1823
+ path: "cart/coupon",
1824
+ body,
1825
+ responseSchema: cartSnapshotSchema,
1826
+ headers: this.headers(),
1827
+ });
1828
+ }
1829
+
1830
+ async removeCoupon(): Promise<CartSnapshot> {
1831
+ return this.client.request({
1832
+ method: "DELETE",
1833
+ path: "cart/coupon",
1834
+ responseSchema: cartSnapshotSchema,
1835
+ headers: this.headers(),
1836
+ });
1837
+ }
1838
+
1839
+ /** Empty all items + drop the stamped coupon. Keeps the cart row itself. */
1840
+ async clear(): Promise<CartSnapshot> {
1841
+ return this.client.request({
1842
+ method: "POST",
1843
+ path: "cart/clear",
1844
+ responseSchema: cartSnapshotSchema,
1845
+ headers: this.headers(),
1846
+ });
1847
+ }
1848
+ }
1849
+
1850
+ export class BiabDevCheckoutClient {
1851
+ constructor(
1852
+ private readonly client: BiabDevClient,
1853
+ private readonly visitorToken: string | null = null,
1854
+ private readonly sessionToken: string | null = null,
1855
+ ) {}
1856
+
1857
+ /**
1858
+ * Authenticate the start-from-cart call as an anonymous shopper
1859
+ * (visitor-token cart). Same UUID you used with `client.cart.forVisitor(...)`.
1860
+ */
1861
+ forVisitor(visitorToken: string): BiabDevCheckoutClient {
1862
+ return new BiabDevCheckoutClient(this.client, visitorToken, null);
1863
+ }
1864
+
1865
+ /**
1866
+ * Authenticate as a signed-in customer (tenantAuth session token).
1867
+ * The Stripe Checkout Session will be created against this
1868
+ * customer's saved cart.
1869
+ */
1870
+ withSession(sessionToken: string): BiabDevCheckoutClient {
1871
+ return new BiabDevCheckoutClient(this.client, null, sessionToken);
1872
+ }
1873
+
1874
+ private headers(): HeadersInit {
1875
+ const out: Record<string, string> = {};
1876
+ if (this.visitorToken) out["X-BIAB-Cart-Visitor"] = this.visitorToken;
1877
+ if (this.sessionToken) out["X-BIAB-Session-Token"] = this.sessionToken;
1878
+ return out;
1879
+ }
1880
+
1881
+ /**
1882
+ * Legacy iframe-embed mint — gives back a tokenized URL to render
1883
+ * inside an iframe that redirects to Stripe. Kept for `<Checkout/>`
1884
+ * consumers; new code should call `start()` for native handoff.
1885
+ */
1886
+ async createSession(
1887
+ input: CheckoutSessionInput,
1888
+ ): Promise<CheckoutSessionResponse> {
1889
+ const body = checkoutSessionInputSchema.parse(input);
1890
+ return this.client.request({
1891
+ method: "POST",
1892
+ path: "checkout/session",
1893
+ body,
1894
+ responseSchema: checkoutSessionResponseSchema,
1895
+ });
1896
+ }
1897
+
1898
+ /**
1899
+ * Convert the caller's saved BIAB cart (resolved via visitor token
1900
+ * or session token) into a Stripe Checkout Session. Returns the
1901
+ * Stripe-hosted URL; redirect the browser there to collect payment.
1902
+ *
1903
+ * `successUrl` + `cancelUrl` may be full URLs on the consumer's own
1904
+ * domain. We append `session_id={CHECKOUT_SESSION_ID}` to the
1905
+ * success URL automatically so the success page can call
1906
+ * `getStatus(sessionId)` to confirm payment.
1907
+ */
1908
+ async start(input?: CheckoutStartInput): Promise<CheckoutStartResponse> {
1909
+ const body = checkoutStartInputSchema.parse(input);
1910
+ return this.client.request({
1911
+ method: "POST",
1912
+ path: "checkout/start",
1913
+ body: body ?? {},
1914
+ responseSchema: checkoutStartResponseSchema,
1915
+ headers: this.headers(),
1916
+ });
1917
+ }
1918
+
1919
+ /**
1920
+ * Look up a Stripe Checkout Session by id. Returns paid / unpaid
1921
+ * status plus the customer details Stripe collected — what the
1922
+ * consumer's "thank you" page calls before rendering.
1923
+ */
1924
+ async getStatus(sessionId: string): Promise<CheckoutStatusResponse> {
1925
+ return this.client.request({
1926
+ path: `checkout/${encodeURIComponent(sessionId)}`,
1927
+ responseSchema: checkoutStatusResponseSchema,
1928
+ });
1929
+ }
1930
+ }
1931
+
1932
+ /**
1933
+ * White-glove Google Places proxy. BIAB owns the API key — consumer
1934
+ * code never sees it. Use at the checkout delivery-address input
1935
+ * to give shoppers text-search predictions + verify deliverability
1936
+ * before submitting the order.
1937
+ *
1938
+ * Mirrors the working reference in `Resources/addressParser/`:
1939
+ * - `search(query)` — text-search predictions (uses Google
1940
+ * `places:searchText`)
1941
+ * - `getDetails(placeId)` — placeId → `GoogleAddress` shape
1942
+ * - `verify(form)` — text-searches the entered address and
1943
+ * compares component-by-component
1944
+ *
1945
+ * Three pure converters are exported alongside as static methods —
1946
+ * the same ones the reference React hooks wrap, just plain functions
1947
+ * so they work outside React too.
1948
+ *
1949
+ * @example
1950
+ * ```tsx
1951
+ * // On each keystroke (debounced):
1952
+ * const result = await client.address.search({ query: input });
1953
+ * if (!result.enabled) return; // platform not configured; show plain input
1954
+ *
1955
+ * // When user picks a prediction:
1956
+ * const detail = await client.address.getDetails(prediction.place_id);
1957
+ * if (detail.enabled && detail.address) {
1958
+ * const form = BiabDevAddressClient.googleToFormValues(detail.address);
1959
+ * reactHookForm.reset(form);
1960
+ * }
1961
+ *
1962
+ * // Before submitting the order:
1963
+ * const v = await client.address.verify(form);
1964
+ * if (!v.isVerified) showDidYouMean(v.googleAddress);
1965
+ * ```
1966
+ */
1967
+ export class BiabDevAddressClient {
1968
+ constructor(private readonly client: BiabDevClient) {}
1969
+
1970
+ async search(input: AddressSearchInput): Promise<AddressSearchResponse> {
1971
+ const body = addressSearchInputSchema.parse(input);
1972
+ return this.client.request({
1973
+ method: "POST",
1974
+ path: "address/search",
1975
+ body,
1976
+ responseSchema: addressSearchResponseSchema,
1977
+ });
1978
+ }
1979
+
1980
+ async getDetails(placeId: string): Promise<AddressDetailsResponse> {
1981
+ return this.client.request({
1982
+ path: `address/details/${encodeURIComponent(placeId)}`,
1983
+ responseSchema: addressDetailsResponseSchema,
1984
+ });
1985
+ }
1986
+
1987
+ async verify(input: AddressFormValues): Promise<AddressVerifyResponse> {
1988
+ const body = addressFormValuesSchema.parse(input);
1989
+ return this.client.request({
1990
+ method: "POST",
1991
+ path: "address/validate",
1992
+ body,
1993
+ responseSchema: addressVerifyResponseSchema,
1994
+ });
1995
+ }
1996
+
1997
+ /** Convert Google's `GoogleAddress` shape into a form-values object. */
1998
+ static googleToFormValues(g: GoogleAddress): AddressFormValues {
1999
+ return {
2000
+ address_ln1: `${g.street_number} ${g.route}`.trim(),
2001
+ address_ln2: g.subpremise ?? "",
2002
+ city: g.locality,
2003
+ state: g.administrative_area_level_1,
2004
+ zip: g.postal_code,
2005
+ country: g.country,
2006
+ };
2007
+ }
2008
+
2009
+ /** Convert a form-values object into Stripe's billing-address shape. */
2010
+ static formToStripeAddress(a: AddressFormValues): {
2011
+ line1: string;
2012
+ line2: string | null;
2013
+ city: string;
2014
+ state: string;
2015
+ postal_code: string;
2016
+ country: string;
2017
+ } {
2018
+ return {
2019
+ line1: a.address_ln1,
2020
+ line2: a.address_ln2 || null,
2021
+ city: a.city,
2022
+ state: a.state,
2023
+ postal_code: a.zip,
2024
+ country: a.country,
2025
+ };
2026
+ }
2027
+ }
2028
+
2029
+ /**
2030
+ * Org-side recurring offerings (gym memberships, monthly service
2031
+ * plans, software subscriptions). Lists are public read; the
2032
+ * checkout-start endpoint hands the customer off to Stripe-hosted
2033
+ * Checkout in `mode: 'subscription'`.
2034
+ *
2035
+ * Requires the org to have synced each offering to Stripe via the
2036
+ * dashboard's "Sync to Stripe" action — otherwise `startCheckout`
2037
+ * returns 409 `subscription_not_synced`.
2038
+ */
2039
+ export class BiabDevSubscriptionsClient {
2040
+ constructor(private readonly client: BiabDevClient) {}
2041
+
2042
+ async list(): Promise<SubscriptionOfferingsListResponse> {
2043
+ return this.client.request({
2044
+ path: "subscriptions",
2045
+ responseSchema: subscriptionOfferingsListResponseSchema,
2046
+ });
2047
+ }
2048
+
2049
+ async get(id: string): Promise<SubscriptionOffering> {
2050
+ return this.client.request({
2051
+ path: `subscriptions/${encodeURIComponent(id)}`,
2052
+ responseSchema: subscriptionOfferingSchema,
2053
+ });
2054
+ }
2055
+
2056
+ async startCheckout(
2057
+ id: string,
2058
+ input?: SubscriptionCheckoutInput,
2059
+ ): Promise<SubscriptionCheckoutResponse> {
2060
+ const body = subscriptionCheckoutInputSchema.parse(input);
2061
+ return this.client.request({
2062
+ method: "POST",
2063
+ path: `subscriptions/${encodeURIComponent(id)}/checkout`,
2064
+ body: body ?? {},
2065
+ responseSchema: subscriptionCheckoutResponseSchema,
2066
+ });
2067
+ }
2068
+ }
2069
+
2070
+ export class BiabDevCouponsClient {
2071
+ constructor(private readonly client: BiabDevClient) {}
2072
+
2073
+ /**
2074
+ * Validate a coupon code against the org's Stripe-connected
2075
+ * account. Returns the discount shape (percent / amount /
2076
+ * duration) when valid; `{ valid: false, reason, message }`
2077
+ * otherwise so the consumer's UI can show "$10 off" before the
2078
+ * shopper proceeds to checkout.
2079
+ *
2080
+ * Source of truth is Stripe — what this endpoint validates is
2081
+ * exactly what Stripe-hosted Checkout will accept.
2082
+ *
2083
+ * @example
2084
+ * ```ts
2085
+ * const result = await client.coupons.validate("SAVE10");
2086
+ * if (result.valid) showDiscount(result.percentOff ?? result.amountOffCents);
2087
+ * else toast.error(result.message);
2088
+ * ```
2089
+ */
2090
+ async validate(code: string): Promise<CouponValidateResponse> {
2091
+ const body = couponValidateInputSchema.parse({ code });
2092
+ return this.client.request({
2093
+ method: "POST",
2094
+ path: "coupons/validate",
2095
+ body,
2096
+ responseSchema: couponValidateResponseSchema,
2097
+ });
2098
+ }
2099
+ }
2100
+
2101
+ /**
2102
+ * Shipping: rate lookups + on-demand tracking. White-glove model —
2103
+ * BIAB owns the carrier integration; consumer code never sees the
2104
+ * underlying provider name. Gated on `ecommerce.shipping` so calls
2105
+ * from orgs on the Launch plan return the standard plan-gate body
2106
+ * via {@link BiabApiError} (status 403).
2107
+ *
2108
+ * @example
2109
+ * ```ts
2110
+ * const { options, requiresShipping } = await client.shipping
2111
+ * .forVisitor(visitorToken)
2112
+ * .getRates({ deliveryAddress });
2113
+ * if (!requiresShipping) skipShippingStep();
2114
+ * else {
2115
+ * const picked = options[0]; // cheapest
2116
+ * await client.checkout.forVisitor(visitorToken).start({
2117
+ * shipping: picked,
2118
+ * successUrl, cancelUrl,
2119
+ * });
2120
+ * }
2121
+ * ```
2122
+ */
2123
+ export class BiabDevShippingClient {
2124
+ constructor(
2125
+ private readonly client: BiabDevClient,
2126
+ private readonly visitorToken: string | null = null,
2127
+ private readonly sessionToken: string | null = null,
2128
+ ) {}
2129
+
2130
+ forVisitor(visitorToken: string): BiabDevShippingClient {
2131
+ return new BiabDevShippingClient(this.client, visitorToken, null);
2132
+ }
2133
+
2134
+ withSession(sessionToken: string): BiabDevShippingClient {
2135
+ return new BiabDevShippingClient(this.client, null, sessionToken);
2136
+ }
2137
+
2138
+ private headers(): HeadersInit {
2139
+ const out: Record<string, string> = {};
2140
+ if (this.visitorToken) out["X-BIAB-Cart-Visitor"] = this.visitorToken;
2141
+ if (this.sessionToken) out["X-BIAB-Session-Token"] = this.sessionToken;
2142
+ return out;
2143
+ }
2144
+
2145
+ /**
2146
+ * Fetch shipping rate options for the caller's cart + a delivery
2147
+ * address. Multi-origin + multi-parcel orders surface as a single
2148
+ * combined cost per service tier (carrier+speed). Pass the chosen
2149
+ * option directly to `client.checkout.start({ shipping })`.
2150
+ */
2151
+ async getRates(input: ShippingRatesInput): Promise<ShippingRatesResponse> {
2152
+ const body = shippingRatesInputSchema.parse(input);
2153
+ return this.client.request({
2154
+ method: "POST",
2155
+ path: "shipping/rates",
2156
+ body,
2157
+ responseSchema: shippingRatesResponseSchema,
2158
+ headers: this.headers(),
2159
+ });
2160
+ }
2161
+
2162
+ /**
2163
+ * On-demand tracking lookup for a known tracking number. Returns
2164
+ * the cached `status_history` we already received via webhook
2165
+ * PLUS a fresh poll against the carrier (`liveStatus`). Consumer
2166
+ * apps that don't subscribe to push notifications use this to
2167
+ * refresh "Where's my order?" pages.
2168
+ */
2169
+ async track(input: {
2170
+ trackingNumber: string;
2171
+ }): Promise<ShippingTrackResponse> {
2172
+ return this.client.request({
2173
+ path: `shipping/track/${encodeURIComponent(input.trackingNumber)}`,
2174
+ responseSchema: shippingTrackResponseSchema,
2175
+ });
2176
+ }
2177
+
2178
+ /**
2179
+ * The org's storefront shipping config — whether shipping is on, a coarse
2180
+ * "ships from" (city/state/country), the destinations it ships to, the duty
2181
+ * model, and preferred carriers. Sanitized (no keys/secrets, no precise
2182
+ * origin address). Render shipping copy without the org re-declaring it.
2183
+ *
2184
+ * @example
2185
+ * ```ts
2186
+ * const s = await client.shipping.settings();
2187
+ * if (s.enabled) show(`Ships to ${s.allowedDestinationCountries?.join(", ") ?? "anywhere"}`);
2188
+ * ```
2189
+ */
2190
+ async settings(): Promise<ShippingSettingsResponse> {
2191
+ return this.client.request({
2192
+ path: "shipping/settings",
2193
+ responseSchema: shippingSettingsResponseSchema,
2194
+ });
2195
+ }
2196
+ }
2197
+
2198
+ /**
2199
+ * Customer notification preferences. Per-customer-per-org channel
2200
+ * settings (in-app / email / SMS / outbound webhook). Identified by
2201
+ * the tenantAuth session token — bind with `.withSession(token)`.
2202
+ *
2203
+ * Outbound-webhook events the consumer site can subscribe to are
2204
+ * the same as the shipping fan-out keys:
2205
+ * - `order.shipped`
2206
+ * - `order.out_for_delivery`
2207
+ * - `order.delivered`
2208
+ * - `order.exception`
2209
+ * - `order.return_label_created`
2210
+ *
2211
+ * The webhook payload is JSON + signed with the customer's saved
2212
+ * `webhookSigningSecret` via `X-BIAB-Signature: <hex hmac-sha256>`.
2213
+ * Consumer's receiver verifies via:
2214
+ * crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
2215
+ */
2216
+ export class BiabDevNotificationsClient {
2217
+ constructor(
2218
+ private readonly client: BiabDevClient,
2219
+ private readonly sessionToken: string | null = null,
2220
+ ) {}
2221
+
2222
+ withSession(sessionToken: string): BiabDevNotificationsClient {
2223
+ return new BiabDevNotificationsClient(this.client, sessionToken);
2224
+ }
2225
+
2226
+ private headers(): HeadersInit {
2227
+ return this.sessionToken
2228
+ ? { "X-BIAB-Session-Token": this.sessionToken }
2229
+ : {};
2230
+ }
2231
+
2232
+ async getPreferences(): Promise<CustomerNotificationPreferencesResponse> {
2233
+ return this.client.request({
2234
+ path: "notifications/preferences",
2235
+ responseSchema: customerNotificationPreferencesResponseSchema,
2236
+ headers: this.headers(),
2237
+ });
2238
+ }
2239
+
2240
+ async updatePreferences(
2241
+ input: CustomerNotificationPreferencesUpdateInput,
2242
+ ): Promise<CustomerNotificationPreferencesUpdateResponse> {
2243
+ const body = customerNotificationPreferencesUpdateInputSchema.parse(input);
2244
+ return this.client.request({
2245
+ method: "PATCH",
2246
+ path: "notifications/preferences",
2247
+ body,
2248
+ responseSchema: customerNotificationPreferencesUpdateResponseSchema,
2249
+ headers: this.headers(),
2250
+ });
2251
+ }
2252
+
2253
+ /**
2254
+ * Start a verification flow for a destination the customer wants to
2255
+ * use for shipping notifications. Email: a verification link is sent
2256
+ * (15-min TTL). Phone: a 6-digit OTP is sent (5-min TTL).
2257
+ *
2258
+ * Until verified, the channel stays inert — preventing the customer
2259
+ * from spamming arbitrary destinations.
2260
+ */
2261
+ async startVerification(
2262
+ input: StartVerificationInput,
2263
+ ): Promise<StartVerificationResponse> {
2264
+ const body = startVerificationInputSchema.parse(input);
2265
+ return this.client.request({
2266
+ method: "POST",
2267
+ path: "notifications/preferences/verify",
2268
+ body,
2269
+ responseSchema: startVerificationResponseSchema,
2270
+ headers: this.headers(),
2271
+ });
2272
+ }
2273
+
2274
+ /**
2275
+ * Consume the verification token from the email link / SMS OTP.
2276
+ * On success: stamps `*VerifiedAt` on the prefs row + activates the
2277
+ * channel.
2278
+ */
2279
+ async confirmVerification(
2280
+ input: ConfirmVerificationInput,
2281
+ ): Promise<ConfirmVerificationResponse> {
2282
+ const body = confirmVerificationInputSchema.parse(input);
2283
+ return this.client.request({
2284
+ method: "POST",
2285
+ path: "notifications/preferences/verify/confirm",
2286
+ body,
2287
+ responseSchema: confirmVerificationResponseSchema,
2288
+ headers: this.headers(),
2289
+ });
2290
+ }
2291
+ }
2292
+
2293
+ export class BiabDevBlogClient {
2294
+ constructor(
2295
+ private readonly client: BiabDevClient,
2296
+ private readonly sessionToken: string | null = null,
2297
+ ) {}
2298
+
2299
+ /**
2300
+ * Chainable: return a blog client whose `postComment` + future
2301
+ * write methods authenticate via the tenantAuth session token.
2302
+ * Reads (listPosts, getPost, listCategories, listTags, listComments)
2303
+ * don't require a session and work the same either way.
2304
+ *
2305
+ * @example
2306
+ * ```ts
2307
+ * await client.blog().withSession(token).postComment(slug, { content });
2308
+ * ```
2309
+ */
2310
+ withSession(sessionToken: string): BiabDevBlogClient {
2311
+ return new BiabDevBlogClient(this.client, sessionToken);
2312
+ }
2313
+
2314
+ private headers(): HeadersInit {
2315
+ return this.sessionToken
2316
+ ? { "X-BIAB-Session-Token": this.sessionToken }
2317
+ : {};
2318
+ }
2319
+
2320
+ /**
2321
+ * Legacy embed-session mint (kept for backwards compat with
2322
+ * embedded-iframe consumers).
2323
+ */
2324
+ async createSession(input?: BlogSessionInput): Promise<BlogSessionResponse> {
2325
+ const body = blogSessionInputSchema.parse(input ?? undefined);
2326
+ return this.client.request({
2327
+ method: "POST",
2328
+ path: "blog/session",
2329
+ body: body ?? {},
2330
+ responseSchema: blogSessionResponseSchema,
2331
+ });
2332
+ }
2333
+
2334
+ /**
2335
+ * Paginated list of published blog posts for the calling org's
2336
+ * site. Cursor is an opaque number — pass back the `nextCursor`
2337
+ * from the previous response to fetch the next page.
2338
+ */
2339
+ async listPosts(input?: {
2340
+ limit?: number;
2341
+ cursor?: number | null;
2342
+ categoryId?: string;
2343
+ }): Promise<BlogListPostsResponse> {
2344
+ const query: Record<string, string | number | undefined> = {};
2345
+ if (input?.limit !== undefined) query["limit"] = input.limit;
2346
+ if (input?.cursor != null) query["cursor"] = input.cursor;
2347
+ if (input?.categoryId) query["categoryId"] = input.categoryId;
2348
+ return this.client.request({
2349
+ path: "blog/posts",
2350
+ query,
2351
+ responseSchema: blogListPostsResponseSchema,
2352
+ });
2353
+ }
2354
+
2355
+ /**
2356
+ * Get a single post by its slug. Returns `{ post, access }`
2357
+ * where `access === 'paywall'` and `post.content === ''` for
2358
+ * posts marked `accessLevel: 'paid' | 'followers'`. The
2359
+ * consumer renders a paywall over the empty body when access
2360
+ * is restricted.
2361
+ */
2362
+ async getPost(slug: string): Promise<BlogPostBySlugResponse> {
2363
+ return this.client.request({
2364
+ path: `blog/posts/${encodeURIComponent(slug)}`,
2365
+ responseSchema: blogPostBySlugResponseSchema,
2366
+ });
2367
+ }
2368
+
2369
+ async listCategories(): Promise<BlogCategoryListResponse> {
2370
+ return this.client.request({
2371
+ path: "blog/categories",
2372
+ responseSchema: blogCategoryListResponseSchema,
2373
+ });
2374
+ }
2375
+
2376
+ async listTags(): Promise<BlogTagListResponse> {
2377
+ return this.client.request({
2378
+ path: "blog/tags",
2379
+ responseSchema: blogTagListResponseSchema,
2380
+ });
2381
+ }
2382
+
2383
+ async listComments(
2384
+ slug: string,
2385
+ input?: { limit?: number; cursor?: number | null },
2386
+ ): Promise<BlogCommentListResponse> {
2387
+ const query: Record<string, string | number | undefined> = {};
2388
+ if (input?.limit !== undefined) query["limit"] = input.limit;
2389
+ if (input?.cursor != null) query["cursor"] = input.cursor;
2390
+ return this.client.request({
2391
+ path: `blog/posts/${encodeURIComponent(slug)}/comments`,
2392
+ query,
2393
+ responseSchema: blogCommentListResponseSchema,
2394
+ });
2395
+ }
2396
+
2397
+ /**
2398
+ * Submit a comment on a published post. Requires `withSession()` —
2399
+ * anonymous comments aren't supported in 0.9.0 (the blog_comments
2400
+ * row references a `user_id` text column that's NOT NULL). The
2401
+ * server runs the same moderation screener used by the dashboard
2402
+ * tRPC path; the response surfaces both the comment row + the
2403
+ * moderation verdict so the consumer can show "your comment is
2404
+ * pending review" when applicable.
2405
+ */
2406
+ async postComment(
2407
+ slug: string,
2408
+ input: BlogCommentPostInput,
2409
+ ): Promise<BlogCommentPostResponse> {
2410
+ const body = blogCommentPostInputSchema.parse(input);
2411
+ return this.client.request({
2412
+ method: "POST",
2413
+ path: `blog/posts/${encodeURIComponent(slug)}/comments`,
2414
+ body,
2415
+ responseSchema: blogCommentPostResponseSchema,
2416
+ headers: this.headers(),
2417
+ });
2418
+ }
2419
+
2420
+ /**
2421
+ * Current like state + count for a post. With `withSession()`, `liked`
2422
+ * reflects whether this visitor already liked it (filled heart on load);
2423
+ * without a session the public count is returned with `liked: false`.
2424
+ */
2425
+ async likeStatus(slug: string): Promise<BlogLikeResponse> {
2426
+ return this.client.request({
2427
+ path: `blog/posts/${encodeURIComponent(slug)}/like`,
2428
+ responseSchema: blogLikeResponseSchema,
2429
+ headers: this.headers(),
2430
+ });
2431
+ }
2432
+
2433
+ /**
2434
+ * Toggle the session visitor's like on a post. Requires `withSession()`
2435
+ * (same identity as `postComment`). Returns the new like state + count.
2436
+ */
2437
+ async toggleLike(slug: string): Promise<BlogLikeResponse> {
2438
+ return this.client.request({
2439
+ method: "POST",
2440
+ path: `blog/posts/${encodeURIComponent(slug)}/like`,
2441
+ responseSchema: blogLikeResponseSchema,
2442
+ headers: this.headers(),
2443
+ });
2444
+ }
2445
+ }
2446
+
2447
+ /**
2448
+ * Public reviews-wall client. Reads the org's external reviews
2449
+ * (Google + Yelp + manual + CSV) — the same store the marketing bundle's
2450
+ * `reviews` aggregate is built from. The bundle ships the first page + the
2451
+ * aggregate (count / average / histogram); use this to lazily fetch deeper
2452
+ * pages on "View all" / scroll.
2453
+ *
2454
+ * @example
2455
+ * ```ts
2456
+ * const page1 = await client.reviews.list({ limit: 10 });
2457
+ * if (page1.nextOffset != null) {
2458
+ * const page2 = await client.reviews.list({ limit: 10, offset: page1.nextOffset });
2459
+ * }
2460
+ * ```
2461
+ */
2462
+ export class BiabDevReviewsClient {
2463
+ constructor(private readonly client: BiabDevClient) {}
2464
+
2465
+ /**
2466
+ * One page of the org's public reviews, newest-first. `limit` defaults to
2467
+ * the server default (10, max 50). Pass the previous response's
2468
+ * `nextOffset` to page forward — it's `null` once the list is exhausted.
2469
+ * `source` narrows to a single platform (e.g. `"yelp"`).
2470
+ */
2471
+ async list(input?: {
2472
+ limit?: number;
2473
+ offset?: number;
2474
+ source?: "google" | "yelp" | "housecall_pro" | "other";
2475
+ }): Promise<ReviewWallListResponse> {
2476
+ const query: Record<string, string | number | undefined> = {};
2477
+ if (input?.limit !== undefined) query["limit"] = input.limit;
2478
+ if (input?.offset !== undefined) query["offset"] = input.offset;
2479
+ if (input?.source) query["source"] = input.source;
2480
+ return this.client.request({
2481
+ path: "reviews",
2482
+ query,
2483
+ responseSchema: reviewWallListResponseSchema,
2484
+ });
2485
+ }
2486
+ }
2487
+
2488
+ /**
2489
+ * Public hiring client — the org's open job postings and their categories, so
2490
+ * a consumer can render a careers page in their own design system.
2491
+ *
2492
+ * Read-only by construction: everything returned is content the org published
2493
+ * for candidates. To take applications, hand the posting's `applicationFormId`
2494
+ * to the forms surface — this client grants no submit path of its own.
2495
+ *
2496
+ * @example
2497
+ * ```ts
2498
+ * const { items } = await client.hiring.list({ limit: 20 });
2499
+ * const posting = await client.hiring.get("senior-installer");
2500
+ * if (posting.applicationFormId) {
2501
+ * const form = await client.forms.schema(posting.applicationFormId);
2502
+ * }
2503
+ * ```
2504
+ */
2505
+ export class BiabDevHiringClient {
2506
+ constructor(private readonly client: BiabDevClient) {}
2507
+
2508
+ /**
2509
+ * One page of open postings, newest-first. `limit` defaults to the server
2510
+ * default (25, max 100). Pass the previous response's `nextOffset` to page
2511
+ * forward — it's `null` once the list is exhausted. `category` narrows to a
2512
+ * single group by its slug.
2513
+ *
2514
+ * Only postings that are published, active, have seats left, and are under
2515
+ * their application cap are returned, so a careers page never advertises a
2516
+ * role that can no longer be applied to.
2517
+ */
2518
+ async list(input?: {
2519
+ limit?: number;
2520
+ offset?: number;
2521
+ category?: string;
2522
+ }): Promise<JobPostingListResponse> {
2523
+ const query: Record<string, string | number | undefined> = {};
2524
+ if (input?.limit !== undefined) query["limit"] = input.limit;
2525
+ if (input?.offset !== undefined) query["offset"] = input.offset;
2526
+ if (input?.category) query["category"] = input.category;
2527
+ return this.client.request({
2528
+ path: "hiring/jobs",
2529
+ query,
2530
+ responseSchema: jobPostingListResponseSchema,
2531
+ });
2532
+ }
2533
+
2534
+ /**
2535
+ * One open posting by slug. Rejects with a 404 error when the posting is a
2536
+ * draft, closed, archived, fully filled, or has hit its application cap —
2537
+ * treat that as "this role is gone" rather than as a transient failure.
2538
+ */
2539
+ async get(slug: string): Promise<JobPosting> {
2540
+ return this.client.request({
2541
+ path: `hiring/jobs/${encodeURIComponent(slug)}`,
2542
+ responseSchema: jobPostingSchema,
2543
+ });
2544
+ }
2545
+
2546
+ /**
2547
+ * The org's job categories with a count of open postings in each — enough to
2548
+ * render filter chips or grouped sections without fetching every posting.
2549
+ */
2550
+ async categories(): Promise<JobCategoryListResponse> {
2551
+ return this.client.request({
2552
+ path: "hiring/categories",
2553
+ responseSchema: jobCategoryListResponseSchema,
2554
+ });
2555
+ }
2556
+ }
2557
+
2558
+ export class BiabDevChatbotClient {
2559
+ constructor(private readonly client: BiabDevClient) {}
2560
+
2561
+ /** Returns the iframe URL for `<Chatbot/>`. */
2562
+ async createSession(): Promise<ChatbotSessionResponse> {
2563
+ return this.client.request({
2564
+ method: "POST",
2565
+ path: "chatbot/session",
2566
+ responseSchema: chatbotSessionResponseSchema,
2567
+ });
2568
+ }
2569
+
2570
+ /** Reads the org's persisted chatbot config (system prompt, KB, model). */
2571
+ async getConfig(): Promise<ChatbotConfig> {
2572
+ return this.client.request({
2573
+ method: "GET",
2574
+ path: "chatbot/config",
2575
+ responseSchema: chatbotConfigSchema,
2576
+ });
2577
+ }
2578
+
2579
+ /**
2580
+ * Persists the org's chatbot config. Requires `chatbot:write` scope on the
2581
+ * API key — typically only used by admin tools, not embedded widgets.
2582
+ */
2583
+ async updateConfig(input: ChatbotConfigUpdateInput): Promise<ChatbotConfig> {
2584
+ const body = chatbotConfigUpdateInputSchema.parse(input);
2585
+ return this.client.request({
2586
+ method: "PATCH",
2587
+ path: "chatbot/config",
2588
+ body,
2589
+ responseSchema: chatbotConfigSchema,
2590
+ });
2591
+ }
2592
+
2593
+ /**
2594
+ * Headless chat — host owns the UI. Each request is fully org-scoped: the
2595
+ * server runs the LLM with the org's saved system prompt + KB + model.
2596
+ *
2597
+ * The response includes `uiActions` (e.g. `show_form`) and `availability`
2598
+ * (front-desk presence + wait-time hint). Headless consumers should resolve
2599
+ * any `show_form` action via {@link getChatForm}, render the fields, and POST
2600
+ * back through {@link submitChatForm} (which carries the chat transcript).
2601
+ */
2602
+ async chat(input: ChatbotChatInput): Promise<ChatbotChatResponse> {
2603
+ const body = chatbotChatInputSchema.parse(input);
2604
+ return this.client.request({
2605
+ method: "POST",
2606
+ path: "chatbot/chat",
2607
+ body,
2608
+ responseSchema: chatbotChatResponseSchema,
2609
+ });
2610
+ }
2611
+
2612
+ /**
2613
+ * Upload a file a visitor attached in the chat. Presigns a direct-to-R2 PUT
2614
+ * (gated by the org's `chat.file_uploads` entitlement — check
2615
+ * `getConfig().fileUploadsEnabled` before offering it), uploads the bytes, and
2616
+ * returns a ref you can attach to the conversation. `sessionId` is any stable
2617
+ * per-chat id (drives the 5-files/hour cap + provenance). Throws on a refused
2618
+ * upload (entitlement, 7 MB cap, or rate limit).
2619
+ */
2620
+ async uploadFile(args: {
2621
+ sessionId: string;
2622
+ file: File;
2623
+ /** Captured visitor email — stored for provenance + checked against the
2624
+ * platform block-list. Optional. */
2625
+ uploaderEmail?: string | null;
2626
+ }): Promise<{ url: string; name: string; type: string; size: number }> {
2627
+ const contentType = args.file.type || "application/octet-stream";
2628
+ const presigned = await this.client.request({
2629
+ method: "POST",
2630
+ path: "chatbot/upload-url",
2631
+ body: {
2632
+ sessionId: args.sessionId,
2633
+ filename: args.file.name,
2634
+ contentType,
2635
+ sizeBytes: args.file.size,
2636
+ uploaderEmail: args.uploaderEmail ?? null,
2637
+ },
2638
+ responseSchema: chatbotUploadUrlResponseSchema,
2639
+ });
2640
+ const put = await fetch(presigned.uploadUrl, {
2641
+ method: "PUT",
2642
+ headers: { "Content-Type": contentType },
2643
+ body: args.file,
2644
+ });
2645
+ if (!put.ok) throw new Error(`Upload failed (${put.status}).`);
2646
+ return {
2647
+ url: presigned.publicUrl,
2648
+ name: args.file.name,
2649
+ type: contentType,
2650
+ size: args.file.size,
2651
+ };
2652
+ }
2653
+
2654
+ /**
2655
+ * Live front-desk availability — tier flag, staff-online presence, and
2656
+ * a wait-time hint string. Safe to poll (every 20–30s) so the SDK
2657
+ * consumer can render or hide a "Talk to a person" CTA without sending
2658
+ * a chat turn.
2659
+ */
2660
+ async getAvailability(): Promise<ChatbotAvailability> {
2661
+ return this.client.request({
2662
+ method: "GET",
2663
+ path: "chatbot/availability",
2664
+ responseSchema: chatbotAvailabilitySchema,
2665
+ });
2666
+ }
2667
+
2668
+ /**
2669
+ * Fetch a **Live** form definition to render inline on your own page.
2670
+ * A form is reachable here when it is Live (`isActive`) — independent of
2671
+ * `chatbotAccess`. Returns a 404-equivalent throw when the form is missing
2672
+ * or not Live. For a form the AI chatbot recommended via a `show_form`
2673
+ * action, use {@link getChatForm} instead (it carries the chat transcript on
2674
+ * submit).
2675
+ */
2676
+ async getForm(slug: string): Promise<ChatbotFormDefinition> {
2677
+ return this.client.request({
2678
+ method: "GET",
2679
+ path: `forms/${encodeURIComponent(slug)}`,
2680
+ responseSchema: chatbotFormDefinitionSchema,
2681
+ });
2682
+ }
2683
+
2684
+ /**
2685
+ * Submit a **Live** form. Fans out to the same hiring/leads/inquiries CRM
2686
+ * mirrors, actions, scheduling, and staff notification as every other ingest
2687
+ * point. Gated on Live only — no `chatbotAccess` required.
2688
+ */
2689
+ async submitForm(
2690
+ slug: string,
2691
+ input: ChatbotFormSubmitInput,
2692
+ ): Promise<ChatbotFormSubmitResponse> {
2693
+ const body = chatbotFormSubmitInputSchema.parse(input);
2694
+ return this.client.request({
2695
+ method: "POST",
2696
+ path: `forms/${encodeURIComponent(slug)}`,
2697
+ body,
2698
+ responseSchema: chatbotFormSubmitResponseSchema,
2699
+ });
2700
+ }
2701
+
2702
+ /**
2703
+ * Fetch a form the AI chatbot recommended via a `show_form` UI action. This
2704
+ * additionally requires the form to be chatbot-accessible (`chatbotAccess`),
2705
+ * which a recommended form always is. Most embeds want {@link getForm}; reach
2706
+ * for this only inside the chat flow.
2707
+ */
2708
+ async getChatForm(slug: string): Promise<ChatbotFormDefinition> {
2709
+ return this.client.request({
2710
+ method: "GET",
2711
+ path: `chatbot/forms/${encodeURIComponent(slug)}`,
2712
+ responseSchema: chatbotFormDefinitionSchema,
2713
+ });
2714
+ }
2715
+
2716
+ /**
2717
+ * Submit a chatbot-recommended form. Same CRM fan-out as {@link submitForm},
2718
+ * but on the chatbot path — pass `priorChat` so the resulting CRM inquiry can
2719
+ * include the chat transcript that led to the submission.
2720
+ */
2721
+ async submitChatForm(
2722
+ slug: string,
2723
+ input: ChatbotFormSubmitInput,
2724
+ ): Promise<ChatbotFormSubmitResponse> {
2725
+ const body = chatbotFormSubmitInputSchema.parse(input);
2726
+ return this.client.request({
2727
+ method: "POST",
2728
+ path: `chatbot/forms/${encodeURIComponent(slug)}`,
2729
+ body,
2730
+ responseSchema: chatbotFormSubmitResponseSchema,
2731
+ });
2732
+ }
2733
+
2734
+ // ── Front Desk (Batch JJ) ────────────────────────────────────────
2735
+
2736
+ /**
2737
+ * Create or resume the persisted `chatSessions` row for this
2738
+ * visitor. Idempotent on `(orgId, visitorToken)`. The returned
2739
+ * `sessionId` is what you pass to `postMessage`, `pollMessages`,
2740
+ * and `requestHuman`.
2741
+ *
2742
+ * Distinct from {@link createSession} — that mints an iframe-embed
2743
+ * token. This one exposes the underlying server session so the
2744
+ * consumer can render their own UI and still reach Front Desk.
2745
+ */
2746
+ async createPersistedSession(
2747
+ input: ChatbotPersistedSessionInput,
2748
+ ): Promise<ChatbotPersistedSessionResponse> {
2749
+ const body = chatbotPersistedSessionInputSchema.parse(input);
2750
+ return this.client.request({
2751
+ method: "POST",
2752
+ path: "chatbot/persisted-session",
2753
+ body,
2754
+ responseSchema: chatbotPersistedSessionResponseSchema,
2755
+ });
2756
+ }
2757
+
2758
+ /**
2759
+ * Poll messages + session status. Pass `since` (ISO timestamp) on
2760
+ * subsequent polls to only fetch new rows. Recommended cadence:
2761
+ * 3–5s while the widget is open and focused.
2762
+ */
2763
+ async pollMessages(input: {
2764
+ sessionId: string;
2765
+ visitorToken: string;
2766
+ since?: string;
2767
+ }): Promise<ChatbotMessagesResponse> {
2768
+ return this.client.request({
2769
+ path: "chatbot/messages",
2770
+ query: {
2771
+ sessionId: input.sessionId,
2772
+ visitorToken: input.visitorToken,
2773
+ since: input.since,
2774
+ },
2775
+ responseSchema: chatbotMessagesResponseSchema,
2776
+ });
2777
+ }
2778
+
2779
+ /**
2780
+ * Append a visitor message. Returns the updated message log so
2781
+ * the consumer can replace its local list with an authoritative
2782
+ * snapshot rather than tracking optimistic updates manually.
2783
+ */
2784
+ async postMessage(
2785
+ input: ChatbotAppendMessageInput,
2786
+ ): Promise<ChatbotMessagesResponse> {
2787
+ const body = chatbotAppendMessageInputSchema.parse(input);
2788
+ return this.client.request({
2789
+ method: "POST",
2790
+ path: "chatbot/messages",
2791
+ body,
2792
+ responseSchema: chatbotMessagesResponseSchema,
2793
+ });
2794
+ }
2795
+
2796
+ /**
2797
+ * Visitor presses "Talk to a person." Plan-gated on
2798
+ * `chat.front_desk` — orgs on Launch get back
2799
+ * `{ available: false, reason: 'plan_required', requiredFeature, upgradeUrl }`.
2800
+ * Orgs on Growth+ get `{ available: true, status, placeInLine, ... }`.
2801
+ *
2802
+ * Branch on `result.available` to render an upgrade CTA vs the
2803
+ * "you're in line" UI.
2804
+ *
2805
+ * @example
2806
+ * ```ts
2807
+ * const result = await client.chatbot.requestHuman({ sessionId, visitorToken });
2808
+ * if (!result.available) return <UpgradeCard feature={result.requiredFeature} />;
2809
+ * return <Queue position={result.placeInLine} eta={result.estimateLabel} />;
2810
+ * ```
2811
+ */
2812
+ async requestHuman(
2813
+ input: ChatbotRequestHumanInput,
2814
+ ): Promise<ChatbotRequestHumanResponse> {
2815
+ const body = chatbotRequestHumanInputSchema.parse(input);
2816
+ return this.client.request({
2817
+ method: "POST",
2818
+ path: "chatbot/request-human",
2819
+ body,
2820
+ responseSchema: chatbotRequestHumanResponseSchema,
2821
+ });
2822
+ }
2823
+ }
2824
+
2825
+ /**
2826
+ * Reads the active customer-portal context (organization + user) and exposes
2827
+ * profile mutations + a list of *only this user's* other customer
2828
+ * relationships.
2829
+ *
2830
+ * @remarks
2831
+ * **Privacy:** When `organizationId` is set, every request is pinned to that
2832
+ * org via the `X-BIAB-Customer-Portal-Org` header. The server-side
2833
+ * `customerPortalProcedure` will reject any access to a different org, so a
2834
+ * customer browsing a Dominoes-branded site can never accidentally see Pizza
2835
+ * Hut data even if their account is linked to both. This is the recommended
2836
+ * mode for embedding the SDK on a tenant's site or app.
2837
+ */
2838
+ /**
2839
+ * Decode a base64 string to bytes. Uses `atob` (available in browsers and
2840
+ * Node >= 16), so it needs no Buffer polyfill in browser bundles.
2841
+ */
2842
+ function decodeBase64ToBytes(base64: string): Uint8Array {
2843
+ const binary = atob(base64);
2844
+ const bytes = new Uint8Array(binary.length);
2845
+ for (let i = 0; i < binary.length; i++) {
2846
+ bytes[i] = binary.charCodeAt(i);
2847
+ }
2848
+ return bytes;
2849
+ }
2850
+
2851
+ export class BiabDevCustomerPortalClient {
2852
+ constructor(
2853
+ private readonly client: BiabDevClient,
2854
+ private readonly organizationId: string | null,
2855
+ /**
2856
+ * Optional tenantAuth session token. When set, requests include
2857
+ * an `X-BIAB-Session-Token` header and the BIAB customer-portal
2858
+ * routes resolve the calling user via the session payload
2859
+ * instead of the API key's user binding.
2860
+ *
2861
+ * This is the right path for consumer storefronts that don't
2862
+ * mint per-customer API keys: sign customers in via
2863
+ * `client.auth.signIn()`, store the resulting session token
2864
+ * (the SDK's `createAuthHandler` does this in an httpOnly
2865
+ * cookie automatically), then pass that token to
2866
+ * `client.customerPortal(orgId).withSession(token)` for every
2867
+ * customer-scoped call.
2868
+ */
2869
+ private readonly sessionToken: string | null = null,
2870
+ ) {}
2871
+
2872
+ /**
2873
+ * Return a customer-portal client that authenticates the calling
2874
+ * user via the tenantAuth session token instead of (or in
2875
+ * addition to) the API key's user binding. Chainable.
2876
+ *
2877
+ * @example
2878
+ * ```ts
2879
+ * const session = await getTenantSessionFromCookie({ cookieValue });
2880
+ * const work = await client
2881
+ * .customerPortal(orgId)
2882
+ * .withSession(session.sessionToken)
2883
+ * .getWork();
2884
+ * ```
2885
+ */
2886
+ withSession(sessionToken: string): BiabDevCustomerPortalClient {
2887
+ return new BiabDevCustomerPortalClient(
2888
+ this.client,
2889
+ this.organizationId,
2890
+ sessionToken,
2891
+ );
2892
+ }
2893
+
2894
+ private headers(): HeadersInit {
2895
+ const out: Record<string, string> = {};
2896
+ if (this.organizationId) {
2897
+ out["X-BIAB-Customer-Portal-Org"] = this.organizationId;
2898
+ }
2899
+ if (this.sessionToken) {
2900
+ out["X-BIAB-Session-Token"] = this.sessionToken;
2901
+ }
2902
+ return out;
2903
+ }
2904
+
2905
+ async context(): Promise<CustomerPortalContextResponse> {
2906
+ return this.client.request({
2907
+ path: "customer-portal/context",
2908
+ responseSchema: customerPortalContextResponseSchema,
2909
+ headers: this.headers(),
2910
+ });
2911
+ }
2912
+
2913
+ async getProfile(): Promise<CustomerPortalProfile> {
2914
+ return this.client.request({
2915
+ path: "customer-portal/profile",
2916
+ responseSchema: customerPortalProfileSchema,
2917
+ headers: this.headers(),
2918
+ });
2919
+ }
2920
+
2921
+ async updateProfile(
2922
+ input: CustomerPortalUpdateProfileInput,
2923
+ ): Promise<CustomerPortalUpdateProfileResponse> {
2924
+ const body = customerPortalUpdateProfileInputSchema.parse(input);
2925
+ return this.client.request({
2926
+ method: "POST",
2927
+ path: "customer-portal/profile",
2928
+ body,
2929
+ responseSchema: customerPortalUpdateProfileResponseSchema,
2930
+ headers: this.headers(),
2931
+ });
2932
+ }
2933
+
2934
+ async myOtherCustomerOrgs(): Promise<CustomerPortalOtherOrgsResponse> {
2935
+ return this.client.request({
2936
+ path: "customer-portal/other-orgs",
2937
+ responseSchema: customerPortalOtherOrgsResponseSchema,
2938
+ headers: this.headers(),
2939
+ });
2940
+ }
2941
+
2942
+ /**
2943
+ * Read the signed-in customer's notification preferences (per category,
2944
+ * per channel) for the bound org. The response also includes the full
2945
+ * category + channel catalog so SDK consumers can render the UI
2946
+ * without hardcoding either — both grow over time on the server side.
2947
+ *
2948
+ * @example
2949
+ * ```ts
2950
+ * const { preferences, categories, channels } =
2951
+ * await client.customerPortal().getNotificationPreferences();
2952
+ *
2953
+ * categories.map((c) => (
2954
+ * <Row category={c} state={preferences[c.id]} />
2955
+ * ));
2956
+ * ```
2957
+ */
2958
+ async getNotificationPreferences(): Promise<NotificationPreferencesResponse> {
2959
+ return this.client.request({
2960
+ path: "customer-portal/notification-preferences",
2961
+ responseSchema: notificationPreferencesResponseSchema,
2962
+ headers: this.headers(),
2963
+ });
2964
+ }
2965
+
2966
+ /**
2967
+ * Update notification preferences. Sparse: only send the toggles you
2968
+ * want to flip — omitted keys keep their persisted (or default) value.
2969
+ * Compliance-required channels (e.g. transactional email) silently
2970
+ * stay true regardless of what's sent.
2971
+ *
2972
+ * @example
2973
+ * ```ts
2974
+ * await client.customerPortal().updateNotificationPreferences({
2975
+ * preferences: { marketing: { email: false, sms: false } },
2976
+ * });
2977
+ * ```
2978
+ */
2979
+ async updateNotificationPreferences(
2980
+ input: NotificationPreferencesUpdateInput,
2981
+ ): Promise<NotificationPreferencesResponse> {
2982
+ const body = notificationPreferencesUpdateInputSchema.parse(input);
2983
+ return this.client.request({
2984
+ method: "POST",
2985
+ path: "customer-portal/notification-preferences",
2986
+ body,
2987
+ responseSchema: notificationPreferencesResponseSchema,
2988
+ headers: this.headers(),
2989
+ });
2990
+ }
2991
+
2992
+ /**
2993
+ * Read the signed-in customer's "what's on my plate" bundle: open
2994
+ * jobs, pending quotes, unpaid invoices, contracts awaiting signature,
2995
+ * and a recent payment history, plus a roll-up summary for the
2996
+ * consumer header.
2997
+ *
2998
+ * URLs are host-less paths — resolve them against the consumer's own
2999
+ * storefront origin to build clickable links.
3000
+ *
3001
+ * @example
3002
+ * ```ts
3003
+ * const work = await client.customerPortal().getWork();
3004
+ * const { openJobCount, unpaidBalance } = work.summary;
3005
+ * work.invoices.filter((i) => i.balanceDue > 0).map(...)
3006
+ * ```
3007
+ */
3008
+ async getWork(): Promise<CustomerWorkBundle> {
3009
+ return this.client.request({
3010
+ path: "customer-portal/work",
3011
+ responseSchema: customerWorkBundleSchema,
3012
+ headers: this.headers(),
3013
+ });
3014
+ }
3015
+
3016
+ /** Read a single job the signed-in customer owns. */
3017
+ async getJob(jobId: string): Promise<CustomerJobDetail> {
3018
+ return this.client.request({
3019
+ path: `customer-portal/jobs/${encodeURIComponent(jobId)}`,
3020
+ responseSchema: customerJobDetailSchema,
3021
+ headers: this.headers(),
3022
+ });
3023
+ }
3024
+
3025
+ /** Read a single quote the signed-in customer owns. */
3026
+ async getQuote(quoteId: string): Promise<CustomerQuoteDetail> {
3027
+ return this.client.request({
3028
+ path: `customer-portal/quotes/${encodeURIComponent(quoteId)}`,
3029
+ responseSchema: customerQuoteDetailSchema,
3030
+ headers: this.headers(),
3031
+ });
3032
+ }
3033
+
3034
+ /** Naming alias for {@link getQuote} — read a single quote the customer owns. */
3035
+ async viewQuote(quoteId: string): Promise<CustomerQuoteDetail> {
3036
+ return this.getQuote(quoteId);
3037
+ }
3038
+
3039
+ /**
3040
+ * Accept a quote as the signed-in customer. Idempotent — re-accepting
3041
+ * an already-accepted quote resolves the existing state without
3042
+ * additional side effects.
3043
+ */
3044
+ async acceptQuote(quoteId: string): Promise<CustomerQuoteActionResponse> {
3045
+ return this.client.request({
3046
+ method: "POST",
3047
+ path: `customer-portal/quotes/${encodeURIComponent(quoteId)}/accept`,
3048
+ responseSchema: customerQuoteActionResponseSchema,
3049
+ headers: this.headers(),
3050
+ });
3051
+ }
3052
+
3053
+ /** Reject a quote as the signed-in customer. Idempotent. */
3054
+ async rejectQuote(quoteId: string): Promise<CustomerQuoteActionResponse> {
3055
+ return this.client.request({
3056
+ method: "POST",
3057
+ path: `customer-portal/quotes/${encodeURIComponent(quoteId)}/reject`,
3058
+ responseSchema: customerQuoteActionResponseSchema,
3059
+ headers: this.headers(),
3060
+ });
3061
+ }
3062
+
3063
+ /** Read a single invoice the signed-in customer owns. */
3064
+ async getInvoice(invoiceId: string): Promise<CustomerInvoiceDetail> {
3065
+ return this.client.request({
3066
+ path: `customer-portal/invoices/${encodeURIComponent(invoiceId)}`,
3067
+ responseSchema: customerInvoiceDetailSchema,
3068
+ headers: this.headers(),
3069
+ });
3070
+ }
3071
+
3072
+ /**
3073
+ * Start payment for an invoice the customer owns. Returns a Stripe-hosted
3074
+ * Checkout URL to redirect to — card entry happens entirely on Stripe, so
3075
+ * your app never touches card data (stays out of PCI scope).
3076
+ *
3077
+ * Pass your own `successUrl` / `cancelUrl` to return into your storefront,
3078
+ * or omit them to land back on the org's branded hosted invoice page.
3079
+ *
3080
+ * @example
3081
+ * ```ts
3082
+ * const { checkoutUrl } = await client
3083
+ * .customerPortal()
3084
+ * .payInvoice(invoiceId, { successUrl: "https://shop.example.com/paid" });
3085
+ * window.location.href = checkoutUrl;
3086
+ * ```
3087
+ */
3088
+ async payInvoice(
3089
+ invoiceId: string,
3090
+ opts?: CustomerInvoicePayInput,
3091
+ ): Promise<CustomerInvoicePayResponse> {
3092
+ const body = customerInvoicePayInputSchema.parse(opts ?? {});
3093
+ return this.client.request({
3094
+ method: "POST",
3095
+ path: `customer-portal/invoices/${encodeURIComponent(invoiceId)}/pay`,
3096
+ body,
3097
+ responseSchema: customerInvoicePayResponseSchema,
3098
+ headers: this.headers(),
3099
+ });
3100
+ }
3101
+
3102
+ /**
3103
+ * Download the customer's invoice as a PDF. Returns the file metadata plus
3104
+ * the decoded `bytes` (a `Uint8Array`) — the auth headers ride along on the
3105
+ * SDK request, so this works for a signed-in customer without exposing a
3106
+ * public URL. Once paid, the PDF carries a PAID marker + payment history.
3107
+ *
3108
+ * @example Browser download
3109
+ * ```ts
3110
+ * const file = await client.customerPortal(orgId).withSession(token)
3111
+ * .downloadInvoicePdf(invoiceId);
3112
+ * const url = URL.createObjectURL(
3113
+ * new Blob([file.bytes], { type: file.contentType }),
3114
+ * );
3115
+ * const a = document.createElement("a");
3116
+ * a.href = url;
3117
+ * a.download = file.filename;
3118
+ * a.click();
3119
+ * URL.revokeObjectURL(url);
3120
+ * ```
3121
+ */
3122
+ async downloadInvoicePdf(
3123
+ invoiceId: string,
3124
+ ): Promise<CustomerInvoicePdf & { bytes: Uint8Array }> {
3125
+ const res = await this.client.request({
3126
+ path: `customer-portal/invoices/${encodeURIComponent(invoiceId)}/pdf`,
3127
+ responseSchema: customerInvoicePdfSchema,
3128
+ headers: this.headers(),
3129
+ });
3130
+ return { ...res, bytes: decodeBase64ToBytes(res.base64) };
3131
+ }
3132
+
3133
+ /** Read a single contract the signed-in customer owns. */
3134
+ async getContract(contractId: string): Promise<CustomerContractDetail> {
3135
+ return this.client.request({
3136
+ path: `customer-portal/contracts/${encodeURIComponent(contractId)}`,
3137
+ responseSchema: customerContractDetailSchema,
3138
+ headers: this.headers(),
3139
+ });
3140
+ }
3141
+
3142
+ /**
3143
+ * Begin signing a contract the customer owns. Returns a ready, absolute
3144
+ * signing URL on the org's branded hostname — redirect the customer there
3145
+ * to BIAB's hosted e-sign page, which captures the signature and generates
3146
+ * the executed PDF. No signature payload is collected client-side, so the
3147
+ * legal record stays consistent across consumers.
3148
+ *
3149
+ * Rejects with `contract_already_signed` (409) or `contract_not_signable`
3150
+ * (409) when the contract isn't in a signable state.
3151
+ *
3152
+ * @example
3153
+ * ```ts
3154
+ * const { signingUrl } = await client
3155
+ * .customerPortal()
3156
+ * .signContract(contractId);
3157
+ * window.location.href = signingUrl;
3158
+ * ```
3159
+ */
3160
+ async signContract(
3161
+ contractId: string,
3162
+ ): Promise<CustomerContractSignResponse> {
3163
+ return this.client.request({
3164
+ method: "POST",
3165
+ path: `customer-portal/contracts/${encodeURIComponent(contractId)}/sign`,
3166
+ responseSchema: customerContractSignResponseSchema,
3167
+ headers: this.headers(),
3168
+ });
3169
+ }
3170
+
3171
+ /** List reviews the signed-in customer has submitted in this org. */
3172
+ async listMyReviews(): Promise<CustomerReviewListResponse> {
3173
+ return this.client.request({
3174
+ path: "customer-portal/reviews",
3175
+ responseSchema: customerReviewListResponseSchema,
3176
+ headers: this.headers(),
3177
+ });
3178
+ }
3179
+
3180
+ /**
3181
+ * Referral roll-up for the signed-in customer (as an affiliate), summed
3182
+ * across every program they're enrolled in. `enrolled: false` with zeroed
3183
+ * counters when the customer holds no affiliate record. Real data appears
3184
+ * once the org's referral attribution + payout pipeline records activity.
3185
+ *
3186
+ * @example
3187
+ * ```ts
3188
+ * const stats = await client.customerPortal().myReferralStats();
3189
+ * if (stats.enrolled) showEarnings(stats.earningsCents);
3190
+ * ```
3191
+ */
3192
+ async myReferralStats(): Promise<CustomerReferralStats> {
3193
+ return this.client.request({
3194
+ path: "customer-portal/referrals/stats",
3195
+ responseSchema: customerReferralStatsSchema,
3196
+ headers: this.headers(),
3197
+ });
3198
+ }
3199
+
3200
+ /**
3201
+ * The customer's referral conversion feed (most recent first). Empty until
3202
+ * the attribution pipeline records conversions. `limit` 1–200 (default 50).
3203
+ */
3204
+ async myReferralHistory(opts?: {
3205
+ limit?: number;
3206
+ }): Promise<CustomerReferralHistoryResponse> {
3207
+ const limit = opts?.limit;
3208
+ const qs =
3209
+ typeof limit === "number"
3210
+ ? `?limit=${encodeURIComponent(String(limit))}`
3211
+ : "";
3212
+ return this.client.request({
3213
+ path: `customer-portal/referrals/history${qs}`,
3214
+ responseSchema: customerReferralHistoryResponseSchema,
3215
+ headers: this.headers(),
3216
+ });
3217
+ }
3218
+
3219
+ /** The customer's referral payout history + upcoming rows (newest first). */
3220
+ async myReferralPayouts(): Promise<CustomerReferralPayoutsResponse> {
3221
+ return this.client.request({
3222
+ path: "customer-portal/referrals/payouts",
3223
+ responseSchema: customerReferralPayoutsResponseSchema,
3224
+ headers: this.headers(),
3225
+ });
3226
+ }
3227
+
3228
+ /**
3229
+ * The signed-in customer's orders (newest first), each with a shipment
3230
+ * roll-up (`overallStatus`, `anyInTransit`, `latestEstimatedDelivery`) for
3231
+ * an orders/shipping dashboard list. `limit` 1–100 (default 30). Requires
3232
+ * the org's `ecommerce.shipping` entitlement.
3233
+ *
3234
+ * @example
3235
+ * ```ts
3236
+ * const { items } = await client.customerPortal().listOrders();
3237
+ * items.filter((o) => o.anyInTransit).map(renderTracker);
3238
+ * ```
3239
+ */
3240
+ async listOrders(opts?: {
3241
+ limit?: number;
3242
+ }): Promise<CustomerOrderListResponse> {
3243
+ const limit = opts?.limit;
3244
+ const qs =
3245
+ typeof limit === "number"
3246
+ ? `?limit=${encodeURIComponent(String(limit))}`
3247
+ : "";
3248
+ return this.client.request({
3249
+ path: `customer-portal/orders${qs}`,
3250
+ responseSchema: customerOrderListResponseSchema,
3251
+ headers: this.headers(),
3252
+ });
3253
+ }
3254
+
3255
+ /**
3256
+ * The customer's full payment history with this org — invoice payments,
3257
+ * store-order charges, and refunds (negative amounts), newest first.
3258
+ * Powers a "Payments" tab on the customer dashboard.
3259
+ *
3260
+ * ```ts
3261
+ * const { items } = await client.customerPortal().listPayments();
3262
+ * items.map((p) => `${p.occurredAt} ${p.kind} $${p.amount}`);
3263
+ * ```
3264
+ */
3265
+ async listPayments(opts?: {
3266
+ limit?: number;
3267
+ }): Promise<import("./contracts").CustomerPaymentHistoryResponse> {
3268
+ const limit = opts?.limit;
3269
+ const qs =
3270
+ typeof limit === "number"
3271
+ ? `?limit=${encodeURIComponent(String(limit))}`
3272
+ : "";
3273
+ return this.client.request({
3274
+ path: `customer-portal/payments${qs}`,
3275
+ responseSchema: customerPaymentHistoryResponseSchema,
3276
+ headers: this.headers(),
3277
+ });
3278
+ }
3279
+
3280
+ /**
3281
+ * One order the customer owns: line items + shipments, each shipment with
3282
+ * its carrier, tracking link, status, and `statusHistory` timeline (read
3283
+ * from stored data — webhook-fresh).
3284
+ */
3285
+ async getOrder(orderId: string): Promise<CustomerOrderDetail> {
3286
+ return this.client.request({
3287
+ path: `customer-portal/orders/${encodeURIComponent(orderId)}`,
3288
+ responseSchema: customerOrderDetailSchema,
3289
+ headers: this.headers(),
3290
+ });
3291
+ }
3292
+
3293
+ /**
3294
+ * Open a return / refund request on an order the customer owns. Creates a
3295
+ * `requested` record and notifies the org's review queue — the org's
3296
+ * approval (not this call) triggers the actual Stripe refund. Omit `items`
3297
+ * for a whole-order request. The request then shows up under
3298
+ * `getOrder(orderId).returnRequests`.
3299
+ *
3300
+ * @example
3301
+ * ```ts
3302
+ * await portal.submitReturnRequest(orderId, {
3303
+ * reason: "damaged",
3304
+ * customerNote: "Arrived cracked.",
3305
+ * items: [{ orderItemId, quantity: 1 }],
3306
+ * });
3307
+ * ```
3308
+ */
3309
+ async submitReturnRequest(
3310
+ orderId: string,
3311
+ input?: CustomerReturnRequestInput,
3312
+ ): Promise<CustomerReturnRequestCreated> {
3313
+ const body = customerReturnRequestInputSchema.parse(input ?? {});
3314
+ return this.client.request({
3315
+ method: "POST",
3316
+ path: `customer-portal/orders/${encodeURIComponent(orderId)}/return-request`,
3317
+ body,
3318
+ responseSchema: customerReturnRequestCreatedSchema,
3319
+ headers: this.headers(),
3320
+ });
3321
+ }
3322
+
3323
+ /**
3324
+ * List the digital downloads the customer is entitled to for a paid order
3325
+ * (P10d). Each item is a ready-to-use URL — a short-lived presigned link for
3326
+ * BIAB-hosted files (`expiresInSeconds` set) or the org's external link.
3327
+ * Returns an empty list until the order's payment settles.
3328
+ *
3329
+ * @example
3330
+ * ```ts
3331
+ * const { items } = await portal.getDownloads(orderId);
3332
+ * for (const d of items) console.log(d.productName, d.url);
3333
+ * ```
3334
+ */
3335
+ async getDownloads(orderId: string): Promise<CustomerDownloadsResponse> {
3336
+ return this.client.request({
3337
+ path: `customer-portal/orders/${encodeURIComponent(orderId)}/downloads`,
3338
+ responseSchema: customerDownloadsResponseSchema,
3339
+ headers: this.headers(),
3340
+ });
3341
+ }
3342
+
3343
+ /**
3344
+ * Track a single shipment the customer owns (resolved through their own
3345
+ * order). Returns the stored shipment plus a best-effort live carrier status
3346
+ * (`liveStatus`) when it's still in transit.
3347
+ */
3348
+ async trackShipment(shipmentId: string): Promise<CustomerShipmentTracking> {
3349
+ return this.client.request({
3350
+ path: `customer-portal/shipments/${encodeURIComponent(shipmentId)}/track`,
3351
+ responseSchema: customerShipmentTrackingSchema,
3352
+ headers: this.headers(),
3353
+ });
3354
+ }
3355
+
3356
+ /**
3357
+ * Submit a review tied to the signed-in customer's email. Land
3358
+ * with `status: pending` so org staff can moderate before the
3359
+ * review surfaces on the public bundle. `jobId` is optional and
3360
+ * validated server-side against the customer's own jobs — a
3361
+ * stranger's id is rejected with a 403.
3362
+ */
3363
+ async submitReview(
3364
+ input: CustomerReviewSubmitInput,
3365
+ ): Promise<CustomerReviewSubmitResponse> {
3366
+ const body = customerReviewSubmitInputSchema.parse(input);
3367
+ return this.client.request({
3368
+ method: "POST",
3369
+ path: "customer-portal/reviews",
3370
+ body,
3371
+ responseSchema: customerReviewSubmitResponseSchema,
3372
+ headers: this.headers(),
3373
+ });
3374
+ }
3375
+
3376
+ /** List the comment thread on a job the customer owns. */
3377
+ async listJobComments(
3378
+ jobId: string,
3379
+ ): Promise<CustomerJobCommentsListResponse> {
3380
+ return this.client.request({
3381
+ path: `customer-portal/jobs/${encodeURIComponent(jobId)}/comments`,
3382
+ responseSchema: customerJobCommentsListResponseSchema,
3383
+ headers: this.headers(),
3384
+ });
3385
+ }
3386
+
3387
+ /** Post a customer comment on one of the customer's own jobs. */
3388
+ async postJobComment(
3389
+ jobId: string,
3390
+ input: CustomerJobCommentPostInput,
3391
+ ): Promise<CustomerJobComment> {
3392
+ const body = customerJobCommentPostInputSchema.parse(input);
3393
+ return this.client.request({
3394
+ method: "POST",
3395
+ path: `customer-portal/jobs/${encodeURIComponent(jobId)}/comments`,
3396
+ body,
3397
+ responseSchema: customerJobCommentSchema,
3398
+ headers: this.headers(),
3399
+ });
3400
+ }
3401
+
3402
+ /**
3403
+ * Open (or reuse) a front-desk chat session bound to the signed-in
3404
+ * customer and post their first message. Staff see the thread in
3405
+ * the chatbot inbox with `linkedContactId` set so they know who's
3406
+ * reaching out. Returns the `sessionId` + `visitorToken` so the
3407
+ * consumer can poll for staff replies or render the chatbot widget
3408
+ * over the same session via the existing chatbot embed flow.
3409
+ */
3410
+ async contactCompany(
3411
+ input: CustomerContactCompanyInput,
3412
+ ): Promise<CustomerContactCompanyResponse> {
3413
+ const body = customerContactCompanyInputSchema.parse(input);
3414
+ return this.client.request({
3415
+ method: "POST",
3416
+ path: "customer-portal/contact-company",
3417
+ body,
3418
+ responseSchema: customerContactCompanyResponseSchema,
3419
+ headers: this.headers(),
3420
+ });
3421
+ }
3422
+
3423
+ /**
3424
+ * Staff-on-the-way view for a job the customer owns. First-cut:
3425
+ * returns assigned-tech display names + dispatch status, the last
3426
+ * known GPS ping per tech (when available), and the most recent
3427
+ * ETA snapshot from the dispatch_offers cascade.
3428
+ *
3429
+ * `liveEtaAvailable: false` today — flips to true once the
3430
+ * dispatch travel-time recompute layer ships, at which point the
3431
+ * `eta` field switches sources without breaking this shape.
3432
+ */
3433
+ async getJobEta(jobId: string): Promise<CustomerJobEtaResponse> {
3434
+ return this.client.request({
3435
+ path: `customer-portal/jobs/${encodeURIComponent(jobId)}/eta`,
3436
+ responseSchema: customerJobEtaResponseSchema,
3437
+ headers: this.headers(),
3438
+ });
3439
+ }
3440
+
3441
+ /**
3442
+ * Unified job activity feed: structured events (status changes,
3443
+ * dispatch lifecycle, media uploads, quote/contract/invoice
3444
+ * transitions) interleaved with chat messages linked to this
3445
+ * customer's contact. One linear timeline so the customer can
3446
+ * see "what happened, then what was said, then what happened
3447
+ * next" without piecing together separate views.
3448
+ *
3449
+ * Each entry is `{ kind: 'event' | 'chat', id, at, ... }`. The
3450
+ * discriminated union means TypeScript narrows correctly when
3451
+ * you branch on `kind`.
3452
+ */
3453
+ async getJobActivity(jobId: string): Promise<CustomerJobActivityResponse> {
3454
+ return this.client.request({
3455
+ path: `customer-portal/jobs/${encodeURIComponent(jobId)}/activity`,
3456
+ responseSchema: customerJobActivityResponseSchema,
3457
+ headers: this.headers(),
3458
+ });
3459
+ }
3460
+ }
3461
+
3462
+ /**
3463
+ * Creates a {@link BiabDevClient} for the given host `baseUrl` and package API key.
3464
+ *
3465
+ * @remarks
3466
+ * **Alpha:** This package is still in alpha release. APIs may change; it is **not** intended
3467
+ * for production use yet. Prefer non-production environments until a stable major is published.
3468
+ */
3469
+ export function createBiabDevClient(options: BiabDevClientOptions) {
3470
+ return new BiabDevClient(options);
3471
+ }
3472
+
3473
+ /**
3474
+ * Per-tenant auth: hosted sign-in/sign-up scoped to the org bound to
3475
+ * the API key. Used by the SDK's `createAuthHandler` (server) and
3476
+ * `<SignIn />` / `<SignUp />` / `<SignOut />` components (client).
3477
+ */
3478
+ export class BiabDevAuthClient {
3479
+ constructor(private readonly client: BiabDevClient) {}
3480
+
3481
+ async start(input: TenantAuthStartInput): Promise<TenantAuthStartResponse> {
3482
+ const body = tenantAuthStartInputSchema.parse(input);
3483
+ return this.client.request({
3484
+ method: "POST",
3485
+ path: "auth/start",
3486
+ body,
3487
+ responseSchema: tenantAuthStartResponseSchema,
3488
+ });
3489
+ }
3490
+
3491
+ /**
3492
+ * Ergonomic shortcut for `start({ intent: "sign-in", ... })`. Returns
3493
+ * the platform-hosted redirect URL the caller should navigate to;
3494
+ * stateless on its own — pair with `exchange()` on your callback
3495
+ * route to mint the session.
3496
+ */
3497
+ async signIn(input: {
3498
+ redirectUri: string;
3499
+ returnTo?: string;
3500
+ loginHint?: string;
3501
+ }): Promise<TenantAuthStartResponse> {
3502
+ return this.start({ intent: "sign-in", ...input });
3503
+ }
3504
+
3505
+ /**
3506
+ * Ergonomic shortcut for `start({ intent: "sign-up", ... })`. Same
3507
+ * shape as `signIn`; only the platform-hosted page that opens differs.
3508
+ */
3509
+ async signUp(input: {
3510
+ redirectUri: string;
3511
+ returnTo?: string;
3512
+ loginHint?: string;
3513
+ }): Promise<TenantAuthStartResponse> {
3514
+ return this.start({ intent: "sign-up", ...input });
3515
+ }
3516
+
3517
+ /**
3518
+ * Alias for `requestPasswordReset`. Always resolves `{ ok: true }`
3519
+ * (the server doesn't reveal whether the email matched an account).
3520
+ */
3521
+ async forgotPassword(
3522
+ input: TenantAuthPasswordResetInput,
3523
+ ): Promise<TenantAuthPasswordResetResponse> {
3524
+ return this.requestPasswordReset(input);
3525
+ }
3526
+
3527
+ async exchange(
3528
+ input: TenantAuthExchangeInput,
3529
+ ): Promise<TenantAuthExchangeResponse> {
3530
+ const body = tenantAuthExchangeInputSchema.parse(input);
3531
+ return this.client.request({
3532
+ method: "POST",
3533
+ path: "auth/exchange",
3534
+ body,
3535
+ responseSchema: tenantAuthExchangeResponseSchema,
3536
+ });
3537
+ }
3538
+
3539
+ async me(input: { sessionToken: string }): Promise<TenantAuthMeResponse> {
3540
+ return this.client.request({
3541
+ method: "GET",
3542
+ path: "auth/me",
3543
+ headers: { "x-biab-session": input.sessionToken },
3544
+ responseSchema: tenantAuthMeResponseSchema,
3545
+ });
3546
+ }
3547
+
3548
+ async signOut(): Promise<TenantAuthSignOutResponse> {
3549
+ return this.client.request({
3550
+ method: "POST",
3551
+ path: "auth/sign-out",
3552
+ responseSchema: tenantAuthSignOutResponseSchema,
3553
+ });
3554
+ }
3555
+
3556
+ /**
3557
+ * Trigger a password-reset email for `input.email`. Always resolves
3558
+ * with `{ ok: true }` — the server intentionally does not reveal
3559
+ * whether a matching account exists. The email (when sent) contains
3560
+ * a platform-hosted reset URL; the consumer never needs to build a
3561
+ * "set new password" page.
3562
+ *
3563
+ * Server-only by design (the API key may not be exposed to the
3564
+ * browser). The matching browser-side `requestPasswordReset()` in
3565
+ * `@businessdash/sdk/react` proxies through the auth-handler.
3566
+ */
3567
+ async requestPasswordReset(
3568
+ input: TenantAuthPasswordResetInput,
3569
+ ): Promise<TenantAuthPasswordResetResponse> {
3570
+ const body = tenantAuthPasswordResetInputSchema.parse(input);
3571
+ return this.client.request({
3572
+ method: "POST",
3573
+ path: "auth/password-reset",
3574
+ body,
3575
+ responseSchema: tenantAuthPasswordResetResponseSchema,
3576
+ });
3577
+ }
3578
+ }