bunderstack 0.17.0-beta.2 → 0.17.0-beta.4

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 (336) hide show
  1. package/CHANGELOG.md +233 -0
  2. package/README.md +11 -1
  3. package/dist/access.d.ts +116 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +325 -0
  6. package/dist/access.js.map +1 -0
  7. package/dist/api/builder.d.ts +145 -0
  8. package/dist/api/builder.d.ts.map +1 -0
  9. package/dist/api/builder.js +28 -0
  10. package/dist/api/builder.js.map +1 -0
  11. package/dist/api/context.d.ts +34 -0
  12. package/dist/api/context.d.ts.map +1 -0
  13. package/dist/api/context.js +32 -0
  14. package/dist/api/context.js.map +1 -0
  15. package/dist/api/crud-router.d.ts +290 -0
  16. package/dist/api/crud-router.d.ts.map +1 -0
  17. package/dist/api/crud-router.js +249 -0
  18. package/dist/api/crud-router.js.map +1 -0
  19. package/dist/api/openapi.d.ts +6 -0
  20. package/dist/api/openapi.d.ts.map +1 -0
  21. package/dist/api/openapi.js +146 -0
  22. package/dist/api/openapi.js.map +1 -0
  23. package/dist/api/realtime-router.d.ts +76 -0
  24. package/dist/api/realtime-router.d.ts.map +1 -0
  25. package/dist/api/realtime-router.js +41 -0
  26. package/dist/api/realtime-router.js.map +1 -0
  27. package/dist/api/registry.d.ts +30 -0
  28. package/dist/api/registry.d.ts.map +1 -0
  29. package/dist/api/registry.js +239 -0
  30. package/dist/api/registry.js.map +1 -0
  31. package/dist/api/router.d.ts +9 -0
  32. package/dist/api/router.d.ts.map +1 -0
  33. package/dist/api/router.js +19 -0
  34. package/dist/api/router.js.map +1 -0
  35. package/dist/api/storage-router.d.ts +279 -0
  36. package/dist/api/storage-router.d.ts.map +1 -0
  37. package/dist/api/storage-router.js +155 -0
  38. package/dist/api/storage-router.js.map +1 -0
  39. package/dist/api/types.d.ts +55 -0
  40. package/dist/api/types.d.ts.map +1 -0
  41. package/dist/api/types.js +2 -0
  42. package/dist/api/types.js.map +1 -0
  43. package/dist/auth.d.ts +31 -0
  44. package/dist/auth.d.ts.map +1 -0
  45. package/dist/auth.js +102 -0
  46. package/dist/auth.js.map +1 -0
  47. package/dist/blueprint-generator.d.ts +18 -0
  48. package/dist/blueprint-generator.d.ts.map +1 -0
  49. package/dist/blueprint-generator.js +125 -0
  50. package/dist/blueprint-generator.js.map +1 -0
  51. package/dist/blueprint.d.ts +46 -0
  52. package/dist/blueprint.d.ts.map +1 -0
  53. package/dist/blueprint.js +162 -0
  54. package/dist/blueprint.js.map +1 -0
  55. package/dist/cli.d.ts +8 -0
  56. package/dist/cli.d.ts.map +1 -0
  57. package/dist/cli.js +68 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/config.d.ts +108 -0
  60. package/dist/config.d.ts.map +1 -0
  61. package/dist/config.js +90 -0
  62. package/dist/config.js.map +1 -0
  63. package/dist/cron.d.ts +4 -0
  64. package/dist/cron.d.ts.map +1 -0
  65. package/dist/cron.js +3 -0
  66. package/dist/cron.js.map +1 -0
  67. package/dist/crud-operations.d.ts +43 -0
  68. package/dist/crud-operations.d.ts.map +1 -0
  69. package/dist/crud-operations.js +238 -0
  70. package/dist/crud-operations.js.map +1 -0
  71. package/dist/database/adapter.d.ts +20 -0
  72. package/dist/database/adapter.d.ts.map +1 -0
  73. package/dist/database/adapter.js +2 -0
  74. package/dist/database/adapter.js.map +1 -0
  75. package/dist/database/bun-sql.d.ts +3 -0
  76. package/dist/database/bun-sql.d.ts.map +1 -0
  77. package/dist/database/bun-sql.js +21 -0
  78. package/dist/database/bun-sql.js.map +1 -0
  79. package/dist/database/libsql.d.ts +3 -0
  80. package/dist/database/libsql.d.ts.map +1 -0
  81. package/dist/database/libsql.js +22 -0
  82. package/dist/database/libsql.js.map +1 -0
  83. package/dist/database/pglite.d.ts +3 -0
  84. package/dist/database/pglite.d.ts.map +1 -0
  85. package/dist/database/pglite.js +27 -0
  86. package/dist/database/pglite.js.map +1 -0
  87. package/dist/database/postgres-js.d.ts +3 -0
  88. package/dist/database/postgres-js.d.ts.map +1 -0
  89. package/dist/database/postgres-js.js +21 -0
  90. package/dist/database/postgres-js.js.map +1 -0
  91. package/dist/db.d.ts +18 -0
  92. package/dist/db.d.ts.map +1 -0
  93. package/dist/db.js +26 -0
  94. package/dist/db.js.map +1 -0
  95. package/dist/dialect.d.ts +17 -0
  96. package/dist/dialect.d.ts.map +1 -0
  97. package/dist/dialect.js +22 -0
  98. package/dist/dialect.js.map +1 -0
  99. package/dist/email/smtp.d.ts +14 -0
  100. package/dist/email/smtp.d.ts.map +1 -0
  101. package/dist/email/smtp.js +27 -0
  102. package/dist/email/smtp.js.map +1 -0
  103. package/dist/email.d.ts +39 -0
  104. package/dist/email.d.ts.map +1 -0
  105. package/dist/email.js +97 -0
  106. package/dist/email.js.map +1 -0
  107. package/dist/env.d.ts +47 -0
  108. package/dist/env.d.ts.map +1 -0
  109. package/dist/env.js +99 -0
  110. package/dist/env.js.map +1 -0
  111. package/dist/errors.d.ts +125 -0
  112. package/dist/errors.d.ts.map +1 -0
  113. package/dist/errors.js +92 -0
  114. package/dist/errors.js.map +1 -0
  115. package/dist/handler.d.ts +9 -0
  116. package/dist/handler.d.ts.map +1 -0
  117. package/dist/handler.js +22 -0
  118. package/dist/handler.js.map +1 -0
  119. package/dist/idempotency.d.ts +17 -0
  120. package/dist/idempotency.d.ts.map +1 -0
  121. package/dist/idempotency.js +65 -0
  122. package/dist/idempotency.js.map +1 -0
  123. package/dist/index.d.ts +134 -0
  124. package/dist/index.d.ts.map +1 -0
  125. package/dist/index.js +480 -0
  126. package/dist/index.js.map +1 -0
  127. package/dist/internal-tables-pg.d.ts +481 -0
  128. package/dist/internal-tables-pg.d.ts.map +1 -0
  129. package/{src/internal-tables-pg.ts → dist/internal-tables-pg.js} +10 -34
  130. package/dist/internal-tables-pg.js.map +1 -0
  131. package/dist/internal-tables.d.ts +2509 -0
  132. package/dist/internal-tables.d.ts.map +1 -0
  133. package/dist/internal-tables.js +108 -0
  134. package/dist/internal-tables.js.map +1 -0
  135. package/dist/jobs/cron.d.ts +15 -0
  136. package/dist/jobs/cron.d.ts.map +1 -0
  137. package/dist/jobs/cron.js +74 -0
  138. package/dist/jobs/cron.js.map +1 -0
  139. package/dist/jobs/define.d.ts +133 -0
  140. package/dist/jobs/define.d.ts.map +1 -0
  141. package/dist/jobs/define.js +74 -0
  142. package/dist/jobs/define.js.map +1 -0
  143. package/dist/jobs/index.d.ts +11 -0
  144. package/dist/jobs/index.d.ts.map +1 -0
  145. package/dist/jobs/index.js +8 -0
  146. package/dist/jobs/index.js.map +1 -0
  147. package/dist/jobs/queue.d.ts +6 -0
  148. package/dist/jobs/queue.d.ts.map +1 -0
  149. package/dist/jobs/queue.js +55 -0
  150. package/dist/jobs/queue.js.map +1 -0
  151. package/dist/jobs/runtime.d.ts +13 -0
  152. package/dist/jobs/runtime.d.ts.map +1 -0
  153. package/dist/jobs/runtime.js +46 -0
  154. package/dist/jobs/runtime.js.map +1 -0
  155. package/dist/jobs/slots.d.ts +25 -0
  156. package/dist/jobs/slots.d.ts.map +1 -0
  157. package/dist/jobs/slots.js +41 -0
  158. package/dist/jobs/slots.js.map +1 -0
  159. package/dist/jobs/worker.d.ts +12 -0
  160. package/dist/jobs/worker.d.ts.map +1 -0
  161. package/dist/jobs/worker.js +311 -0
  162. package/dist/jobs/worker.js.map +1 -0
  163. package/dist/lifecycle.d.ts +10 -0
  164. package/dist/lifecycle.d.ts.map +1 -0
  165. package/dist/lifecycle.js +37 -0
  166. package/dist/lifecycle.js.map +1 -0
  167. package/dist/list-query.d.ts +62 -0
  168. package/dist/list-query.d.ts.map +1 -0
  169. package/dist/list-query.js +194 -0
  170. package/dist/list-query.js.map +1 -0
  171. package/dist/manifest.d.ts +59 -0
  172. package/dist/manifest.d.ts.map +1 -0
  173. package/dist/manifest.js +167 -0
  174. package/dist/manifest.js.map +1 -0
  175. package/dist/provision-internals.d.ts +24 -0
  176. package/dist/provision-internals.d.ts.map +1 -0
  177. package/dist/provision-internals.js +7 -0
  178. package/dist/provision-internals.js.map +1 -0
  179. package/dist/provision.d.ts +23 -0
  180. package/dist/provision.d.ts.map +1 -0
  181. package/dist/provision.js +96 -0
  182. package/dist/provision.js.map +1 -0
  183. package/dist/rate-limit.d.ts +7 -0
  184. package/dist/rate-limit.d.ts.map +1 -0
  185. package/dist/rate-limit.js +49 -0
  186. package/dist/rate-limit.js.map +1 -0
  187. package/dist/realtime/facade.d.ts +18 -0
  188. package/dist/realtime/facade.d.ts.map +1 -0
  189. package/dist/realtime/facade.js +36 -0
  190. package/dist/realtime/facade.js.map +1 -0
  191. package/dist/realtime/filter.d.ts +13 -0
  192. package/dist/realtime/filter.d.ts.map +1 -0
  193. package/dist/realtime/filter.js +48 -0
  194. package/dist/realtime/filter.js.map +1 -0
  195. package/dist/realtime/heartbeat.d.ts +14 -0
  196. package/dist/realtime/heartbeat.d.ts.map +1 -0
  197. package/dist/realtime/heartbeat.js +64 -0
  198. package/dist/realtime/heartbeat.js.map +1 -0
  199. package/dist/realtime/publisher.d.ts +21 -0
  200. package/dist/realtime/publisher.d.ts.map +1 -0
  201. package/dist/realtime/publisher.js +17 -0
  202. package/dist/realtime/publisher.js.map +1 -0
  203. package/dist/schema-export-pg.d.ts +2 -0
  204. package/dist/schema-export-pg.d.ts.map +1 -0
  205. package/dist/schema-export-pg.js +4 -0
  206. package/dist/schema-export-pg.js.map +1 -0
  207. package/dist/schema-export.d.ts +2 -0
  208. package/dist/schema-export.d.ts.map +1 -0
  209. package/dist/schema-export.js +2 -0
  210. package/dist/schema-export.js.map +1 -0
  211. package/dist/scope.d.ts +4 -0
  212. package/dist/scope.d.ts.map +1 -0
  213. package/dist/scope.js +13 -0
  214. package/dist/scope.js.map +1 -0
  215. package/dist/standard-schema.d.ts +13 -0
  216. package/dist/standard-schema.d.ts.map +1 -0
  217. package/dist/standard-schema.js +34 -0
  218. package/dist/standard-schema.js.map +1 -0
  219. package/dist/storage/buckets.d.ts +80 -0
  220. package/dist/storage/buckets.d.ts.map +1 -0
  221. package/dist/storage/buckets.js +178 -0
  222. package/dist/storage/buckets.js.map +1 -0
  223. package/dist/storage/delete.d.ts +11 -0
  224. package/dist/storage/delete.d.ts.map +1 -0
  225. package/dist/storage/delete.js +18 -0
  226. package/dist/storage/delete.js.map +1 -0
  227. package/dist/storage/file-meta.d.ts +37 -0
  228. package/dist/storage/file-meta.d.ts.map +1 -0
  229. package/dist/storage/file-meta.js +166 -0
  230. package/dist/storage/file-meta.js.map +1 -0
  231. package/dist/storage/index.d.ts +28 -0
  232. package/dist/storage/index.d.ts.map +1 -0
  233. package/dist/storage/index.js +5 -0
  234. package/dist/storage/index.js.map +1 -0
  235. package/dist/storage/local.d.ts +15 -0
  236. package/dist/storage/local.d.ts.map +1 -0
  237. package/dist/storage/local.js +56 -0
  238. package/dist/storage/local.js.map +1 -0
  239. package/dist/storage/operations.d.ts +54 -0
  240. package/dist/storage/operations.d.ts.map +1 -0
  241. package/dist/storage/operations.js +250 -0
  242. package/dist/storage/operations.js.map +1 -0
  243. package/dist/storage/registry.d.ts +10 -0
  244. package/dist/storage/registry.d.ts.map +1 -0
  245. package/dist/storage/registry.js +23 -0
  246. package/dist/storage/registry.js.map +1 -0
  247. package/dist/storage/s3.d.ts +28 -0
  248. package/dist/storage/s3.d.ts.map +1 -0
  249. package/dist/storage/s3.js +71 -0
  250. package/dist/storage/s3.js.map +1 -0
  251. package/dist/storage/sweep.d.ts +9 -0
  252. package/dist/storage/sweep.d.ts.map +1 -0
  253. package/dist/storage/sweep.js +17 -0
  254. package/dist/storage/sweep.js.map +1 -0
  255. package/dist/storage/thumbnails.d.ts +11 -0
  256. package/dist/storage/thumbnails.d.ts.map +1 -0
  257. package/dist/storage/thumbnails.js +60 -0
  258. package/dist/storage/thumbnails.js.map +1 -0
  259. package/dist/testing.d.ts +27 -0
  260. package/dist/testing.d.ts.map +1 -0
  261. package/dist/testing.js +11 -0
  262. package/dist/testing.js.map +1 -0
  263. package/dist/typeid-pg.d.ts +19 -0
  264. package/dist/typeid-pg.d.ts.map +1 -0
  265. package/{src/typeid-pg.ts → dist/typeid-pg.js} +10 -13
  266. package/dist/typeid-pg.js.map +1 -0
  267. package/dist/typeid.d.ts +45 -0
  268. package/dist/typeid.d.ts.map +1 -0
  269. package/dist/typeid.js +100 -0
  270. package/dist/typeid.js.map +1 -0
  271. package/package.json +77 -28
  272. package/src/access.ts +0 -532
  273. package/src/api/builder.ts +0 -52
  274. package/src/api/context.ts +0 -83
  275. package/src/api/crud-router.ts +0 -386
  276. package/src/api/openapi.ts +0 -184
  277. package/src/api/realtime-router.ts +0 -75
  278. package/src/api/registry.ts +0 -338
  279. package/src/api/router.ts +0 -34
  280. package/src/api/storage-router.ts +0 -224
  281. package/src/api/types.ts +0 -105
  282. package/src/auth.ts +0 -117
  283. package/src/blueprint-generator.ts +0 -203
  284. package/src/blueprint.ts +0 -264
  285. package/src/cli.ts +0 -90
  286. package/src/config.ts +0 -215
  287. package/src/cron.ts +0 -3
  288. package/src/crud-operations.ts +0 -478
  289. package/src/database/adapter.ts +0 -26
  290. package/src/database/bun-sql.ts +0 -23
  291. package/src/database/libsql.ts +0 -28
  292. package/src/database/pglite.ts +0 -33
  293. package/src/database/postgres-js.ts +0 -33
  294. package/src/db.ts +0 -61
  295. package/src/dialect.ts +0 -38
  296. package/src/email/smtp.ts +0 -45
  297. package/src/email.ts +0 -154
  298. package/src/env.ts +0 -180
  299. package/src/errors.ts +0 -123
  300. package/src/handler.ts +0 -30
  301. package/src/idempotency.ts +0 -97
  302. package/src/index.ts +0 -834
  303. package/src/internal-tables.ts +0 -149
  304. package/src/jobs/cron.ts +0 -87
  305. package/src/jobs/define.ts +0 -283
  306. package/src/jobs/index.ts +0 -37
  307. package/src/jobs/queue.ts +0 -67
  308. package/src/jobs/runtime.ts +0 -58
  309. package/src/jobs/slots.ts +0 -52
  310. package/src/jobs/worker.ts +0 -369
  311. package/src/lifecycle.ts +0 -49
  312. package/src/list-query.ts +0 -333
  313. package/src/manifest.ts +0 -297
  314. package/src/provision-internals.ts +0 -29
  315. package/src/provision.ts +0 -128
  316. package/src/rate-limit.ts +0 -71
  317. package/src/realtime/facade.ts +0 -74
  318. package/src/realtime/filter.ts +0 -81
  319. package/src/realtime/heartbeat.ts +0 -80
  320. package/src/realtime/publisher.ts +0 -46
  321. package/src/schema-export-pg.ts +0 -7
  322. package/src/schema-export.ts +0 -5
  323. package/src/scope.ts +0 -17
  324. package/src/standard-schema.ts +0 -59
  325. package/src/storage/buckets.ts +0 -308
  326. package/src/storage/delete.ts +0 -26
  327. package/src/storage/file-meta.ts +0 -222
  328. package/src/storage/index.ts +0 -39
  329. package/src/storage/local.ts +0 -69
  330. package/src/storage/operations.ts +0 -398
  331. package/src/storage/registry.ts +0 -40
  332. package/src/storage/s3.ts +0 -99
  333. package/src/storage/sweep.ts +0 -25
  334. package/src/storage/thumbnails.ts +0 -65
  335. package/src/testing.ts +0 -31
  336. package/src/typeid.ts +0 -116
package/src/index.ts DELETED
@@ -1,834 +0,0 @@
1
- import type { AnyRouter as AnyORPCRouter } from '@orpc/server'
2
- // src/index.ts
3
-
4
- import { SmartCoercionHandlerPlugin } from '@orpc/json-schema'
5
- import { OpenAPIGenerator, OpenAPIGeneratorError } from '@orpc/openapi'
6
- import { OpenAPIHandler } from '@orpc/openapi/fetch'
7
- import { RPCHandler } from '@orpc/server/fetch'
8
- import { ValibotToJsonSchemaConverter } from '@orpc/valibot'
9
-
10
- import type { TableAccessInput } from './access'
11
- import type {
12
- CrudApiRouterFor,
13
- MergeApiRouterTypes,
14
- UnifiedApiRouter,
15
- } from './api/types'
16
- import type { RealtimeApiRouter } from './api/realtime-router'
17
- import type { DbFor } from './db'
18
- import type {
19
- BunderstackJobsBuilder,
20
- EnqueueOptions,
21
- JobsDefs,
22
- JobsFacade,
23
- StartWorkerOptions,
24
- WorkerHandle,
25
- } from './jobs/index'
26
- import type { StorageConfigInput } from './storage/buckets'
27
- import type { StorageAdapter } from './storage/index'
28
-
29
- import { validateAndResolveAccess } from './access'
30
- import { createApiBuilder } from './api/builder'
31
- import { createApiContext } from './api/context'
32
- import { buildCrudApiRouter } from './api/crud-router'
33
- import { mergeOpenAPISpecs } from './api/openapi'
34
- import { buildRealtimeApiRouter } from './api/realtime-router'
35
- import { buildApiRouter } from './api/router'
36
- import { buildStorageApiRouter } from './api/storage-router'
37
- import {
38
- buildApiRegistry,
39
- normalizeForeignOpenAPISpec,
40
- } from './api/registry'
41
- import {
42
- createAuth,
43
- toAuthSessionResolver,
44
- withEmailAuthDefaults,
45
- } from './auth'
46
- import { resolveConfig, type BunderstackConfig } from './config'
47
- import { resolveRealtimeRedisUrl } from './config'
48
- import { createDb } from './db'
49
- import { detectDialect } from './dialect'
50
- import { createEmail, emailProviderTag, type EmailFacade } from './email'
51
- import { validateEnv, type EnvConfigInput, type ValidatedEnv } from './env'
52
- import { buildHandler } from './handler'
53
- import { withInternalTables } from './internal-tables'
54
- import {
55
- createJobsBuilder,
56
- createJobRunner,
57
- enqueueJob,
58
- startJobWorker,
59
- validateJobsDefs,
60
- } from './jobs/index'
61
- import { Lifecycle, type LifecycleStatus } from './lifecycle'
62
- import { buildManifest, type BunderstackManifest } from './manifest'
63
- import {
64
- PROVISION_INTERNALS,
65
- type WithProvisionInternals,
66
- } from './provision-internals'
67
- import {
68
- createRealtimeFacade,
69
- type RealtimeFacade,
70
- type RealtimeTransport,
71
- } from './realtime/facade'
72
- import {
73
- createMemoryRealtimePublisher,
74
- createRedisRealtimePublisher,
75
- } from './realtime/publisher'
76
- import { deleteFileWithDerivatives } from './storage/delete'
77
- import { deleteFileMetaRow, insertReadyFile } from './storage/file-meta'
78
- import { createBucketStorages } from './storage/registry'
79
- import { createStorageOperations } from './storage/operations'
80
- import { sweepOrphans } from './storage/sweep'
81
-
82
- export type AuthInstance = ReturnType<typeof createAuth>
83
-
84
- function waitForWorkerShutdown(
85
- signal: AbortSignal,
86
- installProcessListeners: boolean,
87
- ): Promise<void> {
88
- if (signal.aborted) return Promise.resolve()
89
-
90
- return new Promise((resolve) => {
91
- const done = () => {
92
- signal.removeEventListener('abort', done)
93
- if (installProcessListeners) {
94
- process.removeListener('SIGINT', done)
95
- process.removeListener('SIGTERM', done)
96
- }
97
- resolve()
98
- }
99
- signal.addEventListener('abort', done, { once: true })
100
- if (installProcessListeners) {
101
- process.once('SIGINT', done)
102
- process.once('SIGTERM', done)
103
- }
104
- })
105
- }
106
-
107
- /** Default age before an unconfirmed `pending` file is treated as an orphan. */
108
- const DEFAULT_PENDING_TTL_MS = 30 * 60_000
109
- /**
110
- * Public storage facade exposed as `app.storage`. Object-level operations live
111
- * on the per-bucket adapters; this surface offers the app-wide deletes that
112
- * must also clean the file-meta row.
113
- */
114
- export interface StorageFacade {
115
- /** Delete a file row and purge all underlying storage derivatives. */
116
- delete(fileId: string): Promise<void>
117
- /** Low-level access to the underlying storage adapter for a bucket. */
118
- bucket(name: string): StorageAdapter | undefined
119
- /**
120
- * Reap stale `pending` uploads older than `olderThanMs` (default 30m). Runs
121
- * only when explicitly invoked by the host or local scheduler. Returns
122
- * the count reaped.
123
- */
124
- sweep(olderThanMs?: number): Promise<number>
125
- /**
126
- * Get a public or presigned download URL for a file key.
127
- * Returns a presigned S3 URL when S3 is configured, or local proxy route `/api/files/...` in development.
128
- */
129
- getUrl(
130
- key: string,
131
- opts?: { bucket?: string; expiresIn?: number },
132
- ): Promise<string>
133
- /**
134
- * Upload bytes as a ready file and register it in file_meta so it is
135
- * accessible via `GET /api/files/<key>`. Use this for server-generated
136
- * files (e.g. generated PDFs) that are not uploaded by end-users.
137
- */
138
- upload(
139
- key: string,
140
- body: ArrayBuffer | Uint8Array,
141
- contentType: string,
142
- opts?: { ownerId?: string; filename?: string },
143
- ): Promise<void>
144
- }
145
-
146
- export type AppStartWorkerOptions = Omit<StartWorkerOptions, 'tick'>
147
- export type AppRunWorkerOptions = AppStartWorkerOptions & {
148
- /**
149
- * Permit process-local realtime in a standalone worker.
150
- *
151
- * Use only when job handlers never call ctx.realtime.publish(). Publications
152
- * made through the memory broker cannot reach SSE clients in another process.
153
- */
154
- allowProcessLocalRealtime?: boolean
155
- }
156
- /** Bucket names declared in a storage config; `string` when unknowable. */
157
- export type BucketNamesOf<TStorage> = TStorage extends {
158
- buckets: infer B extends Record<string, unknown>
159
- }
160
- ? keyof B & string
161
- : string
162
-
163
- export type BunderstackApp<
164
- TSchema extends Record<string, unknown>,
165
- TAccess extends Record<string, TableAccessInput> | undefined = undefined,
166
- TBuckets extends string = string,
167
- TEnv extends EnvConfigInput | undefined = undefined,
168
- TJobsDefs extends JobsDefs | undefined = undefined,
169
- TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
170
- > = {
171
- handler: (req: Request) => Promise<Response>
172
- db: DbFor<TSchema>
173
- auth: AuthInstance
174
- storage: StorageFacade
175
- /** Validated env: bunderstack's base vars plus the config's `env` extension. */
176
- env: ValidatedEnv<TEnv>
177
- /** Email facade; always present — send() throws when email isn't configured. */
178
- email: EmailFacade
179
- /** Job queue facade; always present — enqueue throws when jobs aren't configured. */
180
- jobs: JobsFacade<
181
- TJobsDefs extends JobsDefs ? TJobsDefs : Record<never, never>
182
- >
183
- /** Typed custom row publication; enabled=false/no-op when realtime is off. */
184
- realtime: RealtimeFacade<TSchema>
185
- startWorker(options?: AppStartWorkerOptions): Promise<WorkerHandle>
186
- /** Run a queue worker until aborted, then close the application. */
187
- runWorker(options?: AppRunWorkerOptions): Promise<void>
188
- close(): Promise<void>
189
- /** True when this process is running the background tick loop. */
190
- readonly backgroundRunning: boolean
191
- readonly status: LifecycleStatus
192
- readonly signal: AbortSignal
193
- /** Deploy-time introspection: what this app needs provisioned. */
194
- manifest: BunderstackManifest
195
- /**
196
- * Type-only carrier for client inference (`createClient<typeof app>()`).
197
- * Never assigned at runtime.
198
- */
199
- readonly $inferClient?: {
200
- schema: TSchema
201
- access: TAccess
202
- buckets: TBuckets
203
- api: MergeApiRouterTypes<
204
- UnifiedApiRouter<CrudApiRouterFor<TSchema, TAccess>, TCustomApiRouter>,
205
- RealtimeApiRouter
206
- >
207
- }
208
- }
209
-
210
- export function createBunderstack<
211
- TSchema extends Record<string, unknown>,
212
- const TAccess extends Record<string, TableAccessInput> | undefined = undefined,
213
- const TStorage extends StorageConfigInput | undefined = undefined,
214
- const TEnv extends EnvConfigInput | undefined = undefined,
215
- const TJobsDefs extends JobsDefs | undefined = undefined,
216
- TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
217
- >(
218
- options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv, TCustomApiRouter> & {
219
- jobs?: TJobsDefs | ((j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => TJobsDefs)
220
- },
221
- ): Promise<BunderstackApp<TSchema, TAccess, BucketNamesOf<TStorage>, TEnv, TJobsDefs, TCustomApiRouter>>
222
- export async function createBunderstack<
223
- TSchema extends Record<string, unknown>,
224
- const TAccess extends Record<string, TableAccessInput> | undefined =
225
- undefined,
226
- const TStorage extends StorageConfigInput | undefined = undefined,
227
- const TEnv extends EnvConfigInput | undefined = undefined,
228
- TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
229
- >(
230
- options: BunderstackConfig<
231
- TSchema,
232
- TAccess,
233
- TStorage,
234
- TEnv,
235
- TCustomApiRouter
236
- > & {
237
- jobs?:
238
- | JobsDefs
239
- | ((j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => JobsDefs)
240
- },
241
- ): Promise<
242
- BunderstackApp<
243
- TSchema,
244
- TAccess,
245
- BucketNamesOf<TStorage>,
246
- TEnv,
247
- JobsDefs | undefined,
248
- TCustomApiRouter
249
- >
250
- > {
251
- const dialect = detectDialect(options.schema)
252
- const jobsDefs: JobsDefs | undefined = options.jobs
253
- ? typeof options.jobs === 'function'
254
- ? options.jobs(createJobsBuilder<TSchema, ValidatedEnv<TEnv>>())
255
- : (options.jobs as JobsDefs)
256
- : undefined
257
- if (jobsDefs) validateJobsDefs(jobsDefs)
258
- // Env is validated FIRST: the app refuses to boot on missing/invalid vars,
259
- // and everything downstream consumes the result.
260
- const env = validateEnv(options.env, {
261
- emailProvider: emailProviderTag(options.email),
262
- defaultDatabaseUrl:
263
- dialect === 'pg' ? 'file:./data.pglite' : 'file:./data.db',
264
- source: options.processEnv,
265
- })
266
- const config = resolveConfig(options, env, options.processEnv)
267
- // Adapters use Drizzle mocks during deployment introspection, so the database
268
- // and Redis below never touch external services.
269
- const introspect = process.env.BUNDERSTACK_INTROSPECT === '1'
270
- const email = createEmail(options.email, { env })
271
- // Merge bunderstack's internal tables (file-meta, idempotency) into the
272
- // schema used for the db client + provisioning. CRUD/access stay on the USER
273
- // schema so internal tables never get a CRUD route.
274
- const mergedSchema = withInternalTables(options.schema)
275
- const lifecycle = new Lifecycle()
276
- const {
277
- db,
278
- driver,
279
- close: closeDatabase,
280
- } = await createDb(mergedSchema, {
281
- ...config.database,
282
- dialect,
283
- introspect,
284
- })
285
- if (closeDatabase) lifecycle.add(closeDatabase)
286
- try {
287
- // `db` is typed with the merged schema (user tables + internal tables) so the
288
- // storage/idempotency code can query the internal tables. The public surface
289
- // and CRUD only expose the USER schema. TS can widen the merged-schema db type
290
- // on its own (storage/auth pass `db` directly), but it can't *narrow* a
291
- // generic schema view, so this single intentional cast produces the
292
- // user-facing, per-dialect db type. See `app.db` / crud below.
293
- const userDb = db as unknown as DbFor<TSchema>
294
- const auth = createAuth(
295
- db,
296
- withEmailAuthDefaults(config.auth, email, Boolean(options.email)),
297
- dialect,
298
- options.schema as Record<string, unknown>,
299
- )
300
- // Internal routers consume the narrow AuthSessionResolver contract, not the
301
- // raw better-auth instance. app.auth still exposes `auth` unchanged.
302
- const authResolver = options.authResolver ?? toAuthSessionResolver(auth)
303
- const resolvedAccess = validateAndResolveAccess(
304
- options.schema,
305
- options.access,
306
- )
307
- const realtimeBufferSize =
308
- typeof config.realtime === 'object'
309
- ? config.realtime.bufferSize
310
- : undefined
311
- const realtimeResumeSeconds =
312
- typeof config.realtime === 'object'
313
- ? config.realtime.resumeSeconds
314
- : undefined
315
- const configuredRedisUrl = config.realtime
316
- ? resolveRealtimeRedisUrl(config.realtime, env)
317
- : undefined
318
- const redisUrl = introspect ? undefined : configuredRedisUrl
319
- const publisher = config.realtime
320
- ? redisUrl
321
- ? (() => {
322
- const redis = new Bun.RedisClient(redisUrl)
323
- const subscriber = redis.duplicate()
324
- lifecycle.add(async () => {
325
- redis.close()
326
- ;(await subscriber).close()
327
- })
328
- return createRedisRealtimePublisher(redis, subscriber, {
329
- prefix:
330
- process.env.BUNDERSTACK_REALTIME_PREFIX ?? 'bunderstack:',
331
- maxBufferedEvents: realtimeBufferSize,
332
- resumeSeconds: realtimeResumeSeconds,
333
- })
334
- })()
335
- : createMemoryRealtimePublisher({
336
- maxBufferedEvents: realtimeBufferSize,
337
- resumeSeconds: realtimeResumeSeconds,
338
- })
339
- : undefined
340
- const runtimeRealtimeTransport: RealtimeTransport = !publisher
341
- ? 'disabled'
342
- : redisUrl
343
- ? 'redis'
344
- : 'memory'
345
- const realtime = createRealtimeFacade<TSchema>(
346
- publisher,
347
- runtimeRealtimeTransport,
348
- options.schema,
349
- )
350
- const registry = createBucketStorages(config.storage)
351
- const storageOperations = createStorageOperations({
352
- registry,
353
- db,
354
- })
355
- const storageApiRouter = buildStorageApiRouter(registry, storageOperations)
356
- const realtimeApiRouter = buildRealtimeApiRouter(publisher, resolvedAccess)
357
- const storage: StorageFacade = {
358
- async delete(fileId) {
359
- const bucketName = fileId.split('/')[0] ?? ''
360
- const entry = registry.get(bucketName)
361
- if (entry) {
362
- await deleteFileWithDerivatives(entry.adapter, db, fileId)
363
- } else {
364
- // Unknown bucket: no adapter to clean, but still drop the meta row.
365
- await deleteFileMetaRow(db, fileId)
366
- }
367
- },
368
- bucket(name) {
369
- return registry.get(name)?.adapter
370
- },
371
- sweep(olderThanMs = DEFAULT_PENDING_TTL_MS) {
372
- return sweepOrphans(registry, db, olderThanMs)
373
- },
374
- async getUrl(key, opts = {}) {
375
- const bucketName =
376
- opts.bucket ?? key.split('/')[0] ?? config.storage.defaultBucket
377
- const adapter = registry.get(bucketName)?.adapter
378
- if (adapter?.presignGet) {
379
- return adapter.presignGet(key, {
380
- expiresIn: opts.expiresIn ?? 3600,
381
- })
382
- }
383
- return `/api/files/${key}`
384
- },
385
- async upload(key, body, contentType, opts = {}) {
386
- const bucketName = key.split('/')[0] ?? ''
387
- const adapter = registry.get(bucketName)?.adapter
388
- if (!adapter) throw new Error(`Unknown bucket: ${bucketName}`)
389
- const u8 = body instanceof Uint8Array ? body : new Uint8Array(body)
390
- const buf: ArrayBuffer = u8.buffer.slice(
391
- u8.byteOffset,
392
- u8.byteOffset + u8.byteLength,
393
- ) as ArrayBuffer
394
- await adapter.upload(key, buf, contentType)
395
- await insertReadyFile(db, {
396
- fileId: key,
397
- bucket: bucketName,
398
- ownerId: opts.ownerId ?? null,
399
- scopeJson: null,
400
- filename: opts.filename ?? key.split('/').at(-1) ?? null,
401
- contentType,
402
- size: buf.byteLength,
403
- })
404
- },
405
- }
406
- const storageConfigured = Boolean(options.storage)
407
- // The storage sweep used to be a hardcoded maintenance route. It is an
408
- // ordinary cron now, so it inherits retries, timeout and onFailed.
409
- const resolvedDefs: JobsDefs | undefined = storageConfigured
410
- ? {
411
- ...jobsDefs,
412
- 'bunderstack:storage-sweep': {
413
- kind: 'cron',
414
- schedule: '0 4 * * *',
415
- handler: async () => {
416
- await storage.sweep()
417
- },
418
- },
419
- }
420
- : jobsDefs
421
-
422
- const jobRunner = resolvedDefs
423
- ? createJobRunner({
424
- db,
425
- defs: resolvedDefs,
426
- ctx: { db: userDb, env, email, storage, realtime },
427
- })
428
- : undefined
429
- const jobs = {
430
- async enqueue(name: string, input?: unknown, opts?: EnqueueOptions) {
431
- if (!resolvedDefs) {
432
- throw new Error(
433
- '[bunderstack] no jobs configured — add a `jobs` key to createBunderstack',
434
- )
435
- }
436
- const result = await enqueueJob(db, resolvedDefs, name, input, opts)
437
- return result
438
- },
439
- tick(now?: number) {
440
- return jobRunner
441
- ? jobRunner.tick(now)
442
- : Promise.resolve({ claimed: 0, ran: 0, failed: 0 })
443
- },
444
- }
445
- if (jobRunner) jobRunner.setJobsFacade(jobs)
446
- const startWorker = async (
447
- options: AppStartWorkerOptions = {},
448
- ): Promise<WorkerHandle> => {
449
- if (introspect) {
450
- return { closed: Promise.resolve(), close: async () => {} }
451
- }
452
- if (!jobRunner) {
453
- throw new Error('[bunderstack] no queue jobs configured')
454
- }
455
- if (lifecycle.status !== 'ready') {
456
- throw new Error('[bunderstack] application lifecycle is closed')
457
- }
458
- const signal = options.signal
459
- ? AbortSignal.any([lifecycle.signal, options.signal])
460
- : lifecycle.signal
461
- const handle = startJobWorker({
462
- ...options,
463
- signal,
464
- // The runtime loop only cares that a tick completed; TickResult is for
465
- // callers that invoke tick() directly.
466
- tick: async (now) => {
467
- await jobRunner.tick(now)
468
- },
469
- })
470
- const unregister = lifecycle.add(() => handle.close())
471
- void handle.closed.finally(unregister)
472
- return handle
473
- }
474
- const runWorker = async (
475
- options: AppRunWorkerOptions = {},
476
- ): Promise<void> => {
477
- if (introspect) return
478
- if (
479
- realtime.transport === 'memory' &&
480
- !options.allowProcessLocalRealtime
481
- ) {
482
- throw new Error(
483
- '[bunderstack] runWorker() cannot deliver realtime events through the in-memory broker. Configure REDIS_URL or realtime.redis, embed the worker with startWorker(), or pass allowProcessLocalRealtime: true only when jobs never publish realtime.',
484
- )
485
- }
486
- const {
487
- allowProcessLocalRealtime: _allowProcessLocalRealtime,
488
- ...workerOptions
489
- } = options
490
- const handle = await startWorker(workerOptions)
491
- try {
492
- const signal = workerOptions.signal
493
- ? AbortSignal.any([lifecycle.signal, workerOptions.signal])
494
- : lifecycle.signal
495
- await waitForWorkerShutdown(signal, !workerOptions.signal)
496
- } finally {
497
- await handle.close()
498
- await lifecycle.close()
499
- }
500
- }
501
- const crudApiRouter = buildCrudApiRouter(options.schema, userDb, {
502
- access: resolvedAccess,
503
- idempotency: options.idempotency,
504
- realtime,
505
- })
506
-
507
- const customApiRouter = options.api
508
- ? options.api(createApiBuilder<TSchema, ValidatedEnv<TEnv>>())
509
- : undefined
510
-
511
- const nativeRouter = buildApiRouter({
512
- crud: crudApiRouter as Record<string, unknown>,
513
- storage: storageApiRouter as Record<string, unknown>,
514
- realtime: realtimeApiRouter as Record<string, unknown> | undefined,
515
- custom: customApiRouter as Record<string, unknown> | undefined,
516
- }) as any
517
-
518
- const authOpenAPISpecRaw =
519
- options.openapi &&
520
- auth.api &&
521
- 'generateOpenAPISchema' in auth.api &&
522
- typeof auth.api.generateOpenAPISchema === 'function'
523
- ? await auth.api.generateOpenAPISchema()
524
- : undefined
525
-
526
- const authOpenAPISpec = authOpenAPISpecRaw
527
- ? normalizeForeignOpenAPISpec(authOpenAPISpecRaw, {
528
- prefix: '/api/auth',
529
- source: 'auth',
530
- })
531
- : undefined
532
-
533
- await buildApiRegistry({
534
- nativeRouter,
535
- foreignSpecs: authOpenAPISpec ? [authOpenAPISpec] : [],
536
- reservedCoreHandles: new Set([
537
- 'health',
538
- ...(publisher ? ['realtime.changes'] : []),
539
- ...[...registry.keys()].flatMap((name) =>
540
- ['prepareUpload', 'upload', 'confirmUpload', 'download', 'delete'].map(
541
- (operation) => `files.${name}.${operation}`,
542
- ),
543
- ),
544
- ]),
545
- })
546
-
547
- const valibotConverter = new ValibotToJsonSchemaConverter()
548
-
549
- const combinedOpenAPISpec = options.openapi
550
- ? mergeOpenAPISpecs({
551
- nativeSpec: await new OpenAPIGenerator({
552
- converters: [
553
- valibotConverter,
554
- {
555
- condition: (schema: any) =>
556
- Boolean(
557
- schema?.['~standard'] &&
558
- !schema['~standard'].jsonSchema,
559
- ),
560
- convert: (schema: any) => {
561
- const vendor = schema?.['~standard']?.vendor ?? 'unknown'
562
- throw new OpenAPIGeneratorError(
563
- `No JSON Schema converter is configured for Standard Schema vendor "${vendor}"`,
564
- )
565
- },
566
- },
567
- ],
568
- }).generate(nativeRouter),
569
- authSpec: authOpenAPISpec,
570
- })
571
- : undefined
572
-
573
- const openapiHandler = new OpenAPIHandler(nativeRouter, {
574
- // Query strings and form bodies are strings; this coerces them to the
575
- // types each procedure's input schema declares, so schemas stay honest
576
- // (`v.number()`, not a string-union pipe) and REST matches RPC.
577
- plugins: [
578
- new SmartCoercionHandlerPlugin({ converters: [valibotConverter] }),
579
- ],
580
- customErrorResponseBodyEncoder: (error: any) => ({
581
- error: error.message,
582
- code: error.data?.code ?? error.code,
583
- // oRPC reports schema failures as `data.issues`; forwarding them tells
584
- // the client which field was rejected instead of just "invalid".
585
- ...(error.data?.details !== undefined
586
- ? { details: error.data.details }
587
- : error.data?.issues !== undefined
588
- ? { details: error.data.issues }
589
- : {}),
590
- }),
591
- fetchInterceptors: [
592
- async (options) => {
593
- const res = await options.next()
594
- if (res.matched && options.context.resHeaders) {
595
- options.context.resHeaders.forEach((v: string, k: string) =>
596
- res.response.headers.set(k, v),
597
- )
598
- }
599
- return res
600
- },
601
- ],
602
- })
603
- const rpcHandler = new RPCHandler(nativeRouter)
604
-
605
- const apiHandler = async (req: Request): Promise<Response | null> => {
606
- const urlString = typeof req === 'string' ? (req as string) : req.url
607
- if (!urlString) return null
608
- const url = new URL(urlString, 'http://localhost')
609
- if (
610
- combinedOpenAPISpec &&
611
- url.pathname === '/api/openapi.json' &&
612
- req.method === 'GET'
613
- ) {
614
- return new Response(JSON.stringify(combinedOpenAPISpec), {
615
- headers: { 'Content-Type': 'application/json' },
616
- })
617
- }
618
-
619
- const apiCtx = createApiContext(
620
- {
621
- db: userDb,
622
- env,
623
- storage,
624
- email,
625
- jobs,
626
- realtime,
627
- auth,
628
- authResolver,
629
- },
630
- req,
631
- )
632
-
633
- if (url.pathname.startsWith('/api/rpc')) {
634
- const res = await rpcHandler.handle(req, {
635
- prefix: '/api/rpc',
636
- context: apiCtx,
637
- })
638
- if (res.matched) return res.response
639
- }
640
-
641
- const openapiRes = await openapiHandler.handle(req, { context: apiCtx })
642
- if (openapiRes.matched) return openapiRes.response
643
-
644
- return null
645
- }
646
-
647
- const handler = buildHandler({
648
- authHandler: (req) => auth.handler(req),
649
- apiHandler,
650
- rateLimit: options.rateLimit,
651
- })
652
-
653
- // Topology is a deployment concern: the role decides whether this process
654
- // runs background work, so application code never has to.
655
- const roleWantsWorker =
656
- env.BUNDERSTACK_ROLE === 'all' || env.BUNDERSTACK_ROLE === 'worker'
657
- const autoStart =
658
- options.background?.autoStart ??
659
- (roleWantsWorker && !introspect && resolvedDefs !== undefined)
660
- let backgroundRunning = false
661
- if (autoStart) {
662
- await startWorker()
663
- backgroundRunning = true
664
- }
665
-
666
- const app: BunderstackApp<
667
- TSchema,
668
- TAccess,
669
- BucketNamesOf<TStorage>,
670
- TEnv,
671
- JobsDefs | undefined,
672
- TCustomApiRouter
673
- > = {
674
- handler,
675
- // Internal tables live on the runtime db but stay out of the public type.
676
- db: userDb,
677
- auth,
678
- storage,
679
- env,
680
- email,
681
- realtime,
682
- // Runtime facade is untyped (JobsRuntimeFacade); the generic-typed field
683
- // narrows `enqueue` per-app from the declared job defs — same relationship
684
- // as `userDb` above.
685
- jobs: jobs as never,
686
- startWorker,
687
- runWorker,
688
- close: () => lifecycle.close(),
689
- backgroundRunning,
690
- get status() {
691
- return lifecycle.status
692
- },
693
- signal: lifecycle.signal,
694
- manifest: buildManifest({
695
- schema: options.schema,
696
- dialect,
697
- migrationsDirectory: config.database.migrations,
698
- storage: config.storage,
699
- envConfig: options.env as EnvConfigInput | undefined,
700
- emailProvider: emailProviderTag(options.email),
701
- realtime: Boolean(config.realtime),
702
- jobs: resolvedDefs,
703
- }),
704
- }
705
-
706
- // Hidden handle for the optional `bunderstack/provision` entry. Kept off the
707
- // public type so provisioning stays opt-in (and drizzle-kit out of this
708
- // module graph).
709
- ;(app as WithProvisionInternals)[PROVISION_INTERNALS] = {
710
- db,
711
- schema: mergedSchema,
712
- databaseUrl: config.database.url,
713
- migrationsFolder: config.database.migrations,
714
- dialect,
715
- driver,
716
- adapter: config.database.adapter,
717
- }
718
-
719
- return app
720
- } catch (cause) {
721
- try {
722
- await lifecycle.close()
723
- } catch (cleanupCause) {
724
- throw new AggregateError(
725
- [cause, cleanupCause],
726
- '[bunderstack] application initialization failed and cleanup failed',
727
- )
728
- }
729
- throw cause
730
- }
731
- }
732
-
733
- export { MAX_LIST_LIMIT } from './list-query'
734
- export { BunderstackError } from './errors'
735
- export type { BunderstackErrorCode } from './errors'
736
- export { resolveConfig } from './config'
737
- export type {
738
- BetterAuthConfig,
739
- BunderstackConfig,
740
- ResolvedConfig,
741
- } from './config'
742
- export { validateEnv, createClientEnv, BunderstackEnvError } from './env'
743
- export type { EnvConfigInput, BaseEnv, ValidatedEnv } from './env'
744
- export { buildManifest, parseManifest } from './manifest'
745
- export type { BunderstackManifest, ManifestEnvVar } from './manifest'
746
- export { createEmail } from './email'
747
- export type {
748
- EmailMessage,
749
- EmailAdapter,
750
- EmailConfigInput,
751
- EmailFacade,
752
- } from './email'
753
- export { createJobsBuilder } from './jobs/index'
754
- export type {
755
- BunderstackJobContext,
756
- BunderstackJobsBuilder,
757
- BackgroundDefinition,
758
- BackgroundDefs,
759
- CronDefinition,
760
- CronInvocation,
761
- EnqueueOptions,
762
- JobContext,
763
- JobDefinition,
764
- JobsDefs,
765
- JobsFacade,
766
- JobsRuntimeFacade,
767
- QueueJobDefinition,
768
- QueueJobKeys,
769
- RunWorkerOptions,
770
- StartWorkerOptions,
771
- WorkerHandle,
772
- } from './jobs/index'
773
- export {
774
- defineAccess,
775
- validateAndResolveAccess,
776
- checkAccess,
777
- AUTH_TABLE_NAMES,
778
- } from './access'
779
- export type {
780
- TableAccessInput,
781
- OperationRule,
782
- AccessContext,
783
- AccessUser,
784
- } from './access'
785
- export {
786
- typeid,
787
- generate as generateTypeId,
788
- parse as parseTypeId,
789
- asTypeId,
790
- } from './typeid'
791
- export type { TypeId } from './typeid'
792
- export type {
793
- DatabaseAdapter,
794
- DatabaseConnectOptions,
795
- DatabaseConnection,
796
- DatabaseConnectionResult,
797
- } from './database/adapter'
798
- export type { StorageAdapter } from './storage/index'
799
- export type {
800
- StorageConfigInput,
801
- BucketConfigInput,
802
- ResolvedBucket,
803
- } from './storage/buckets'
804
- // StorageFacade is declared+exported inline above.
805
- export type { TransformSpec } from './storage/thumbnails'
806
- export { mockAuthSession } from './testing'
807
-
808
- export type { RealtimeAction } from './realtime/publisher'
809
- export { createRealtimeFacade } from './realtime/facade'
810
- export type {
811
- RealtimeFacade,
812
- RealtimeTransport,
813
- SchemaTable,
814
- } from './realtime/facade'
815
-
816
- export { createApiBuilder } from './api/builder'
817
- export type { BunderstackApiBuilder, ApiFactory } from './api/builder'
818
- // Needed to declare shared middleware over the app's context, e.g.
819
- // `os.$context<ApiContext<typeof schema>>().middleware(...)`.
820
- export type { ApiContext } from './api/context'
821
- export type {
822
- CrudApiRouterFor,
823
- ExposedApiTables,
824
- MergeApiRouterTypes,
825
- UnifiedApiRouter,
826
- } from './api/types'
827
- export type { TableCrudProcedures } from './api/crud-router'
828
- export {
829
- buildApiRegistry,
830
- mergeApiRoutersStrict,
831
- normalizeApiPath,
832
- normalizeForeignOpenAPISpec,
833
- } from './api/registry'
834
- export { mergeOpenAPISpecs } from './api/openapi'