@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
@@ -0,0 +1,3390 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * A string field whose values the SERVER owns and may extend.
5
+ *
6
+ * `T` gives callers the known members — autocomplete, and a `switch` that
7
+ * reads as a real enum — while `string & {}` keeps any other string
8
+ * assignable. (The `& {}` is load-bearing: a bare `T | string` collapses to
9
+ * `string` and the literals vanish from tooling.)
10
+ */
11
+ export type OpenEnum<T extends string> = T | (string & {});
12
+
13
+ /**
14
+ * Declare a server-owned enum on a RESPONSE field.
15
+ *
16
+ * ## Why this is not `z.enum([...])`
17
+ *
18
+ * Response schemas here are parsed at runtime — `client.ts` ends every request
19
+ * with `options.responseSchema.parse(body)`, and `.parse` **throws**. So a
20
+ * `z.enum` on a response is a promise that the set never grows. It does grow:
21
+ * these mirror pg enums, and a migration can add a member and ship long before
22
+ * a consumer upgrades their pinned `@businessdash/sdk`. The old value keeps
23
+ * working; the new one takes down every call that returns a row carrying it.
24
+ *
25
+ * So runtime stays permissive (`z.string()` — a string is still a string) and
26
+ * the precision goes into the TYPE, where being wrong costs a stale hint
27
+ * instead of an outage. The cast is sound rather than a convenience: every
28
+ * `string` genuinely is an `OpenEnum<T>`.
29
+ *
30
+ * The known members aren't lost — they ride along in `.describe()`, so they
31
+ * reach `openapi.json` and the clients generated from it.
32
+ *
33
+ * Use `z.enum` freely on REQUEST fields: rejecting a bad value the caller
34
+ * just typed is the entire point, and the blast radius is one call.
35
+ */
36
+ export function openEnum<const T extends readonly [string, ...string[]]>(
37
+ ...values: T
38
+ ): z.ZodType<OpenEnum<T[number]>> {
39
+ return z
40
+ .string()
41
+ .describe(
42
+ `Known values: ${values.join(" | ")}. The server may add more — treat unrecognised values as forward-compatible rather than invalid.`,
43
+ ) as z.ZodType<OpenEnum<T[number]>>;
44
+ }
45
+
46
+ export const siteIdSchema = z.string().uuid();
47
+ export const collectionIdSchema = z.string().uuid();
48
+ export const rowIdSchema = z.string().uuid();
49
+ export const fieldIdSchema = z.string().uuid();
50
+ export const apiKeyIdSchema = z.string().uuid();
51
+ export const timestampSchema = z.string().min(1);
52
+
53
+ export const collectionSlugSchema = z
54
+ .string()
55
+ .min(1)
56
+ .max(80)
57
+ .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
58
+
59
+ export const collectionFieldNameSchema = z
60
+ .string()
61
+ .min(1)
62
+ .max(80)
63
+ .regex(/^[a-zA-Z][a-zA-Z0-9_]*$/);
64
+
65
+ export const collectionFieldTypeValues = [
66
+ "string",
67
+ "number",
68
+ "boolean",
69
+ "json",
70
+ "date",
71
+ "richtext",
72
+ ] as const;
73
+
74
+ export const collectionFieldTypeSchema = z.enum(collectionFieldTypeValues);
75
+
76
+ export const jsonValueSchema: z.ZodType<
77
+ string | number | boolean | null | { [key: string]: unknown } | unknown[]
78
+ > = z.lazy(() =>
79
+ z.union([
80
+ z.string(),
81
+ z.number(),
82
+ z.boolean(),
83
+ z.null(),
84
+ z.record(z.unknown()),
85
+ z.array(z.unknown()),
86
+ ]),
87
+ );
88
+
89
+ export const collectionFieldInputSchema = z.object({
90
+ name: collectionFieldNameSchema,
91
+ label: z.string().max(120).optional(),
92
+ type: collectionFieldTypeSchema,
93
+ required: z.boolean().optional(),
94
+ searchable: z.boolean().optional(),
95
+ queryable: z.boolean().optional(),
96
+ validation: z.record(z.unknown()).optional(),
97
+ displayConfig: z.record(z.unknown()).optional(),
98
+ position: z.number().int().min(0).optional(),
99
+ });
100
+
101
+ export const collectionFieldSchema = z.object({
102
+ id: fieldIdSchema,
103
+ tableId: collectionIdSchema,
104
+ siteId: siteIdSchema,
105
+ name: collectionFieldNameSchema,
106
+ label: z.string().max(120).nullable().optional(),
107
+ type: collectionFieldTypeSchema,
108
+ required: z.boolean(),
109
+ searchable: z.boolean(),
110
+ queryable: z.boolean(),
111
+ validation: z.record(z.unknown()).nullable().optional(),
112
+ displayConfig: z.record(z.unknown()).nullable().optional(),
113
+ position: z.number().int(),
114
+ createdAt: timestampSchema,
115
+ updatedAt: timestampSchema,
116
+ });
117
+
118
+ export const collectionSchema = z.object({
119
+ id: collectionIdSchema,
120
+ siteId: siteIdSchema,
121
+ name: z.string().min(1).max(120),
122
+ slug: collectionSlugSchema,
123
+ description: z.string().max(500).nullable().optional(),
124
+ createdByUserId: z.string().uuid().nullable().optional(),
125
+ createdAt: timestampSchema,
126
+ updatedAt: timestampSchema,
127
+ });
128
+
129
+ export const createCollectionInputSchema = z.object({
130
+ name: z.string().min(1).max(120),
131
+ slug: collectionSlugSchema.optional(),
132
+ description: z.string().max(500).optional(),
133
+ fields: z.array(collectionFieldInputSchema).max(64).optional(),
134
+ });
135
+
136
+ export const createCollectionResponseSchema = z.object({
137
+ collection: collectionSchema,
138
+ fields: z.array(collectionFieldSchema),
139
+ });
140
+
141
+ export const listCollectionsResponseSchema = z.object({
142
+ collections: z.array(collectionSchema),
143
+ });
144
+
145
+ export const getCollectionResponseSchema = z.object({
146
+ collection: collectionSchema,
147
+ fields: z.array(collectionFieldSchema),
148
+ });
149
+
150
+ export const rowDataSchema = z.record(z.unknown());
151
+
152
+ export const collectionRowSchema = z.object({
153
+ id: rowIdSchema,
154
+ tableId: collectionIdSchema,
155
+ siteId: siteIdSchema,
156
+ data: rowDataSchema,
157
+ createdByUserId: z.string().uuid().nullable().optional(),
158
+ updatedByUserId: z.string().uuid().nullable().optional(),
159
+ createdAt: timestampSchema,
160
+ updatedAt: timestampSchema,
161
+ });
162
+
163
+ export const listRowsInputSchema = z.object({
164
+ limit: z.number().int().min(1).max(100).optional(),
165
+ });
166
+
167
+ export const listRowsResponseSchema = z.object({
168
+ rows: z.array(collectionRowSchema),
169
+ });
170
+
171
+ export const upsertRowInputSchema = z.object({
172
+ rowId: rowIdSchema.optional(),
173
+ data: rowDataSchema,
174
+ });
175
+
176
+ export const upsertRowResponseSchema = z.object({
177
+ row: collectionRowSchema,
178
+ });
179
+
180
+ export const queryOperatorValues = ["equals", "contains"] as const;
181
+ export const queryOperatorSchema = z.enum(queryOperatorValues);
182
+
183
+ export const queryFilterSchema = z.object({
184
+ fieldName: collectionFieldNameSchema,
185
+ value: z.union([z.string().min(1), z.number(), z.boolean()]),
186
+ operator: queryOperatorSchema.optional(),
187
+ });
188
+
189
+ export const queryRowsInputSchema = z.object({
190
+ filters: z.array(queryFilterSchema).min(1).max(10),
191
+ limit: z.number().int().min(1).max(100).optional(),
192
+ });
193
+
194
+ export const queryRowsResponseSchema = z.object({
195
+ rows: z.array(collectionRowSchema),
196
+ });
197
+
198
+ export const siteAssetInputSchema = z.object({
199
+ filename: z.string().min(1),
200
+ url: z.string().url(),
201
+ mimeType: z.string().optional(),
202
+ sizeBytes: z.number().int().nonnegative().optional(),
203
+ altText: z.string().optional(),
204
+ folder: z.string().optional(),
205
+ });
206
+
207
+ export const siteAssetSchema = z.object({
208
+ id: z.number().int(),
209
+ siteId: siteIdSchema,
210
+ filename: z.string(),
211
+ url: z.string().url(),
212
+ mimeType: z.string().nullable().optional(),
213
+ sizeBytes: z.number().int().nullable().optional(),
214
+ altText: z.string().nullable().optional(),
215
+ folder: z.string().nullable().optional(),
216
+ createdAt: timestampSchema,
217
+ });
218
+
219
+ export const uploadSiteAssetResponseSchema = z.object({
220
+ asset: siteAssetSchema,
221
+ });
222
+
223
+ export const followerSchema = z.object({
224
+ id: z.string().uuid(),
225
+ email: z.string().email(),
226
+ name: z.string().nullable().optional(),
227
+ source: z.string().nullable().optional(),
228
+ isActive: z.boolean(),
229
+ joinedAt: timestampSchema,
230
+ unsubscribedAt: timestampSchema.nullable().optional(),
231
+ });
232
+
233
+ export const followerJoinInputSchema = z.object({
234
+ email: z.string().email(),
235
+ name: z.string().min(1).max(200).optional(),
236
+ source: z.string().min(1).max(80).optional(),
237
+ });
238
+
239
+ export const followerJoinResponseSchema = z.object({
240
+ follower: followerSchema,
241
+ created: z.boolean(),
242
+ });
243
+
244
+ export const followerMeInputSchema = z.object({
245
+ email: z.string().email(),
246
+ });
247
+
248
+ export const followerMeResponseSchema = z.object({
249
+ follower: followerSchema.nullable(),
250
+ });
251
+
252
+ export const followerEditInputSchema = z.object({
253
+ email: z.string().email(),
254
+ patch: z.object({
255
+ name: z.string().min(1).max(200).optional(),
256
+ source: z.string().min(1).max(80).optional(),
257
+ }),
258
+ });
259
+
260
+ export const followerEditResponseSchema = z.object({
261
+ follower: followerSchema,
262
+ });
263
+
264
+ export const followerLeaveInputSchema = z.object({
265
+ email: z.string().email(),
266
+ });
267
+
268
+ export const followerLeaveResponseSchema = z.object({
269
+ follower: followerSchema.nullable(),
270
+ });
271
+
272
+ export const dashboardSessionResponseSchema = z.object({
273
+ url: z.string().url(),
274
+ token: z.string().min(1),
275
+ expiresAt: timestampSchema,
276
+ });
277
+
278
+ // ── Storefront / Cart / Checkout / Blog embed sessions ─────────────────────
279
+
280
+ const baseEmbedSessionResponse = z.object({
281
+ url: z.string().url(),
282
+ token: z.string().min(1),
283
+ expiresAt: timestampSchema,
284
+ orgSlug: z.string().min(1),
285
+ });
286
+
287
+ export const storefrontSessionInputSchema = z
288
+ .object({
289
+ productId: z.string().uuid().optional(),
290
+ })
291
+ .optional();
292
+ export type StorefrontSessionInput = z.infer<
293
+ typeof storefrontSessionInputSchema
294
+ >;
295
+
296
+ export const storefrontSessionResponseSchema = baseEmbedSessionResponse;
297
+ export type StorefrontSessionResponse = z.infer<
298
+ typeof storefrontSessionResponseSchema
299
+ >;
300
+
301
+ export const cartSessionResponseSchema = baseEmbedSessionResponse;
302
+ export type CartSessionResponse = z.infer<typeof cartSessionResponseSchema>;
303
+
304
+ export const checkoutLineSchema = z.object({
305
+ productId: z.string().uuid(),
306
+ variantId: z.string().uuid(),
307
+ combinationKey: z.string().optional(),
308
+ quantity: z.number().int().positive(),
309
+ });
310
+ export type CheckoutLine = z.infer<typeof checkoutLineSchema>;
311
+
312
+ export const checkoutSessionInputSchema = z.object({
313
+ lines: z.array(checkoutLineSchema).min(1),
314
+ customerEmail: z.string().email().optional(),
315
+ successUrl: z.string().url().optional(),
316
+ cancelUrl: z.string().url().optional(),
317
+ });
318
+ export type CheckoutSessionInput = z.infer<typeof checkoutSessionInputSchema>;
319
+
320
+ export const checkoutSessionResponseSchema = z.object({
321
+ url: z.string().url(),
322
+ stripeUrl: z.string().url(),
323
+ sessionId: z.string().min(1),
324
+ totalAmountCents: z.number().int(),
325
+ currency: z.string().min(1),
326
+ token: z.string().min(1),
327
+ expiresAt: timestampSchema,
328
+ });
329
+ export type CheckoutSessionResponse = z.infer<
330
+ typeof checkoutSessionResponseSchema
331
+ >;
332
+
333
+ // ── Programmatic checkout-from-cart (Batch II, 0.9.0) ────────────────
334
+
335
+ export const checkoutStartInputSchema = z
336
+ .object({
337
+ customerEmail: z.string().email().optional(),
338
+ successUrl: z.string().url().optional(),
339
+ cancelUrl: z.string().url().optional(),
340
+ })
341
+ .optional();
342
+ export type CheckoutStartInput = z.infer<typeof checkoutStartInputSchema>;
343
+
344
+ export const checkoutStartResponseSchema = z.object({
345
+ sessionId: z.string().min(1),
346
+ stripeUrl: z.string().url(),
347
+ totalAmountCents: z.number().int(),
348
+ currency: z.string().min(1),
349
+ });
350
+ export type CheckoutStartResponse = z.infer<typeof checkoutStartResponseSchema>;
351
+
352
+ export const checkoutStatusResponseSchema = z.object({
353
+ sessionId: z.string().min(1),
354
+ paymentStatus: z.enum(["no_payment_required", "paid", "unpaid"]),
355
+ amountTotalCents: z.number().int().nullable(),
356
+ currency: z.string().nullable(),
357
+ customerEmail: z.string().nullable(),
358
+ customerName: z.string().nullable(),
359
+ });
360
+ export type CheckoutStatusResponse = z.infer<
361
+ typeof checkoutStatusResponseSchema
362
+ >;
363
+
364
+ export const blogSessionInputSchema = z
365
+ .object({
366
+ postSlug: z.string().min(1).max(200).optional(),
367
+ })
368
+ .optional();
369
+ export type BlogSessionInput = z.infer<typeof blogSessionInputSchema>;
370
+
371
+ export const blogSessionResponseSchema = baseEmbedSessionResponse;
372
+ export type BlogSessionResponse = z.infer<typeof blogSessionResponseSchema>;
373
+
374
+ // ── Chatbot ────────────────────────────────────────────────────────────────
375
+
376
+ export const chatbotSessionResponseSchema = baseEmbedSessionResponse;
377
+ export type ChatbotSessionResponse = z.infer<
378
+ typeof chatbotSessionResponseSchema
379
+ >;
380
+
381
+ /** How proactively the bot should suggest "Talk to a person" — server-driven. */
382
+ export const chatbotHandoffThresholdSchema = z.enum([
383
+ "never",
384
+ "low",
385
+ "medium",
386
+ "high",
387
+ "always",
388
+ ]);
389
+ export type ChatbotHandoffThreshold = z.infer<
390
+ typeof chatbotHandoffThresholdSchema
391
+ >;
392
+
393
+ export const chatbotConfigSchema = z.object({
394
+ systemPrompt: z.string(),
395
+ knowledgeBase: z.string(),
396
+ model: z.string().min(1),
397
+ temperature: z.number().min(0).max(2),
398
+ lastUpdated: timestampSchema.nullable(),
399
+ usingDefaults: z.boolean(),
400
+ /**
401
+ * Live-handoff dial. The host bakes the matching guidance into the
402
+ * system prompt; SDK consumers can still branch on it to render their
403
+ * own UI hints.
404
+ */
405
+ handoffThreshold: chatbotHandoffThresholdSchema.default("medium"),
406
+ /**
407
+ * `true` when the org's subscription plan unlocks live front-desk
408
+ * handoff (Growth+ on the host's pricing). Use this to render a
409
+ * "Talk to a person" button or hide it when the org is on a tier that
410
+ * is bot-only.
411
+ */
412
+ frontDeskEnabled: z.boolean().default(false),
413
+ /**
414
+ * `true` while the org's visitor file-upload "receive window" is open. Use it
415
+ * to show/hide an upload affordance. Uploads also require the
416
+ * `chat.file_uploads` entitlement (enforced on the upload route).
417
+ */
418
+ fileUploadsEnabled: z.boolean().default(false),
419
+ /**
420
+ * Whether/how the chat should ask the visitor for an email: `"request"` is a
421
+ * skippable ask, `"require"` blocks the first message until one is provided.
422
+ */
423
+ emailCaptureMode: z.enum(["off", "request", "require"]).default("off"),
424
+ });
425
+ export type ChatbotConfig = z.infer<typeof chatbotConfigSchema>;
426
+
427
+ /** Response from the chat file-upload presign endpoint. */
428
+ export const chatbotUploadUrlResponseSchema = z.object({
429
+ uploadUrl: z.string(),
430
+ mediaId: z.string(),
431
+ publicUrl: z.string(),
432
+ storageId: z.string(),
433
+ uploadExpiresAt: z.string(),
434
+ });
435
+ export type ChatbotUploadUrlResponse = z.infer<
436
+ typeof chatbotUploadUrlResponseSchema
437
+ >;
438
+
439
+ export const chatbotConfigUpdateInputSchema = z.object({
440
+ systemPrompt: z.string().min(1).max(10_000).optional(),
441
+ knowledgeBase: z.string().max(50_000).optional(),
442
+ model: z.string().min(1).max(120).optional(),
443
+ temperature: z.number().min(0).max(2).optional(),
444
+ });
445
+ export type ChatbotConfigUpdateInput = z.infer<
446
+ typeof chatbotConfigUpdateInputSchema
447
+ >;
448
+
449
+ export const chatbotMessageSchema = z.object({
450
+ role: z.enum(["user", "assistant", "system"]),
451
+ content: z.string().max(16_000),
452
+ });
453
+ export type ChatbotMessage = z.infer<typeof chatbotMessageSchema>;
454
+
455
+ /**
456
+ * Per-call provider override (BYO key). When set, the turn runs against the
457
+ * SDK consumer's own LLM account — their `apiKey`, their billing — instead
458
+ * of the BIAB platform's configured provider, and the platform's monthly
459
+ * token budget is bypassed for that call.
460
+ *
461
+ * **Server-only.** Never send this from a browser: the key would be visible
462
+ * in DevTools. Hold it in your server env (e.g. `process.env.OPENAI_API_KEY`)
463
+ * and pass it on the server-side `client.chatbot.chat()` call. The
464
+ * `useChatbot` browser hook deliberately does not surface this field.
465
+ */
466
+ export const chatbotByoConfigSchema = z.object({
467
+ provider: z.enum(["openai", "anthropic", "grok"]),
468
+ apiKey: z.string().min(1).max(500),
469
+ model: z.string().min(1).max(120).optional(),
470
+ });
471
+ export type ChatbotByoConfig = z.infer<typeof chatbotByoConfigSchema>;
472
+
473
+ export const chatbotChatInputSchema = z.object({
474
+ messages: z.array(chatbotMessageSchema).min(1).max(40),
475
+ /** See {@link chatbotByoConfigSchema}. Server-side use only. */
476
+ byo: chatbotByoConfigSchema.optional(),
477
+ });
478
+ export type ChatbotChatInput = z.infer<typeof chatbotChatInputSchema>;
479
+
480
+ /**
481
+ * Side-effect the assistant requested mid-turn. The SDK consumer renders
482
+ * each action — currently only `show_form`, which the consumer resolves by
483
+ * fetching the form definition and rendering inline in the chat thread.
484
+ */
485
+ export const chatbotUiActionSchema = z.discriminatedUnion("kind", [
486
+ z.object({
487
+ kind: z.literal("show_form"),
488
+ formSlug: z.string().min(1).max(120),
489
+ }),
490
+ ]);
491
+ export type ChatbotUiAction = z.infer<typeof chatbotUiActionSchema>;
492
+
493
+ /**
494
+ * Live front-desk availability for the org. Mirrors what the public-site
495
+ * widget surfaces in its presence strip; SDK consumers can render a
496
+ * "Talk to a person" button conditional on `staffAvailable`, and a
497
+ * wait-time hint from `estimateLabel`.
498
+ */
499
+ export const chatbotAvailabilitySchema = z.object({
500
+ frontDeskEnabled: z.boolean(),
501
+ staffAvailable: z.boolean(),
502
+ offline: z.boolean(),
503
+ estimateLabel: z.string().nullable(),
504
+ onlineHandlers: z.array(
505
+ z.object({
506
+ userId: z.string(),
507
+ name: z.string(),
508
+ avatarUrl: z.string().nullable(),
509
+ }),
510
+ ),
511
+ });
512
+ export type ChatbotAvailability = z.infer<typeof chatbotAvailabilitySchema>;
513
+
514
+ export const chatbotChatResponseSchema = z.object({
515
+ text: z.string(),
516
+ model: z.string(),
517
+ usingDefaults: z.boolean(),
518
+ uiActions: z.array(chatbotUiActionSchema).default([]),
519
+ availability: chatbotAvailabilitySchema.optional(),
520
+ /** True when the turn ran against a BYO provider key (consumer-paid). */
521
+ byo: z.boolean().default(false),
522
+ });
523
+ export type ChatbotChatResponse = z.infer<typeof chatbotChatResponseSchema>;
524
+
525
+ /**
526
+ * Minimal field shape the SDK needs to render an inline form. Mirrors the
527
+ * host's `FormField` type but kept loose (only the keys the renderer
528
+ * actually reads) so additive server-side changes don't break old SDK
529
+ * builds.
530
+ */
531
+ export const chatbotFormFieldSchema = z
532
+ .object({
533
+ id: z.string(),
534
+ label: z.string(),
535
+ type: z.string(),
536
+ required: z.boolean().default(false),
537
+ placeholder: z.string().optional(),
538
+ helpText: z.string().optional(),
539
+ options: z.array(z.string()).optional(),
540
+ })
541
+ .passthrough();
542
+ export type ChatbotFormField = z.infer<typeof chatbotFormFieldSchema>;
543
+
544
+ export const chatbotFormDefinitionSchema = z.object({
545
+ id: z.string(),
546
+ slug: z.string(),
547
+ name: z.string(),
548
+ description: z.string(),
549
+ fields: z.array(chatbotFormFieldSchema),
550
+ addsInquiry: z.boolean(),
551
+ addsLead: z.boolean(),
552
+ });
553
+ export type ChatbotFormDefinition = z.infer<typeof chatbotFormDefinitionSchema>;
554
+
555
+ export const chatbotFormSubmitInputSchema = z.object({
556
+ data: z.record(z.unknown()),
557
+ submitterEmail: z.string().email().optional(),
558
+ submitterName: z.string().optional(),
559
+ priorChat: z
560
+ .array(
561
+ z.object({
562
+ role: z.string().min(1).max(40),
563
+ content: z.string().min(1).max(16_000),
564
+ createdAt: z.number().optional(),
565
+ }),
566
+ )
567
+ .max(200)
568
+ .optional(),
569
+ });
570
+ export type ChatbotFormSubmitInput = z.infer<
571
+ typeof chatbotFormSubmitInputSchema
572
+ >;
573
+
574
+ export const chatbotFormSubmitResponseSchema = z.object({
575
+ success: z.literal(true),
576
+ submissionId: z.string(),
577
+ slug: z.string(),
578
+ });
579
+ export type ChatbotFormSubmitResponse = z.infer<
580
+ typeof chatbotFormSubmitResponseSchema
581
+ >;
582
+
583
+ // ── Front Desk programmatic surface (Batch JJ, 0.9.0) ────────────────
584
+
585
+ export const chatbotPersistedSessionInputSchema = z.object({
586
+ visitorToken: z.string().min(8).max(200),
587
+ visitorName: z.string().max(200).optional(),
588
+ visitorEmail: z.string().email().optional(),
589
+ });
590
+ export type ChatbotPersistedSessionInput = z.infer<
591
+ typeof chatbotPersistedSessionInputSchema
592
+ >;
593
+
594
+ export const chatbotPersistedSessionResponseSchema = z.object({
595
+ sessionId: z.string().uuid(),
596
+ /** Mirrors the `chat_session_status` pg enum. */
597
+ status: openEnum("bot", "awaiting_human", "assigned", "closed"),
598
+ startedAt: timestampSchema,
599
+ visitorToken: z.string(),
600
+ });
601
+ export type ChatbotPersistedSessionResponse = z.infer<
602
+ typeof chatbotPersistedSessionResponseSchema
603
+ >;
604
+
605
+ export const chatbotPersistedMessageSchema = z.object({
606
+ id: z.string(),
607
+ /** Mirrors the `chat_message_role` pg enum. */
608
+ role: openEnum("visitor", "assistant_bot", "staff", "system", "staff_note"),
609
+ content: z.string(),
610
+ createdAt: timestampSchema,
611
+ authorUserId: z.string().nullable(),
612
+ });
613
+ export type ChatbotPersistedMessage = z.infer<
614
+ typeof chatbotPersistedMessageSchema
615
+ >;
616
+
617
+ export const chatbotSessionStatusSchema = z.object({
618
+ sessionId: z.string(),
619
+ /** Mirrors the `chat_session_status` pg enum. */
620
+ status: openEnum("bot", "awaiting_human", "assigned", "closed"),
621
+ assignedUserId: z.string().nullable(),
622
+ placeInLine: z.number().int().nullable(),
623
+ stalled: z.boolean(),
624
+ });
625
+ export type ChatbotSessionStatus = z.infer<typeof chatbotSessionStatusSchema>;
626
+
627
+ export const chatbotMessagesResponseSchema = z.object({
628
+ session: chatbotSessionStatusSchema,
629
+ messages: z.array(chatbotPersistedMessageSchema),
630
+ });
631
+ export type ChatbotMessagesResponse = z.infer<
632
+ typeof chatbotMessagesResponseSchema
633
+ >;
634
+
635
+ export const chatbotAppendMessageInputSchema = z.object({
636
+ sessionId: z.string().uuid(),
637
+ visitorToken: z.string().min(8).max(200),
638
+ content: z.string().min(1).max(16_000),
639
+ role: z.enum(["visitor", "assistant_bot"]).default("visitor"),
640
+ });
641
+ export type ChatbotAppendMessageInput = z.infer<
642
+ typeof chatbotAppendMessageInputSchema
643
+ >;
644
+
645
+ /**
646
+ * Plan-gate body shape. Identical across every gated endpoint —
647
+ * lift this to model upgrade-card props in one place.
648
+ */
649
+ export const planGatedUnavailableSchema = z.object({
650
+ available: z.literal(false),
651
+ reason: z.literal("plan_required"),
652
+ requiredFeature: z.string(),
653
+ requiredPlan: z.string(),
654
+ planDisplayName: z.string(),
655
+ upgradeUrl: z.string(),
656
+ });
657
+ export type PlanGatedUnavailable = z.infer<typeof planGatedUnavailableSchema>;
658
+
659
+ export const chatbotRequestHumanInputSchema = z.object({
660
+ sessionId: z.string().uuid(),
661
+ visitorToken: z.string().min(8).max(200),
662
+ });
663
+ export type ChatbotRequestHumanInput = z.infer<
664
+ typeof chatbotRequestHumanInputSchema
665
+ >;
666
+
667
+ export const chatbotRequestHumanAvailableSchema = z.object({
668
+ available: z.literal(true),
669
+ status: z.string(),
670
+ requestedAt: z.string().nullable(),
671
+ placeInLine: z.number().int().nullable(),
672
+ estimateLabel: z.string().nullable(),
673
+ staffAvailable: z.boolean(),
674
+ onlineHandlers: z.array(
675
+ z.object({
676
+ userId: z.string(),
677
+ name: z.string(),
678
+ avatarUrl: z.string().nullable(),
679
+ }),
680
+ ),
681
+ });
682
+ export type ChatbotRequestHumanAvailable = z.infer<
683
+ typeof chatbotRequestHumanAvailableSchema
684
+ >;
685
+
686
+ /**
687
+ * Discriminated union — every Front Desk write returns one of these.
688
+ * Branch on `.available` to render the upgrade CTA vs the queue UI.
689
+ */
690
+ export const chatbotRequestHumanResponseSchema = z.union([
691
+ chatbotRequestHumanAvailableSchema,
692
+ planGatedUnavailableSchema,
693
+ ]);
694
+ export type ChatbotRequestHumanResponse = z.infer<
695
+ typeof chatbotRequestHumanResponseSchema
696
+ >;
697
+
698
+ export const executeActionInputSchema = z.object({
699
+ payload: z.record(z.unknown()).default({}),
700
+ });
701
+
702
+ export const executeActionResponseSchema = z.object({
703
+ actionName: z.string().min(1),
704
+ result: z.unknown(),
705
+ });
706
+
707
+ export const siteMarketingPageKeySchema = z
708
+ .string()
709
+ .min(1)
710
+ .max(120)
711
+ .regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/);
712
+
713
+ export const siteMarketingPageSchema = z.object({
714
+ siteId: siteIdSchema,
715
+ pageKey: siteMarketingPageKeySchema,
716
+ payload: jsonValueSchema,
717
+ updatedAt: timestampSchema,
718
+ });
719
+
720
+ export const getSiteMarketingPageResponseSchema = z.object({
721
+ page: siteMarketingPageSchema.nullable(),
722
+ });
723
+
724
+ /**
725
+ * Lightweight directory entry — `pageKey` + `updatedAt` only. Pair with
726
+ * a per-page `get(pageKey)` (or React `useMarketingPage(pageKey)`) to
727
+ * load the payload on demand. Splitting list from content keeps a
728
+ * many-page site from paying the full-payload cost just to render a
729
+ * navigation index.
730
+ */
731
+ export const siteMarketingPageSummarySchema = z.object({
732
+ siteId: siteIdSchema,
733
+ pageKey: siteMarketingPageKeySchema,
734
+ updatedAt: timestampSchema,
735
+ });
736
+
737
+ export const listSiteMarketingPagesResponseSchema = z.object({
738
+ pages: z.array(siteMarketingPageSummarySchema),
739
+ });
740
+
741
+ export const authIntrospectResponseSchema = z.object({
742
+ apiKeyId: apiKeyIdSchema.optional(),
743
+ orgId: z.string().uuid(),
744
+ userId: z.string().uuid().nullable().optional(),
745
+ siteIds: z.array(siteIdSchema).optional(),
746
+ scopes: z.array(z.string()).default([]),
747
+ expiresAt: timestampSchema.nullable().optional(),
748
+ label: z.string().optional(),
749
+ });
750
+
751
+ export const emailTemplateRendererValues = ["native-email-builder"] as const;
752
+ export const emailTemplateRendererSchema = z.enum(emailTemplateRendererValues);
753
+
754
+ export const emailTemplateCategoryValues = [
755
+ "system",
756
+ "marketing",
757
+ "transactional",
758
+ ] as const;
759
+ export const emailTemplateCategorySchema = z.enum(emailTemplateCategoryValues);
760
+
761
+ export const emailTemplateVariableTypeValues = [
762
+ "string",
763
+ "number",
764
+ "url",
765
+ "currency",
766
+ "date",
767
+ ] as const;
768
+ export const emailTemplateVariableTypeSchema = z.enum(
769
+ emailTemplateVariableTypeValues,
770
+ );
771
+
772
+ export const emailTemplateVariableSchema = z.object({
773
+ key: z.string().min(1),
774
+ label: z.string().min(1),
775
+ type: emailTemplateVariableTypeSchema,
776
+ required: z.boolean(),
777
+ defaultValue: z.unknown().optional(),
778
+ });
779
+
780
+ export const emailTemplateSummarySchema = z.object({
781
+ id: z.number().int(),
782
+ name: z.string().min(1),
783
+ slug: z.string().min(1),
784
+ description: z.string().nullable().optional(),
785
+ ownerId: z.string().min(1),
786
+ organizationId: z.string().nullable().optional(),
787
+ renderer: emailTemplateRendererSchema,
788
+ category: emailTemplateCategorySchema,
789
+ isActive: z.boolean(),
790
+ isArchived: z.boolean(),
791
+ createdAt: timestampSchema,
792
+ updatedAt: timestampSchema,
793
+ });
794
+
795
+ export const emailTemplateVersionSchema = z.object({
796
+ id: z.number().int(),
797
+ templateId: z.number().int(),
798
+ version: z.number().int(),
799
+ subject: z.string().min(1),
800
+ renderer: emailTemplateRendererSchema,
801
+ unlayerDesign: z.unknown().optional(),
802
+ builderDocument: z.unknown().optional(),
803
+ html: z.string().nullable().optional(),
804
+ reactComponent: z.string().nullable().optional(),
805
+ reactPropsSchema: z.unknown().optional(),
806
+ variables: z.array(emailTemplateVariableSchema),
807
+ createdBy: z.string().min(1),
808
+ createdAt: timestampSchema,
809
+ isPublished: z.boolean(),
810
+ });
811
+
812
+ export const listEmailTemplatesInputSchema = z.object({
813
+ category: emailTemplateCategorySchema.optional(),
814
+ activeOnly: z.boolean().optional(),
815
+ });
816
+
817
+ export const listEmailTemplatesResponseSchema = z.object({
818
+ templates: z.array(emailTemplateSummarySchema),
819
+ });
820
+
821
+ export const getEmailTemplateResponseSchema = z.object({
822
+ template: emailTemplateSummarySchema,
823
+ versions: z.array(emailTemplateVersionSchema),
824
+ });
825
+
826
+ export const createEmailTemplateInputSchema = z.object({
827
+ name: z.string().min(1),
828
+ slug: z.string().min(1),
829
+ description: z.string().optional(),
830
+ category: emailTemplateCategorySchema,
831
+ isActive: z.boolean().optional(),
832
+ });
833
+
834
+ export const createEmailTemplateResponseSchema = z.object({
835
+ template: emailTemplateSummarySchema,
836
+ });
837
+
838
+ export const createEmailTemplateVersionInputSchema = z.object({
839
+ subject: z.string().min(1),
840
+ builderDocument: z.unknown().optional(),
841
+ html: z.string().optional(),
842
+ reactComponent: z.string().optional(),
843
+ reactPropsSchema: z.unknown().optional(),
844
+ variables: z.array(emailTemplateVariableSchema).optional(),
845
+ isPublished: z.boolean().optional(),
846
+ });
847
+
848
+ export const createEmailTemplateVersionResponseSchema = z.object({
849
+ version: emailTemplateVersionSchema,
850
+ });
851
+
852
+ export type CollectionFieldType = z.infer<typeof collectionFieldTypeSchema>;
853
+ export type CollectionFieldInput = z.infer<typeof collectionFieldInputSchema>;
854
+ export type CollectionField = z.infer<typeof collectionFieldSchema>;
855
+ export type Collection = z.infer<typeof collectionSchema>;
856
+ export type CreateCollectionInput = z.infer<typeof createCollectionInputSchema>;
857
+ export type CreateCollectionResponse = z.infer<
858
+ typeof createCollectionResponseSchema
859
+ >;
860
+ export type ListCollectionsResponse = z.infer<
861
+ typeof listCollectionsResponseSchema
862
+ >;
863
+ export type GetCollectionResponse = z.infer<typeof getCollectionResponseSchema>;
864
+ export type CollectionRow = z.infer<typeof collectionRowSchema>;
865
+ export type ListRowsInput = z.infer<typeof listRowsInputSchema>;
866
+ export type ListRowsResponse = z.infer<typeof listRowsResponseSchema>;
867
+ export type UpsertRowInput = z.infer<typeof upsertRowInputSchema>;
868
+ export type UpsertRowResponse = z.infer<typeof upsertRowResponseSchema>;
869
+ export type QueryFilter = z.infer<typeof queryFilterSchema>;
870
+ export type QueryRowsInput = z.infer<typeof queryRowsInputSchema>;
871
+ export type QueryRowsResponse = z.infer<typeof queryRowsResponseSchema>;
872
+ export type SiteAssetInput = z.infer<typeof siteAssetInputSchema>;
873
+ export type SiteAsset = z.infer<typeof siteAssetSchema>;
874
+ export type UploadSiteAssetResponse = z.infer<
875
+ typeof uploadSiteAssetResponseSchema
876
+ >;
877
+ export type ExecuteActionInput = z.infer<typeof executeActionInputSchema>;
878
+ export type ExecuteActionResponse = z.infer<typeof executeActionResponseSchema>;
879
+ export type AuthIntrospectResponse = z.infer<
880
+ typeof authIntrospectResponseSchema
881
+ >;
882
+ export type EmailTemplateRenderer = z.infer<typeof emailTemplateRendererSchema>;
883
+ export type EmailTemplateCategory = z.infer<typeof emailTemplateCategorySchema>;
884
+ export type EmailTemplateVariable = z.infer<typeof emailTemplateVariableSchema>;
885
+ export type EmailTemplateSummary = z.infer<typeof emailTemplateSummarySchema>;
886
+ export type EmailTemplateVersion = z.infer<typeof emailTemplateVersionSchema>;
887
+ export type ListEmailTemplatesInput = z.infer<
888
+ typeof listEmailTemplatesInputSchema
889
+ >;
890
+ export type ListEmailTemplatesResponse = z.infer<
891
+ typeof listEmailTemplatesResponseSchema
892
+ >;
893
+ export type GetEmailTemplateResponse = z.infer<
894
+ typeof getEmailTemplateResponseSchema
895
+ >;
896
+ export type CreateEmailTemplateInput = z.infer<
897
+ typeof createEmailTemplateInputSchema
898
+ >;
899
+ export type CreateEmailTemplateResponse = z.infer<
900
+ typeof createEmailTemplateResponseSchema
901
+ >;
902
+ export type CreateEmailTemplateVersionInput = z.infer<
903
+ typeof createEmailTemplateVersionInputSchema
904
+ >;
905
+ export type CreateEmailTemplateVersionResponse = z.infer<
906
+ typeof createEmailTemplateVersionResponseSchema
907
+ >;
908
+ export type Follower = z.infer<typeof followerSchema>;
909
+ export type FollowerJoinInput = z.infer<typeof followerJoinInputSchema>;
910
+ export type FollowerJoinResponse = z.infer<typeof followerJoinResponseSchema>;
911
+ export type FollowerMeInput = z.infer<typeof followerMeInputSchema>;
912
+ export type FollowerMeResponse = z.infer<typeof followerMeResponseSchema>;
913
+ export type FollowerEditInput = z.infer<typeof followerEditInputSchema>;
914
+ export type FollowerEditResponse = z.infer<typeof followerEditResponseSchema>;
915
+ export type FollowerLeaveInput = z.infer<typeof followerLeaveInputSchema>;
916
+ export type FollowerLeaveResponse = z.infer<typeof followerLeaveResponseSchema>;
917
+ export type DashboardSessionResponse = z.infer<
918
+ typeof dashboardSessionResponseSchema
919
+ >;
920
+ export type SiteMarketingPage = z.infer<typeof siteMarketingPageSchema>;
921
+ export type GetSiteMarketingPageResponse = z.infer<
922
+ typeof getSiteMarketingPageResponseSchema
923
+ >;
924
+ export type SiteMarketingPageSummary = z.infer<
925
+ typeof siteMarketingPageSummarySchema
926
+ >;
927
+ export type ListSiteMarketingPagesResponse = z.infer<
928
+ typeof listSiteMarketingPagesResponseSchema
929
+ >;
930
+
931
+ // ── Customer Portal ──────────────────────────────────────────────────────────
932
+ //
933
+ // The customer portal is host- or session-scoped to one organization. The
934
+ // contracts mirror `(public)/portal/*` routes on the platform and the
935
+ // `customerPortal` tRPC router. Hosts: a tenant subdomain like
936
+ // `app.<slug>.<platform>` resolves to one org; in absence of a slug the
937
+ // caller's session organization id is used.
938
+
939
+ export const customerPortalOrgSchema = z.object({
940
+ id: z.string().uuid(),
941
+ name: z.string().nullable(),
942
+ /** @deprecated Internal auth-provider org id — use `id` (the BIAB org id).
943
+ * Kept for backwards compatibility; will be removed in a future release. */
944
+ workosOrganizationId: z.string().nullable().optional(),
945
+ });
946
+
947
+ export const customerPortalUserSchema = z.object({
948
+ id: z.string().uuid(),
949
+ email: z.string().nullable(),
950
+ displayName: z.string().nullable(),
951
+ avatarUrl: z.string().nullable(),
952
+ });
953
+
954
+ export const customerPortalContextResponseSchema = z.object({
955
+ organization: customerPortalOrgSchema,
956
+ user: customerPortalUserSchema,
957
+ isCustomer: z.boolean(),
958
+ });
959
+
960
+ export const customerPortalProfileSchema = z
961
+ .object({
962
+ externalCustomerId: z.string().nullable(),
963
+ profile: z.record(z.unknown()).nullable(),
964
+ lastSeenAt: timestampSchema.nullable(),
965
+ joinedAt: timestampSchema.nullable(),
966
+ })
967
+ .nullable();
968
+
969
+ export const customerPortalUpdateProfileInputSchema = z.object({
970
+ displayName: z.string().min(1).max(120).optional(),
971
+ profile: z.record(z.unknown()).optional(),
972
+ });
973
+
974
+ export const customerPortalUpdateProfileResponseSchema = z.object({
975
+ ok: z.literal(true),
976
+ });
977
+
978
+ export const customerPortalOtherOrgSchema = z.object({
979
+ orgId: z.string().uuid(),
980
+ name: z.string().nullable(),
981
+ /** @deprecated Internal auth-provider org id — use `orgId` (the BIAB org id).
982
+ * Kept for backwards compatibility; will be removed in a future release. */
983
+ workosOrganizationId: z.string().nullable().optional(),
984
+ });
985
+ export const customerPortalOtherOrgsResponseSchema = z.array(
986
+ customerPortalOtherOrgSchema,
987
+ );
988
+
989
+ export type CustomerPortalOrg = z.infer<typeof customerPortalOrgSchema>;
990
+ export type CustomerPortalUser = z.infer<typeof customerPortalUserSchema>;
991
+ export type CustomerPortalContextResponse = z.infer<
992
+ typeof customerPortalContextResponseSchema
993
+ >;
994
+ export type CustomerPortalProfile = z.infer<typeof customerPortalProfileSchema>;
995
+ export type CustomerPortalUpdateProfileInput = z.infer<
996
+ typeof customerPortalUpdateProfileInputSchema
997
+ >;
998
+ export type CustomerPortalUpdateProfileResponse = z.infer<
999
+ typeof customerPortalUpdateProfileResponseSchema
1000
+ >;
1001
+ export type CustomerPortalOtherOrg = z.infer<
1002
+ typeof customerPortalOtherOrgSchema
1003
+ >;
1004
+ export type CustomerPortalOtherOrgsResponse = z.infer<
1005
+ typeof customerPortalOtherOrgsResponseSchema
1006
+ >;
1007
+
1008
+ // ── Customer notification preferences (per-customer, per-channel) ─────
1009
+ //
1010
+ // The matrix shape is `{ [categoryId]: { email, sms, push, in_app } }`.
1011
+ // The server returns a fully-defaulted matrix plus the category metadata
1012
+ // so SDK consumers can render the UI without hardcoding either the
1013
+ // categories or the channels list — both grow over time.
1014
+
1015
+ export const notificationChannelSchema = z.enum([
1016
+ "email",
1017
+ "sms",
1018
+ "push",
1019
+ "in_app",
1020
+ ]);
1021
+ export type NotificationChannel = z.infer<typeof notificationChannelSchema>;
1022
+
1023
+ export const notificationChannelMapSchema = z.object({
1024
+ email: z.boolean(),
1025
+ sms: z.boolean(),
1026
+ push: z.boolean(),
1027
+ in_app: z.boolean(),
1028
+ });
1029
+
1030
+ export const notificationCategoryDefSchema = z.object({
1031
+ id: z.string(),
1032
+ label: z.string(),
1033
+ description: z.string(),
1034
+ defaults: notificationChannelMapSchema,
1035
+ required: z.record(notificationChannelSchema, z.literal(true)).optional(),
1036
+ });
1037
+
1038
+ export const notificationPreferencesResponseSchema = z.object({
1039
+ preferences: z.record(notificationChannelMapSchema),
1040
+ categories: z.array(notificationCategoryDefSchema),
1041
+ channels: z.array(notificationChannelSchema),
1042
+ });
1043
+
1044
+ export const notificationPreferencesUpdateInputSchema = z.object({
1045
+ /**
1046
+ * Sparse — only the categories / channels you want to flip need to
1047
+ * be present. Omitted keys keep their persisted (or default) value.
1048
+ */
1049
+ preferences: z.record(
1050
+ z.object({
1051
+ email: z.boolean().optional(),
1052
+ sms: z.boolean().optional(),
1053
+ push: z.boolean().optional(),
1054
+ in_app: z.boolean().optional(),
1055
+ }),
1056
+ ),
1057
+ });
1058
+
1059
+ export type NotificationChannelMap = z.infer<
1060
+ typeof notificationChannelMapSchema
1061
+ >;
1062
+ export type NotificationCategoryDef = z.infer<
1063
+ typeof notificationCategoryDefSchema
1064
+ >;
1065
+ export type NotificationPreferencesResponse = z.infer<
1066
+ typeof notificationPreferencesResponseSchema
1067
+ >;
1068
+ export type NotificationPreferencesUpdateInput = z.infer<
1069
+ typeof notificationPreferencesUpdateInputSchema
1070
+ >;
1071
+
1072
+ // ── Customer-portal "work" bundle (jobs / quotes / invoices / contracts / payments) ─────
1073
+
1074
+ export const customerJobSummarySchema = z.object({
1075
+ id: z.string(),
1076
+ name: z.string().nullable(),
1077
+ status: z.string(),
1078
+ startDate: z.string().nullable(),
1079
+ dueDate: z.string().nullable(),
1080
+ statusCompletedAt: z.string().nullable(),
1081
+ });
1082
+
1083
+ export const customerQuoteSummarySchema = z.object({
1084
+ id: z.string(),
1085
+ quoteNumber: z.string(),
1086
+ /** Mirrors the `quote_status` pg enum. */
1087
+ status: openEnum(
1088
+ "draft",
1089
+ "sent",
1090
+ "accepted",
1091
+ "rejected",
1092
+ "converted",
1093
+ "expired",
1094
+ ),
1095
+ totalAmount: z.number(),
1096
+ validUntil: z.string(),
1097
+ publicAcceptUrl: z.string().nullable(),
1098
+ });
1099
+
1100
+ export const customerInvoiceSummarySchema = z.object({
1101
+ id: z.string(),
1102
+ invoiceNumber: z.string(),
1103
+ /** Mirrors the `invoice_status` pg enum. */
1104
+ status: openEnum(
1105
+ "draft",
1106
+ "sent",
1107
+ "partial_paid",
1108
+ "paid",
1109
+ "overdue",
1110
+ "cancelled",
1111
+ ),
1112
+ /** Mirrors the `invoice_job_status` pg enum. */
1113
+ jobStatus: openEnum("pending", "in_progress", "complete").nullable(),
1114
+ totalAmount: z.number(),
1115
+ paidAmount: z.number(),
1116
+ balanceDue: z.number(),
1117
+ dueDate: z.string(),
1118
+ issueDate: z.string(),
1119
+ paidAt: z.string().nullable(),
1120
+ publicPayUrl: z.string().nullable(),
1121
+ });
1122
+
1123
+ export const customerContractSummarySchema = z.object({
1124
+ id: z.string(),
1125
+ title: z.string(),
1126
+ /** Mirrors the `contract_status` pg enum. */
1127
+ status: openEnum("draft", "sent", "signed", "expired", "cancelled"),
1128
+ sentAt: z.string().nullable(),
1129
+ signedAt: z.string().nullable(),
1130
+ signingUrl: z.string().nullable(),
1131
+ signedDocumentUrl: z.string().nullable(),
1132
+ });
1133
+
1134
+ export const customerPaymentSummarySchema = z.object({
1135
+ id: z.string(),
1136
+ invoiceId: z.string(),
1137
+ amount: z.number(),
1138
+ /** Mirrors the `payment_method` pg enum. */
1139
+ method: openEnum("card", "bank_transfer", "cash", "check", "other"),
1140
+ date: z.string(),
1141
+ });
1142
+
1143
+ export const customerWorkBundleSchema = z
1144
+ .object({
1145
+ /** True when the customer's email matches no CRM contact in this org. */
1146
+ unlinked: z.boolean(),
1147
+ jobs: z.array(customerJobSummarySchema),
1148
+ quotes: z.array(customerQuoteSummarySchema),
1149
+ invoices: z.array(customerInvoiceSummarySchema),
1150
+ contracts: z.array(customerContractSummarySchema),
1151
+ payments: z.array(customerPaymentSummarySchema),
1152
+ summary: z.object({
1153
+ openJobCount: z.number(),
1154
+ pendingQuoteCount: z.number(),
1155
+ unpaidInvoiceCount: z.number(),
1156
+ unpaidBalance: z.number(),
1157
+ awaitingSignatureCount: z.number(),
1158
+ }),
1159
+ })
1160
+ .passthrough();
1161
+
1162
+ export type CustomerJobSummary = z.infer<typeof customerJobSummarySchema>;
1163
+ export type CustomerQuoteSummary = z.infer<typeof customerQuoteSummarySchema>;
1164
+ export type CustomerInvoiceSummary = z.infer<
1165
+ typeof customerInvoiceSummarySchema
1166
+ >;
1167
+ export type CustomerContractSummary = z.infer<
1168
+ typeof customerContractSummarySchema
1169
+ >;
1170
+ export type CustomerPaymentSummary = z.infer<
1171
+ typeof customerPaymentSummarySchema
1172
+ >;
1173
+ export type CustomerWorkBundle = z.infer<typeof customerWorkBundleSchema>;
1174
+
1175
+ // ── Customer-portal per-item detail responses ─────────────────────────
1176
+
1177
+ export const customerJobDetailSchema = customerJobSummarySchema.passthrough();
1178
+
1179
+ export const customerQuoteDetailSchema = z
1180
+ .object({
1181
+ id: z.string(),
1182
+ quoteNumber: z.string(),
1183
+ /** Mirrors the `quote_status` pg enum. */
1184
+ status: openEnum(
1185
+ "draft",
1186
+ "sent",
1187
+ "accepted",
1188
+ "rejected",
1189
+ "converted",
1190
+ "expired",
1191
+ ),
1192
+ items: z.array(z.unknown()),
1193
+ subtotalAmount: z.number(),
1194
+ discountAmount: z.number(),
1195
+ taxRate: z.number(),
1196
+ taxAmount: z.number(),
1197
+ totalAmount: z.number(),
1198
+ validUntil: z.string(),
1199
+ publicAcceptUrl: z.string().nullable(),
1200
+ notes: z.string().nullable(),
1201
+ })
1202
+ .passthrough();
1203
+
1204
+ /** One entry in the customer's cross-surface payment history — invoice
1205
+ * payments, store-order charges, and refunds (negative amount), newest
1206
+ * first. `reference` is the confirmation # (Stripe PaymentIntent id for
1207
+ * card, provider refund id, or a manual reference). */
1208
+ export const customerPaymentHistoryEntrySchema = z.object({
1209
+ id: z.string(),
1210
+ kind: z.enum(["invoice_payment", "order_charge", "order_refund"]),
1211
+ /** Dollars. Negative for refunds. */
1212
+ amount: z.number(),
1213
+ /** Integer cents for exact math. Negative for refunds. */
1214
+ amountCents: z.number(),
1215
+ /** Mirrors the `payment_method` pg enum. */
1216
+ method: openEnum(
1217
+ "card",
1218
+ "bank_transfer",
1219
+ "cash",
1220
+ "check",
1221
+ "other",
1222
+ ).nullable(),
1223
+ status: z.string(),
1224
+ reference: z.string().nullable(),
1225
+ occurredAt: z.string(),
1226
+ invoiceId: z.string().optional(),
1227
+ invoiceNumber: z.string().optional(),
1228
+ orderId: z.string().optional(),
1229
+ orderNumber: z.string().optional(),
1230
+ });
1231
+
1232
+ export const customerPaymentHistoryResponseSchema = z.object({
1233
+ items: z.array(customerPaymentHistoryEntrySchema),
1234
+ });
1235
+
1236
+ /** A single recorded payment/refund on an invoice. */
1237
+ export const customerInvoicePaymentSchema = z.object({
1238
+ amount: z.number(),
1239
+ /** Mirrors the `payment_method` pg enum. */
1240
+ method: openEnum("card", "bank_transfer", "cash", "check", "other"),
1241
+ date: z.string(),
1242
+ /** Stripe PaymentIntent id (card) or manual reference (check #, etc.). */
1243
+ reference: z.string().nullable(),
1244
+ /**
1245
+ * These three are what the platform writes today, but unlike the other
1246
+ * statuses in this file there is no enum behind them — `payments.status` is
1247
+ * plain `text` with a `"completed"` default, so the column accepts anything.
1248
+ * `openEnum` is the honest shape for that: helpful to callers, and it can't
1249
+ * reject a row some other writer put there.
1250
+ */
1251
+ status: openEnum("completed", "refunded", "failed"),
1252
+ });
1253
+
1254
+ export const customerInvoiceDetailSchema = z
1255
+ .object({
1256
+ id: z.string(),
1257
+ invoiceNumber: z.string(),
1258
+ /** Mirrors the `invoice_status` pg enum. */
1259
+ status: openEnum(
1260
+ "draft",
1261
+ "sent",
1262
+ "partial_paid",
1263
+ "paid",
1264
+ "overdue",
1265
+ "cancelled",
1266
+ ),
1267
+ /** Mirrors the `invoice_job_status` pg enum. */
1268
+ jobStatus: openEnum("pending", "in_progress", "complete").nullable(),
1269
+ totalAmount: z.number(),
1270
+ paidAmount: z.number(),
1271
+ balanceDue: z.number(),
1272
+ dueDate: z.string(),
1273
+ issueDate: z.string(),
1274
+ paidAt: z.string().nullable(),
1275
+ paymentSchedule: z.unknown().nullable(),
1276
+ notes: z.string().nullable(),
1277
+ terms: z.string().nullable(),
1278
+ publicPayUrl: z.string().nullable(),
1279
+ // Optional so the SDK stays compatible with older BIAB deployments
1280
+ // that don't return these yet.
1281
+ /** Recorded payments/refunds, oldest first. */
1282
+ payments: z.array(customerInvoicePaymentSchema).optional(),
1283
+ /** Most-recent completed payment reference — the confirmation number. */
1284
+ confirmationNumber: z.string().nullable().optional(),
1285
+ /** Host-less path to the downloadable invoice PDF. */
1286
+ pdfPath: z.string().nullable().optional(),
1287
+ })
1288
+ .passthrough();
1289
+
1290
+ /**
1291
+ * Response of `downloadInvoicePdf` — the invoice rendered as a PDF, base64
1292
+ * encoded (the SDK decodes it to bytes for you).
1293
+ */
1294
+ export const customerInvoicePdfSchema = z.object({
1295
+ invoiceNumber: z.string(),
1296
+ filename: z.string(),
1297
+ contentType: z.string(),
1298
+ base64: z.string(),
1299
+ });
1300
+
1301
+ export const customerContractDetailSchema = z
1302
+ .object({
1303
+ id: z.string(),
1304
+ title: z.string(),
1305
+ /** Mirrors the `contract_status` pg enum. */
1306
+ status: openEnum("draft", "sent", "signed", "expired", "cancelled"),
1307
+ contractNumber: z.string().nullable(),
1308
+ sentAt: z.string().nullable(),
1309
+ signedAt: z.string().nullable(),
1310
+ signedDocumentUrl: z.string().nullable(),
1311
+ signingUrl: z.string().nullable(),
1312
+ pdfUrl: z.string().nullable(),
1313
+ })
1314
+ .passthrough();
1315
+
1316
+ export const customerQuoteActionResponseSchema = z.object({
1317
+ ok: z.literal(true),
1318
+ status: z.string(),
1319
+ dealId: z.string().nullable().optional(),
1320
+ });
1321
+
1322
+ /** Optional storefront return pages for the hosted Stripe Checkout. */
1323
+ export const customerInvoicePayInputSchema = z.object({
1324
+ successUrl: z.string().url().max(2000).optional(),
1325
+ cancelUrl: z.string().url().max(2000).optional(),
1326
+ });
1327
+
1328
+ /** Response of `payInvoice` — a Stripe-hosted Checkout URL to redirect to. */
1329
+ export const customerInvoicePayResponseSchema = z.object({
1330
+ invoiceId: z.string(),
1331
+ status: z.string(),
1332
+ checkoutUrl: z.string().url(),
1333
+ checkoutSessionId: z.string(),
1334
+ });
1335
+
1336
+ /** Response of `signContract` — a ready, absolute hosted signing URL. */
1337
+ export const customerContractSignResponseSchema = z.object({
1338
+ contractId: z.string(),
1339
+ status: z.string(),
1340
+ signingUrl: z.string().url(),
1341
+ });
1342
+
1343
+ export type CustomerInvoicePayInput = z.infer<
1344
+ typeof customerInvoicePayInputSchema
1345
+ >;
1346
+ export type CustomerInvoicePayResponse = z.infer<
1347
+ typeof customerInvoicePayResponseSchema
1348
+ >;
1349
+ export type CustomerContractSignResponse = z.infer<
1350
+ typeof customerContractSignResponseSchema
1351
+ >;
1352
+
1353
+ // ── Customer-portal: referrals (the customer as an affiliate) ──────────
1354
+ // Read-only self surface. Counters are real (denormalized on the affiliate
1355
+ // row); they read zero / empty until the org's referral attribution +
1356
+ // payout pipeline records conversions for this customer.
1357
+
1358
+ /** Roll-up of the customer's referral activity, summed across programs. */
1359
+ export const customerReferralStatsSchema = z.object({
1360
+ /** True when the customer holds at least one affiliate record. */
1361
+ enrolled: z.boolean(),
1362
+ programCount: z.number(),
1363
+ claimsCount: z.number(),
1364
+ conversionsCount: z.number(),
1365
+ revenueCents: z.number(),
1366
+ earningsCents: z.number(),
1367
+ nextPayoutCents: z.number(),
1368
+ nextPayoutAt: z.string().nullable(),
1369
+ });
1370
+
1371
+ export const customerReferralConversionSchema = z.object({
1372
+ id: z.string(),
1373
+ programId: z.string(),
1374
+ amountCents: z.number(),
1375
+ commissionCents: z.number(),
1376
+ currency: z.string(),
1377
+ /** Mirrors the `referral_conversion_status` pg enum. */
1378
+ status: openEnum("pending", "confirmed", "reversed"),
1379
+ createdAt: z.string(),
1380
+ });
1381
+
1382
+ export const customerReferralHistoryResponseSchema = z.object({
1383
+ items: z.array(customerReferralConversionSchema),
1384
+ });
1385
+
1386
+ export const customerReferralPayoutSchema = z.object({
1387
+ id: z.string(),
1388
+ programId: z.string(),
1389
+ periodStart: z.string(),
1390
+ periodEnd: z.string(),
1391
+ amountCents: z.number(),
1392
+ feeCents: z.number(),
1393
+ totalCents: z.number(),
1394
+ currency: z.string(),
1395
+ /** Mirrors the `referral_payout_status` pg enum. */
1396
+ status: openEnum("upcoming", "pending", "paid", "failed"),
1397
+ paidAt: z.string().nullable(),
1398
+ });
1399
+
1400
+ export const customerReferralPayoutsResponseSchema = z.object({
1401
+ items: z.array(customerReferralPayoutSchema),
1402
+ });
1403
+
1404
+ export type CustomerReferralStats = z.infer<typeof customerReferralStatsSchema>;
1405
+ export type CustomerReferralConversion = z.infer<
1406
+ typeof customerReferralConversionSchema
1407
+ >;
1408
+ export type CustomerReferralHistoryResponse = z.infer<
1409
+ typeof customerReferralHistoryResponseSchema
1410
+ >;
1411
+ export type CustomerReferralPayout = z.infer<
1412
+ typeof customerReferralPayoutSchema
1413
+ >;
1414
+ export type CustomerReferralPayoutsResponse = z.infer<
1415
+ typeof customerReferralPayoutsResponseSchema
1416
+ >;
1417
+
1418
+ // ── Customer-portal: orders + shipping status tracker ──────────────────
1419
+
1420
+ /** One order in the customer's list, with a shipment roll-up for the chip. */
1421
+ export const customerOrderSummarySchema = z.object({
1422
+ id: z.string(),
1423
+ orderNumber: z.string(),
1424
+ /** Mirrors the `order_status` pg enum. */
1425
+ status: openEnum(
1426
+ "pending",
1427
+ "confirmed",
1428
+ "processing",
1429
+ "packed",
1430
+ "shipped",
1431
+ "delivered",
1432
+ "cancelled",
1433
+ "refunded",
1434
+ ),
1435
+ /** Mirrors the `order_payment_status` pg enum. */
1436
+ paymentStatus: openEnum("unpaid", "paid", "partial", "refunded"),
1437
+ totalCents: z.number(),
1438
+ createdAt: z.string(),
1439
+ shipmentCount: z.number(),
1440
+ /** Rolled up across the order's shipments. Mirrors `order_shipment_status`. */
1441
+ overallStatus: openEnum(
1442
+ "pending",
1443
+ "label_created",
1444
+ "in_transit",
1445
+ "delivered",
1446
+ "returned",
1447
+ ),
1448
+ anyInTransit: z.boolean(),
1449
+ allDelivered: z.boolean(),
1450
+ latestEstimatedDelivery: z.string().nullable(),
1451
+ });
1452
+
1453
+ export const customerOrderListResponseSchema = z.object({
1454
+ items: z.array(customerOrderSummarySchema),
1455
+ });
1456
+
1457
+ export const customerOrderItemSchema = z.object({
1458
+ id: z.string(),
1459
+ productName: z.string(),
1460
+ variantName: z.string().nullable(),
1461
+ sku: z.string().nullable(),
1462
+ quantity: z.number(),
1463
+ unitPriceCents: z.number(),
1464
+ totalCents: z.number(),
1465
+ requiresShipping: z.boolean(),
1466
+ });
1467
+
1468
+ export const customerShipmentSchema = z.object({
1469
+ id: z.string(),
1470
+ carrier: z.string().nullable(),
1471
+ trackingNumber: z.string().nullable(),
1472
+ trackingUrl: z.string().nullable(),
1473
+ serviceName: z.string().nullable(),
1474
+ /** Mirrors the `order_shipment_status` pg enum. */
1475
+ status: openEnum(
1476
+ "pending",
1477
+ "label_created",
1478
+ "in_transit",
1479
+ "delivered",
1480
+ "returned",
1481
+ ),
1482
+ estimatedDelivery: z.string().nullable(),
1483
+ shippedAt: z.string().nullable(),
1484
+ deliveredAt: z.string().nullable(),
1485
+ isReturn: z.boolean(),
1486
+ /** Append-only carrier-event timeline (one entry per status transition). */
1487
+ statusHistory: z.array(z.unknown()),
1488
+ });
1489
+
1490
+ /** A return/refund request the customer opened on this order (P10b). */
1491
+ export const customerReturnRequestSchema = z.object({
1492
+ id: z.string(),
1493
+ /**
1494
+ * Mirrors `order_return_request_status`. Note the spelling: this enum uses
1495
+ * `cancelled` (two Ls), while `booking_status` uses `canceled` (one). They
1496
+ * are separate enums and each is spelled as the database spells it.
1497
+ */
1498
+ status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1499
+ reason: z.string().nullable(),
1500
+ items: z
1501
+ .array(
1502
+ z.object({
1503
+ orderItemId: z.string(),
1504
+ productName: z.string(),
1505
+ quantity: z.number(),
1506
+ }),
1507
+ )
1508
+ .nullable(),
1509
+ customerNote: z.string().nullable(),
1510
+ resolutionNote: z.string().nullable(),
1511
+ createdAt: z.string(),
1512
+ resolvedAt: z.string().nullable(),
1513
+ });
1514
+
1515
+ export const customerOrderDetailSchema = z.object({
1516
+ id: z.string(),
1517
+ orderNumber: z.string(),
1518
+ /** Mirrors the `order_status` pg enum. */
1519
+ status: openEnum(
1520
+ "pending",
1521
+ "confirmed",
1522
+ "processing",
1523
+ "packed",
1524
+ "shipped",
1525
+ "delivered",
1526
+ "cancelled",
1527
+ "refunded",
1528
+ ),
1529
+ /** Mirrors the `order_payment_status` pg enum. */
1530
+ paymentStatus: openEnum("unpaid", "paid", "partial", "refunded"),
1531
+ subtotalCents: z.number(),
1532
+ shippingCents: z.number(),
1533
+ taxCents: z.number(),
1534
+ discountCents: z.number(),
1535
+ totalCents: z.number(),
1536
+ createdAt: z.string(),
1537
+ items: z.array(customerOrderItemSchema),
1538
+ shipments: z.array(customerShipmentSchema),
1539
+ // Optional for back-compat with older BIAB deployments.
1540
+ returnRequests: z.array(customerReturnRequestSchema).optional(),
1541
+ });
1542
+
1543
+ /** Input for `submitReturnRequest`. Omit `items` for a whole-order request. */
1544
+ export const customerReturnRequestInputSchema = z.object({
1545
+ reason: z.string().max(120).optional(),
1546
+ customerNote: z.string().max(2000).optional(),
1547
+ items: z
1548
+ .array(
1549
+ z.object({
1550
+ orderItemId: z.string().uuid(),
1551
+ quantity: z.number().int().positive(),
1552
+ }),
1553
+ )
1554
+ .optional(),
1555
+ });
1556
+
1557
+ /** Response of `submitReturnRequest` — the newly opened request. */
1558
+ export const customerReturnRequestCreatedSchema = z.object({
1559
+ id: z.string().nullable(),
1560
+ /** Mirrors the `order_return_request_status` pg enum. */
1561
+ status: openEnum("requested", "approved", "denied", "refunded", "cancelled"),
1562
+ createdAt: z.string().nullable(),
1563
+ });
1564
+
1565
+ /** One downloadable digital deliverable for a paid order (P10d). */
1566
+ export const customerDownloadSchema = z.object({
1567
+ productName: z.string(),
1568
+ fileName: z.string().nullable(),
1569
+ /** Short-lived presigned URL (BIAB-hosted) or the org's external link. */
1570
+ url: z.string(),
1571
+ /** Seconds until `url` expires; null for a non-expiring external link. */
1572
+ expiresInSeconds: z.number().nullable(),
1573
+ });
1574
+
1575
+ /** Response of `getDownloads` — the order's entitlement-checked downloads. */
1576
+ export const customerDownloadsResponseSchema = z.object({
1577
+ items: z.array(customerDownloadSchema),
1578
+ });
1579
+
1580
+ /** `trackShipment` — stored shipment + a best-effort live carrier status. */
1581
+ export const customerShipmentTrackingSchema = z.object({
1582
+ id: z.string(),
1583
+ orderId: z.string().nullable(),
1584
+ carrier: z.string().nullable(),
1585
+ trackingNumber: z.string().nullable(),
1586
+ trackingUrl: z.string().nullable(),
1587
+ serviceName: z.string().nullable(),
1588
+ /** Mirrors the `order_shipment_status` pg enum. */
1589
+ status: openEnum(
1590
+ "pending",
1591
+ "label_created",
1592
+ "in_transit",
1593
+ "delivered",
1594
+ "returned",
1595
+ ),
1596
+ estimatedDelivery: z.string().nullable(),
1597
+ shippedAt: z.string().nullable(),
1598
+ deliveredAt: z.string().nullable(),
1599
+ statusHistory: z.array(z.unknown()),
1600
+ /** Live Shippo tracking_status when polled; null otherwise. */
1601
+ liveStatus: z.unknown().nullable(),
1602
+ });
1603
+
1604
+ export type CustomerOrderSummary = z.infer<typeof customerOrderSummarySchema>;
1605
+ export type CustomerOrderListResponse = z.infer<
1606
+ typeof customerOrderListResponseSchema
1607
+ >;
1608
+ export type CustomerOrderItem = z.infer<typeof customerOrderItemSchema>;
1609
+ export type CustomerShipment = z.infer<typeof customerShipmentSchema>;
1610
+ export type CustomerOrderDetail = z.infer<typeof customerOrderDetailSchema>;
1611
+ export type CustomerReturnRequest = z.infer<typeof customerReturnRequestSchema>;
1612
+ export type CustomerReturnRequestInput = z.infer<
1613
+ typeof customerReturnRequestInputSchema
1614
+ >;
1615
+ export type CustomerReturnRequestCreated = z.infer<
1616
+ typeof customerReturnRequestCreatedSchema
1617
+ >;
1618
+ export type CustomerDownload = z.infer<typeof customerDownloadSchema>;
1619
+ export type CustomerDownloadsResponse = z.infer<
1620
+ typeof customerDownloadsResponseSchema
1621
+ >;
1622
+ export type CustomerShipmentTracking = z.infer<
1623
+ typeof customerShipmentTrackingSchema
1624
+ >;
1625
+
1626
+ // ── Storefront branding ────────────────────────────────────────────────
1627
+
1628
+ /** Org storefront branding — logo + favicon (site media) + org name/icon. */
1629
+ export const siteBrandingSchema = z.object({
1630
+ siteId: z.string(),
1631
+ siteName: z.string().nullable(),
1632
+ logoUrl: z.string().nullable(),
1633
+ faviconUrl: z.string().nullable(),
1634
+ orgName: z.string().nullable(),
1635
+ orgIcon: z.string().nullable(),
1636
+ });
1637
+
1638
+ export type SiteBranding = z.infer<typeof siteBrandingSchema>;
1639
+
1640
+ export type CustomerJobDetail = z.infer<typeof customerJobDetailSchema>;
1641
+ export type CustomerQuoteDetail = z.infer<typeof customerQuoteDetailSchema>;
1642
+ export type CustomerInvoiceDetail = z.infer<typeof customerInvoiceDetailSchema>;
1643
+ export type CustomerInvoicePayment = z.infer<
1644
+ typeof customerInvoicePaymentSchema
1645
+ >;
1646
+ export type CustomerPaymentHistoryEntry = z.infer<
1647
+ typeof customerPaymentHistoryEntrySchema
1648
+ >;
1649
+ export type CustomerPaymentHistoryResponse = z.infer<
1650
+ typeof customerPaymentHistoryResponseSchema
1651
+ >;
1652
+ export type CustomerInvoicePdf = z.infer<typeof customerInvoicePdfSchema>;
1653
+ export type CustomerContractDetail = z.infer<
1654
+ typeof customerContractDetailSchema
1655
+ >;
1656
+ export type CustomerQuoteActionResponse = z.infer<
1657
+ typeof customerQuoteActionResponseSchema
1658
+ >;
1659
+
1660
+ // ── Customer-portal: first-party reviews ──────────────────────────────
1661
+
1662
+ export const customerReviewSummarySchema = z.object({
1663
+ id: z.string(),
1664
+ rating: z.number().int(),
1665
+ title: z.string().nullable(),
1666
+ body: z.string(),
1667
+ /** Mirrors the `first_party_review_status` pg enum. */
1668
+ status: openEnum("pending", "published", "hidden"),
1669
+ jobId: z.string().nullable(),
1670
+ submittedAt: z.string(),
1671
+ publishedAt: z.string().nullable(),
1672
+ });
1673
+
1674
+ export const customerReviewListResponseSchema = z.object({
1675
+ items: z.array(customerReviewSummarySchema),
1676
+ });
1677
+
1678
+ export const customerReviewSubmitInputSchema = z.object({
1679
+ rating: z.number().int().min(1).max(5),
1680
+ title: z.string().max(200).optional(),
1681
+ body: z.string().min(1).max(5000),
1682
+ jobId: z.string().uuid().optional(),
1683
+ });
1684
+
1685
+ export const customerReviewSubmitResponseSchema = z
1686
+ .object({
1687
+ id: z.string().optional(),
1688
+ status: z.string().optional(),
1689
+ submittedAt: z.string().nullable().optional(),
1690
+ })
1691
+ .passthrough();
1692
+
1693
+ export type CustomerReviewSummary = z.infer<typeof customerReviewSummarySchema>;
1694
+ export type CustomerReviewListResponse = z.infer<
1695
+ typeof customerReviewListResponseSchema
1696
+ >;
1697
+ export type CustomerReviewSubmitInput = z.infer<
1698
+ typeof customerReviewSubmitInputSchema
1699
+ >;
1700
+ export type CustomerReviewSubmitResponse = z.infer<
1701
+ typeof customerReviewSubmitResponseSchema
1702
+ >;
1703
+
1704
+ // ── Customer-portal: per-job comments ─────────────────────────────────
1705
+
1706
+ export const customerJobCommentSchema = z.object({
1707
+ id: z.string(),
1708
+ authorKind: z.enum(["customer", "staff", "system"]),
1709
+ authorName: z.string().nullable(),
1710
+ body: z.string(),
1711
+ createdAt: z.string(),
1712
+ });
1713
+
1714
+ export const customerJobCommentsListResponseSchema = z.object({
1715
+ items: z.array(customerJobCommentSchema),
1716
+ });
1717
+
1718
+ export const customerJobCommentPostInputSchema = z.object({
1719
+ body: z.string().min(1).max(5000),
1720
+ });
1721
+
1722
+ export type CustomerJobComment = z.infer<typeof customerJobCommentSchema>;
1723
+ export type CustomerJobCommentsListResponse = z.infer<
1724
+ typeof customerJobCommentsListResponseSchema
1725
+ >;
1726
+ export type CustomerJobCommentPostInput = z.infer<
1727
+ typeof customerJobCommentPostInputSchema
1728
+ >;
1729
+
1730
+ // ── Customer-portal: contact-the-company (front-desk session start) ───
1731
+
1732
+ export const customerContactCompanyInputSchema = z.object({
1733
+ message: z.string().min(1).max(8000),
1734
+ subject: z.string().max(200).optional(),
1735
+ });
1736
+
1737
+ export const customerContactCompanyResponseSchema = z.object({
1738
+ sessionId: z.string(),
1739
+ visitorToken: z.string(),
1740
+ status: z.string(),
1741
+ });
1742
+
1743
+ export type CustomerContactCompanyInput = z.infer<
1744
+ typeof customerContactCompanyInputSchema
1745
+ >;
1746
+ export type CustomerContactCompanyResponse = z.infer<
1747
+ typeof customerContactCompanyResponseSchema
1748
+ >;
1749
+
1750
+ // ── Customer-portal: staff ETA on a job ────────────────────────────────
1751
+
1752
+ export const customerJobEtaAssignmentSchema = z.object({
1753
+ displayName: z.string(),
1754
+ role: z.string().nullable(),
1755
+ dispatchStatus: z.string(),
1756
+ lastKnownLocation: z
1757
+ .object({
1758
+ lat: z.number(),
1759
+ lng: z.number(),
1760
+ capturedAt: z.string(),
1761
+ })
1762
+ .nullable(),
1763
+ });
1764
+
1765
+ export const customerJobEtaResponseSchema = z.object({
1766
+ assignments: z.array(customerJobEtaAssignmentSchema),
1767
+ eta: z
1768
+ .object({
1769
+ seconds: z.number().nullable(),
1770
+ distanceMeters: z.number().nullable(),
1771
+ snapshotAt: z.string(),
1772
+ source: z.literal("dispatch_offer_snapshot"),
1773
+ })
1774
+ .nullable(),
1775
+ liveEtaAvailable: z.boolean(),
1776
+ });
1777
+
1778
+ export type CustomerJobEtaAssignment = z.infer<
1779
+ typeof customerJobEtaAssignmentSchema
1780
+ >;
1781
+ export type CustomerJobEtaResponse = z.infer<
1782
+ typeof customerJobEtaResponseSchema
1783
+ >;
1784
+
1785
+ // ── Customer-portal: job activity feed (events + chat interleaved) ────
1786
+
1787
+ export const customerJobActivityEntrySchema = z.discriminatedUnion("kind", [
1788
+ z
1789
+ .object({
1790
+ kind: z.literal("event"),
1791
+ id: z.string(),
1792
+ at: z.string(),
1793
+ event: z.object({
1794
+ eventKind: z.string(),
1795
+ title: z.string(),
1796
+ body: z.string().nullable(),
1797
+ actorName: z.string().nullable(),
1798
+ metadata: z.record(z.string(), z.unknown()).nullable(),
1799
+ }),
1800
+ })
1801
+ .passthrough(),
1802
+ z
1803
+ .object({
1804
+ kind: z.literal("chat"),
1805
+ id: z.string(),
1806
+ at: z.string(),
1807
+ chat: z.object({
1808
+ role: z.string(),
1809
+ content: z.string(),
1810
+ sessionId: z.string(),
1811
+ }),
1812
+ })
1813
+ .passthrough(),
1814
+ ]);
1815
+
1816
+ export const customerJobActivityResponseSchema = z.object({
1817
+ items: z.array(customerJobActivityEntrySchema),
1818
+ });
1819
+
1820
+ export type CustomerJobActivityEntry = z.infer<
1821
+ typeof customerJobActivityEntrySchema
1822
+ >;
1823
+ export type CustomerJobActivityResponse = z.infer<
1824
+ typeof customerJobActivityResponseSchema
1825
+ >;
1826
+
1827
+ // ── Blog programmatic surface (0.9.0) ─────────────────────────────────
1828
+
1829
+ export const blogPublicPostSchema = z
1830
+ .object({
1831
+ id: z.string(),
1832
+ orgId: z.string().nullable(),
1833
+ title: z.string(),
1834
+ slug: z.string(),
1835
+ content: z.string(),
1836
+ excerpt: z.string().nullable(),
1837
+ imageUrl: z.string().nullable(),
1838
+ imageUrlDark: z.string().nullable(),
1839
+ muxPlaybackId: z.string().nullable(),
1840
+ muxStatus: z.string().nullable(),
1841
+ accessLevel: z.enum(["public", "followers", "paid"]),
1842
+ downloadAccessLevel: z.enum(["public", "followers", "paid"]),
1843
+ publishedAt: z.string().nullable(),
1844
+ isPinned: z.boolean(),
1845
+ authorName: z.string().nullable(),
1846
+ authorAvatarUrl: z.string().nullable(),
1847
+ tags: z.array(z.string()),
1848
+ likes: z.number(),
1849
+ commentsCount: z.number(),
1850
+ viewCount: z.number(),
1851
+ createdAt: z.string(),
1852
+ updatedAt: z.string(),
1853
+ ctaLabel: z.string().nullable(),
1854
+ ctaHref: z.string().nullable(),
1855
+ ctaDescription: z.string().nullable(),
1856
+ })
1857
+ .passthrough();
1858
+
1859
+ export const blogListPostsResponseSchema = z.object({
1860
+ items: z.array(blogPublicPostSchema),
1861
+ nextCursor: z.number().nullable(),
1862
+ });
1863
+
1864
+ export const blogPostBySlugResponseSchema = z.object({
1865
+ post: blogPublicPostSchema,
1866
+ access: z.enum(["granted", "paywall"]),
1867
+ });
1868
+
1869
+ export const blogCategorySchema = z.object({
1870
+ id: z.string(),
1871
+ name: z.string(),
1872
+ slug: z.string(),
1873
+ description: z.string().nullable(),
1874
+ });
1875
+ export const blogCategoryListResponseSchema = z.object({
1876
+ items: z.array(blogCategorySchema),
1877
+ });
1878
+
1879
+ export const blogTagListResponseSchema = z.object({
1880
+ items: z.array(z.string()),
1881
+ });
1882
+
1883
+ export const blogPublicCommentSchema = z.object({
1884
+ id: z.string(),
1885
+ postId: z.string(),
1886
+ parentId: z.string().nullable(),
1887
+ content: z.string(),
1888
+ authorName: z.string().nullable(),
1889
+ authorAvatarUrl: z.string().nullable(),
1890
+ createdAt: z.string(),
1891
+ });
1892
+ export const blogCommentListResponseSchema = z.object({
1893
+ items: z.array(blogPublicCommentSchema),
1894
+ nextCursor: z.number().nullable(),
1895
+ });
1896
+
1897
+ export const blogCommentPostInputSchema = z.object({
1898
+ content: z.string().min(1).max(5000),
1899
+ parentId: z.string().uuid().optional(),
1900
+ });
1901
+ export const blogCommentPostResponseSchema = z.object({
1902
+ comment: z
1903
+ .object({
1904
+ id: z.string(),
1905
+ postId: z.string(),
1906
+ content: z.string(),
1907
+ isApproved: z.boolean(),
1908
+ /** Mirrors the `blog_comment_screening_status` pg enum. */
1909
+ screeningStatus: openEnum(
1910
+ "ok",
1911
+ "flagged",
1912
+ "blocked",
1913
+ "reviewed_ok",
1914
+ "reviewed_blocked",
1915
+ ),
1916
+ createdAt: z.string(),
1917
+ })
1918
+ .passthrough(),
1919
+ moderation: z.object({
1920
+ status: z.string(),
1921
+ isApproved: z.boolean(),
1922
+ }),
1923
+ });
1924
+
1925
+ /** Like state + count returned by GET/POST `blog/posts/:slug/like`. */
1926
+ export const blogLikeResponseSchema = z.object({
1927
+ liked: z.boolean(),
1928
+ likesCount: z.number(),
1929
+ });
1930
+
1931
+ export type BlogPublicPost = z.infer<typeof blogPublicPostSchema>;
1932
+ export type BlogListPostsResponse = z.infer<typeof blogListPostsResponseSchema>;
1933
+ export type BlogPostBySlugResponse = z.infer<
1934
+ typeof blogPostBySlugResponseSchema
1935
+ >;
1936
+ export type BlogCategory = z.infer<typeof blogCategorySchema>;
1937
+ export type BlogCategoryListResponse = z.infer<
1938
+ typeof blogCategoryListResponseSchema
1939
+ >;
1940
+ export type BlogTagListResponse = z.infer<typeof blogTagListResponseSchema>;
1941
+ export type BlogPublicComment = z.infer<typeof blogPublicCommentSchema>;
1942
+ export type BlogCommentListResponse = z.infer<
1943
+ typeof blogCommentListResponseSchema
1944
+ >;
1945
+ export type BlogCommentPostInput = z.infer<typeof blogCommentPostInputSchema>;
1946
+ export type BlogCommentPostResponse = z.infer<
1947
+ typeof blogCommentPostResponseSchema
1948
+ >;
1949
+ export type BlogLikeResponse = z.infer<typeof blogLikeResponseSchema>;
1950
+
1951
+ // ── Storefront programmatic surface (0.9.0) ───────────────────────────
1952
+
1953
+ /**
1954
+ * Loose-shape storefront product row. Server-side schema has many
1955
+ * columns; we keep this `.passthrough()` so consumers don't break
1956
+ * when new columns ship and so cross-variant fields (deferred to
1957
+ * 0.10) flow through untyped today.
1958
+ */
1959
+ /** Resolved category attached to a product row/card (mirrors a storefront category). */
1960
+ export const storefrontProductCategorySchema = z.object({
1961
+ id: z.string(),
1962
+ name: z.string(),
1963
+ description: z.string().nullable(),
1964
+ });
1965
+
1966
+ export const storefrontProductRowSchema = z
1967
+ .object({
1968
+ id: z.string(),
1969
+ orgId: z.string(),
1970
+ name: z.string(),
1971
+ isLive: z.boolean(),
1972
+ categoryId: z.string().nullable().optional(),
1973
+ /** Resolved category (name/description), when the product is categorized. */
1974
+ category: storefrontProductCategorySchema.nullable().optional(),
1975
+ images: z.array(z.string()).nullable().optional(),
1976
+ createdAt: z.string(),
1977
+ })
1978
+ .passthrough();
1979
+
1980
+ export const storefrontListProductsResponseSchema = z.object({
1981
+ items: z.array(storefrontProductRowSchema),
1982
+ nextCursor: z.number().nullable(),
1983
+ });
1984
+
1985
+ export const storefrontProductDetailSchema = storefrontProductRowSchema.and(
1986
+ z.object({
1987
+ variants: z.array(z.record(z.string(), z.unknown())),
1988
+ productImages: z.array(z.record(z.string(), z.unknown())),
1989
+ variantImages: z.array(z.record(z.string(), z.unknown())),
1990
+ // Cross-variant option groups (Size/Color/…) — the combinatorial picker.
1991
+ crossVariantTitles: z.array(z.record(z.string(), z.unknown())),
1992
+ crossVariants: z.array(z.record(z.string(), z.unknown())),
1993
+ // Raw specifications jsonb (shape is org-defined) — rides on the product
1994
+ // row; surfaced here for typing.
1995
+ specifications: z.unknown().optional(),
1996
+ }),
1997
+ );
1998
+
1999
+ /** A single approved product review. Lenient — server columns evolve. */
2000
+ export const storefrontReviewSchema = z
2001
+ .object({
2002
+ id: z.string(),
2003
+ productId: z.string(),
2004
+ rating: z.number(),
2005
+ title: z.string().nullable().optional(),
2006
+ body: z.string().nullable().optional(),
2007
+ content: z.string().nullable().optional(),
2008
+ reviewerName: z.string().nullable().optional(),
2009
+ createdAt: z.string().nullable().optional(),
2010
+ })
2011
+ .passthrough();
2012
+
2013
+ export const storefrontProductReviewsResponseSchema = z.object({
2014
+ items: z.array(storefrontReviewSchema),
2015
+ nextCursor: z.number().nullable(),
2016
+ avgRating: z.number(),
2017
+ totalCount: z.number(),
2018
+ });
2019
+
2020
+ /** A "you may also like" recommendation. */
2021
+ export const storefrontRelatedProductSchema = z
2022
+ .object({
2023
+ id: z.string(),
2024
+ name: z.string(),
2025
+ description: z.string().nullable(),
2026
+ categoryId: z.string().nullable(),
2027
+ coverImageUrl: z.string().nullable(),
2028
+ minPriceDollars: z.number().nullable(),
2029
+ reasons: z.array(z.string()).optional(),
2030
+ })
2031
+ .passthrough();
2032
+
2033
+ export const storefrontRelatedProductsResponseSchema = z.object({
2034
+ items: z.array(storefrontRelatedProductSchema),
2035
+ });
2036
+
2037
+ /** A companion/exclusive cross-sell addon for the "complete your X" rail. */
2038
+ export const storefrontAddonSchema = z
2039
+ .object({
2040
+ id: z.string(),
2041
+ addonProductId: z.string(),
2042
+ addonName: z.string(),
2043
+ addonDescription: z.string().nullable(),
2044
+ kind: z.string(),
2045
+ groupLabel: z.string().nullable(),
2046
+ priceCents: z.number().nullable(),
2047
+ originalPriceCents: z.number().nullable(),
2048
+ imageUrl: z.string().nullable(),
2049
+ variants: z.array(z.record(z.string(), z.unknown())),
2050
+ crossVariantTitles: z.array(z.record(z.string(), z.unknown())),
2051
+ })
2052
+ .passthrough();
2053
+
2054
+ export const storefrontAddonsResponseSchema = z.object({
2055
+ items: z.array(storefrontAddonSchema),
2056
+ });
2057
+
2058
+ export type StorefrontAddon = z.infer<typeof storefrontAddonSchema>;
2059
+ export type StorefrontAddonsResponse = z.infer<
2060
+ typeof storefrontAddonsResponseSchema
2061
+ >;
2062
+
2063
+ export type StorefrontReview = z.infer<typeof storefrontReviewSchema>;
2064
+ export type StorefrontProductReviewsResponse = z.infer<
2065
+ typeof storefrontProductReviewsResponseSchema
2066
+ >;
2067
+ export type StorefrontRelatedProduct = z.infer<
2068
+ typeof storefrontRelatedProductSchema
2069
+ >;
2070
+ export type StorefrontRelatedProductsResponse = z.infer<
2071
+ typeof storefrontRelatedProductsResponseSchema
2072
+ >;
2073
+
2074
+ export type StorefrontProductRow = z.infer<typeof storefrontProductRowSchema>;
2075
+ export type StorefrontListProductsResponse = z.infer<
2076
+ typeof storefrontListProductsResponseSchema
2077
+ >;
2078
+ export type StorefrontProductDetail = z.infer<
2079
+ typeof storefrontProductDetailSchema
2080
+ >;
2081
+
2082
+ /**
2083
+ * Storefront sort options for the listing grid (mirrors the platform shop demo).
2084
+ */
2085
+ export const storefrontSortValues = [
2086
+ "featured",
2087
+ "newest",
2088
+ "price-asc",
2089
+ "price-desc",
2090
+ "rating-desc",
2091
+ ] as const;
2092
+ export type StorefrontSort = (typeof storefrontSortValues)[number];
2093
+
2094
+ /** Enriched grid card — price, ratings, badges, cover image — for the
2095
+ * filterable listing page (`listProductsWithMeta`). */
2096
+ export const storefrontProductCardSchema = z.object({
2097
+ id: z.string(),
2098
+ name: z.string(),
2099
+ description: z.string().nullable(),
2100
+ categoryId: z.string().nullable(),
2101
+ /** Resolved category (name/description), when the product is categorized. */
2102
+ category: storefrontProductCategorySchema.nullable().optional(),
2103
+ coverImage: z.string().nullable(),
2104
+ cheapestPriceCents: z.number().nullable(),
2105
+ comparePriceCents: z.number().nullable(),
2106
+ isOnSale: z.boolean(),
2107
+ isLowStock: z.boolean(),
2108
+ hasMultipleVariants: z.boolean(),
2109
+ avgRating: z.number(),
2110
+ reviewCount: z.number(),
2111
+ isBestSeller: z.boolean(),
2112
+ isNew: z.boolean(),
2113
+ });
2114
+
2115
+ export const storefrontProductsWithMetaResponseSchema = z.object({
2116
+ items: z.array(storefrontProductCardSchema),
2117
+ categoryCounts: z.array(
2118
+ z.object({ categoryId: z.string(), count: z.number() }),
2119
+ ),
2120
+ priceRange: z.object({ minDollars: z.number(), maxDollars: z.number() }),
2121
+ });
2122
+
2123
+ export const storefrontCategorySchema = z.object({
2124
+ id: z.string(),
2125
+ name: z.string(),
2126
+ description: z.string().nullable(),
2127
+ });
2128
+
2129
+ export const storefrontCategoriesResponseSchema = z.object({
2130
+ items: z.array(storefrontCategorySchema),
2131
+ });
2132
+
2133
+ export type StorefrontProductCard = z.infer<typeof storefrontProductCardSchema>;
2134
+ export type StorefrontProductsWithMetaResponse = z.infer<
2135
+ typeof storefrontProductsWithMetaResponseSchema
2136
+ >;
2137
+ export type StorefrontCategory = z.infer<typeof storefrontCategorySchema>;
2138
+ export type StorefrontCategoriesResponse = z.infer<
2139
+ typeof storefrontCategoriesResponseSchema
2140
+ >;
2141
+
2142
+ // ── Cart programmatic surface (0.9.0) ─────────────────────────────────
2143
+
2144
+ export const cartItemSnapshotSchema = z.object({
2145
+ id: z.string(),
2146
+ productId: z.string(),
2147
+ variantId: z.string().nullable(),
2148
+ quantity: z.number().int(),
2149
+ options: z.record(z.string(), z.unknown()).nullable(),
2150
+ addedAt: z.string(),
2151
+ updatedAt: z.string(),
2152
+ productName: z.string().nullable(),
2153
+ productImage: z.string().nullable(),
2154
+ variantTitle: z.string().nullable(),
2155
+ unitPrice: z.number(),
2156
+ currency: z.string(),
2157
+ subtotal: z.number(),
2158
+ });
2159
+
2160
+ export const cartSnapshotSchema = z.object({
2161
+ id: z.string(),
2162
+ orgId: z.string(),
2163
+ visitorToken: z.string().nullable(),
2164
+ customerUserId: z.string().nullable(),
2165
+ couponCode: z.string().nullable(),
2166
+ currency: z.string(),
2167
+ items: z.array(cartItemSnapshotSchema),
2168
+ subtotal: z.number(),
2169
+ itemCount: z.number(),
2170
+ lastActivityAt: z.string(),
2171
+ createdAt: z.string(),
2172
+ updatedAt: z.string(),
2173
+ });
2174
+
2175
+ export const cartAddItemInputSchema = z.object({
2176
+ productId: z.string().uuid(),
2177
+ variantId: z.string().uuid().optional(),
2178
+ quantity: z.number().int().min(1).max(999).default(1),
2179
+ options: z.record(z.string(), z.unknown()).nullable().optional(),
2180
+ });
2181
+
2182
+ export const cartUpdateItemInputSchema = z.object({
2183
+ quantity: z.number().int().min(0).max(999),
2184
+ });
2185
+
2186
+ export const cartCouponInputSchema = z.object({
2187
+ code: z.string().min(1).max(80),
2188
+ });
2189
+
2190
+ export type CartItemSnapshot = z.infer<typeof cartItemSnapshotSchema>;
2191
+ export type CartSnapshot = z.infer<typeof cartSnapshotSchema>;
2192
+ export type CartAddItemInput = z.infer<typeof cartAddItemInputSchema>;
2193
+ export type CartUpdateItemInput = z.infer<typeof cartUpdateItemInputSchema>;
2194
+ export type CartCouponInput = z.infer<typeof cartCouponInputSchema>;
2195
+
2196
+ // ── Shipping (Batch shipping, 0.9.0) ──────────────────────────────────
2197
+
2198
+ export const shippingDeliveryAddressSchema = z.object({
2199
+ name: z.string().min(1).max(120),
2200
+ street1: z.string().min(1).max(200),
2201
+ street2: z.string().max(200).optional(),
2202
+ city: z.string().min(1).max(120),
2203
+ state: z.string().min(1).max(80),
2204
+ postalCode: z.string().min(1).max(20),
2205
+ country: z.string().length(2),
2206
+ phone: z.string().max(40).optional(),
2207
+ email: z.string().email().optional(),
2208
+ });
2209
+ export type ShippingDeliveryAddress = z.infer<
2210
+ typeof shippingDeliveryAddressSchema
2211
+ >;
2212
+
2213
+ export const shippingRatesInputSchema = z.object({
2214
+ deliveryAddress: shippingDeliveryAddressSchema,
2215
+ });
2216
+ export type ShippingRatesInput = z.infer<typeof shippingRatesInputSchema>;
2217
+
2218
+ export const shippingOptionBreakdownSchema = z.object({
2219
+ originId: z.string().nullable(),
2220
+ shippoShipmentId: z.string(),
2221
+ shippoRateId: z.string(),
2222
+ amountCents: z.number().int().nonnegative(),
2223
+ parcelSummary: z.string(),
2224
+ });
2225
+
2226
+ export const shippingOptionSchema = z.object({
2227
+ id: z.string(),
2228
+ displayName: z.string(),
2229
+ carrierName: z.string(),
2230
+ serviceToken: z.string(),
2231
+ totalCents: z.number().int().nonnegative(),
2232
+ currency: z.string().min(3),
2233
+ estimatedDays: z.number().int().nullable(),
2234
+ durationTerms: z.string().nullable(),
2235
+ breakdown: z.array(shippingOptionBreakdownSchema),
2236
+ });
2237
+ export type ShippingOption = z.infer<typeof shippingOptionSchema>;
2238
+
2239
+ export const shippingRatesResponseSchema = z.object({
2240
+ options: z.array(shippingOptionSchema),
2241
+ requiresShipping: z.boolean(),
2242
+ });
2243
+ export type ShippingRatesResponse = z.infer<typeof shippingRatesResponseSchema>;
2244
+
2245
+ export const shippingTrackResponseSchema = z.object({
2246
+ shipmentId: z.string(),
2247
+ orderId: z.string(),
2248
+ carrier: z.string().nullable(),
2249
+ trackingNumber: z.string().nullable(),
2250
+ trackingUrl: z.string().nullable(),
2251
+ labelUrl: z.string().nullable(),
2252
+ /** Mirrors the `order_shipment_status` pg enum. */
2253
+ status: openEnum(
2254
+ "pending",
2255
+ "label_created",
2256
+ "in_transit",
2257
+ "delivered",
2258
+ "returned",
2259
+ ),
2260
+ estimatedDelivery: z.string().nullable(),
2261
+ shippedAt: z.string().nullable(),
2262
+ deliveredAt: z.string().nullable(),
2263
+ serviceName: z.string().nullable(),
2264
+ rateAmountCents: z.number().int().nullable(),
2265
+ currency: z.string().nullable(),
2266
+ statusHistory: z.unknown(),
2267
+ liveStatus: z
2268
+ .object({
2269
+ status: z.string(),
2270
+ status_details: z.string().optional(),
2271
+ status_date: z.string().optional(),
2272
+ location: z
2273
+ .object({
2274
+ city: z.string().optional(),
2275
+ state: z.string().optional(),
2276
+ zip: z.string().optional(),
2277
+ country: z.string().optional(),
2278
+ })
2279
+ .nullable()
2280
+ .optional(),
2281
+ })
2282
+ .nullable(),
2283
+ });
2284
+ export type ShippingTrackResponse = z.infer<typeof shippingTrackResponseSchema>;
2285
+
2286
+ /**
2287
+ * The org's storefront-facing shipping config. Sanitized: no API keys/secrets,
2288
+ * and only a coarse city/state/country "ships from" (never the precise origin
2289
+ * street).
2290
+ */
2291
+ export const shippingSettingsResponseSchema = z.object({
2292
+ enabled: z.boolean(),
2293
+ shipsFrom: z
2294
+ .object({
2295
+ city: z.string().nullable(),
2296
+ state: z.string().nullable(),
2297
+ country: z.string().nullable(),
2298
+ })
2299
+ .nullable(),
2300
+ /** ISO-3166-1 alpha-2 list. null = ships anywhere; [] = ships nowhere. */
2301
+ allowedDestinationCountries: z.array(z.string()).nullable(),
2302
+ /** DDU = customer pays duties, DDP = seller pays, FCA = free carrier. */
2303
+ defaultIncoterm: openEnum("DDU", "DDP", "FCA"),
2304
+ preferredCarriers: z.array(z.string()).nullable(),
2305
+ });
2306
+ export type ShippingSettingsResponse = z.infer<
2307
+ typeof shippingSettingsResponseSchema
2308
+ >;
2309
+
2310
+ // ── Customer notification preferences (Batch shipping, 0.9.0) ─────────
2311
+ // Distinct from the BIAB-user notification prefs in this file —
2312
+ // these are per-customer-per-org preferences exposed via the SDK so
2313
+ // the *consumer's* site can render a preferences UI for its signed-
2314
+ // in shoppers (in-app / email / SMS / outbound webhook).
2315
+
2316
+ export const customerNotificationEventMapSchema = z.record(
2317
+ z.string(),
2318
+ z.boolean(),
2319
+ );
2320
+
2321
+ export const customerNotificationPreferencesResponseSchema = z.object({
2322
+ exists: z.boolean(),
2323
+ inAppEvents: customerNotificationEventMapSchema,
2324
+ emailEvents: customerNotificationEventMapSchema,
2325
+ smsEvents: customerNotificationEventMapSchema,
2326
+ webhookEvents: customerNotificationEventMapSchema,
2327
+ emailAddress: z.string().nullable(),
2328
+ phoneNumber: z.string().nullable(),
2329
+ webhookUrl: z.string().nullable(),
2330
+ webhookHasSigningSecret: z.boolean(),
2331
+ emailVerified: z.boolean(),
2332
+ phoneVerified: z.boolean(),
2333
+ });
2334
+ export type CustomerNotificationPreferencesResponse = z.infer<
2335
+ typeof customerNotificationPreferencesResponseSchema
2336
+ >;
2337
+
2338
+ export const customerNotificationPreferencesUpdateInputSchema = z.object({
2339
+ inAppEvents: customerNotificationEventMapSchema.optional(),
2340
+ emailEvents: customerNotificationEventMapSchema.optional(),
2341
+ smsEvents: customerNotificationEventMapSchema.optional(),
2342
+ webhookEvents: customerNotificationEventMapSchema.optional(),
2343
+ emailAddress: z.string().email().nullable().optional(),
2344
+ phoneNumber: z.string().min(7).max(40).nullable().optional(),
2345
+ webhookUrl: z.string().url().nullable().optional(),
2346
+ webhookSigningSecret: z.string().min(16).max(128).nullable().optional(),
2347
+ });
2348
+ export type CustomerNotificationPreferencesUpdateInput = z.infer<
2349
+ typeof customerNotificationPreferencesUpdateInputSchema
2350
+ >;
2351
+
2352
+ export const customerNotificationPreferencesUpdateResponseSchema = z.object({
2353
+ ok: z.literal(true),
2354
+ });
2355
+ export type CustomerNotificationPreferencesUpdateResponse = z.infer<
2356
+ typeof customerNotificationPreferencesUpdateResponseSchema
2357
+ >;
2358
+
2359
+ // ── Customer destination verification (Batch shipping, 0.9.0) ─────────
2360
+
2361
+ export const startVerificationInputSchema = z.object({
2362
+ kind: z.enum(["email", "phone"]),
2363
+ destination: z.string().min(3).max(200),
2364
+ });
2365
+ export type StartVerificationInput = z.infer<
2366
+ typeof startVerificationInputSchema
2367
+ >;
2368
+
2369
+ export const startVerificationResponseSchema = z.object({
2370
+ ok: z.literal(true),
2371
+ message: z.string(),
2372
+ flow: z.enum(["link", "otp"]),
2373
+ });
2374
+ export type StartVerificationResponse = z.infer<
2375
+ typeof startVerificationResponseSchema
2376
+ >;
2377
+
2378
+ export const confirmVerificationInputSchema = z.object({
2379
+ token: z.string().min(4).max(128),
2380
+ });
2381
+ export type ConfirmVerificationInput = z.infer<
2382
+ typeof confirmVerificationInputSchema
2383
+ >;
2384
+
2385
+ export const confirmVerificationResponseSchema = z.object({
2386
+ ok: z.literal(true),
2387
+ verifiedKind: z.enum(["email", "phone"]),
2388
+ destination: z.string(),
2389
+ });
2390
+ export type ConfirmVerificationResponse = z.infer<
2391
+ typeof confirmVerificationResponseSchema
2392
+ >;
2393
+
2394
+ // ── Address search + verify (0.9.0) ───────────────────────────────────
2395
+ // Field shape mirrors Resources/addressParser/src/types/address.ts so
2396
+ // the working reference React components copy in cleanly.
2397
+
2398
+ export const addressSearchInputSchema = z.object({
2399
+ query: z.string().min(1).max(200),
2400
+ });
2401
+ export type AddressSearchInput = z.infer<typeof addressSearchInputSchema>;
2402
+
2403
+ export const addressPlacePredictionSchema = z.object({
2404
+ place_id: z.string(),
2405
+ structured_formatting: z.object({
2406
+ main_text: z.string(),
2407
+ secondary_text: z.string(),
2408
+ }),
2409
+ });
2410
+ export type AddressPlacePrediction = z.infer<
2411
+ typeof addressPlacePredictionSchema
2412
+ >;
2413
+
2414
+ export const addressSearchResponseSchema = z.union([
2415
+ z.object({
2416
+ enabled: z.literal(true),
2417
+ predictions: z.array(addressPlacePredictionSchema),
2418
+ }),
2419
+ z.object({
2420
+ enabled: z.literal(false),
2421
+ reason: z.string(),
2422
+ predictions: z.array(addressPlacePredictionSchema),
2423
+ }),
2424
+ ]);
2425
+ export type AddressSearchResponse = z.infer<typeof addressSearchResponseSchema>;
2426
+
2427
+ /**
2428
+ * Structured address as Google returns it. Pipe through
2429
+ * `googleAddressToFormValues` (exported from the SDK helpers) to
2430
+ * fill a form whose fields match the reference `AddressFormValues`
2431
+ * shape.
2432
+ */
2433
+ export const googleAddressSchema = z.object({
2434
+ street_number: z.string(),
2435
+ route: z.string(),
2436
+ subpremise: z.string().optional(),
2437
+ locality: z.string(),
2438
+ administrative_area_level_1: z.string(),
2439
+ postal_code: z.string(),
2440
+ country: z.string(),
2441
+ });
2442
+ export type GoogleAddress = z.infer<typeof googleAddressSchema>;
2443
+
2444
+ export const addressDetailsResponseSchema = z.union([
2445
+ z.object({ enabled: z.literal(true), address: googleAddressSchema }),
2446
+ z.object({
2447
+ enabled: z.literal(false),
2448
+ reason: z.string(),
2449
+ address: z.null(),
2450
+ }),
2451
+ ]);
2452
+ export type AddressDetailsResponse = z.infer<
2453
+ typeof addressDetailsResponseSchema
2454
+ >;
2455
+
2456
+ export const addressFormValuesSchema = z.object({
2457
+ address_ln1: z.string().min(1).max(200),
2458
+ address_ln2: z.string().max(200).optional(),
2459
+ city: z.string().min(1).max(120),
2460
+ state: z.string().min(1).max(80),
2461
+ zip: z.string().min(1).max(20),
2462
+ country: z.string().min(2).max(80),
2463
+ });
2464
+ export type AddressFormValues = z.infer<typeof addressFormValuesSchema>;
2465
+
2466
+ export const addressVerifyResponseSchema = z.object({
2467
+ isVerified: z.boolean(),
2468
+ googleAddress: z.string().nullable(),
2469
+ });
2470
+ export type AddressVerifyResponse = z.infer<typeof addressVerifyResponseSchema>;
2471
+
2472
+ // ── Subscriptions (consumer-facing) ───────────────────────────────────
2473
+
2474
+ export const subscriptionOfferingSchema = z.object({
2475
+ id: z.string().uuid(),
2476
+ name: z.string(),
2477
+ description: z.string().nullable(),
2478
+ imageUrl: z.string().nullable(),
2479
+ amountCents: z.number().int(),
2480
+ interval: z.enum(["day", "week", "month", "year"]),
2481
+ createdAt: z.string(),
2482
+ updatedAt: z.string(),
2483
+ });
2484
+ export type SubscriptionOffering = z.infer<typeof subscriptionOfferingSchema>;
2485
+
2486
+ export const subscriptionOfferingsListResponseSchema = z.object({
2487
+ items: z.array(subscriptionOfferingSchema),
2488
+ });
2489
+ export type SubscriptionOfferingsListResponse = z.infer<
2490
+ typeof subscriptionOfferingsListResponseSchema
2491
+ >;
2492
+
2493
+ export const subscriptionCheckoutInputSchema = z
2494
+ .object({
2495
+ customerEmail: z.string().email().optional(),
2496
+ successUrl: z.string().url().optional(),
2497
+ cancelUrl: z.string().url().optional(),
2498
+ })
2499
+ .optional();
2500
+ export type SubscriptionCheckoutInput = z.infer<
2501
+ typeof subscriptionCheckoutInputSchema
2502
+ >;
2503
+
2504
+ export const subscriptionCheckoutResponseSchema = z.object({
2505
+ sessionId: z.string(),
2506
+ stripeUrl: z.string().url(),
2507
+ amountCents: z.number().int(),
2508
+ currency: z.string(),
2509
+ /** Mirrors the `subscription_interval` pg enum. */
2510
+ interval: openEnum("day", "week", "month", "year"),
2511
+ });
2512
+ export type SubscriptionCheckoutResponse = z.infer<
2513
+ typeof subscriptionCheckoutResponseSchema
2514
+ >;
2515
+
2516
+ // ── Coupons (Batch LL, 0.9.0) ─────────────────────────────────────────
2517
+
2518
+ export const couponValidateInputSchema = z.object({
2519
+ code: z.string().min(1).max(80),
2520
+ });
2521
+ export type CouponValidateInput = z.infer<typeof couponValidateInputSchema>;
2522
+
2523
+ export const couponValidateValidSchema = z.object({
2524
+ valid: z.literal(true),
2525
+ code: z.string(),
2526
+ discountType: z.enum(["percent", "amount"]),
2527
+ percentOff: z.number().nullable(),
2528
+ amountOffCents: z.number().int().nullable(),
2529
+ currency: z.string().nullable(),
2530
+ duration: z.enum(["once", "forever", "repeating"]),
2531
+ durationInMonths: z.number().int().nullable(),
2532
+ expiresAt: z.string().nullable(),
2533
+ });
2534
+ export type CouponValidateValid = z.infer<typeof couponValidateValidSchema>;
2535
+
2536
+ export const couponValidateInvalidSchema = z.object({
2537
+ valid: z.literal(false),
2538
+ reason: z.enum([
2539
+ "unknown_code",
2540
+ "expired",
2541
+ "inactive",
2542
+ "no_stripe_account",
2543
+ "lookup_failed",
2544
+ ]),
2545
+ message: z.string(),
2546
+ });
2547
+ export type CouponValidateInvalid = z.infer<typeof couponValidateInvalidSchema>;
2548
+
2549
+ export const couponValidateResponseSchema = z.union([
2550
+ couponValidateValidSchema,
2551
+ couponValidateInvalidSchema,
2552
+ ]);
2553
+ export type CouponValidateResponse = z.infer<
2554
+ typeof couponValidateResponseSchema
2555
+ >;
2556
+
2557
+ // ── Tenant auth (per-org hosted sign-in / sign-up / sign-out) ─────────
2558
+
2559
+ export const tenantAuthIntentSchema = z.enum(["sign-in", "sign-up"]);
2560
+
2561
+ export const tenantAuthStartInputSchema = z.object({
2562
+ intent: tenantAuthIntentSchema,
2563
+ /** Where the tenant's site should send the user after auth completes. */
2564
+ returnTo: z.string().url().optional(),
2565
+ /** Pre-fills the email field on the platform-hosted page. */
2566
+ loginHint: z.string().email().optional(),
2567
+ /**
2568
+ * The tenant's installed callback URL — must match what was registered with
2569
+ * the platform. Required because the tenant lives on its own domain.
2570
+ */
2571
+ redirectUri: z.string().url(),
2572
+ });
2573
+
2574
+ export const tenantAuthStartResponseSchema = z.object({
2575
+ url: z.string().url(),
2576
+ });
2577
+
2578
+ export const tenantAuthExchangeInputSchema = z.object({
2579
+ code: z.string().min(1),
2580
+ /** Sealed OAuth `state` returned on the callback URL — contains the PKCE verifier for the hosted auth flow. */
2581
+ state: z.string().min(1),
2582
+ /** The same redirectUri used when starting; the platform validates this matches. */
2583
+ redirectUri: z.string().url(),
2584
+ });
2585
+
2586
+ export const tenantAuthUserSchema = z.object({
2587
+ id: z.string(),
2588
+ email: z.string().email().nullable(),
2589
+ firstName: z.string().nullable(),
2590
+ lastName: z.string().nullable(),
2591
+ });
2592
+
2593
+ export const tenantAuthSessionSchema = z.object({
2594
+ user: tenantAuthUserSchema,
2595
+ organizationId: z.string(),
2596
+ role: z.string().nullable(),
2597
+ /** Opaque token the tenant cookie should store; pass back as Bearer for `/me`. */
2598
+ sessionToken: z.string(),
2599
+ expiresAt: timestampSchema,
2600
+ });
2601
+
2602
+ export const tenantAuthExchangeResponseSchema = tenantAuthSessionSchema;
2603
+
2604
+ export const tenantAuthMeResponseSchema = z
2605
+ .object({
2606
+ user: tenantAuthUserSchema,
2607
+ organizationId: z.string(),
2608
+ role: z.string().nullable(),
2609
+ })
2610
+ .nullable();
2611
+
2612
+ export const tenantAuthSignOutResponseSchema = z.object({
2613
+ ok: z.literal(true),
2614
+ });
2615
+
2616
+ /**
2617
+ * Programmatic password-reset request. Always succeeds from the caller's
2618
+ * perspective — the server returns `ok: true` regardless of whether an
2619
+ * account matched, to avoid leaking which emails are registered. The
2620
+ * actual reset link is mailed out-of-band when a match exists.
2621
+ */
2622
+ export const tenantAuthPasswordResetInputSchema = z.object({
2623
+ email: z.string().email().max(320),
2624
+ /**
2625
+ * Optional override for the redirect after the user sets a new password.
2626
+ * Falls back to the org's default sign-in landing page server-side.
2627
+ */
2628
+ returnTo: z.string().url().max(2048).optional(),
2629
+ });
2630
+
2631
+ export const tenantAuthPasswordResetResponseSchema = z.object({
2632
+ ok: z.literal(true),
2633
+ });
2634
+
2635
+ export type TenantAuthIntent = z.infer<typeof tenantAuthIntentSchema>;
2636
+ export type TenantAuthStartInput = z.infer<typeof tenantAuthStartInputSchema>;
2637
+ export type TenantAuthStartResponse = z.infer<
2638
+ typeof tenantAuthStartResponseSchema
2639
+ >;
2640
+ export type TenantAuthExchangeInput = z.infer<
2641
+ typeof tenantAuthExchangeInputSchema
2642
+ >;
2643
+ export type TenantAuthExchangeResponse = z.infer<
2644
+ typeof tenantAuthExchangeResponseSchema
2645
+ >;
2646
+ export type TenantAuthSession = z.infer<typeof tenantAuthSessionSchema>;
2647
+ export type TenantAuthUser = z.infer<typeof tenantAuthUserSchema>;
2648
+ export type TenantAuthMeResponse = z.infer<typeof tenantAuthMeResponseSchema>;
2649
+ export type TenantAuthSignOutResponse = z.infer<
2650
+ typeof tenantAuthSignOutResponseSchema
2651
+ >;
2652
+ export type TenantAuthPasswordResetInput = z.infer<
2653
+ typeof tenantAuthPasswordResetInputSchema
2654
+ >;
2655
+ export type TenantAuthPasswordResetResponse = z.infer<
2656
+ typeof tenantAuthPasswordResetResponseSchema
2657
+ >;
2658
+
2659
+ // ── Schema-driven marketing flow (Phase 2 — V7 dashboard) ────────────────
2660
+ //
2661
+ // The bundle endpoint is the live-site read path. Each section is tagged
2662
+ // `{ ok: true, data, source } | { ok: false, error }` so one malformed
2663
+ // row degrades to empty in the consumer's renderer instead of crashing
2664
+ // the page. `source` records whether the value came from the requested
2665
+ // locale, fell back to EN, or was deep-merged from both.
2666
+
2667
+ export const siteMarketingSectionEntrySchema = z.discriminatedUnion("ok", [
2668
+ z.object({
2669
+ ok: z.literal(true),
2670
+ sectionKey: z.string(),
2671
+ data: z.unknown(),
2672
+ source: z.enum(["locale", "en-fallback", "merged"]),
2673
+ updatedAt: timestampSchema,
2674
+ }),
2675
+ z.object({
2676
+ ok: z.literal(false),
2677
+ sectionKey: z.string(),
2678
+ error: z.string(),
2679
+ }),
2680
+ ]);
2681
+
2682
+ export const siteMarketingSeoDataSchema = z.object({
2683
+ seoTitle: z.string().nullable(),
2684
+ seoDescription: z.string().nullable(),
2685
+ canonicalUrl: z.string().nullable(),
2686
+ noIndex: z.boolean(),
2687
+ ogTitle: z.string().nullable(),
2688
+ ogDescription: z.string().nullable(),
2689
+ ogImageUrl: z.string().nullable(),
2690
+ twitterCard: z.enum(["summary", "summary_large_image", "app", "player"]),
2691
+ keywords: z.array(z.string()),
2692
+ jsonldNodes: z.array(z.unknown()),
2693
+ hreflang: z.record(z.string()),
2694
+ });
2695
+
2696
+ // ──────────────────────────────────────────────────────────────────────
2697
+ // Bundle: brand / company / gallery / reviews surfaces
2698
+ //
2699
+ // Added in SDK 0.8.4. Prior versions zod-stripped these keys at the
2700
+ // response boundary even though the BIAB server already shipped them
2701
+ // (the underlying `.passthrough()` in this file is on a chatbot
2702
+ // schema, NOT on the bundle response). Consumers that need the
2703
+ // stripped data on older SDK versions can raw-fetch
2704
+ // /sites/{id}/marketing/bundle directly — same auth, same caching —
2705
+ // until they bump.
2706
+ //
2707
+ // The new keys are all optional + null-able to match the
2708
+ // server's opt-in posture: company.profile is null when the org
2709
+ // hasn't filled out /settings/company-profile; gallery is null when
2710
+ // no public-flagged media exists; reviews is null when the org
2711
+ // hasn't set their Google Place ID. Consumers branch on null and
2712
+ // either render an empty section or hide it.
2713
+
2714
+ const brandTokensSchema = z.object({
2715
+ logoUrl: z.string().nullable(),
2716
+ faviconUrl: z.string().nullable(),
2717
+ });
2718
+
2719
+ export const companyAddressSchema = z
2720
+ .object({
2721
+ addressLine1: z.string().nullable().optional(),
2722
+ addressLine2: z.string().nullable().optional(),
2723
+ city: z.string().nullable().optional(),
2724
+ state: z.string().nullable().optional(),
2725
+ zip: z.string().nullable().optional(),
2726
+ country: z.string().nullable().optional(),
2727
+ })
2728
+ .nullable();
2729
+
2730
+ export const companyLocationSchema = z.object({
2731
+ id: z.string(),
2732
+ name: z.string(),
2733
+ slug: z.string(),
2734
+ isPrimary: z.boolean(),
2735
+ isPublic: z.boolean(),
2736
+ type: z
2737
+ .enum([
2738
+ "office",
2739
+ "store",
2740
+ "showroom",
2741
+ "warehouse",
2742
+ "service_center",
2743
+ "other",
2744
+ ])
2745
+ .optional(),
2746
+ businessType: z.string().nullable().optional(),
2747
+ address: companyAddressSchema.unwrap(),
2748
+ phone: z.string().nullable().optional(),
2749
+ webPhone: z.string().nullable().optional(),
2750
+ supportEmail: z.string().nullable().optional(),
2751
+ website: z.string().nullable().optional(),
2752
+ googlePlaceId: z.string().nullable().optional(),
2753
+ latitude: z.number().nullable().optional(),
2754
+ longitude: z.number().nullable().optional(),
2755
+ timezone: z.string().nullable().optional(),
2756
+ operationHours: z.record(z.string(), z.unknown()).nullable().optional(),
2757
+ serviceAreas: z.record(z.string(), z.unknown()).nullable().optional(),
2758
+ });
2759
+
2760
+ /**
2761
+ * Loose-typed CompanyProfileRow. The server-side shape is fully
2762
+ * typed; we keep this looser at the SDK boundary so adding a column
2763
+ * on the BIAB side doesn't break older SDK consumers.
2764
+ */
2765
+ const companyProfileRowSchema = z
2766
+ .object({
2767
+ id: z.string(),
2768
+ orgId: z.string(),
2769
+ website: z.string().nullable(),
2770
+ phone: z.string().nullable(),
2771
+ webPhone: z.string().nullable(),
2772
+ supportEmail: z.string().nullable(),
2773
+ aboutShort: z.string().nullable(),
2774
+ aboutLongHtml: z.string().nullable(),
2775
+ shareImageUrl: z.string().nullable(),
2776
+ googlePlaceId: z.string().nullable().optional(),
2777
+ address: companyAddressSchema,
2778
+ locations: z.array(companyLocationSchema).nullable().optional(),
2779
+ operationHours: z.record(z.string(), z.unknown()).nullable(),
2780
+ serviceAreas: z.record(z.string(), z.unknown()).nullable(),
2781
+ paymentOptions: z.record(z.string(), z.unknown()).nullable(),
2782
+ warranties: z.record(z.string(), z.unknown()).nullable(),
2783
+ socials: z.record(z.string(), z.unknown()).nullable(),
2784
+ })
2785
+ .passthrough()
2786
+ .nullable();
2787
+
2788
+ const bundleCompanySchema = z.object({
2789
+ profile: companyProfileRowSchema,
2790
+ jsonLd: z.record(z.string(), z.unknown()),
2791
+ locationJsonLd: z.array(z.record(z.string(), z.unknown())).optional(),
2792
+ shareMetadata: z.object({
2793
+ title: z.string(),
2794
+ description: z.string().nullable(),
2795
+ imageUrl: z.string().nullable(),
2796
+ siteName: z.string(),
2797
+ }),
2798
+ });
2799
+
2800
+ const bundleGalleryItemSchema = z.object({
2801
+ id: z.string(),
2802
+ type: z.enum(["image", "video"]),
2803
+ src: z.string(),
2804
+ width: z.number().nullable(),
2805
+ height: z.number().nullable(),
2806
+ blurDataURL: z.string().nullable(),
2807
+ alt: z.string().nullable(),
2808
+ title: z.string().nullable(),
2809
+ category: z.string().nullable(),
2810
+ jobId: z.string().nullable(),
2811
+ jobName: z.string().nullable(),
2812
+ takenAt: z.string().nullable(),
2813
+ location: z.string().nullable(),
2814
+ system: z.string().nullable(),
2815
+ year: z.number().nullable(),
2816
+ aboutHtml: z.string().nullable(),
2817
+ });
2818
+
2819
+ const bundleGallerySchema = z
2820
+ .object({
2821
+ items: z.array(bundleGalleryItemSchema),
2822
+ totalCount: z.number(),
2823
+ })
2824
+ .nullable();
2825
+
2826
+ const bundleReviewSchema = z.object({
2827
+ reviewee: z.string(),
2828
+ rating: z.number(),
2829
+ image: z.string().nullable(),
2830
+ description: z.string(),
2831
+ date: z.string(),
2832
+ // Reviewer-attached gallery photos (URLs). Optional for back-compat with
2833
+ // bundles served by pre-photo servers; empty/absent when the review has none.
2834
+ photos: z.array(z.string()).optional(),
2835
+ // Widened from the 0.9.x `"Google"` literal: reviews now come from the
2836
+ // org's unified review store (Google + Yelp + manual + CSV), so the
2837
+ // platform can be any of those. Consumers should render this as a label.
2838
+ platform: z.string(),
2839
+ });
2840
+
2841
+ /** Per-rating histogram bucket (e.g. `{ rating: 5, count: 182 }`). */
2842
+ const bundleReviewCountSchema = z.object({
2843
+ rating: z.number(),
2844
+ count: z.number(),
2845
+ });
2846
+
2847
+ const bundleReviewsSchema = z
2848
+ .object({
2849
+ enabled: z.literal(true),
2850
+ // `org_external_reviews` is the canonical store; `google_places` is the
2851
+ // legacy Place-ID cache, kept as a fallback. Widened from the 0.9.x
2852
+ // `"google_places"` literal.
2853
+ source: z.enum(["org_external_reviews", "google_places"]),
2854
+ // Null when the payload didn't originate from a Place-ID source.
2855
+ placeId: z.string().nullable(),
2856
+ // Average + count are computed over ALL of the org's reviews, not just
2857
+ // the `items` page below.
2858
+ rating: z.number().nullable(),
2859
+ totalCount: z.number().nullable(),
2860
+ // Per-rating distribution over ALL reviews (newest 5→1), so the consumer
2861
+ // renders the histogram without holding every review client-side.
2862
+ // Optional for back-compat with bundles served by pre-0.9.4 servers.
2863
+ counts: z.array(bundleReviewCountSchema).optional(),
2864
+ // Only the first `reviewsLimit` reviews (newest-first). The rest are
2865
+ // fetched lazily via `client.reviews.list({ offset })`.
2866
+ items: z.array(bundleReviewSchema),
2867
+ // items.length — paired with `totalCount` so the consumer knows whether
2868
+ // more pages exist. Optional for back-compat.
2869
+ returnedCount: z.number().optional(),
2870
+ cachedAt: z.string(),
2871
+ })
2872
+ .nullable();
2873
+
2874
+ // ── Reviews wall: paginated public reviews ────────────────────────────
2875
+ // Backs `client.reviews.list({ limit, offset, source })` → the key-scoped
2876
+ // GET /reviews route. The marketing bundle ships the first page + the
2877
+ // aggregate; this endpoint fetches deeper pages on demand (View All / scroll).
2878
+
2879
+ export const reviewWallItemSchema = z.object({
2880
+ id: z.string(),
2881
+ source: z.enum(["google", "yelp", "housecall_pro", "other"]),
2882
+ rating: z.number(),
2883
+ text: z.string(),
2884
+ reviewUrl: z.string().nullable(),
2885
+ reviewerName: z.string(),
2886
+ reviewerImageUrl: z.string().nullable(),
2887
+ /** Reviewer-attached gallery photos (URLs); optional for back-compat,
2888
+ * empty/absent when the review has no media. */
2889
+ photos: z.array(z.string()).optional(),
2890
+ /** ISO 8601 timestamp of the original review. */
2891
+ timeCreated: z.string(),
2892
+ verified: z.boolean(),
2893
+ });
2894
+
2895
+ export const reviewWallListResponseSchema = z.object({
2896
+ items: z.array(reviewWallItemSchema),
2897
+ /** Total reviews matching the (optional) source filter — for "N reviews". */
2898
+ totalCount: z.number(),
2899
+ /** Offset to pass back for the next page, or null when the list is exhausted. */
2900
+ nextOffset: z.number().nullable(),
2901
+ });
2902
+
2903
+ export type ReviewWallItem = z.infer<typeof reviewWallItemSchema>;
2904
+ export type ReviewWallListResponse = z.infer<
2905
+ typeof reviewWallListResponseSchema
2906
+ >;
2907
+
2908
+ /**
2909
+ * Org-owned marketing-page banner (announcement strip / marquee).
2910
+ * Mirrors the platform-banner shape so consumer storefronts can
2911
+ * render a near-identical UI to BIAB's own marketing banner.
2912
+ * `null` when the org hasn't enabled one or has no visible messages.
2913
+ */
2914
+ const bundleBannerMessageSchema = z
2915
+ .object({
2916
+ id: z.string(),
2917
+ enabled: z.boolean(),
2918
+ text: z.string(),
2919
+ marqueeLines: z.array(z.string()).optional(),
2920
+ linkUrl: z.string(),
2921
+ buttonText: z.string(),
2922
+ openInNewTab: z.boolean(),
2923
+ linkStyle: z.enum(["link", "button"]).optional(),
2924
+ urgent: z.boolean().optional(),
2925
+ appearance: z.enum(["always", "once", "until_dismiss", "every"]),
2926
+ everyIntervalHours: z.number().optional(),
2927
+ scheduleActive: z.boolean().optional(),
2928
+ displayFromUtc: z.string().nullable().optional(),
2929
+ displayUntilUtc: z.string().nullable().optional(),
2930
+ })
2931
+ .passthrough();
2932
+
2933
+ const bundleBannerSchema = z
2934
+ .object({
2935
+ enabled: z.boolean(),
2936
+ hiddenOnHome: z.boolean(),
2937
+ messages: z.array(bundleBannerMessageSchema),
2938
+ })
2939
+ .passthrough()
2940
+ .nullable();
2941
+
2942
+ export type BundleBannerMessage = z.infer<typeof bundleBannerMessageSchema>;
2943
+ export type BundleBanner = z.infer<typeof bundleBannerSchema>;
2944
+
2945
+ /**
2946
+ * Org Updates feed — cached Google Business Profile "Posts" (the Updates tab):
2947
+ * a blurb + image(s) + deep link, optionally with an offer/event window. Lets a
2948
+ * consumer storefront render a "News"/"Updates" feed from the bundle without a
2949
+ * second API call. `null` when the org has none.
2950
+ */
2951
+ const bundleUpdateItemSchema = z
2952
+ .object({
2953
+ id: z.string(),
2954
+ kind: z.string().nullable(),
2955
+ title: z.string().nullable(),
2956
+ body: z.string(),
2957
+ link: z.string().nullable(),
2958
+ imageUrl: z.string().nullable(),
2959
+ images: z.array(z.string()),
2960
+ postedAt: z.string().nullable(),
2961
+ startsAt: z.string().nullable(),
2962
+ endsAt: z.string().nullable(),
2963
+ })
2964
+ .passthrough();
2965
+
2966
+ const bundleUpdatesSchema = z
2967
+ .object({
2968
+ items: z.array(bundleUpdateItemSchema),
2969
+ })
2970
+ .passthrough()
2971
+ .nullable();
2972
+
2973
+ export type BundleUpdateItem = z.infer<typeof bundleUpdateItemSchema>;
2974
+ export type BundleUpdates = z.infer<typeof bundleUpdatesSchema>;
2975
+
2976
+ export const getMarketingPageBundleResponseSchema = z
2977
+ .object({
2978
+ pageKey: z.string(),
2979
+ locale: z.string(),
2980
+ sections: z.record(z.string(), siteMarketingSectionEntrySchema),
2981
+ availableLocales: z.array(z.string()),
2982
+ seo: siteMarketingSeoDataSchema,
2983
+ // New in 0.8.4:
2984
+ brand: brandTokensSchema.optional(),
2985
+ company: bundleCompanySchema.optional(),
2986
+ gallery: bundleGallerySchema.optional(),
2987
+ reviews: bundleReviewsSchema.optional(),
2988
+ // New in 0.8.9:
2989
+ banner: bundleBannerSchema.optional(),
2990
+ // New in 0.9.6:
2991
+ updates: bundleUpdatesSchema.optional(),
2992
+ })
2993
+ // `.passthrough()` at the top level so any future BIAB-side
2994
+ // addition (e.g. bundle.events, bundle.faq, etc.) flows through
2995
+ // to consumers without an SDK release — the lesson from the
2996
+ // brand/company/gallery/reviews oversight on this version.
2997
+ .passthrough();
2998
+
2999
+ export const marketingSchemaSlotRowSchema = z.object({
3000
+ slot: z.enum(["draft", "published"]),
3001
+ schema: z.record(z.string(), z.unknown()),
3002
+ checksum: z.string().length(64),
3003
+ version: z.number().int().positive(),
3004
+ updatedAt: timestampSchema,
3005
+ });
3006
+
3007
+ export const getPublishedMarketingSchemaResponseSchema = z.object({
3008
+ schema: marketingSchemaSlotRowSchema.nullable(),
3009
+ });
3010
+
3011
+ export const upsertDraftMarketingSchemaInputSchema = z.object({
3012
+ schema: z.record(z.string(), z.unknown()),
3013
+ checksum: z.string().length(64),
3014
+ note: z.string().max(280).nullable().optional(),
3015
+ });
3016
+
3017
+ export const upsertDraftMarketingSchemaResponseSchema = z.object({
3018
+ ok: z.literal(true),
3019
+ version: z.number().int().positive(),
3020
+ checksum: z.string().length(64),
3021
+ noop: z.boolean(),
3022
+ });
3023
+
3024
+ export const getMarketingLocalesResponseSchema = z.object({
3025
+ locales: z.array(z.string()),
3026
+ brandTokens: z.record(z.string()),
3027
+ });
3028
+
3029
+ /**
3030
+ * Bulk section upsert — sent by `biab-dev sync-content`.
3031
+ *
3032
+ * One request carries up to 256 items (server-enforced). The CLI batches
3033
+ * larger imports automatically. Each item is a single
3034
+ * `(pageKey, locale, sectionKey)` value and goes through the same
3035
+ * validation pipeline as a dashboard save, so a partial failure on one
3036
+ * row reports `ok: false, error: "..."` without rolling back the rest.
3037
+ */
3038
+ export const publishMarketingSectionsItemSchema = z.object({
3039
+ pageKey: z.string().min(1).max(120),
3040
+ locale: z.string().min(1).max(20),
3041
+ sectionKey: z.string().min(1).max(120),
3042
+ payload: z.unknown(),
3043
+ });
3044
+
3045
+ export const publishMarketingSectionsInputSchema = z.object({
3046
+ items: z.array(publishMarketingSectionsItemSchema).min(1).max(256),
3047
+ validate: z.boolean().default(true).optional(),
3048
+ note: z.string().max(280).nullable().optional(),
3049
+ });
3050
+
3051
+ export const publishMarketingSectionsResultItemSchema = z.discriminatedUnion(
3052
+ "ok",
3053
+ [
3054
+ z.object({
3055
+ ok: z.literal(true),
3056
+ pageKey: z.string(),
3057
+ locale: z.string(),
3058
+ sectionKey: z.string(),
3059
+ version: z.number().int().positive(),
3060
+ }),
3061
+ z.object({
3062
+ ok: z.literal(false),
3063
+ pageKey: z.string(),
3064
+ locale: z.string(),
3065
+ sectionKey: z.string(),
3066
+ error: z.string(),
3067
+ }),
3068
+ ],
3069
+ );
3070
+
3071
+ export const publishMarketingSectionsResponseSchema = z.object({
3072
+ results: z.array(publishMarketingSectionsResultItemSchema),
3073
+ });
3074
+
3075
+ export type SiteMarketingSectionEntry = z.infer<
3076
+ typeof siteMarketingSectionEntrySchema
3077
+ >;
3078
+ export type SiteMarketingSeoData = z.infer<typeof siteMarketingSeoDataSchema>;
3079
+ export type BusinessAddress = NonNullable<z.infer<typeof companyAddressSchema>>;
3080
+ export type BusinessLocation = z.infer<typeof companyLocationSchema>;
3081
+ export type GetMarketingPageBundleResponse = z.infer<
3082
+ typeof getMarketingPageBundleResponseSchema
3083
+ >;
3084
+ export type MarketingSchemaSlotRow = z.infer<
3085
+ typeof marketingSchemaSlotRowSchema
3086
+ >;
3087
+ export type GetPublishedMarketingSchemaResponse = z.infer<
3088
+ typeof getPublishedMarketingSchemaResponseSchema
3089
+ >;
3090
+ export type UpsertDraftMarketingSchemaInput = z.infer<
3091
+ typeof upsertDraftMarketingSchemaInputSchema
3092
+ >;
3093
+ export type UpsertDraftMarketingSchemaResponse = z.infer<
3094
+ typeof upsertDraftMarketingSchemaResponseSchema
3095
+ >;
3096
+ export type GetMarketingLocalesResponse = z.infer<
3097
+ typeof getMarketingLocalesResponseSchema
3098
+ >;
3099
+ export type PublishMarketingSectionsItem = z.infer<
3100
+ typeof publishMarketingSectionsItemSchema
3101
+ >;
3102
+ export type PublishMarketingSectionsInput = z.infer<
3103
+ typeof publishMarketingSectionsInputSchema
3104
+ >;
3105
+ export type PublishMarketingSectionsResultItem = z.infer<
3106
+ typeof publishMarketingSectionsResultItemSchema
3107
+ >;
3108
+ export type PublishMarketingSectionsResponse = z.infer<
3109
+ typeof publishMarketingSectionsResponseSchema
3110
+ >;
3111
+
3112
+ // ── Data model: reading records ──────────────────────────────────────
3113
+ //
3114
+ // The read half of the custom data model. Until these existed the model was
3115
+ // write-only from outside: `sync-data-model` pushed a schema and `sync-records`
3116
+ // seeded rows, and nothing could read either back.
3117
+ //
3118
+ // Note this is NOT the `collections:*` family above. Those read `site_data_rows`
3119
+ // (Site Data / the site builder's tables). Custom-object rows live in
3120
+ // `org_object_records` and are reached only through here.
3121
+
3122
+ /** One edge out of a record. Relations are links, not embedded rows. */
3123
+ export const recordRelationLinkSchema = z.object({
3124
+ /** The linked row's `id`. Read it with a follow-up `listRecords` call. */
3125
+ recordId: z.string().uuid(),
3126
+ /** `universalIdentifier` of the object the linked row belongs to. */
3127
+ object: z.string(),
3128
+ });
3129
+ export type RecordRelationLink = z.infer<typeof recordRelationLinkSchema>;
3130
+
3131
+ export const dataModelRecordSchema = z.object({
3132
+ id: z.string().uuid(),
3133
+ /**
3134
+ * The identity a seed file gave this row, or null when a human created it
3135
+ * in the dashboard. Null means "not reproducible from your config".
3136
+ */
3137
+ universalIdentifier: z.string().uuid().nullable(),
3138
+ /** Scalar values, keyed by field name. */
3139
+ fields: z.record(z.string(), z.unknown()),
3140
+ /**
3141
+ * Keyed by field name. EVERY declared relation field is present — `[]` when
3142
+ * it has no links — so an empty relation is never mistaken for a missing one.
3143
+ */
3144
+ relations: z.record(z.string(), z.array(recordRelationLinkSchema)),
3145
+ createdAt: timestampSchema,
3146
+ updatedAt: timestampSchema,
3147
+ });
3148
+ export type DataModelRecord = z.infer<typeof dataModelRecordSchema>;
3149
+
3150
+ export const listDataModelRecordsInputSchema = z.object({
3151
+ /**
3152
+ * The object's `universalIdentifier` — the same value your config declares,
3153
+ * and the same one `sync-records` writes against. Not its name, which can
3154
+ * change without the identity changing.
3155
+ */
3156
+ object: z.string().min(1),
3157
+ limit: z.number().int().min(1).max(200).optional(),
3158
+ /** Pass back the previous page's `nextCursor`. Opaque; don't construct one. */
3159
+ cursor: z.string().optional(),
3160
+ });
3161
+ export type ListDataModelRecordsInput = z.infer<
3162
+ typeof listDataModelRecordsInputSchema
3163
+ >;
3164
+
3165
+ export const listDataModelRecordsAvailableSchema = z.object({
3166
+ available: z.literal(true),
3167
+ records: z.array(dataModelRecordSchema),
3168
+ /** Null on the last page. */
3169
+ nextCursor: z.string().nullable(),
3170
+ });
3171
+ export type ListDataModelRecordsAvailable = z.infer<
3172
+ typeof listDataModelRecordsAvailableSchema
3173
+ >;
3174
+
3175
+ /**
3176
+ * Discriminated union — branch on `.available`. Custom objects are an à-la-carte
3177
+ * surface (`crm.custom_objects`), so an org without it gets the upgrade shape
3178
+ * rather than an exception.
3179
+ */
3180
+ export const listDataModelRecordsResponseSchema = z.union([
3181
+ listDataModelRecordsAvailableSchema,
3182
+ planGatedUnavailableSchema,
3183
+ ]);
3184
+ export type ListDataModelRecordsResponse = z.infer<
3185
+ typeof listDataModelRecordsResponseSchema
3186
+ >;
3187
+
3188
+ // ── Analytics: reading visitor counts ────────────────────────────────
3189
+ //
3190
+ // The read half of site analytics. The `<BIABAnalytics/>` tracker records
3191
+ // pageviews (write-only); these read the totals back so a site can show them.
3192
+
3193
+ export const viewCountSchema = z.object({
3194
+ /** Pageviews (`kind = 'pageview'` events). */
3195
+ views: z.number().int().nonnegative(),
3196
+ /**
3197
+ * Distinct anonId — an approximation of unique visitors. anonId rotates every
3198
+ * UTC day, so this is exact within a day and, over a longer window, counts a
3199
+ * returning visitor once per day they came (visitor-days). Never identifies
3200
+ * anyone; never undercounts.
3201
+ */
3202
+ visitors: z.number().int().nonnegative(),
3203
+ });
3204
+ export type ViewCount = z.infer<typeof viewCountSchema>;
3205
+
3206
+ export const pageViewsInputSchema = z.object({
3207
+ /**
3208
+ * Exact paths to count. Omit (or pass `[]`) for a SITE-WIDE total. Each path
3209
+ * comes back in `paths`, zero-filled if it has no views yet.
3210
+ */
3211
+ paths: z.array(z.string().min(1)).max(50).optional(),
3212
+ /** Window in days. Omit for all-time. */
3213
+ days: z.number().int().min(1).optional(),
3214
+ });
3215
+ export type PageViewsInput = z.infer<typeof pageViewsInputSchema>;
3216
+
3217
+ export const pageViewsAvailableSchema = z.object({
3218
+ available: z.literal(true),
3219
+ /** ISO start of the window, or null for all-time. */
3220
+ since: z.string().nullable(),
3221
+ /** Over the whole scope: site-wide when no paths, else summed over them. */
3222
+ total: viewCountSchema,
3223
+ /** One entry per requested path (empty for a site-wide query). */
3224
+ paths: z.array(viewCountSchema.extend({ path: z.string() })),
3225
+ });
3226
+ export type PageViewsAvailable = z.infer<typeof pageViewsAvailableSchema>;
3227
+
3228
+ /**
3229
+ * Discriminated union — branch on `.available`. Visitor analytics is an
3230
+ * entitlement (`analytics.web`), so an org without it gets the upgrade shape
3231
+ * rather than an exception.
3232
+ */
3233
+ export const pageViewsResponseSchema = z.union([
3234
+ pageViewsAvailableSchema,
3235
+ planGatedUnavailableSchema,
3236
+ ]);
3237
+ export type PageViewsResponse = z.infer<typeof pageViewsResponseSchema>;
3238
+
3239
+ // ── AI-crawler visibility (AEO) ──────────────────────────────────────
3240
+
3241
+ export const aiVisibilityInputSchema = z.object({
3242
+ /** Window in days (default 30 server-side). */
3243
+ days: z.number().int().min(1).optional(),
3244
+ });
3245
+ export type AiVisibilityInput = z.infer<typeof aiVisibilityInputSchema>;
3246
+
3247
+ export const aiCrawlerVisibilitySchema = z.object({
3248
+ /** Stable crawler name, e.g. "GPTBot". */
3249
+ name: z.string(),
3250
+ /** Operator, e.g. "OpenAI". Null when the platform can't say. */
3251
+ vendor: z.string().nullable(),
3252
+ /**
3253
+ * What the fetch means: "training" (corpus building), "search" (answer-
3254
+ * engine indexing — citation supply), or "user" (a live assistant reading
3255
+ * on a person's behalf). Null when unknown.
3256
+ */
3257
+ intent: z.enum(["training", "search", "user"]).nullable(),
3258
+ /** Fetches in the window. */
3259
+ fetches: z.number().int().nonnegative(),
3260
+ /** Distinct paths this crawler read. */
3261
+ distinctPages: z.number().int().nonnegative(),
3262
+ /** ISO timestamp of the crawler's most recent fetch. */
3263
+ lastSeenAt: z.string(),
3264
+ });
3265
+ export type AiCrawlerVisibility = z.infer<typeof aiCrawlerVisibilitySchema>;
3266
+
3267
+ export const aiVisibilityAvailableSchema = z.object({
3268
+ available: z.literal(true),
3269
+ /** ISO start of the window. */
3270
+ since: z.string(),
3271
+ /** Ordered most-fetches-first. Empty = no AI crawler came. */
3272
+ crawlers: z.array(aiCrawlerVisibilitySchema),
3273
+ });
3274
+ export type AiVisibilityAvailable = z.infer<typeof aiVisibilityAvailableSchema>;
3275
+
3276
+ /** Branch on `.available` — same entitlement gating as pageViews. */
3277
+ export const aiVisibilityResponseSchema = z.union([
3278
+ aiVisibilityAvailableSchema,
3279
+ planGatedUnavailableSchema,
3280
+ ]);
3281
+ export type AiVisibilityResponse = z.infer<typeof aiVisibilityResponseSchema>;
3282
+
3283
+ // ── Attribution footer ───────────────────────────────────────────────
3284
+ //
3285
+ // The "Powered by BusinessDash" badge's one narrow function. READ its policy;
3286
+ // WRITE an (untrusted) heartbeat. See the plan in the app repo.
3287
+
3288
+ export const attributionPolicySchema = z.object({
3289
+ /** The site the policy is for — build the badge's `?ref=<siteId>` from this. */
3290
+ siteId: z.string().uuid(),
3291
+ /**
3292
+ * Whether the badge must stay. `true` on launch plans (which can't remove
3293
+ * it); `false` on growth/scale. Advisory — the real check is server-side, but
3294
+ * an honest site uses this to decide whether it may hide the badge.
3295
+ */
3296
+ required: z.boolean(),
3297
+ });
3298
+ export type AttributionPolicy = z.infer<typeof attributionPolicySchema>;
3299
+
3300
+ export const attributionHeartbeatInputSchema = z.object({
3301
+ /** Absolute URL the badge rendered on. Normalized + de-duped server-side. */
3302
+ url: z.string().min(1).max(2048),
3303
+ });
3304
+ export type AttributionHeartbeatInput = z.infer<
3305
+ typeof attributionHeartbeatInputSchema
3306
+ >;
3307
+
3308
+ export const attributionHeartbeatResponseSchema = z.object({
3309
+ /** `false` when the URL was unparseable — a heartbeat never errors. */
3310
+ recorded: z.boolean(),
3311
+ });
3312
+ export type AttributionHeartbeatResponse = z.infer<
3313
+ typeof attributionHeartbeatResponseSchema
3314
+ >;
3315
+
3316
+ /* ---------------------------------------------------------------------------
3317
+ Hiring — public job postings for a consumer-built careers page.
3318
+
3319
+ Read-only and deliberately narrower than the dashboard's job row: internal
3320
+ hiring state (openings filled, application caps, default permissions/roles,
3321
+ the role-ladder pointer, interview questions) never crosses this boundary.
3322
+ Applications ride the existing forms surface via `applicationFormId`.
3323
+ --------------------------------------------------------------------------- */
3324
+
3325
+ export const jobCategorySchema = z.object({
3326
+ id: z.string(),
3327
+ name: z.string(),
3328
+ slug: z.string(),
3329
+ description: z.string().nullable(),
3330
+ /** Free-form chip tint set in the dashboard (hex or CSS colour). */
3331
+ color: z.string().nullable(),
3332
+ });
3333
+ export type JobCategory = z.infer<typeof jobCategorySchema>;
3334
+
3335
+ export const jobCategoryWithCountSchema = jobCategorySchema.extend({
3336
+ /** Open postings currently in this category. */
3337
+ openCount: z.number(),
3338
+ });
3339
+ export type JobCategoryWithCount = z.infer<typeof jobCategoryWithCountSchema>;
3340
+
3341
+ export const jobPostingSchema = z.object({
3342
+ id: z.string(),
3343
+ slug: z.string(),
3344
+ title: z.string(),
3345
+ /** Rich-text HTML, sanitized server-side. Safe to render. */
3346
+ description: z.string(),
3347
+ department: z.string().nullable(),
3348
+ level: z.string().nullable(),
3349
+ employmentType: z.string(),
3350
+ workMode: z.string(),
3351
+ location: z.string().nullable(),
3352
+ region: z.object({ id: z.string(), name: z.string() }).nullable(),
3353
+ /** The posting's single group. `null` when uncategorized. */
3354
+ category: jobCategorySchema.nullable(),
3355
+ /** Free-form labels for filtering/search; never a grouping. */
3356
+ tags: z.array(z.string()),
3357
+ responsibilities: z.array(z.string()).nullable(),
3358
+ requirements: z.string().nullable(),
3359
+ languages: z.array(z.string()).nullable(),
3360
+ payRangeText: z.string().nullable(),
3361
+ salaryMin: z.number().nullable(),
3362
+ salaryMax: z.number().nullable(),
3363
+ currency: z.string().nullable(),
3364
+ /**
3365
+ * Pass to `client.forms.schema()` / `<BiabForm/>` to render the application
3366
+ * form. `null` means the posting is listed but not yet accepting applications.
3367
+ */
3368
+ applicationFormId: z.string().nullable(),
3369
+ publishedAt: z.string().nullable(),
3370
+ updatedAt: z.string(),
3371
+ });
3372
+ export type JobPosting = z.infer<typeof jobPostingSchema>;
3373
+
3374
+ export const jobPostingListResponseSchema = z.object({
3375
+ items: z.array(jobPostingSchema),
3376
+ /** Total open postings matching the (optional) category filter. */
3377
+ totalCount: z.number(),
3378
+ /** Offset to pass back for the next page, or null when exhausted. */
3379
+ nextOffset: z.number().nullable(),
3380
+ });
3381
+ export type JobPostingListResponse = z.infer<
3382
+ typeof jobPostingListResponseSchema
3383
+ >;
3384
+
3385
+ export const jobCategoryListResponseSchema = z.object({
3386
+ items: z.array(jobCategoryWithCountSchema),
3387
+ });
3388
+ export type JobCategoryListResponse = z.infer<
3389
+ typeof jobCategoryListResponseSchema
3390
+ >;