@businessdash/sdk 0.9.60 → 0.9.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/README.md +1012 -186
  2. package/assets/biab-sw.js +92 -0
  3. package/dist/account-core/index.d.ts +126 -0
  4. package/dist/account-core/index.d.ts.map +1 -0
  5. package/dist/account-core/index.js +191 -0
  6. package/dist/account-core/index.js.map +1 -0
  7. package/dist/account.d.ts +11 -0
  8. package/dist/account.d.ts.map +1 -0
  9. package/dist/account.js +11 -0
  10. package/dist/account.js.map +1 -0
  11. package/dist/auth-handler.d.ts +1 -1
  12. package/dist/auth-links.d.ts +33 -0
  13. package/dist/auth-links.d.ts.map +1 -0
  14. package/dist/auth-links.js +64 -0
  15. package/dist/auth-links.js.map +1 -0
  16. package/dist/blog-core/index.d.ts +112 -0
  17. package/dist/blog-core/index.d.ts.map +1 -0
  18. package/dist/blog-core/index.js +330 -0
  19. package/dist/blog-core/index.js.map +1 -0
  20. package/dist/blog.d.ts +11 -0
  21. package/dist/blog.d.ts.map +1 -0
  22. package/dist/blog.js +11 -0
  23. package/dist/blog.js.map +1 -0
  24. package/dist/chat-core/index.d.ts +97 -0
  25. package/dist/chat-core/index.d.ts.map +1 -0
  26. package/dist/chat-core/index.js +237 -0
  27. package/dist/chat-core/index.js.map +1 -0
  28. package/dist/chat.d.ts +11 -0
  29. package/dist/chat.d.ts.map +1 -0
  30. package/dist/chat.js +11 -0
  31. package/dist/chat.js.map +1 -0
  32. package/dist/checkout-core/index.d.ts +65 -0
  33. package/dist/checkout-core/index.d.ts.map +1 -0
  34. package/dist/checkout-core/index.js +159 -0
  35. package/dist/checkout-core/index.js.map +1 -0
  36. package/dist/checkout.d.ts +10 -0
  37. package/dist/checkout.d.ts.map +1 -0
  38. package/dist/checkout.js +10 -0
  39. package/dist/checkout.js.map +1 -0
  40. package/dist/cli.js +133 -25
  41. package/dist/cli.js.map +1 -1
  42. package/dist/client.d.ts +567 -1
  43. package/dist/client.d.ts.map +1 -1
  44. package/dist/client.js +807 -1
  45. package/dist/client.js.map +1 -1
  46. package/dist/contracts.d.ts +3309 -761
  47. package/dist/contracts.d.ts.map +1 -1
  48. package/dist/contracts.js +541 -4
  49. package/dist/contracts.js.map +1 -1
  50. package/dist/data-model-schema.d.ts +1 -1
  51. package/dist/data-model-schema.d.ts.map +1 -1
  52. package/dist/element.d.ts +30 -0
  53. package/dist/element.d.ts.map +1 -1
  54. package/dist/element.js +56 -0
  55. package/dist/element.js.map +1 -1
  56. package/dist/embed-protocol.d.ts +86 -0
  57. package/dist/embed-protocol.d.ts.map +1 -0
  58. package/dist/embed-protocol.js +60 -0
  59. package/dist/embed-protocol.js.map +1 -0
  60. package/dist/framework-support.d.ts +122 -0
  61. package/dist/framework-support.d.ts.map +1 -0
  62. package/dist/framework-support.js +323 -0
  63. package/dist/framework-support.js.map +1 -0
  64. package/dist/index.cjs +1244 -5
  65. package/dist/legal-core/index.cjs +115 -0
  66. package/dist/legal-core/index.d.ts +160 -0
  67. package/dist/legal-core/index.d.ts.map +1 -0
  68. package/dist/legal-core/index.js +220 -0
  69. package/dist/legal-core/index.js.map +1 -0
  70. package/dist/marketing-core/index.d.ts +66 -0
  71. package/dist/marketing-core/index.d.ts.map +1 -0
  72. package/dist/marketing-core/index.js +154 -0
  73. package/dist/marketing-core/index.js.map +1 -0
  74. package/dist/marketing.d.ts +7 -0
  75. package/dist/marketing.d.ts.map +1 -0
  76. package/dist/marketing.js +7 -0
  77. package/dist/marketing.js.map +1 -0
  78. package/dist/mcp-adapters/index.d.ts +135 -0
  79. package/dist/mcp-adapters/index.d.ts.map +1 -0
  80. package/dist/mcp-adapters/index.js +187 -0
  81. package/dist/mcp-adapters/index.js.map +1 -0
  82. package/dist/mcp-adapters.d.ts +7 -0
  83. package/dist/mcp-adapters.d.ts.map +1 -0
  84. package/dist/mcp-adapters.js +7 -0
  85. package/dist/mcp-adapters.js.map +1 -0
  86. package/dist/model3d-core/index.cjs +95 -0
  87. package/dist/model3d-core/index.d.ts +167 -0
  88. package/dist/model3d-core/index.d.ts.map +1 -0
  89. package/dist/model3d-core/index.js +158 -0
  90. package/dist/model3d-core/index.js.map +1 -0
  91. package/dist/notifications-core/index.cjs +315 -0
  92. package/dist/notifications-core/index.d.ts +229 -0
  93. package/dist/notifications-core/index.d.ts.map +1 -0
  94. package/dist/notifications-core/index.js +445 -0
  95. package/dist/notifications-core/index.js.map +1 -0
  96. package/dist/portal-core/index.d.ts +451 -0
  97. package/dist/portal-core/index.d.ts.map +1 -0
  98. package/dist/portal-core/index.js +819 -0
  99. package/dist/portal-core/index.js.map +1 -0
  100. package/dist/portal.d.ts +7 -0
  101. package/dist/portal.d.ts.map +1 -0
  102. package/dist/portal.js +7 -0
  103. package/dist/portal.js.map +1 -0
  104. package/dist/push-core/index.cjs +171 -0
  105. package/dist/push-core/index.d.ts +149 -0
  106. package/dist/push-core/index.d.ts.map +1 -0
  107. package/dist/push-core/index.js +231 -0
  108. package/dist/push-core/index.js.map +1 -0
  109. package/dist/react-marketing.d.ts +1 -17
  110. package/dist/react-marketing.d.ts.map +1 -1
  111. package/dist/react-marketing.js +4 -0
  112. package/dist/react-marketing.js.map +1 -1
  113. package/dist/react.d.ts +294 -101
  114. package/dist/react.d.ts.map +1 -1
  115. package/dist/react.js +302 -246
  116. package/dist/react.js.map +1 -1
  117. package/dist/seed-core/index.d.ts +354 -0
  118. package/dist/seed-core/index.d.ts.map +1 -0
  119. package/dist/seed-core/index.js +420 -0
  120. package/dist/seed-core/index.js.map +1 -0
  121. package/dist/seed.d.ts +7 -0
  122. package/dist/seed.d.ts.map +1 -0
  123. package/dist/seed.js +7 -0
  124. package/dist/seed.js.map +1 -0
  125. package/dist/seo-core/blog.d.ts +94 -0
  126. package/dist/seo-core/blog.d.ts.map +1 -0
  127. package/dist/seo-core/blog.js +149 -0
  128. package/dist/seo-core/blog.js.map +1 -0
  129. package/dist/seo-core/commerce.d.ts +64 -0
  130. package/dist/seo-core/commerce.d.ts.map +1 -0
  131. package/dist/seo-core/commerce.js +87 -0
  132. package/dist/seo-core/commerce.js.map +1 -0
  133. package/dist/seo-core/index.cjs +751 -0
  134. package/dist/seo-core/index.d.ts +226 -0
  135. package/dist/seo-core/index.d.ts.map +1 -0
  136. package/dist/seo-core/index.js +455 -0
  137. package/dist/seo-core/index.js.map +1 -0
  138. package/dist/seo-core/local.d.ts +181 -0
  139. package/dist/seo-core/local.d.ts.map +1 -0
  140. package/dist/seo-core/local.js +193 -0
  141. package/dist/seo-core/local.js.map +1 -0
  142. package/dist/sitemap-core/index.cjs +167 -0
  143. package/dist/sitemap-core/index.d.ts +155 -0
  144. package/dist/sitemap-core/index.d.ts.map +1 -0
  145. package/dist/sitemap-core/index.js +195 -0
  146. package/dist/sitemap-core/index.js.map +1 -0
  147. package/dist/social-core/index.d.ts +74 -0
  148. package/dist/social-core/index.d.ts.map +1 -0
  149. package/dist/social-core/index.js +72 -0
  150. package/dist/social-core/index.js.map +1 -0
  151. package/dist/social.d.ts +7 -0
  152. package/dist/social.d.ts.map +1 -0
  153. package/dist/social.js +7 -0
  154. package/dist/social.js.map +1 -0
  155. package/dist/solid-compat.d.ts +204 -0
  156. package/dist/solid-compat.d.ts.map +1 -0
  157. package/dist/solid-compat.js +221 -0
  158. package/dist/solid-compat.js.map +1 -0
  159. package/dist/store-core/index.d.ts +141 -0
  160. package/dist/store-core/index.d.ts.map +1 -0
  161. package/dist/store-core/index.js +277 -0
  162. package/dist/store-core/index.js.map +1 -0
  163. package/dist/store.d.ts +12 -0
  164. package/dist/store.d.ts.map +1 -0
  165. package/dist/store.js +12 -0
  166. package/dist/store.js.map +1 -0
  167. package/dist/vanilla.d.ts +111 -0
  168. package/dist/vanilla.d.ts.map +1 -1
  169. package/dist/vanilla.js +206 -0
  170. package/dist/vanilla.js.map +1 -1
  171. package/dist/verify-core/index.d.ts +98 -0
  172. package/dist/verify-core/index.d.ts.map +1 -0
  173. package/dist/verify-core/index.js +186 -0
  174. package/dist/verify-core/index.js.map +1 -0
  175. package/dist/verify.d.ts +8 -0
  176. package/dist/verify.d.ts.map +1 -0
  177. package/dist/verify.js +8 -0
  178. package/dist/verify.js.map +1 -0
  179. package/openapi.json +4316 -1313
  180. package/package.json +121 -9
  181. package/src/account-core/index.ts +309 -0
  182. package/src/account.ts +11 -0
  183. package/src/angular/headless.ts +519 -0
  184. package/src/angular/index.ts +3 -0
  185. package/src/auth-links.ts +76 -0
  186. package/src/blog-core/index.ts +423 -0
  187. package/src/blog.ts +11 -0
  188. package/src/chat-core/index.ts +335 -0
  189. package/src/chat.ts +11 -0
  190. package/src/checkout-core/index.ts +225 -0
  191. package/src/checkout.ts +10 -0
  192. package/src/cli.ts +184 -28
  193. package/src/client.ts +998 -0
  194. package/src/contracts.ts +730 -11
  195. package/src/data-model-schema.ts +1 -1
  196. package/src/element.ts +102 -0
  197. package/src/embed-protocol.ts +100 -0
  198. package/src/framework-support.ts +440 -0
  199. package/src/legal-core/index.ts +276 -0
  200. package/src/marketing-core/index.ts +209 -0
  201. package/src/marketing.ts +7 -0
  202. package/src/mcp-adapters/index.ts +264 -0
  203. package/src/mcp-adapters.ts +7 -0
  204. package/src/model3d-core/index.ts +248 -0
  205. package/src/notifications-core/index.ts +650 -0
  206. package/src/portal-core/index.ts +1564 -0
  207. package/src/portal.ts +7 -0
  208. package/src/push-core/index.ts +335 -0
  209. package/src/qwik.tsx +602 -2
  210. package/src/react-marketing.tsx +5 -0
  211. package/src/react.tsx +434 -319
  212. package/src/seed-core/index.ts +735 -0
  213. package/src/seed.ts +7 -0
  214. package/src/seo-core/blog.ts +216 -0
  215. package/src/seo-core/commerce.ts +138 -0
  216. package/src/seo-core/index.ts +605 -0
  217. package/src/seo-core/local.ts +333 -0
  218. package/src/sitemap-core/index.ts +296 -0
  219. package/src/social-core/index.ts +103 -0
  220. package/src/social.ts +7 -0
  221. package/src/solid-compat.tsx +347 -0
  222. package/src/solid-surfaces.tsx +1311 -0
  223. package/src/solid.tsx +82 -60
  224. package/src/store-core/index.ts +426 -0
  225. package/src/store.ts +12 -0
  226. package/src/svelte/SocialLinks.svelte +84 -0
  227. package/src/svelte/headless.ts +435 -0
  228. package/src/svelte/index.ts +9 -0
  229. package/src/vanilla.ts +367 -0
  230. package/src/verify-core/index.ts +288 -0
  231. package/src/verify.ts +8 -0
  232. package/src/vue/headless.ts +527 -0
  233. package/src/vue/index.ts +3 -0
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@businessdash/sdk",
3
- "version": "0.9.60",
3
+ "version": "0.9.70",
4
4
  "description": "BusinessDash developer SDK \u2014 the data layer that lets you build customer-facing surfaces on your own domain while BusinessDash handles the operations stack behind the scenes. 0.9.x ships a comprehensive programmatic surface: storefront (products, categories, cart, checkout via Stripe Connect with Link payment), customer portal (work feed, per-item detail, quote accept/reject, job activity, comment threads, reviews, notification preferences), blog (posts, categories, threaded comments, reactions, paywalls, Mux video), Front Desk chat (sessions, AI bot, live human handoff, queue + place-in-line), coupons (validation against synced Stripe Promotion Codes \u2014 BOGO/BXGY/free-shipping kinds), shipping (multi-carrier rates + tracking via Shippo), address validation (Google Places, platform-proxied), customer notification preferences across in-app/email/SMS/webhook, subscriptions admin + Stripe sync, and followers/subscribers. Also: tenant sign-in/up/out via the auth handler, schema-driven marketing pages with restore semantics, typed JSON-LD SEO builders (LocalBusiness/Service/FAQ/Article/Breadcrumb/Review), and a CLI (`biab-dev sync-schema`) for the schema flow. The consumer renders the UI in their own design system on their own domain; BusinessDash supplies the data, the business logic, and the Stripe/Mux/Google/Shippo plumbing.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
7
7
  "files": [
8
8
  "dist",
9
9
  "openapi.json",
10
- "src"
10
+ "src",
11
+ "assets"
11
12
  ],
12
13
  "main": "./dist/index.js",
13
14
  "module": "./dist/index.js",
@@ -31,6 +32,10 @@
31
32
  "types": "./dist/contracts.d.ts",
32
33
  "import": "./dist/contracts.js"
33
34
  },
35
+ "./framework-support": {
36
+ "types": "./dist/framework-support.d.ts",
37
+ "import": "./dist/framework-support.js"
38
+ },
34
39
  "./native": {
35
40
  "types": "./dist/native.d.ts",
36
41
  "import": "./dist/native.js"
@@ -51,6 +56,46 @@
51
56
  "types": "./dist/forms.d.ts",
52
57
  "import": "./dist/forms.js"
53
58
  },
59
+ "./store": {
60
+ "types": "./dist/store.d.ts",
61
+ "import": "./dist/store.js"
62
+ },
63
+ "./chat": {
64
+ "types": "./dist/chat.d.ts",
65
+ "import": "./dist/chat.js"
66
+ },
67
+ "./blog": {
68
+ "types": "./dist/blog.d.ts",
69
+ "import": "./dist/blog.js"
70
+ },
71
+ "./checkout": {
72
+ "types": "./dist/checkout.d.ts",
73
+ "import": "./dist/checkout.js"
74
+ },
75
+ "./account": {
76
+ "types": "./dist/account.d.ts",
77
+ "import": "./dist/account.js"
78
+ },
79
+ "./marketing": {
80
+ "types": "./dist/marketing.d.ts",
81
+ "import": "./dist/marketing.js"
82
+ },
83
+ "./verify": {
84
+ "types": "./dist/verify.d.ts",
85
+ "import": "./dist/verify.js"
86
+ },
87
+ "./portal": {
88
+ "types": "./dist/portal.d.ts",
89
+ "import": "./dist/portal.js"
90
+ },
91
+ "./social": {
92
+ "types": "./dist/social.d.ts",
93
+ "import": "./dist/social.js"
94
+ },
95
+ "./seed": {
96
+ "types": "./dist/seed.d.ts",
97
+ "import": "./dist/seed.js"
98
+ },
54
99
  "./collections": {
55
100
  "types": "./dist/collections.d.ts",
56
101
  "import": "./dist/collections.js"
@@ -113,6 +158,10 @@
113
158
  "types": "./dist/mcp.d.ts",
114
159
  "import": "./dist/mcp.js"
115
160
  },
161
+ "./mcp-adapters": {
162
+ "types": "./dist/mcp-adapters.d.ts",
163
+ "import": "./dist/mcp-adapters.js"
164
+ },
116
165
  "./proxy": {
117
166
  "types": "./dist/proxy.d.ts",
118
167
  "import": "./dist/proxy.js"
@@ -141,7 +190,38 @@
141
190
  "types": "./dist/adapters-revalidate.d.ts",
142
191
  "import": "./dist/adapters-revalidate.js"
143
192
  },
144
- "./biab-forms.css": "./dist/biab-forms.css"
193
+ "./biab-forms.css": "./dist/biab-forms.css",
194
+ "./seo-core": {
195
+ "types": "./dist/seo-core/index.d.ts",
196
+ "import": "./dist/seo-core/index.js",
197
+ "require": "./dist/seo-core/index.cjs"
198
+ },
199
+ "./push-core": {
200
+ "types": "./dist/push-core/index.d.ts",
201
+ "import": "./dist/push-core/index.js",
202
+ "require": "./dist/push-core/index.cjs"
203
+ },
204
+ "./notifications": {
205
+ "types": "./dist/notifications-core/index.d.ts",
206
+ "import": "./dist/notifications-core/index.js",
207
+ "require": "./dist/notifications-core/index.cjs"
208
+ },
209
+ "./legal": {
210
+ "types": "./dist/legal-core/index.d.ts",
211
+ "import": "./dist/legal-core/index.js",
212
+ "require": "./dist/legal-core/index.cjs"
213
+ },
214
+ "./sitemap": {
215
+ "types": "./dist/sitemap-core/index.d.ts",
216
+ "import": "./dist/sitemap-core/index.js",
217
+ "require": "./dist/sitemap-core/index.cjs"
218
+ },
219
+ "./model3d": {
220
+ "types": "./dist/model3d-core/index.d.ts",
221
+ "import": "./dist/model3d-core/index.js",
222
+ "require": "./dist/model3d-core/index.cjs"
223
+ },
224
+ "./biab-sw.js": "./assets/biab-sw.js"
145
225
  },
146
226
  "scripts": {
147
227
  "clean": "node -e \"const fs=require('node:fs');fs.rmSync('dist',{recursive:true,force:true})\"",
@@ -149,19 +229,25 @@
149
229
  "openapi": "tsx scripts/openapi.ts",
150
230
  "openapi:check": "tsx scripts/openapi.ts --check",
151
231
  "postbuild": "node scripts/publish-types.js",
152
- "typecheck": "tsc -p tsconfig.json --noEmit",
232
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.solid.json --noEmit && tsc -p tsconfig.qwik.json --noEmit && tsc -p tsconfig.headless.json --noEmit",
153
233
  "test:types": "tsc -p tsconfig.type-tests.json --noEmit",
154
234
  "test": "tsx --test test/*.test.ts test/*.test.tsx",
155
235
  "lint": "biome check .",
156
236
  "lint:fix": "biome check --write .",
157
- "prepublishOnly": "npm run build"
237
+ "prepublishOnly": "npm run build",
238
+ "typecheck:solid": "tsc -p tsconfig.solid.json --noEmit",
239
+ "typecheck:headless": "tsc -p tsconfig.headless.json --noEmit",
240
+ "typecheck:qwik": "tsc -p tsconfig.qwik.json --noEmit",
241
+ "gen:socials": "tsx scripts/gen-social-tables.ts",
242
+ "gen:socials:check": "tsx scripts/gen-social-tables.ts --check",
243
+ "typecheck:solid2": "tsc -p tsconfig.solid2.json --noEmit"
158
244
  },
159
245
  "publishConfig": {
160
246
  "access": "public"
161
247
  },
162
248
  "repository": {
163
249
  "type": "git",
164
- "url": "git+https://github.com/Gold240sx/business-in-a-box.git",
250
+ "url": "git+https://github.com/businessdash/platform.git",
165
251
  "directory": "biab-dev"
166
252
  },
167
253
  "license": "MIT",
@@ -178,8 +264,13 @@
178
264
  "zod-to-json-schema": "^3.25.2"
179
265
  },
180
266
  "peerDependencies": {
181
- "react": ">=18.0.0",
182
- "react-dom": ">=18.0.0"
267
+ "@angular/core": ">=17.0.0",
268
+ "@builder.io/qwik": ">=1.5.0 <2",
269
+ "react": ">=18.0.0 <20",
270
+ "react-dom": ">=18.0.0 <20",
271
+ "solid-js": ">=1.8.0 <3",
272
+ "svelte": ">=5.0.0 <6",
273
+ "vue": ">=3.3.0 <4"
183
274
  },
184
275
  "peerDependenciesMeta": {
185
276
  "react": {
@@ -187,14 +278,35 @@
187
278
  },
188
279
  "react-dom": {
189
280
  "optional": true
281
+ },
282
+ "solid-js": {
283
+ "optional": true
284
+ },
285
+ "vue": {
286
+ "optional": true
287
+ },
288
+ "svelte": {
289
+ "optional": true
290
+ },
291
+ "@builder.io/qwik": {
292
+ "optional": true
293
+ },
294
+ "@angular/core": {
295
+ "optional": true
190
296
  }
191
297
  },
192
298
  "devDependencies": {
193
299
  "@biomejs/biome": "^2.5.1",
300
+ "@builder.io/qwik": "^1.5.0",
301
+ "@solidjs/web": "2.0.0-rc.1",
194
302
  "@types/node": "^20.19.43",
195
303
  "@types/react": "^19.2.17",
196
304
  "esbuild": "^0.28.1",
197
- "react": "^19.2.7",
305
+ "preact": "^10.29.8",
306
+ "preact-render-to-string": "^6.7.0",
307
+ "react": "^19.2.8",
308
+ "react-dom": "^19.2.8",
309
+ "solid-js-v2": "npm:solid-js@2.0.0-rc.1",
198
310
  "tsx": "^4.22.4",
199
311
  "typescript": "^5.9.3"
200
312
  },
@@ -0,0 +1,309 @@
1
+ /**
2
+ * `@businessdash/sdk/account` — headless customer session and followers.
3
+ *
4
+ * Two small controllers that were React-only (plus Solid) and had no reason to
5
+ * be: neither has any framework-specific logic.
6
+ *
7
+ * ## Session
8
+ *
9
+ * `createUserController` reads the auth handler's `/me` route with the session
10
+ * cookie. It never touches an API key — the handler the consumer mounts owns
11
+ * the credential, and this only asks it who is signed in.
12
+ *
13
+ * Sign-in, sign-up and sign-out are navigations, not requests, so they are
14
+ * plain functions over `buildAuthHref` rather than controller actions. Every
15
+ * one of them no-ops outside a browser instead of throwing, so an SSR render
16
+ * that touches them degrades rather than crashes.
17
+ *
18
+ * ## Followers
19
+ *
20
+ * `createFollowersController` wraps subscribe/unsubscribe with the local
21
+ * "this browser already subscribed" hint. The hint is a convenience for hiding
22
+ * a footer form without a round trip — it is NOT authority. `me()` is.
23
+ *
24
+ * @module
25
+ */
26
+
27
+ import {
28
+ buildAuthHref,
29
+ clearLocalFollow,
30
+ DEFAULT_AUTH_BASE_PATH,
31
+ followerStoreKey,
32
+ type LocalFollow,
33
+ readLocalFollow,
34
+ writeLocalFollow,
35
+ } from "../auth-links.js";
36
+ import type { TenantAuthMeResponse } from "../contracts.js";
37
+
38
+ // ─── Session ─────────────────────────────────────────────────────────────────
39
+
40
+ export type UserStatus = "loading" | "signed-in" | "signed-out";
41
+
42
+ export type UserSnapshot = {
43
+ status: UserStatus;
44
+ user: TenantAuthMeResponse | null;
45
+ };
46
+
47
+ export type UserController = {
48
+ snapshot(): UserSnapshot;
49
+ subscribe(callback: (snapshot: UserSnapshot) => void): () => void;
50
+ /** Read `/me`. Called once by `start()`; call again to re-check. */
51
+ refresh(): Promise<UserSnapshot>;
52
+ /** Kick off the first read. Bindings call this on mount. */
53
+ start(): void;
54
+ destroy(): void;
55
+ };
56
+
57
+ export type CreateUserControllerOptions = {
58
+ /** Where `createAuthHandler` is mounted. Defaults to `/api/biab-auth`. */
59
+ basePath?: string;
60
+ /** Injectable for tests; defaults to global `fetch`. */
61
+ fetchImpl?: typeof fetch;
62
+ };
63
+
64
+ export function createUserController(
65
+ options: CreateUserControllerOptions = {},
66
+ ): UserController {
67
+ const basePath = (options.basePath ?? DEFAULT_AUTH_BASE_PATH).replace(
68
+ /\/$/,
69
+ "",
70
+ );
71
+ const doFetch = options.fetchImpl ?? globalThis.fetch;
72
+
73
+ let destroyed = false;
74
+ const subscribers = new Set<(s: UserSnapshot) => void>();
75
+ let state: UserSnapshot = { status: "loading", user: null };
76
+
77
+ function notify(): void {
78
+ if (destroyed) return;
79
+ for (const cb of subscribers) cb(state);
80
+ }
81
+
82
+ async function refresh(): Promise<UserSnapshot> {
83
+ if (destroyed) return state;
84
+ try {
85
+ const res = await doFetch(`${basePath}/me`, { credentials: "include" });
86
+ const body = res.ok ? ((await res.json()) as TenantAuthMeResponse) : null;
87
+ if (destroyed) return state;
88
+ state = body
89
+ ? { status: "signed-in", user: body }
90
+ : { status: "signed-out", user: null };
91
+ } catch {
92
+ // A network failure is indistinguishable from "not signed in" from the
93
+ // page's point of view, and treating it as an error would leave every
94
+ // consumer writing the same fallback.
95
+ if (!destroyed) state = { status: "signed-out", user: null };
96
+ }
97
+ notify();
98
+ return state;
99
+ }
100
+
101
+ return {
102
+ snapshot: () => state,
103
+ subscribe(callback) {
104
+ subscribers.add(callback);
105
+ return () => {
106
+ subscribers.delete(callback);
107
+ };
108
+ },
109
+ refresh,
110
+ start() {
111
+ void refresh();
112
+ },
113
+ destroy() {
114
+ destroyed = true;
115
+ subscribers.clear();
116
+ },
117
+ };
118
+ }
119
+
120
+ export type ProgrammaticAuthOptions = {
121
+ /** Where to send the customer afterwards. */
122
+ returnTo?: string;
123
+ /** Pre-fill the email on the hosted page (sign-in / sign-up only). */
124
+ loginHint?: string;
125
+ /** Override the auth-handler basePath. */
126
+ basePath?: string;
127
+ };
128
+
129
+ /** Href for the auth handler's sign-in route — render it in your own link. */
130
+ export function signInHref(options: ProgrammaticAuthOptions = {}): string {
131
+ return buildAuthHref("sign-in", options);
132
+ }
133
+
134
+ /** Href for the auth handler's sign-up route. */
135
+ export function signUpHref(options: ProgrammaticAuthOptions = {}): string {
136
+ return buildAuthHref("sign-up", options);
137
+ }
138
+
139
+ /** Href for the auth handler's sign-out route. */
140
+ export function signOutHref(options: { basePath?: string } = {}): string {
141
+ return buildAuthHref("sign-out", options);
142
+ }
143
+
144
+ /** Navigate to sign-in. No-ops outside a browser. */
145
+ export function signIn(options: ProgrammaticAuthOptions = {}): void {
146
+ if (typeof window === "undefined") return;
147
+ window.location.assign(signInHref(options));
148
+ }
149
+
150
+ /** Navigate to sign-up. No-ops outside a browser. */
151
+ export function signUp(options: ProgrammaticAuthOptions = {}): void {
152
+ if (typeof window === "undefined") return;
153
+ window.location.assign(signUpHref(options));
154
+ }
155
+
156
+ /** Navigate to sign-out. No-ops outside a browser. */
157
+ export function signOut(options: { basePath?: string } = {}): void {
158
+ if (typeof window === "undefined") return;
159
+ window.location.assign(signOutHref(options));
160
+ }
161
+
162
+ // ─── Followers ───────────────────────────────────────────────────────────────
163
+
164
+ export type FollowersCoreClient = {
165
+ followers: {
166
+ join(input: {
167
+ email: string;
168
+ name?: string;
169
+ source?: string;
170
+ }): Promise<unknown>;
171
+ leave(input: { email: string }): Promise<unknown>;
172
+ me(input: { email: string }): Promise<unknown>;
173
+ edit(input: {
174
+ email: string;
175
+ patch: { name?: string; source?: string };
176
+ }): Promise<unknown>;
177
+ };
178
+ };
179
+
180
+ export type FollowersSnapshot = {
181
+ /** This browser already subscribed. A hint for hiding a form, not authority. */
182
+ subscribedLocally: boolean;
183
+ /** The email this browser subscribed with, or `null`. */
184
+ localEmail: string | null;
185
+ isPending: boolean;
186
+ error: Error | null;
187
+ };
188
+
189
+ export type FollowersController = {
190
+ snapshot(): FollowersSnapshot;
191
+ subscribe(callback: (snapshot: FollowersSnapshot) => void): () => void;
192
+ join(input: {
193
+ email: string;
194
+ name?: string;
195
+ source?: string;
196
+ }): Promise<unknown>;
197
+ leave(email: string): Promise<unknown>;
198
+ /** Authoritative status for an email — unlike `subscribedLocally`. */
199
+ me(email: string): Promise<unknown>;
200
+ edit(
201
+ email: string,
202
+ patch: { name?: string; source?: string },
203
+ ): Promise<unknown>;
204
+ destroy(): void;
205
+ };
206
+
207
+ export type CreateFollowersControllerOptions = {
208
+ client: FollowersCoreClient;
209
+ /** Site id — namespaces the local hint so two sites don't share it. */
210
+ siteId: string;
211
+ onError?: (error: Error) => void;
212
+ };
213
+
214
+ export function createFollowersController(
215
+ options: CreateFollowersControllerOptions,
216
+ ): FollowersController {
217
+ const { client, siteId, onError } = options;
218
+ const storeKey = followerStoreKey(siteId);
219
+
220
+ let destroyed = false;
221
+ const subscribers = new Set<(s: FollowersSnapshot) => void>();
222
+ let local: LocalFollow | null = readLocalFollow(storeKey);
223
+ let isPending = false;
224
+ let error: Error | null = null;
225
+ let cached: FollowersSnapshot | null = null;
226
+
227
+ function snapshot(): FollowersSnapshot {
228
+ if (!cached) {
229
+ cached = {
230
+ subscribedLocally: local !== null,
231
+ localEmail: local?.email ?? null,
232
+ isPending,
233
+ error,
234
+ };
235
+ }
236
+ return cached;
237
+ }
238
+
239
+ function notify(): void {
240
+ if (destroyed) return;
241
+ cached = null;
242
+ const next = snapshot();
243
+ for (const cb of subscribers) cb(next);
244
+ }
245
+
246
+ async function run<T>(work: () => Promise<T>, fallback: string): Promise<T> {
247
+ isPending = true;
248
+ error = null;
249
+ notify();
250
+ try {
251
+ return await work();
252
+ } catch (cause) {
253
+ const err = cause instanceof Error ? cause : new Error(fallback);
254
+ error = err;
255
+ onError?.(err);
256
+ throw err;
257
+ } finally {
258
+ isPending = false;
259
+ notify();
260
+ }
261
+ }
262
+
263
+ return {
264
+ snapshot,
265
+ subscribe(callback) {
266
+ subscribers.add(callback);
267
+ return () => {
268
+ subscribers.delete(callback);
269
+ };
270
+ },
271
+
272
+ join(input) {
273
+ return run(async () => {
274
+ const res = await client.followers.join(input);
275
+ local = { email: input.email, at: Date.now() };
276
+ writeLocalFollow(storeKey, local);
277
+ return res;
278
+ }, "Could not subscribe.");
279
+ },
280
+
281
+ leave(email) {
282
+ return run(async () => {
283
+ const res = await client.followers.leave({ email });
284
+ clearLocalFollow(storeKey);
285
+ local = null;
286
+ return res;
287
+ }, "Could not unsubscribe.");
288
+ },
289
+
290
+ me(email) {
291
+ return run(
292
+ () => client.followers.me({ email }),
293
+ "Could not read status.",
294
+ );
295
+ },
296
+
297
+ edit(email, patch) {
298
+ return run(
299
+ () => client.followers.edit({ email, patch }),
300
+ "Could not update the subscription.",
301
+ );
302
+ },
303
+
304
+ destroy() {
305
+ destroyed = true;
306
+ subscribers.clear();
307
+ },
308
+ };
309
+ }
package/src/account.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@businessdash/sdk/account` — the framework-agnostic customer session and
3
+ * followers cores.
4
+ *
5
+ * Source-shipped bindings import through this public subpath, as they do for
6
+ * `forms`, `store`, `chat` and `blog`.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ export * from "./account-core/index.js";