@businessdash/sdk 0.9.60 → 0.9.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/README.md +991 -295
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +622 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +888 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/consent-core/index.cjs +107 -0
  47. package/dist/consent-core/index.d.ts +103 -0
  48. package/dist/consent-core/index.d.ts.map +1 -0
  49. package/dist/consent-core/index.js +157 -0
  50. package/dist/consent-core/index.js.map +1 -0
  51. package/dist/contracts.d.ts +3526 -767
  52. package/dist/contracts.d.ts.map +1 -1
  53. package/dist/contracts.js +571 -4
  54. package/dist/contracts.js.map +1 -1
  55. package/dist/data-model-schema.d.ts +1 -1
  56. package/dist/data-model-schema.d.ts.map +1 -1
  57. package/dist/element.d.ts +30 -0
  58. package/dist/element.d.ts.map +1 -1
  59. package/dist/element.js +56 -0
  60. package/dist/element.js.map +1 -1
  61. package/dist/embed-protocol.d.ts +86 -0
  62. package/dist/embed-protocol.d.ts.map +1 -0
  63. package/dist/embed-protocol.js +60 -0
  64. package/dist/embed-protocol.js.map +1 -0
  65. package/dist/framework-support.d.ts +122 -0
  66. package/dist/framework-support.d.ts.map +1 -0
  67. package/dist/framework-support.js +323 -0
  68. package/dist/framework-support.js.map +1 -0
  69. package/dist/index.cjs +1355 -24
  70. package/dist/legal-core/index.cjs +115 -0
  71. package/dist/legal-core/index.d.ts +160 -0
  72. package/dist/legal-core/index.d.ts.map +1 -0
  73. package/dist/legal-core/index.js +220 -0
  74. package/dist/legal-core/index.js.map +1 -0
  75. package/dist/marketing-core/index.d.ts +66 -0
  76. package/dist/marketing-core/index.d.ts.map +1 -0
  77. package/dist/marketing-core/index.js +154 -0
  78. package/dist/marketing-core/index.js.map +1 -0
  79. package/dist/marketing.d.ts +7 -0
  80. package/dist/marketing.d.ts.map +1 -0
  81. package/dist/marketing.js +7 -0
  82. package/dist/marketing.js.map +1 -0
  83. package/dist/mcp-adapters/index.d.ts +135 -0
  84. package/dist/mcp-adapters/index.d.ts.map +1 -0
  85. package/dist/mcp-adapters/index.js +187 -0
  86. package/dist/mcp-adapters/index.js.map +1 -0
  87. package/dist/mcp-adapters.d.ts +7 -0
  88. package/dist/mcp-adapters.d.ts.map +1 -0
  89. package/dist/mcp-adapters.js +7 -0
  90. package/dist/mcp-adapters.js.map +1 -0
  91. package/dist/model3d-core/index.cjs +95 -0
  92. package/dist/model3d-core/index.d.ts +167 -0
  93. package/dist/model3d-core/index.d.ts.map +1 -0
  94. package/dist/model3d-core/index.js +158 -0
  95. package/dist/model3d-core/index.js.map +1 -0
  96. package/dist/notifications-core/index.cjs +315 -0
  97. package/dist/notifications-core/index.d.ts +229 -0
  98. package/dist/notifications-core/index.d.ts.map +1 -0
  99. package/dist/notifications-core/index.js +445 -0
  100. package/dist/notifications-core/index.js.map +1 -0
  101. package/dist/portal-core/index.d.ts +451 -0
  102. package/dist/portal-core/index.d.ts.map +1 -0
  103. package/dist/portal-core/index.js +819 -0
  104. package/dist/portal-core/index.js.map +1 -0
  105. package/dist/portal.d.ts +7 -0
  106. package/dist/portal.d.ts.map +1 -0
  107. package/dist/portal.js +7 -0
  108. package/dist/portal.js.map +1 -0
  109. package/dist/push-core/index.cjs +171 -0
  110. package/dist/push-core/index.d.ts +149 -0
  111. package/dist/push-core/index.d.ts.map +1 -0
  112. package/dist/push-core/index.js +231 -0
  113. package/dist/push-core/index.js.map +1 -0
  114. package/dist/react-marketing.d.ts +1 -17
  115. package/dist/react-marketing.d.ts.map +1 -1
  116. package/dist/react-marketing.js +4 -0
  117. package/dist/react-marketing.js.map +1 -1
  118. package/dist/react.d.ts +294 -101
  119. package/dist/react.d.ts.map +1 -1
  120. package/dist/react.js +302 -246
  121. package/dist/react.js.map +1 -1
  122. package/dist/seed-core/index.d.ts +354 -0
  123. package/dist/seed-core/index.d.ts.map +1 -0
  124. package/dist/seed-core/index.js +420 -0
  125. package/dist/seed-core/index.js.map +1 -0
  126. package/dist/seed.d.ts +7 -0
  127. package/dist/seed.d.ts.map +1 -0
  128. package/dist/seed.js +7 -0
  129. package/dist/seed.js.map +1 -0
  130. package/dist/seo-core/blog.d.ts +94 -0
  131. package/dist/seo-core/blog.d.ts.map +1 -0
  132. package/dist/seo-core/blog.js +149 -0
  133. package/dist/seo-core/blog.js.map +1 -0
  134. package/dist/seo-core/commerce.d.ts +64 -0
  135. package/dist/seo-core/commerce.d.ts.map +1 -0
  136. package/dist/seo-core/commerce.js +87 -0
  137. package/dist/seo-core/commerce.js.map +1 -0
  138. package/dist/seo-core/index.cjs +751 -0
  139. package/dist/seo-core/index.d.ts +226 -0
  140. package/dist/seo-core/index.d.ts.map +1 -0
  141. package/dist/seo-core/index.js +455 -0
  142. package/dist/seo-core/index.js.map +1 -0
  143. package/dist/seo-core/local.d.ts +181 -0
  144. package/dist/seo-core/local.d.ts.map +1 -0
  145. package/dist/seo-core/local.js +193 -0
  146. package/dist/seo-core/local.js.map +1 -0
  147. package/dist/sitemap-core/index.cjs +167 -0
  148. package/dist/sitemap-core/index.d.ts +155 -0
  149. package/dist/sitemap-core/index.d.ts.map +1 -0
  150. package/dist/sitemap-core/index.js +195 -0
  151. package/dist/sitemap-core/index.js.map +1 -0
  152. package/dist/social-core/index.d.ts +74 -0
  153. package/dist/social-core/index.d.ts.map +1 -0
  154. package/dist/social-core/index.js +72 -0
  155. package/dist/social-core/index.js.map +1 -0
  156. package/dist/social.d.ts +7 -0
  157. package/dist/social.d.ts.map +1 -0
  158. package/dist/social.js +7 -0
  159. package/dist/social.js.map +1 -0
  160. package/dist/solid-compat.d.ts +204 -0
  161. package/dist/solid-compat.d.ts.map +1 -0
  162. package/dist/solid-compat.js +221 -0
  163. package/dist/solid-compat.js.map +1 -0
  164. package/dist/store-core/index.d.ts +141 -0
  165. package/dist/store-core/index.d.ts.map +1 -0
  166. package/dist/store-core/index.js +277 -0
  167. package/dist/store-core/index.js.map +1 -0
  168. package/dist/store.d.ts +12 -0
  169. package/dist/store.d.ts.map +1 -0
  170. package/dist/store.js +12 -0
  171. package/dist/store.js.map +1 -0
  172. package/dist/vanilla.d.ts +111 -0
  173. package/dist/vanilla.d.ts.map +1 -1
  174. package/dist/vanilla.js +206 -0
  175. package/dist/vanilla.js.map +1 -1
  176. package/dist/verify-core/index.d.ts +98 -0
  177. package/dist/verify-core/index.d.ts.map +1 -0
  178. package/dist/verify-core/index.js +186 -0
  179. package/dist/verify-core/index.js.map +1 -0
  180. package/dist/verify.d.ts +8 -0
  181. package/dist/verify.d.ts.map +1 -0
  182. package/dist/verify.js +8 -0
  183. package/dist/verify.js.map +1 -0
  184. package/openapi.json +4516 -1311
  185. package/package.json +128 -11
  186. package/src/account-core/index.ts +309 -0
  187. package/src/account.ts +11 -0
  188. package/src/angular/headless.ts +519 -0
  189. package/src/angular/index.ts +3 -0
  190. package/src/auth-links.ts +76 -0
  191. package/src/blog-core/index.ts +423 -0
  192. package/src/blog.ts +11 -0
  193. package/src/chat-core/index.ts +335 -0
  194. package/src/chat.ts +11 -0
  195. package/src/checkout-core/index.ts +225 -0
  196. package/src/checkout.ts +10 -0
  197. package/src/cli.ts +184 -28
  198. package/src/client.ts +1097 -0
  199. package/src/consent-core/index.ts +213 -0
  200. package/src/contracts.ts +768 -11
  201. package/src/data-model-schema.ts +1 -1
  202. package/src/element.ts +102 -0
  203. package/src/embed-protocol.ts +100 -0
  204. package/src/framework-support.ts +440 -0
  205. package/src/legal-core/index.ts +276 -0
  206. package/src/marketing-core/index.ts +209 -0
  207. package/src/marketing.ts +7 -0
  208. package/src/mcp-adapters/index.ts +264 -0
  209. package/src/mcp-adapters.ts +7 -0
  210. package/src/model3d-core/index.ts +248 -0
  211. package/src/notifications-core/index.ts +650 -0
  212. package/src/portal-core/index.ts +1564 -0
  213. package/src/portal.ts +7 -0
  214. package/src/push-core/index.ts +335 -0
  215. package/src/qwik.tsx +602 -2
  216. package/src/react-marketing.tsx +5 -0
  217. package/src/react.tsx +434 -319
  218. package/src/seed-core/index.ts +735 -0
  219. package/src/seed.ts +7 -0
  220. package/src/seo-core/blog.ts +216 -0
  221. package/src/seo-core/commerce.ts +138 -0
  222. package/src/seo-core/index.ts +605 -0
  223. package/src/seo-core/local.ts +333 -0
  224. package/src/sitemap-core/index.ts +296 -0
  225. package/src/social-core/index.ts +103 -0
  226. package/src/social.ts +7 -0
  227. package/src/solid-compat.tsx +347 -0
  228. package/src/solid-surfaces.tsx +1311 -0
  229. package/src/solid.tsx +82 -60
  230. package/src/store-core/index.ts +426 -0
  231. package/src/store.ts +12 -0
  232. package/src/svelte/SocialLinks.svelte +84 -0
  233. package/src/svelte/headless.ts +435 -0
  234. package/src/svelte/index.ts +9 -0
  235. package/src/vanilla.ts +367 -0
  236. package/src/verify-core/index.ts +288 -0
  237. package/src/verify.ts +8 -0
  238. package/src/vue/headless.ts +527 -0
  239. package/src/vue/index.ts +3 -0
package/dist/contracts.js CHANGED
@@ -417,7 +417,8 @@ export const chatbotFormFieldSchema = z
417
417
  options: z.array(z.string()).optional(),
418
418
  })
419
419
  .passthrough();
420
- export const chatbotFormDefinitionSchema = z.object({
420
+ export const chatbotFormDefinitionSchema = z
421
+ .object({
421
422
  id: z.string(),
422
423
  slug: z.string(),
423
424
  name: z.string(),
@@ -425,11 +426,33 @@ export const chatbotFormDefinitionSchema = z.object({
425
426
  fields: z.array(chatbotFormFieldSchema),
426
427
  addsInquiry: z.boolean(),
427
428
  addsLead: z.boolean(),
428
- });
429
+ /**
430
+ * The RENDER contract — animation, multi-step + progress style, exit
431
+ * confirmation, URL safety. The server has always sent this; the schema
432
+ * dropped it, so anyone building a form UI on the typed client got a
433
+ * bare field list and none of the behaviour the dashboard configured.
434
+ */
435
+ settings: z.record(z.unknown()).nullish(),
436
+ /** Org avatar for the form header, when the form opts in. */
437
+ orgIcon: z.string().nullish(),
438
+ /** Public action previews ("submitting adds you to the list"). */
439
+ actions: z.array(z.record(z.unknown())).nullish(),
440
+ /** Non-sensitive business functions so `calculation` fields compute. */
441
+ businessFunctions: z.array(z.record(z.unknown())).nullish(),
442
+ })
443
+ // Additive server fields reach consumers instead of being silently dropped
444
+ // — the failure this schema just demonstrated.
445
+ .passthrough();
429
446
  export const chatbotFormSubmitInputSchema = z.object({
430
447
  data: z.record(z.unknown()),
431
448
  submitterEmail: z.string().email().optional(),
432
449
  submitterName: z.string().optional(),
450
+ /** Validate and run the full pipeline WITHOUT persisting. */
451
+ dryRun: z.boolean().optional(),
452
+ /** Provenance the route accepts and this schema previously omitted. */
453
+ source: z.string().max(200).optional(),
454
+ referrer: z.string().max(500).optional(),
455
+ metadata: z.record(z.unknown()).optional(),
433
456
  priorChat: z
434
457
  .array(z.object({
435
458
  role: z.string().min(1).max(40),
@@ -441,8 +464,14 @@ export const chatbotFormSubmitInputSchema = z.object({
441
464
  });
442
465
  export const chatbotFormSubmitResponseSchema = z.object({
443
466
  success: z.literal(true),
444
- submissionId: z.string(),
467
+ /**
468
+ * NULL on a dry run — the submission validated but was deliberately not
469
+ * persisted. Requiring a string here made every test submit throw.
470
+ */
471
+ submissionId: z.string().nullable(),
445
472
  slug: z.string(),
473
+ /** True when nothing was written. Pairs with a null `submissionId`. */
474
+ dryRun: z.boolean().optional(),
446
475
  });
447
476
  // ── Front Desk programmatic surface (Batch JJ, 0.9.0) ────────────────
448
477
  export const chatbotPersistedSessionInputSchema = z.object({
@@ -684,6 +713,36 @@ export const customerPortalProfileSchema = z
684
713
  joinedAt: timestampSchema.nullable(),
685
714
  })
686
715
  .nullable();
716
+ /**
717
+ * One quiz outcome for the signed-in customer. `scoreWithheld` marks an
718
+ * interview-mode quiz: the attempt exists, the verdict is staff-only —
719
+ * render "completed" and nothing else.
720
+ */
721
+ export const customerPortalQuizResultSchema = z.object({
722
+ invitationId: z.string(),
723
+ quizName: z.string(),
724
+ competency: z.string().nullable(),
725
+ status: z.string(),
726
+ completedAt: z.string().nullable(),
727
+ scoreWithheld: z.boolean(),
728
+ result: z
729
+ .object({
730
+ status: z.string(),
731
+ scorePercent: z.number().nullable(),
732
+ provisionalScorePercent: z.number().nullable(),
733
+ passed: z.boolean().nullable(),
734
+ pointsEarned: z.number().nullable(),
735
+ pointsPossible: z.number().nullable(),
736
+ passingScore: z.number(),
737
+ results: z.array(z.record(z.unknown())),
738
+ submittedAt: z.string().nullable(),
739
+ durationSeconds: z.number().nullable(),
740
+ })
741
+ .nullable(),
742
+ });
743
+ export const customerPortalQuizResultsResponseSchema = z.object({
744
+ items: z.array(customerPortalQuizResultSchema),
745
+ });
687
746
  export const customerPortalUpdateProfileInputSchema = z.object({
688
747
  displayName: z.string().min(1).max(120).optional(),
689
748
  profile: z.record(z.unknown()).optional(),
@@ -1045,6 +1104,16 @@ export const customerReturnRequestSchema = z.object({
1045
1104
  * are separate enums and each is spelled as the database spells it.
1046
1105
  */
1047
1106
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1107
+ /**
1108
+ * What was asked for: money back, or a replacement. Mirrors
1109
+ * `order_return_request_kind`. Rows created before this existed read
1110
+ * `refund`, which is what they were.
1111
+ */
1112
+ kind: openEnum("refund", "exchange"),
1113
+ /** On an exchange: the variant wanted instead, when one was chosen. */
1114
+ exchangeForVariantId: z.string().nullable(),
1115
+ /** Set once a replacement ships — track it like any other shipment. */
1116
+ exchangeShipmentId: z.string().nullable(),
1048
1117
  reason: z.string().nullable(),
1049
1118
  items: z
1050
1119
  .array(z.object({
@@ -1078,6 +1147,16 @@ export const customerOrderDetailSchema = z.object({
1078
1147
  });
1079
1148
  /** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
1080
1149
  export const customerReturnRequestInputSchema = z.object({
1150
+ /**
1151
+ * `refund` returns the money; `exchange` asks for a replacement. Defaults
1152
+ * to `refund`, so an omitted field means what it always meant.
1153
+ */
1154
+ kind: z.enum(["refund", "exchange"]).optional(),
1155
+ /**
1156
+ * On an exchange, the variant wanted instead. Optional even then — "a
1157
+ * different size" is a real request that needs a conversation first.
1158
+ */
1159
+ exchangeForVariantId: z.string().uuid().optional(),
1081
1160
  reason: z.string().max(120).optional(),
1082
1161
  customerNote: z.string().max(2000).optional(),
1083
1162
  items: z
@@ -1092,6 +1171,292 @@ export const customerReturnRequestCreatedSchema = z.object({
1092
1171
  id: z.string().nullable(),
1093
1172
  /** Mirrors the `order_return_request_status` pg enum. */
1094
1173
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1174
+ /** Echoes back what was opened, so a UI need not assume. */
1175
+ kind: openEnum("refund", "exchange"),
1176
+ createdAt: z.string().nullable(),
1177
+ });
1178
+ /**
1179
+ * Result of reporting that a parcel never arrived.
1180
+ *
1181
+ * `carrierStatus` is the carrier's own verdict, UNTOUCHED by the report — the
1182
+ * two facts travel together on purpose. When `contested` is true the carrier
1183
+ * says delivered and the customer says otherwise, which is the case an org
1184
+ * needs to see first.
1185
+ */
1186
+ export const customerNotReceivedResultSchema = z.object({
1187
+ id: z.string(),
1188
+ /** True when a claim was already open; the original report is unchanged. */
1189
+ alreadyOpen: z.boolean(),
1190
+ reportedAt: z.string(),
1191
+ carrierStatus: openEnum("pending", "label_created", "in_transit", "delivered", "returned"),
1192
+ carrierDeliveredAt: z.string().nullable(),
1193
+ contested: z.boolean().optional(),
1194
+ });
1195
+ /** How an org's legal pages are dressed. Shared by all of them. */
1196
+ export const legalPresentationSchema = z.object({
1197
+ /** Already resolved against the company logo — render as given. */
1198
+ logoUrl: z.string().nullable(),
1199
+ showLogoInHeader: z.boolean(),
1200
+ showLogoInFooter: z.boolean(),
1201
+ headerHtml: z.string().nullable(),
1202
+ footerHtml: z.string().nullable(),
1203
+ });
1204
+ /** A legal page in a list — enough for a footer, without the body. */
1205
+ export const legalDocumentSummarySchema = z.object({
1206
+ /** The URL path, without a leading slash: `privacy`, `terms`, … */
1207
+ slug: z.string(),
1208
+ title: z.string(),
1209
+ version: z.number(),
1210
+ /** ISO 8601. Render as "Last updated". */
1211
+ effectiveDate: z.string().nullable(),
1212
+ showInFooter: z.boolean(),
1213
+ updatedAt: z.string(),
1214
+ });
1215
+ export const legalListResponseSchema = z.object({
1216
+ items: z.array(legalDocumentSummarySchema),
1217
+ presentation: legalPresentationSchema,
1218
+ });
1219
+ /** One legal page, body included. */
1220
+ export const legalDocumentSchema = legalDocumentSummarySchema.extend({
1221
+ /** Sanitised server-side on write, so this is safe to render as HTML. */
1222
+ contentHtml: z.string(),
1223
+ presentation: legalPresentationSchema,
1224
+ });
1225
+ /** One browser a customer has registered for push. */
1226
+ export const customerPushDeviceSchema = z.object({
1227
+ id: z.string(),
1228
+ endpoint: z.string(),
1229
+ userAgent: z.string().nullable(),
1230
+ label: z.string().nullable(),
1231
+ /** Null = every category. `[]` = none, but the device stays registered. */
1232
+ categories: z.array(z.string()).nullable(),
1233
+ lastSeenAt: z.string().nullable(),
1234
+ createdAt: z.string(),
1235
+ });
1236
+ /**
1237
+ * Push config plus this customer's devices.
1238
+ *
1239
+ * `vapidPublicKey` rides along because a page cannot call `subscribe()` without
1240
+ * it, and a second round trip for one string is one nobody remembers to make.
1241
+ * `enabled: false` means the platform has no key configured — render nothing
1242
+ * rather than a button that cannot work.
1243
+ */
1244
+ export const customerPushConfigSchema = z.object({
1245
+ vapidPublicKey: z.string().nullable(),
1246
+ enabled: z.boolean(),
1247
+ devices: z.array(customerPushDeviceSchema),
1248
+ });
1249
+ /** Result of handing a customer-portal invite link out again. */
1250
+ export const customerInviteResendResponseSchema = z.object({
1251
+ id: z.string(),
1252
+ /** The NEW link. The previous one no longer works. */
1253
+ url: z.string(),
1254
+ expiresAt: z.string().nullable(),
1255
+ /** Includes this send, so a list can show "sent 3 times". */
1256
+ sendCount: z.number(),
1257
+ });
1258
+ // ── Scheduling / conference calls ─────────────────────────────────────
1259
+ //
1260
+ // The booking surface: what an org offers, when it is free, and the three
1261
+ // token-gated things an invitee can do afterwards.
1262
+ //
1263
+ // `conferenceLink` is the conference-call half — a Meet/Zoom/Teams URL minted
1264
+ // when the event type has a provider. It is null until the booking is
1265
+ // confirmed, and re-minted on reschedule, so a UI must read it back after
1266
+ // either rather than caching the one it first saw.
1267
+ export const schedulingEventTypeSchema = z.object({
1268
+ id: z.string(),
1269
+ name: z.string(),
1270
+ slug: z.string(),
1271
+ description: z.string().nullable(),
1272
+ durationMinutes: z.number(),
1273
+ locationType: z.string(),
1274
+ /** google_meet | zoom | teams | null when the meeting is in person. */
1275
+ meetingProvider: z.string().nullable(),
1276
+ color: z.string().nullable(),
1277
+ /** When true a booking lands `pending`, not `confirmed`. */
1278
+ requiresApproval: z.boolean(),
1279
+ });
1280
+ export const schedulingQuestionSchema = z
1281
+ .object({
1282
+ id: z.string(),
1283
+ label: z.string(),
1284
+ })
1285
+ .passthrough();
1286
+ export const schedulingEventTypeListSchema = z.object({
1287
+ eventTypes: z.array(schedulingEventTypeSchema),
1288
+ });
1289
+ export const schedulingEventTypeDetailSchema = z.object({
1290
+ eventType: schedulingEventTypeSchema,
1291
+ questions: z.array(schedulingQuestionSchema),
1292
+ });
1293
+ export const schedulingSlotSchema = z.object({
1294
+ startAt: z.string(),
1295
+ endAt: z.string(),
1296
+ });
1297
+ export const schedulingSlotsResponseSchema = z.object({
1298
+ slots: z.array(schedulingSlotSchema),
1299
+ });
1300
+ export const schedulingBookingStatusSchema = openEnum("pending", "confirmed", "canceled", "rescheduled");
1301
+ export const schedulingBookingViewSchema = z.object({
1302
+ booking: z.object({
1303
+ id: z.string(),
1304
+ startAt: z.string(),
1305
+ endAt: z.string(),
1306
+ status: schedulingBookingStatusSchema,
1307
+ title: z.string(),
1308
+ notes: z.string().nullable(),
1309
+ /** The conference URL, once there is one. */
1310
+ conferenceLink: z.string().nullable(),
1311
+ }),
1312
+ invitee: z
1313
+ .object({ name: z.string(), email: z.string(), timezone: z.string() })
1314
+ .nullable(),
1315
+ });
1316
+ export const schedulingBookingResultSchema = z
1317
+ .object({
1318
+ bookingId: z.string(),
1319
+ status: schedulingBookingStatusSchema,
1320
+ /**
1321
+ * Three separate signed tokens, deliberately.
1322
+ *
1323
+ * A cancel link in an email must not also be able to reschedule, and
1324
+ * neither should read the whole booking. Handing out one token for all
1325
+ * three would make every emailed link as powerful as the strongest
1326
+ * action it enables.
1327
+ */
1328
+ manageToken: z.string(),
1329
+ rescheduleToken: z.string(),
1330
+ cancelToken: z.string(),
1331
+ })
1332
+ .passthrough();
1333
+ export const schedulingRescheduleResultSchema = z
1334
+ .object({
1335
+ bookingId: z.string(),
1336
+ startAt: z.string(),
1337
+ endAt: z.string(),
1338
+ /** Re-minted on reschedule — read it back rather than reusing the old one. */
1339
+ conferenceLink: z.string().nullable().optional(),
1340
+ })
1341
+ .passthrough();
1342
+ export const schedulingCancelResultSchema = z
1343
+ .object({ ok: z.boolean() })
1344
+ .passthrough();
1345
+ /** One of the org's live subscription offerings. */
1346
+ export const customerSubscriptionOfferingSchema = z.object({
1347
+ id: z.string(),
1348
+ name: z.string(),
1349
+ description: z.string().nullable(),
1350
+ imageUrl: z.string().nullable(),
1351
+ amountCents: z.number(),
1352
+ interval: z.string(),
1353
+ });
1354
+ /** The customer's subscription state with one org. */
1355
+ export const customerSubscriptionSchema = z.object({
1356
+ /** active | expired | cancelled | pending | failed */
1357
+ status: openEnum("active", "expired", "cancelled", "pending", "failed"),
1358
+ /** trial | monthly_subscribed | annual_subscribed | lifetime */
1359
+ type: openEnum("trial", "monthly_subscribed", "annual_subscribed", "lifetime").nullable(),
1360
+ /**
1361
+ * The entitlement decision, computed server-side by the same function the
1362
+ * content gates use.
1363
+ *
1364
+ * **Render from this, not from `status`.** Two cases break a naive
1365
+ * `status === "active"` check: a lifetime purchase has no period to expire,
1366
+ * and a cancelled subscription keeps access until the period already paid
1367
+ * for runs out.
1368
+ */
1369
+ hasAccess: z.boolean(),
1370
+ /**
1371
+ * Cancelled, but the paid period has not ended. The honest label is
1372
+ * "active until {expiresAt}" — not "cancelled".
1373
+ */
1374
+ cancelledButActive: z.boolean(),
1375
+ startedAt: z.string().nullable(),
1376
+ /** End of the paid period. Null for lifetime. */
1377
+ expiresAt: z.string().nullable(),
1378
+ cancelledAt: z.string().nullable(),
1379
+ renewedAt: z.string().nullable(),
1380
+ trialEndsAt: z.string().nullable(),
1381
+ trialUsed: z.boolean(),
1382
+ provider: z.string().nullable(),
1383
+ /** The offering they are on, when it still exists. */
1384
+ plan: customerSubscriptionOfferingSchema.nullable(),
1385
+ /** Null means the plan includes no service calls at all. */
1386
+ includedServiceCallsPerPeriod: z.number().nullable(),
1387
+ serviceCallsUsedInPeriod: z.number(),
1388
+ periodStartedAt: z.string().nullable(),
1389
+ });
1390
+ export const customerSubscriptionResponseSchema = z.object({
1391
+ /** Null when this customer has never subscribed to this org. */
1392
+ subscription: customerSubscriptionSchema.nullable(),
1393
+ /** The org's live offerings, cheapest first. */
1394
+ offerings: z.array(customerSubscriptionOfferingSchema),
1395
+ });
1396
+ /** Result of cancelling — or un-cancelling — a subscription. */
1397
+ export const customerSubscriptionCancelResponseSchema = z.object({
1398
+ /** True when a cancellation is now pending at the end of the period. */
1399
+ cancelAtPeriodEnd: z.boolean(),
1400
+ resumed: z.boolean(),
1401
+ /**
1402
+ * Access continues until this moment. Say "active until {this}", because
1403
+ * that is what is true — cancelling ends the renewal, not the access.
1404
+ */
1405
+ accessUntil: z.string().nullable(),
1406
+ /**
1407
+ * The stored status still reads its old value until the provider's webhook
1408
+ * lands. Render from `cancelAtPeriodEnd`, not from a re-read.
1409
+ */
1410
+ statusPending: z.boolean(),
1411
+ });
1412
+ /** One piece of subscriber-gated content. */
1413
+ export const customerSubscriberContentItemSchema = z.object({
1414
+ id: z.string(),
1415
+ slug: z.string(),
1416
+ title: z.string(),
1417
+ /** The pitch. Travels even when locked — the BODY never does. */
1418
+ excerpt: z.string().nullable(),
1419
+ featuredImageUrl: z.string().nullable(),
1420
+ publishedAt: z.string().nullable(),
1421
+ tags: z.array(z.string()),
1422
+ accessLevel: z.string(),
1423
+ downloadAccessLevel: z.string(),
1424
+ /** False when the post is readable but its downloads are not. */
1425
+ downloadsAvailable: z.boolean(),
1426
+ });
1427
+ export const customerSubscriberContentResponseSchema = z.object({
1428
+ items: z.array(customerSubscriberContentItemSchema),
1429
+ /**
1430
+ * False means these are LOCKED previews, not an empty entitlement. Show
1431
+ * them with the offer — an empty list would hide the pitch at exactly the
1432
+ * moment it is most relevant.
1433
+ */
1434
+ entitled: z.boolean(),
1435
+ });
1436
+ /** One product review the customer has written. */
1437
+ export const customerProductReviewSchema = z.object({
1438
+ id: z.string(),
1439
+ productId: z.string(),
1440
+ productName: z.string(),
1441
+ rating: z.number(),
1442
+ content: z.string(),
1443
+ /**
1444
+ * False means QUEUED for the org to approve, not rejected. Surface it as
1445
+ * "awaiting review" — a customer who thinks their review vanished writes it
1446
+ * again, or writes to support.
1447
+ */
1448
+ isApproved: z.boolean(),
1449
+ createdAt: z.string(),
1450
+ });
1451
+ export const customerProductReviewListSchema = z.object({
1452
+ items: z.array(customerProductReviewSchema),
1453
+ });
1454
+ /** Result of submitting a product review from the portal. */
1455
+ export const customerProductReviewCreatedSchema = z.object({
1456
+ id: z.string().nullable(),
1457
+ isApproved: z.boolean(),
1458
+ /** The inverse of `isApproved`, named for what the UI should say. */
1459
+ awaitingModeration: z.boolean(),
1095
1460
  createdAt: z.string().nullable(),
1096
1461
  });
1097
1462
  /** One downloadable digital deliverable for a paid order (P10d). */
@@ -1134,6 +1499,100 @@ export const siteBrandingSchema = z.object({
1134
1499
  orgName: z.string().nullable(),
1135
1500
  orgIcon: z.string().nullable(),
1136
1501
  });
1502
+ // ── Customer portal: list endpoints (0.9.61) ──────────────────────────
1503
+ //
1504
+ // The portal had detail-by-id routes for invoices, quotes, contracts and
1505
+ // shipments and no way to LIST any of them, so "show me everything I owe" had
1506
+ // no call behind it.
1507
+ //
1508
+ // The rows reuse the existing `customer*SummarySchema` shapes rather than
1509
+ // defining parallel ones — those already carry the right open enums, and two
1510
+ // definitions of "an invoice summary" would drift.
1511
+ export const customerInvoiceListResponseSchema = z.object({
1512
+ items: z.array(customerInvoiceSummarySchema),
1513
+ });
1514
+ export const customerQuoteListResponseSchema = z.object({
1515
+ items: z.array(customerQuoteSummarySchema),
1516
+ });
1517
+ export const customerContractListResponseSchema = z.object({
1518
+ items: z.array(customerContractSummarySchema),
1519
+ });
1520
+ /** No summary shape existed for shipments, so this is the first one. */
1521
+ export const customerShipmentSummarySchema = z.object({
1522
+ id: z.string(),
1523
+ orderId: z.string(),
1524
+ orderNumber: z.string().nullable(),
1525
+ carrier: z.string().nullable(),
1526
+ trackingNumber: z.string().nullable(),
1527
+ trackingUrl: z.string().nullable(),
1528
+ serviceName: z.string().nullable(),
1529
+ status: z.string(),
1530
+ /** A date STRING, not a timestamp — the column is text. */
1531
+ estimatedDelivery: z.string().nullable(),
1532
+ shippedAt: z.string().nullable(),
1533
+ deliveredAt: z.string().nullable(),
1534
+ });
1535
+ export const customerShipmentListResponseSchema = z.object({
1536
+ items: z.array(customerShipmentSummarySchema),
1537
+ });
1538
+ // ── Customer portal: staff chat (0.9.61) ──────────────────────────────
1539
+ //
1540
+ // `org_customer_messages` has always carried a `portal` channel and staff have
1541
+ // always read and written it from the CRM timeline. What was missing was any
1542
+ // package-api route, so a customer could be messaged and had no way to read it
1543
+ // back or reply. Both directions land in the SAME table the staff timeline
1544
+ // reads — a reply appears in the CRM beside everything else about that
1545
+ // customer, not in a parallel inbox nobody watches.
1546
+ export const customerPortalMessageSchema = z.object({
1547
+ id: z.string(),
1548
+ /** `inbound` = the customer wrote it; `outbound` = staff did. */
1549
+ direction: openEnum("inbound", "outbound"),
1550
+ channel: openEnum("portal", "email", "sms"),
1551
+ authorName: z.string().nullable(),
1552
+ body: z.string(),
1553
+ attachment: z
1554
+ .object({
1555
+ url: z.string(),
1556
+ name: z.string(),
1557
+ mimeType: z.string(),
1558
+ sizeBytes: z.number(),
1559
+ })
1560
+ .nullable(),
1561
+ jobId: z.string().nullable(),
1562
+ readByCustomerAt: z.string().nullable(),
1563
+ createdAt: z.string(),
1564
+ });
1565
+ export const customerPortalMessageListResponseSchema = z.object({
1566
+ items: z.array(customerPortalMessageSchema),
1567
+ });
1568
+ export const customerPortalMessagePostInputSchema = z.object({
1569
+ body: z.string().min(1).max(4000),
1570
+ /** Thread the message under a specific job. */
1571
+ jobId: z.string().uuid().optional(),
1572
+ });
1573
+ export const customerPortalMessagesMarkReadResponseSchema = z.object({
1574
+ marked: z.number(),
1575
+ });
1576
+ // ── Customer portal: return status readback (0.9.61) ──────────────────
1577
+ //
1578
+ // Closes the audit's gap #5. The submit route existed and nothing read the
1579
+ // status back, so a customer could start a return and then had no way to see
1580
+ // what happened to it — which is the exact moment they pick up the phone.
1581
+ export const customerReturnRequestStatusSchema = z.object({
1582
+ id: z.string(),
1583
+ /** Mirrors the `order_return_request_status` pg enum. */
1584
+ status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1585
+ reason: z.string().nullable(),
1586
+ customerNote: z.string().nullable(),
1587
+ /** Staff's answer. The whole point when a return is denied. */
1588
+ resolutionNote: z.string().nullable(),
1589
+ items: z.array(z.record(z.string(), z.unknown())).nullable(),
1590
+ resolvedAt: z.string().nullable(),
1591
+ createdAt: z.string(),
1592
+ });
1593
+ export const customerReturnRequestListResponseSchema = z.object({
1594
+ items: z.array(customerReturnRequestStatusSchema),
1595
+ });
1137
1596
  // ── Customer-portal: first-party reviews ──────────────────────────────
1138
1597
  export const customerReviewSummarySchema = z.object({
1139
1598
  id: z.string(),
@@ -1190,7 +1649,12 @@ export const customerContactCompanyResponseSchema = z.object({
1190
1649
  export const customerJobEtaAssignmentSchema = z.object({
1191
1650
  displayName: z.string(),
1192
1651
  role: z.string().nullable(),
1193
- dispatchStatus: z.string(),
1652
+ /**
1653
+ * This technician's own stage. `openEnum` because the column is TEXT and
1654
+ * an org's board can grow a value before the SDK knows about it — an
1655
+ * unknown status should render as itself, not break the response.
1656
+ */
1657
+ dispatchStatus: openEnum("pending", "en_route", "on_site", "in_progress", "completed", "declined"),
1194
1658
  lastKnownLocation: z
1195
1659
  .object({
1196
1660
  lat: z.number(),
@@ -1200,6 +1664,28 @@ export const customerJobEtaAssignmentSchema = z.object({
1200
1664
  .nullable(),
1201
1665
  });
1202
1666
  export const customerJobEtaResponseSchema = z.object({
1667
+ /**
1668
+ * Where the JOB is — promoted from the assignees by the dispatch board,
1669
+ * NOT the same as any one technician's status.
1670
+ *
1671
+ * The promotion rules are why this is returned rather than left to the
1672
+ * consumer: `en_route`/`arrived`/`started` land on the FIRST assignee to
1673
+ * reach them, while `completed` waits for the LAST. Aggregating that
1674
+ * client-side gets it wrong in the direction that tells a customer the work
1675
+ * is finished when one of three techs has packed up.
1676
+ */
1677
+ dispatchStatus: openEnum("unscheduled", "scheduled", "en_route", "arrived", "started", "completed"),
1678
+ /**
1679
+ * When each stage happened. A customer reading "en route" wants to know
1680
+ * since when — a status with no timestamp is why people phone to ask.
1681
+ */
1682
+ timeline: z.object({
1683
+ scheduledStartAt: z.string().nullable(),
1684
+ enRouteAt: z.string().nullable(),
1685
+ arrivedAt: z.string().nullable(),
1686
+ startedAt: z.string().nullable(),
1687
+ completedAt: z.string().nullable(),
1688
+ }),
1203
1689
  assignments: z.array(customerJobEtaAssignmentSchema),
1204
1690
  eta: z
1205
1691
  .object({
@@ -1355,6 +1841,30 @@ export const storefrontProductRowSchema = z
1355
1841
  /** Resolved category (name/description), when the product is categorized. */
1356
1842
  category: storefrontProductCategorySchema.nullable().optional(),
1357
1843
  images: z.array(z.string()).nullable().optional(),
1844
+ /**
1845
+ * Web-renderable 3D model — `.glb`, `.gltf`, `.obj` or `.stl`.
1846
+ *
1847
+ * All four fields below are ABSENT (not null) unless the org has the
1848
+ * `ecommerce.product_3d` add-on. Absence is the normal case: most
1849
+ * businesses do not sell anything with a model. Use
1850
+ * `@businessdash/sdk/model3d` to turn these into something renderable.
1851
+ */
1852
+ model3dUrl: z.string().nullable().optional(),
1853
+ /** Apple AR Quick Look companion, `.usdz`. iOS/iPadOS/visionOS Safari. */
1854
+ model3dUsdzUrl: z.string().nullable().optional(),
1855
+ /**
1856
+ * True when the org attached a CAD file (`.step`/`.iges`/native).
1857
+ *
1858
+ * The URL itself is deliberately NOT here. CAD files are the largest
1859
+ * thing the platform serves and are the org's intellectual property, so
1860
+ * they are issued by `GET storefront/products/{id}/cad`, which requires
1861
+ * cookies, rate-limits the caller and re-checks the entitlement.
1862
+ * Publishing the raw URL in a payload any visitor can fetch would route
1863
+ * around all three.
1864
+ */
1865
+ model3dCadAvailable: z.boolean().optional(),
1866
+ /** Filename for the download link — "bracket-v3.step". */
1867
+ model3dCadName: z.string().nullable().optional(),
1358
1868
  createdAt: z.string(),
1359
1869
  })
1360
1870
  .passthrough();
@@ -1362,6 +1872,61 @@ export const storefrontListProductsResponseSchema = z.object({
1362
1872
  items: z.array(storefrontProductRowSchema),
1363
1873
  nextCursor: z.number().nullable(),
1364
1874
  });
1875
+ /**
1876
+ * Result of pushing the catalog to the org's own Stripe account.
1877
+ * Per-product outcomes rather than one all-or-nothing verdict: a single bad
1878
+ * product shouldn't abandon a batch, and the caller needs to know which.
1879
+ */
1880
+ export const couponSchema = z.object({
1881
+ id: z.string(),
1882
+ code: z.string(),
1883
+ discountType: z.string(),
1884
+ value: z.number(),
1885
+ isActive: z.boolean(),
1886
+ maxUses: z.number().nullable(),
1887
+ usedCount: z.number(),
1888
+ validUntil: z.string().nullable(),
1889
+ stripeCouponId: z.string().nullable(),
1890
+ stripePromotionCodeId: z.string().nullable(),
1891
+ });
1892
+ export const couponCreateInputSchema = z.object({
1893
+ code: z.string().min(3).max(64),
1894
+ discountType: z.enum(["percent", "amount"]),
1895
+ value: z.number().positive(),
1896
+ validFrom: z.coerce.date().optional(),
1897
+ validUntil: z.coerce.date().nullish(),
1898
+ maxUses: z.number().int().positive().nullish(),
1899
+ isActive: z.boolean().optional(),
1900
+ duration: z.enum(["once", "forever", "repeating"]).optional(),
1901
+ durationInMonths: z.number().int().positive().nullish(),
1902
+ appliesTo: z.enum(["order", "shipping", "both"]).optional(),
1903
+ });
1904
+ /**
1905
+ * `stripeSynced: false` means the coupon EXISTS locally but is not yet live in
1906
+ * Stripe — reported rather than thrown, so the caller can retry the sync
1907
+ * without re-creating the row.
1908
+ */
1909
+ export const couponCreateResponseSchema = z.object({
1910
+ coupon: couponSchema,
1911
+ stripeSynced: z.boolean(),
1912
+ stripeError: z.string().nullable(),
1913
+ });
1914
+ export const couponListResponseSchema = z.object({
1915
+ coupons: z.array(couponSchema),
1916
+ });
1917
+ export const storefrontSyncToStripeResponseSchema = z.object({
1918
+ synced: z.array(z.object({
1919
+ productId: z.string(),
1920
+ stripeProductId: z.string(),
1921
+ /** Variant combinations that got a Stripe Price. */
1922
+ combinationsSynced: z.number(),
1923
+ })),
1924
+ failed: z.array(z.object({ productId: z.string(), error: z.string() })),
1925
+ /** Products still unsynced after this call — keep calling until 0. */
1926
+ remaining: z.number(),
1927
+ /** Connected account written to; null means the platform account. */
1928
+ connectedAccountId: z.string().nullable(),
1929
+ });
1365
1930
  export const storefrontProductDetailSchema = storefrontProductRowSchema.and(z.object({
1366
1931
  variants: z.array(z.record(z.string(), z.unknown())),
1367
1932
  productImages: z.array(z.record(z.string(), z.unknown())),
@@ -1969,6 +2534,8 @@ const bundleCompanySchema = z.object({
1969
2534
  });
1970
2535
  const bundleGalleryItemSchema = z.object({
1971
2536
  id: z.string(),
2537
+ /** Sent by the server on every item and dropped here until now. */
2538
+ description: z.string().nullish(),
1972
2539
  type: z.enum(["image", "video"]),
1973
2540
  src: z.string(),
1974
2541
  width: z.number().nullable(),