@businessdash/sdk 0.9.60 → 0.9.70

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 (233) hide show
  1. package/README.md +1012 -186
  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 +567 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +807 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/contracts.d.ts +3309 -761
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js +541 -4
  49. package/dist/contracts.js.map +1 -1
  50. package/dist/data-model-schema.d.ts +1 -1
  51. package/dist/data-model-schema.d.ts.map +1 -1
  52. package/dist/element.d.ts +30 -0
  53. package/dist/element.d.ts.map +1 -1
  54. package/dist/element.js +56 -0
  55. package/dist/element.js.map +1 -1
  56. package/dist/embed-protocol.d.ts +86 -0
  57. package/dist/embed-protocol.d.ts.map +1 -0
  58. package/dist/embed-protocol.js +60 -0
  59. package/dist/embed-protocol.js.map +1 -0
  60. package/dist/framework-support.d.ts +122 -0
  61. package/dist/framework-support.d.ts.map +1 -0
  62. package/dist/framework-support.js +323 -0
  63. package/dist/framework-support.js.map +1 -0
  64. package/dist/index.cjs +1244 -5
  65. package/dist/legal-core/index.cjs +115 -0
  66. package/dist/legal-core/index.d.ts +160 -0
  67. package/dist/legal-core/index.d.ts.map +1 -0
  68. package/dist/legal-core/index.js +220 -0
  69. package/dist/legal-core/index.js.map +1 -0
  70. package/dist/marketing-core/index.d.ts +66 -0
  71. package/dist/marketing-core/index.d.ts.map +1 -0
  72. package/dist/marketing-core/index.js +154 -0
  73. package/dist/marketing-core/index.js.map +1 -0
  74. package/dist/marketing.d.ts +7 -0
  75. package/dist/marketing.d.ts.map +1 -0
  76. package/dist/marketing.js +7 -0
  77. package/dist/marketing.js.map +1 -0
  78. package/dist/mcp-adapters/index.d.ts +135 -0
  79. package/dist/mcp-adapters/index.d.ts.map +1 -0
  80. package/dist/mcp-adapters/index.js +187 -0
  81. package/dist/mcp-adapters/index.js.map +1 -0
  82. package/dist/mcp-adapters.d.ts +7 -0
  83. package/dist/mcp-adapters.d.ts.map +1 -0
  84. package/dist/mcp-adapters.js +7 -0
  85. package/dist/mcp-adapters.js.map +1 -0
  86. package/dist/model3d-core/index.cjs +95 -0
  87. package/dist/model3d-core/index.d.ts +167 -0
  88. package/dist/model3d-core/index.d.ts.map +1 -0
  89. package/dist/model3d-core/index.js +158 -0
  90. package/dist/model3d-core/index.js.map +1 -0
  91. package/dist/notifications-core/index.cjs +315 -0
  92. package/dist/notifications-core/index.d.ts +229 -0
  93. package/dist/notifications-core/index.d.ts.map +1 -0
  94. package/dist/notifications-core/index.js +445 -0
  95. package/dist/notifications-core/index.js.map +1 -0
  96. package/dist/portal-core/index.d.ts +451 -0
  97. package/dist/portal-core/index.d.ts.map +1 -0
  98. package/dist/portal-core/index.js +819 -0
  99. package/dist/portal-core/index.js.map +1 -0
  100. package/dist/portal.d.ts +7 -0
  101. package/dist/portal.d.ts.map +1 -0
  102. package/dist/portal.js +7 -0
  103. package/dist/portal.js.map +1 -0
  104. package/dist/push-core/index.cjs +171 -0
  105. package/dist/push-core/index.d.ts +149 -0
  106. package/dist/push-core/index.d.ts.map +1 -0
  107. package/dist/push-core/index.js +231 -0
  108. package/dist/push-core/index.js.map +1 -0
  109. package/dist/react-marketing.d.ts +1 -17
  110. package/dist/react-marketing.d.ts.map +1 -1
  111. package/dist/react-marketing.js +4 -0
  112. package/dist/react-marketing.js.map +1 -1
  113. package/dist/react.d.ts +294 -101
  114. package/dist/react.d.ts.map +1 -1
  115. package/dist/react.js +302 -246
  116. package/dist/react.js.map +1 -1
  117. package/dist/seed-core/index.d.ts +354 -0
  118. package/dist/seed-core/index.d.ts.map +1 -0
  119. package/dist/seed-core/index.js +420 -0
  120. package/dist/seed-core/index.js.map +1 -0
  121. package/dist/seed.d.ts +7 -0
  122. package/dist/seed.d.ts.map +1 -0
  123. package/dist/seed.js +7 -0
  124. package/dist/seed.js.map +1 -0
  125. package/dist/seo-core/blog.d.ts +94 -0
  126. package/dist/seo-core/blog.d.ts.map +1 -0
  127. package/dist/seo-core/blog.js +149 -0
  128. package/dist/seo-core/blog.js.map +1 -0
  129. package/dist/seo-core/commerce.d.ts +64 -0
  130. package/dist/seo-core/commerce.d.ts.map +1 -0
  131. package/dist/seo-core/commerce.js +87 -0
  132. package/dist/seo-core/commerce.js.map +1 -0
  133. package/dist/seo-core/index.cjs +751 -0
  134. package/dist/seo-core/index.d.ts +226 -0
  135. package/dist/seo-core/index.d.ts.map +1 -0
  136. package/dist/seo-core/index.js +455 -0
  137. package/dist/seo-core/index.js.map +1 -0
  138. package/dist/seo-core/local.d.ts +181 -0
  139. package/dist/seo-core/local.d.ts.map +1 -0
  140. package/dist/seo-core/local.js +193 -0
  141. package/dist/seo-core/local.js.map +1 -0
  142. package/dist/sitemap-core/index.cjs +167 -0
  143. package/dist/sitemap-core/index.d.ts +155 -0
  144. package/dist/sitemap-core/index.d.ts.map +1 -0
  145. package/dist/sitemap-core/index.js +195 -0
  146. package/dist/sitemap-core/index.js.map +1 -0
  147. package/dist/social-core/index.d.ts +74 -0
  148. package/dist/social-core/index.d.ts.map +1 -0
  149. package/dist/social-core/index.js +72 -0
  150. package/dist/social-core/index.js.map +1 -0
  151. package/dist/social.d.ts +7 -0
  152. package/dist/social.d.ts.map +1 -0
  153. package/dist/social.js +7 -0
  154. package/dist/social.js.map +1 -0
  155. package/dist/solid-compat.d.ts +204 -0
  156. package/dist/solid-compat.d.ts.map +1 -0
  157. package/dist/solid-compat.js +221 -0
  158. package/dist/solid-compat.js.map +1 -0
  159. package/dist/store-core/index.d.ts +141 -0
  160. package/dist/store-core/index.d.ts.map +1 -0
  161. package/dist/store-core/index.js +277 -0
  162. package/dist/store-core/index.js.map +1 -0
  163. package/dist/store.d.ts +12 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +12 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/vanilla.d.ts +111 -0
  168. package/dist/vanilla.d.ts.map +1 -1
  169. package/dist/vanilla.js +206 -0
  170. package/dist/vanilla.js.map +1 -1
  171. package/dist/verify-core/index.d.ts +98 -0
  172. package/dist/verify-core/index.d.ts.map +1 -0
  173. package/dist/verify-core/index.js +186 -0
  174. package/dist/verify-core/index.js.map +1 -0
  175. package/dist/verify.d.ts +8 -0
  176. package/dist/verify.d.ts.map +1 -0
  177. package/dist/verify.js +8 -0
  178. package/dist/verify.js.map +1 -0
  179. package/openapi.json +4316 -1313
  180. package/package.json +121 -9
  181. package/src/account-core/index.ts +309 -0
  182. package/src/account.ts +11 -0
  183. package/src/angular/headless.ts +519 -0
  184. package/src/angular/index.ts +3 -0
  185. package/src/auth-links.ts +76 -0
  186. package/src/blog-core/index.ts +423 -0
  187. package/src/blog.ts +11 -0
  188. package/src/chat-core/index.ts +335 -0
  189. package/src/chat.ts +11 -0
  190. package/src/checkout-core/index.ts +225 -0
  191. package/src/checkout.ts +10 -0
  192. package/src/cli.ts +184 -28
  193. package/src/client.ts +998 -0
  194. package/src/contracts.ts +730 -11
  195. package/src/data-model-schema.ts +1 -1
  196. package/src/element.ts +102 -0
  197. package/src/embed-protocol.ts +100 -0
  198. package/src/framework-support.ts +440 -0
  199. package/src/legal-core/index.ts +276 -0
  200. package/src/marketing-core/index.ts +209 -0
  201. package/src/marketing.ts +7 -0
  202. package/src/mcp-adapters/index.ts +264 -0
  203. package/src/mcp-adapters.ts +7 -0
  204. package/src/model3d-core/index.ts +248 -0
  205. package/src/notifications-core/index.ts +650 -0
  206. package/src/portal-core/index.ts +1564 -0
  207. package/src/portal.ts +7 -0
  208. package/src/push-core/index.ts +335 -0
  209. package/src/qwik.tsx +602 -2
  210. package/src/react-marketing.tsx +5 -0
  211. package/src/react.tsx +434 -319
  212. package/src/seed-core/index.ts +735 -0
  213. package/src/seed.ts +7 -0
  214. package/src/seo-core/blog.ts +216 -0
  215. package/src/seo-core/commerce.ts +138 -0
  216. package/src/seo-core/index.ts +605 -0
  217. package/src/seo-core/local.ts +333 -0
  218. package/src/sitemap-core/index.ts +296 -0
  219. package/src/social-core/index.ts +103 -0
  220. package/src/social.ts +7 -0
  221. package/src/solid-compat.tsx +347 -0
  222. package/src/solid-surfaces.tsx +1311 -0
  223. package/src/solid.tsx +82 -60
  224. package/src/store-core/index.ts +426 -0
  225. package/src/store.ts +12 -0
  226. package/src/svelte/SocialLinks.svelte +84 -0
  227. package/src/svelte/headless.ts +435 -0
  228. package/src/svelte/index.ts +9 -0
  229. package/src/vanilla.ts +367 -0
  230. package/src/verify-core/index.ts +288 -0
  231. package/src/verify.ts +8 -0
  232. package/src/vue/headless.ts +527 -0
  233. 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({
@@ -1045,6 +1074,16 @@ export const customerReturnRequestSchema = z.object({
1045
1074
  * are separate enums and each is spelled as the database spells it.
1046
1075
  */
1047
1076
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1077
+ /**
1078
+ * What was asked for: money back, or a replacement. Mirrors
1079
+ * `order_return_request_kind`. Rows created before this existed read
1080
+ * `refund`, which is what they were.
1081
+ */
1082
+ kind: openEnum("refund", "exchange"),
1083
+ /** On an exchange: the variant wanted instead, when one was chosen. */
1084
+ exchangeForVariantId: z.string().nullable(),
1085
+ /** Set once a replacement ships — track it like any other shipment. */
1086
+ exchangeShipmentId: z.string().nullable(),
1048
1087
  reason: z.string().nullable(),
1049
1088
  items: z
1050
1089
  .array(z.object({
@@ -1078,6 +1117,16 @@ export const customerOrderDetailSchema = z.object({
1078
1117
  });
1079
1118
  /** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
1080
1119
  export const customerReturnRequestInputSchema = z.object({
1120
+ /**
1121
+ * `refund` returns the money; `exchange` asks for a replacement. Defaults
1122
+ * to `refund`, so an omitted field means what it always meant.
1123
+ */
1124
+ kind: z.enum(["refund", "exchange"]).optional(),
1125
+ /**
1126
+ * On an exchange, the variant wanted instead. Optional even then — "a
1127
+ * different size" is a real request that needs a conversation first.
1128
+ */
1129
+ exchangeForVariantId: z.string().uuid().optional(),
1081
1130
  reason: z.string().max(120).optional(),
1082
1131
  customerNote: z.string().max(2000).optional(),
1083
1132
  items: z
@@ -1092,6 +1141,292 @@ export const customerReturnRequestCreatedSchema = z.object({
1092
1141
  id: z.string().nullable(),
1093
1142
  /** Mirrors the `order_return_request_status` pg enum. */
1094
1143
  status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1144
+ /** Echoes back what was opened, so a UI need not assume. */
1145
+ kind: openEnum("refund", "exchange"),
1146
+ createdAt: z.string().nullable(),
1147
+ });
1148
+ /**
1149
+ * Result of reporting that a parcel never arrived.
1150
+ *
1151
+ * `carrierStatus` is the carrier's own verdict, UNTOUCHED by the report — the
1152
+ * two facts travel together on purpose. When `contested` is true the carrier
1153
+ * says delivered and the customer says otherwise, which is the case an org
1154
+ * needs to see first.
1155
+ */
1156
+ export const customerNotReceivedResultSchema = z.object({
1157
+ id: z.string(),
1158
+ /** True when a claim was already open; the original report is unchanged. */
1159
+ alreadyOpen: z.boolean(),
1160
+ reportedAt: z.string(),
1161
+ carrierStatus: openEnum("pending", "label_created", "in_transit", "delivered", "returned"),
1162
+ carrierDeliveredAt: z.string().nullable(),
1163
+ contested: z.boolean().optional(),
1164
+ });
1165
+ /** How an org's legal pages are dressed. Shared by all of them. */
1166
+ export const legalPresentationSchema = z.object({
1167
+ /** Already resolved against the company logo — render as given. */
1168
+ logoUrl: z.string().nullable(),
1169
+ showLogoInHeader: z.boolean(),
1170
+ showLogoInFooter: z.boolean(),
1171
+ headerHtml: z.string().nullable(),
1172
+ footerHtml: z.string().nullable(),
1173
+ });
1174
+ /** A legal page in a list — enough for a footer, without the body. */
1175
+ export const legalDocumentSummarySchema = z.object({
1176
+ /** The URL path, without a leading slash: `privacy`, `terms`, … */
1177
+ slug: z.string(),
1178
+ title: z.string(),
1179
+ version: z.number(),
1180
+ /** ISO 8601. Render as "Last updated". */
1181
+ effectiveDate: z.string().nullable(),
1182
+ showInFooter: z.boolean(),
1183
+ updatedAt: z.string(),
1184
+ });
1185
+ export const legalListResponseSchema = z.object({
1186
+ items: z.array(legalDocumentSummarySchema),
1187
+ presentation: legalPresentationSchema,
1188
+ });
1189
+ /** One legal page, body included. */
1190
+ export const legalDocumentSchema = legalDocumentSummarySchema.extend({
1191
+ /** Sanitised server-side on write, so this is safe to render as HTML. */
1192
+ contentHtml: z.string(),
1193
+ presentation: legalPresentationSchema,
1194
+ });
1195
+ /** One browser a customer has registered for push. */
1196
+ export const customerPushDeviceSchema = z.object({
1197
+ id: z.string(),
1198
+ endpoint: z.string(),
1199
+ userAgent: z.string().nullable(),
1200
+ label: z.string().nullable(),
1201
+ /** Null = every category. `[]` = none, but the device stays registered. */
1202
+ categories: z.array(z.string()).nullable(),
1203
+ lastSeenAt: z.string().nullable(),
1204
+ createdAt: z.string(),
1205
+ });
1206
+ /**
1207
+ * Push config plus this customer's devices.
1208
+ *
1209
+ * `vapidPublicKey` rides along because a page cannot call `subscribe()` without
1210
+ * it, and a second round trip for one string is one nobody remembers to make.
1211
+ * `enabled: false` means the platform has no key configured — render nothing
1212
+ * rather than a button that cannot work.
1213
+ */
1214
+ export const customerPushConfigSchema = z.object({
1215
+ vapidPublicKey: z.string().nullable(),
1216
+ enabled: z.boolean(),
1217
+ devices: z.array(customerPushDeviceSchema),
1218
+ });
1219
+ /** Result of handing a customer-portal invite link out again. */
1220
+ export const customerInviteResendResponseSchema = z.object({
1221
+ id: z.string(),
1222
+ /** The NEW link. The previous one no longer works. */
1223
+ url: z.string(),
1224
+ expiresAt: z.string().nullable(),
1225
+ /** Includes this send, so a list can show "sent 3 times". */
1226
+ sendCount: z.number(),
1227
+ });
1228
+ // ── Scheduling / conference calls ─────────────────────────────────────
1229
+ //
1230
+ // The booking surface: what an org offers, when it is free, and the three
1231
+ // token-gated things an invitee can do afterwards.
1232
+ //
1233
+ // `conferenceLink` is the conference-call half — a Meet/Zoom/Teams URL minted
1234
+ // when the event type has a provider. It is null until the booking is
1235
+ // confirmed, and re-minted on reschedule, so a UI must read it back after
1236
+ // either rather than caching the one it first saw.
1237
+ export const schedulingEventTypeSchema = z.object({
1238
+ id: z.string(),
1239
+ name: z.string(),
1240
+ slug: z.string(),
1241
+ description: z.string().nullable(),
1242
+ durationMinutes: z.number(),
1243
+ locationType: z.string(),
1244
+ /** google_meet | zoom | teams | null when the meeting is in person. */
1245
+ meetingProvider: z.string().nullable(),
1246
+ color: z.string().nullable(),
1247
+ /** When true a booking lands `pending`, not `confirmed`. */
1248
+ requiresApproval: z.boolean(),
1249
+ });
1250
+ export const schedulingQuestionSchema = z
1251
+ .object({
1252
+ id: z.string(),
1253
+ label: z.string(),
1254
+ })
1255
+ .passthrough();
1256
+ export const schedulingEventTypeListSchema = z.object({
1257
+ eventTypes: z.array(schedulingEventTypeSchema),
1258
+ });
1259
+ export const schedulingEventTypeDetailSchema = z.object({
1260
+ eventType: schedulingEventTypeSchema,
1261
+ questions: z.array(schedulingQuestionSchema),
1262
+ });
1263
+ export const schedulingSlotSchema = z.object({
1264
+ startAt: z.string(),
1265
+ endAt: z.string(),
1266
+ });
1267
+ export const schedulingSlotsResponseSchema = z.object({
1268
+ slots: z.array(schedulingSlotSchema),
1269
+ });
1270
+ export const schedulingBookingStatusSchema = openEnum("pending", "confirmed", "canceled", "rescheduled");
1271
+ export const schedulingBookingViewSchema = z.object({
1272
+ booking: z.object({
1273
+ id: z.string(),
1274
+ startAt: z.string(),
1275
+ endAt: z.string(),
1276
+ status: schedulingBookingStatusSchema,
1277
+ title: z.string(),
1278
+ notes: z.string().nullable(),
1279
+ /** The conference URL, once there is one. */
1280
+ conferenceLink: z.string().nullable(),
1281
+ }),
1282
+ invitee: z
1283
+ .object({ name: z.string(), email: z.string(), timezone: z.string() })
1284
+ .nullable(),
1285
+ });
1286
+ export const schedulingBookingResultSchema = z
1287
+ .object({
1288
+ bookingId: z.string(),
1289
+ status: schedulingBookingStatusSchema,
1290
+ /**
1291
+ * Three separate signed tokens, deliberately.
1292
+ *
1293
+ * A cancel link in an email must not also be able to reschedule, and
1294
+ * neither should read the whole booking. Handing out one token for all
1295
+ * three would make every emailed link as powerful as the strongest
1296
+ * action it enables.
1297
+ */
1298
+ manageToken: z.string(),
1299
+ rescheduleToken: z.string(),
1300
+ cancelToken: z.string(),
1301
+ })
1302
+ .passthrough();
1303
+ export const schedulingRescheduleResultSchema = z
1304
+ .object({
1305
+ bookingId: z.string(),
1306
+ startAt: z.string(),
1307
+ endAt: z.string(),
1308
+ /** Re-minted on reschedule — read it back rather than reusing the old one. */
1309
+ conferenceLink: z.string().nullable().optional(),
1310
+ })
1311
+ .passthrough();
1312
+ export const schedulingCancelResultSchema = z
1313
+ .object({ ok: z.boolean() })
1314
+ .passthrough();
1315
+ /** One of the org's live subscription offerings. */
1316
+ export const customerSubscriptionOfferingSchema = z.object({
1317
+ id: z.string(),
1318
+ name: z.string(),
1319
+ description: z.string().nullable(),
1320
+ imageUrl: z.string().nullable(),
1321
+ amountCents: z.number(),
1322
+ interval: z.string(),
1323
+ });
1324
+ /** The customer's subscription state with one org. */
1325
+ export const customerSubscriptionSchema = z.object({
1326
+ /** active | expired | cancelled | pending | failed */
1327
+ status: openEnum("active", "expired", "cancelled", "pending", "failed"),
1328
+ /** trial | monthly_subscribed | annual_subscribed | lifetime */
1329
+ type: openEnum("trial", "monthly_subscribed", "annual_subscribed", "lifetime").nullable(),
1330
+ /**
1331
+ * The entitlement decision, computed server-side by the same function the
1332
+ * content gates use.
1333
+ *
1334
+ * **Render from this, not from `status`.** Two cases break a naive
1335
+ * `status === "active"` check: a lifetime purchase has no period to expire,
1336
+ * and a cancelled subscription keeps access until the period already paid
1337
+ * for runs out.
1338
+ */
1339
+ hasAccess: z.boolean(),
1340
+ /**
1341
+ * Cancelled, but the paid period has not ended. The honest label is
1342
+ * "active until {expiresAt}" — not "cancelled".
1343
+ */
1344
+ cancelledButActive: z.boolean(),
1345
+ startedAt: z.string().nullable(),
1346
+ /** End of the paid period. Null for lifetime. */
1347
+ expiresAt: z.string().nullable(),
1348
+ cancelledAt: z.string().nullable(),
1349
+ renewedAt: z.string().nullable(),
1350
+ trialEndsAt: z.string().nullable(),
1351
+ trialUsed: z.boolean(),
1352
+ provider: z.string().nullable(),
1353
+ /** The offering they are on, when it still exists. */
1354
+ plan: customerSubscriptionOfferingSchema.nullable(),
1355
+ /** Null means the plan includes no service calls at all. */
1356
+ includedServiceCallsPerPeriod: z.number().nullable(),
1357
+ serviceCallsUsedInPeriod: z.number(),
1358
+ periodStartedAt: z.string().nullable(),
1359
+ });
1360
+ export const customerSubscriptionResponseSchema = z.object({
1361
+ /** Null when this customer has never subscribed to this org. */
1362
+ subscription: customerSubscriptionSchema.nullable(),
1363
+ /** The org's live offerings, cheapest first. */
1364
+ offerings: z.array(customerSubscriptionOfferingSchema),
1365
+ });
1366
+ /** Result of cancelling — or un-cancelling — a subscription. */
1367
+ export const customerSubscriptionCancelResponseSchema = z.object({
1368
+ /** True when a cancellation is now pending at the end of the period. */
1369
+ cancelAtPeriodEnd: z.boolean(),
1370
+ resumed: z.boolean(),
1371
+ /**
1372
+ * Access continues until this moment. Say "active until {this}", because
1373
+ * that is what is true — cancelling ends the renewal, not the access.
1374
+ */
1375
+ accessUntil: z.string().nullable(),
1376
+ /**
1377
+ * The stored status still reads its old value until the provider's webhook
1378
+ * lands. Render from `cancelAtPeriodEnd`, not from a re-read.
1379
+ */
1380
+ statusPending: z.boolean(),
1381
+ });
1382
+ /** One piece of subscriber-gated content. */
1383
+ export const customerSubscriberContentItemSchema = z.object({
1384
+ id: z.string(),
1385
+ slug: z.string(),
1386
+ title: z.string(),
1387
+ /** The pitch. Travels even when locked — the BODY never does. */
1388
+ excerpt: z.string().nullable(),
1389
+ featuredImageUrl: z.string().nullable(),
1390
+ publishedAt: z.string().nullable(),
1391
+ tags: z.array(z.string()),
1392
+ accessLevel: z.string(),
1393
+ downloadAccessLevel: z.string(),
1394
+ /** False when the post is readable but its downloads are not. */
1395
+ downloadsAvailable: z.boolean(),
1396
+ });
1397
+ export const customerSubscriberContentResponseSchema = z.object({
1398
+ items: z.array(customerSubscriberContentItemSchema),
1399
+ /**
1400
+ * False means these are LOCKED previews, not an empty entitlement. Show
1401
+ * them with the offer — an empty list would hide the pitch at exactly the
1402
+ * moment it is most relevant.
1403
+ */
1404
+ entitled: z.boolean(),
1405
+ });
1406
+ /** One product review the customer has written. */
1407
+ export const customerProductReviewSchema = z.object({
1408
+ id: z.string(),
1409
+ productId: z.string(),
1410
+ productName: z.string(),
1411
+ rating: z.number(),
1412
+ content: z.string(),
1413
+ /**
1414
+ * False means QUEUED for the org to approve, not rejected. Surface it as
1415
+ * "awaiting review" — a customer who thinks their review vanished writes it
1416
+ * again, or writes to support.
1417
+ */
1418
+ isApproved: z.boolean(),
1419
+ createdAt: z.string(),
1420
+ });
1421
+ export const customerProductReviewListSchema = z.object({
1422
+ items: z.array(customerProductReviewSchema),
1423
+ });
1424
+ /** Result of submitting a product review from the portal. */
1425
+ export const customerProductReviewCreatedSchema = z.object({
1426
+ id: z.string().nullable(),
1427
+ isApproved: z.boolean(),
1428
+ /** The inverse of `isApproved`, named for what the UI should say. */
1429
+ awaitingModeration: z.boolean(),
1095
1430
  createdAt: z.string().nullable(),
1096
1431
  });
1097
1432
  /** One downloadable digital deliverable for a paid order (P10d). */
@@ -1134,6 +1469,100 @@ export const siteBrandingSchema = z.object({
1134
1469
  orgName: z.string().nullable(),
1135
1470
  orgIcon: z.string().nullable(),
1136
1471
  });
1472
+ // ── Customer portal: list endpoints (0.9.61) ──────────────────────────
1473
+ //
1474
+ // The portal had detail-by-id routes for invoices, quotes, contracts and
1475
+ // shipments and no way to LIST any of them, so "show me everything I owe" had
1476
+ // no call behind it.
1477
+ //
1478
+ // The rows reuse the existing `customer*SummarySchema` shapes rather than
1479
+ // defining parallel ones — those already carry the right open enums, and two
1480
+ // definitions of "an invoice summary" would drift.
1481
+ export const customerInvoiceListResponseSchema = z.object({
1482
+ items: z.array(customerInvoiceSummarySchema),
1483
+ });
1484
+ export const customerQuoteListResponseSchema = z.object({
1485
+ items: z.array(customerQuoteSummarySchema),
1486
+ });
1487
+ export const customerContractListResponseSchema = z.object({
1488
+ items: z.array(customerContractSummarySchema),
1489
+ });
1490
+ /** No summary shape existed for shipments, so this is the first one. */
1491
+ export const customerShipmentSummarySchema = z.object({
1492
+ id: z.string(),
1493
+ orderId: z.string(),
1494
+ orderNumber: z.string().nullable(),
1495
+ carrier: z.string().nullable(),
1496
+ trackingNumber: z.string().nullable(),
1497
+ trackingUrl: z.string().nullable(),
1498
+ serviceName: z.string().nullable(),
1499
+ status: z.string(),
1500
+ /** A date STRING, not a timestamp — the column is text. */
1501
+ estimatedDelivery: z.string().nullable(),
1502
+ shippedAt: z.string().nullable(),
1503
+ deliveredAt: z.string().nullable(),
1504
+ });
1505
+ export const customerShipmentListResponseSchema = z.object({
1506
+ items: z.array(customerShipmentSummarySchema),
1507
+ });
1508
+ // ── Customer portal: staff chat (0.9.61) ──────────────────────────────
1509
+ //
1510
+ // `org_customer_messages` has always carried a `portal` channel and staff have
1511
+ // always read and written it from the CRM timeline. What was missing was any
1512
+ // package-api route, so a customer could be messaged and had no way to read it
1513
+ // back or reply. Both directions land in the SAME table the staff timeline
1514
+ // reads — a reply appears in the CRM beside everything else about that
1515
+ // customer, not in a parallel inbox nobody watches.
1516
+ export const customerPortalMessageSchema = z.object({
1517
+ id: z.string(),
1518
+ /** `inbound` = the customer wrote it; `outbound` = staff did. */
1519
+ direction: openEnum("inbound", "outbound"),
1520
+ channel: openEnum("portal", "email", "sms"),
1521
+ authorName: z.string().nullable(),
1522
+ body: z.string(),
1523
+ attachment: z
1524
+ .object({
1525
+ url: z.string(),
1526
+ name: z.string(),
1527
+ mimeType: z.string(),
1528
+ sizeBytes: z.number(),
1529
+ })
1530
+ .nullable(),
1531
+ jobId: z.string().nullable(),
1532
+ readByCustomerAt: z.string().nullable(),
1533
+ createdAt: z.string(),
1534
+ });
1535
+ export const customerPortalMessageListResponseSchema = z.object({
1536
+ items: z.array(customerPortalMessageSchema),
1537
+ });
1538
+ export const customerPortalMessagePostInputSchema = z.object({
1539
+ body: z.string().min(1).max(4000),
1540
+ /** Thread the message under a specific job. */
1541
+ jobId: z.string().uuid().optional(),
1542
+ });
1543
+ export const customerPortalMessagesMarkReadResponseSchema = z.object({
1544
+ marked: z.number(),
1545
+ });
1546
+ // ── Customer portal: return status readback (0.9.61) ──────────────────
1547
+ //
1548
+ // Closes the audit's gap #5. The submit route existed and nothing read the
1549
+ // status back, so a customer could start a return and then had no way to see
1550
+ // what happened to it — which is the exact moment they pick up the phone.
1551
+ export const customerReturnRequestStatusSchema = z.object({
1552
+ id: z.string(),
1553
+ /** Mirrors the `order_return_request_status` pg enum. */
1554
+ status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1555
+ reason: z.string().nullable(),
1556
+ customerNote: z.string().nullable(),
1557
+ /** Staff's answer. The whole point when a return is denied. */
1558
+ resolutionNote: z.string().nullable(),
1559
+ items: z.array(z.record(z.string(), z.unknown())).nullable(),
1560
+ resolvedAt: z.string().nullable(),
1561
+ createdAt: z.string(),
1562
+ });
1563
+ export const customerReturnRequestListResponseSchema = z.object({
1564
+ items: z.array(customerReturnRequestStatusSchema),
1565
+ });
1137
1566
  // ── Customer-portal: first-party reviews ──────────────────────────────
1138
1567
  export const customerReviewSummarySchema = z.object({
1139
1568
  id: z.string(),
@@ -1190,7 +1619,12 @@ export const customerContactCompanyResponseSchema = z.object({
1190
1619
  export const customerJobEtaAssignmentSchema = z.object({
1191
1620
  displayName: z.string(),
1192
1621
  role: z.string().nullable(),
1193
- dispatchStatus: z.string(),
1622
+ /**
1623
+ * This technician's own stage. `openEnum` because the column is TEXT and
1624
+ * an org's board can grow a value before the SDK knows about it — an
1625
+ * unknown status should render as itself, not break the response.
1626
+ */
1627
+ dispatchStatus: openEnum("pending", "en_route", "on_site", "in_progress", "completed", "declined"),
1194
1628
  lastKnownLocation: z
1195
1629
  .object({
1196
1630
  lat: z.number(),
@@ -1200,6 +1634,28 @@ export const customerJobEtaAssignmentSchema = z.object({
1200
1634
  .nullable(),
1201
1635
  });
1202
1636
  export const customerJobEtaResponseSchema = z.object({
1637
+ /**
1638
+ * Where the JOB is — promoted from the assignees by the dispatch board,
1639
+ * NOT the same as any one technician's status.
1640
+ *
1641
+ * The promotion rules are why this is returned rather than left to the
1642
+ * consumer: `en_route`/`arrived`/`started` land on the FIRST assignee to
1643
+ * reach them, while `completed` waits for the LAST. Aggregating that
1644
+ * client-side gets it wrong in the direction that tells a customer the work
1645
+ * is finished when one of three techs has packed up.
1646
+ */
1647
+ dispatchStatus: openEnum("unscheduled", "scheduled", "en_route", "arrived", "started", "completed"),
1648
+ /**
1649
+ * When each stage happened. A customer reading "en route" wants to know
1650
+ * since when — a status with no timestamp is why people phone to ask.
1651
+ */
1652
+ timeline: z.object({
1653
+ scheduledStartAt: z.string().nullable(),
1654
+ enRouteAt: z.string().nullable(),
1655
+ arrivedAt: z.string().nullable(),
1656
+ startedAt: z.string().nullable(),
1657
+ completedAt: z.string().nullable(),
1658
+ }),
1203
1659
  assignments: z.array(customerJobEtaAssignmentSchema),
1204
1660
  eta: z
1205
1661
  .object({
@@ -1355,6 +1811,30 @@ export const storefrontProductRowSchema = z
1355
1811
  /** Resolved category (name/description), when the product is categorized. */
1356
1812
  category: storefrontProductCategorySchema.nullable().optional(),
1357
1813
  images: z.array(z.string()).nullable().optional(),
1814
+ /**
1815
+ * Web-renderable 3D model — `.glb`, `.gltf`, `.obj` or `.stl`.
1816
+ *
1817
+ * All four fields below are ABSENT (not null) unless the org has the
1818
+ * `ecommerce.product_3d` add-on. Absence is the normal case: most
1819
+ * businesses do not sell anything with a model. Use
1820
+ * `@businessdash/sdk/model3d` to turn these into something renderable.
1821
+ */
1822
+ model3dUrl: z.string().nullable().optional(),
1823
+ /** Apple AR Quick Look companion, `.usdz`. iOS/iPadOS/visionOS Safari. */
1824
+ model3dUsdzUrl: z.string().nullable().optional(),
1825
+ /**
1826
+ * True when the org attached a CAD file (`.step`/`.iges`/native).
1827
+ *
1828
+ * The URL itself is deliberately NOT here. CAD files are the largest
1829
+ * thing the platform serves and are the org's intellectual property, so
1830
+ * they are issued by `GET storefront/products/{id}/cad`, which requires
1831
+ * cookies, rate-limits the caller and re-checks the entitlement.
1832
+ * Publishing the raw URL in a payload any visitor can fetch would route
1833
+ * around all three.
1834
+ */
1835
+ model3dCadAvailable: z.boolean().optional(),
1836
+ /** Filename for the download link — "bracket-v3.step". */
1837
+ model3dCadName: z.string().nullable().optional(),
1358
1838
  createdAt: z.string(),
1359
1839
  })
1360
1840
  .passthrough();
@@ -1362,6 +1842,61 @@ export const storefrontListProductsResponseSchema = z.object({
1362
1842
  items: z.array(storefrontProductRowSchema),
1363
1843
  nextCursor: z.number().nullable(),
1364
1844
  });
1845
+ /**
1846
+ * Result of pushing the catalog to the org's own Stripe account.
1847
+ * Per-product outcomes rather than one all-or-nothing verdict: a single bad
1848
+ * product shouldn't abandon a batch, and the caller needs to know which.
1849
+ */
1850
+ export const couponSchema = z.object({
1851
+ id: z.string(),
1852
+ code: z.string(),
1853
+ discountType: z.string(),
1854
+ value: z.number(),
1855
+ isActive: z.boolean(),
1856
+ maxUses: z.number().nullable(),
1857
+ usedCount: z.number(),
1858
+ validUntil: z.string().nullable(),
1859
+ stripeCouponId: z.string().nullable(),
1860
+ stripePromotionCodeId: z.string().nullable(),
1861
+ });
1862
+ export const couponCreateInputSchema = z.object({
1863
+ code: z.string().min(3).max(64),
1864
+ discountType: z.enum(["percent", "amount"]),
1865
+ value: z.number().positive(),
1866
+ validFrom: z.coerce.date().optional(),
1867
+ validUntil: z.coerce.date().nullish(),
1868
+ maxUses: z.number().int().positive().nullish(),
1869
+ isActive: z.boolean().optional(),
1870
+ duration: z.enum(["once", "forever", "repeating"]).optional(),
1871
+ durationInMonths: z.number().int().positive().nullish(),
1872
+ appliesTo: z.enum(["order", "shipping", "both"]).optional(),
1873
+ });
1874
+ /**
1875
+ * `stripeSynced: false` means the coupon EXISTS locally but is not yet live in
1876
+ * Stripe — reported rather than thrown, so the caller can retry the sync
1877
+ * without re-creating the row.
1878
+ */
1879
+ export const couponCreateResponseSchema = z.object({
1880
+ coupon: couponSchema,
1881
+ stripeSynced: z.boolean(),
1882
+ stripeError: z.string().nullable(),
1883
+ });
1884
+ export const couponListResponseSchema = z.object({
1885
+ coupons: z.array(couponSchema),
1886
+ });
1887
+ export const storefrontSyncToStripeResponseSchema = z.object({
1888
+ synced: z.array(z.object({
1889
+ productId: z.string(),
1890
+ stripeProductId: z.string(),
1891
+ /** Variant combinations that got a Stripe Price. */
1892
+ combinationsSynced: z.number(),
1893
+ })),
1894
+ failed: z.array(z.object({ productId: z.string(), error: z.string() })),
1895
+ /** Products still unsynced after this call — keep calling until 0. */
1896
+ remaining: z.number(),
1897
+ /** Connected account written to; null means the platform account. */
1898
+ connectedAccountId: z.string().nullable(),
1899
+ });
1365
1900
  export const storefrontProductDetailSchema = storefrontProductRowSchema.and(z.object({
1366
1901
  variants: z.array(z.record(z.string(), z.unknown())),
1367
1902
  productImages: z.array(z.record(z.string(), z.unknown())),
@@ -1969,6 +2504,8 @@ const bundleCompanySchema = z.object({
1969
2504
  });
1970
2505
  const bundleGalleryItemSchema = z.object({
1971
2506
  id: z.string(),
2507
+ /** Sent by the server on every item and dropped here until now. */
2508
+ description: z.string().nullish(),
1972
2509
  type: z.enum(["image", "video"]),
1973
2510
  src: z.string(),
1974
2511
  width: z.number().nullable(),