bunderstack 0.17.0-beta.0 → 0.17.0-beta.3

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 (337) hide show
  1. package/CHANGELOG.md +227 -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 +76 -0
  60. package/dist/config.d.ts.map +1 -0
  61. package/dist/config.js +81 -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 +477 -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 +81 -29
  272. package/src/access.ts +0 -552
  273. package/src/api/api-types.types.ts +0 -106
  274. package/src/api/builder.ts +0 -52
  275. package/src/api/context.ts +0 -83
  276. package/src/api/crud-router.ts +0 -321
  277. package/src/api/openapi.ts +0 -184
  278. package/src/api/realtime-router.ts +0 -75
  279. package/src/api/registry.ts +0 -338
  280. package/src/api/router.ts +0 -34
  281. package/src/api/storage-router.ts +0 -224
  282. package/src/api/types.ts +0 -84
  283. package/src/auth.ts +0 -117
  284. package/src/blueprint-generator.ts +0 -203
  285. package/src/blueprint.ts +0 -264
  286. package/src/cli.ts +0 -90
  287. package/src/config.ts +0 -215
  288. package/src/cron.ts +0 -3
  289. package/src/crud-operations.ts +0 -488
  290. package/src/database/adapter.ts +0 -26
  291. package/src/database/bun-sql.ts +0 -23
  292. package/src/database/libsql.ts +0 -28
  293. package/src/database/pglite.ts +0 -33
  294. package/src/database/postgres-js.ts +0 -33
  295. package/src/db.ts +0 -61
  296. package/src/dialect.ts +0 -38
  297. package/src/email/smtp.ts +0 -45
  298. package/src/email.ts +0 -154
  299. package/src/env.ts +0 -180
  300. package/src/errors.ts +0 -118
  301. package/src/handler.ts +0 -30
  302. package/src/idempotency.ts +0 -97
  303. package/src/index.ts +0 -822
  304. package/src/internal-tables.ts +0 -149
  305. package/src/jobs/cron.ts +0 -87
  306. package/src/jobs/define.ts +0 -283
  307. package/src/jobs/index.ts +0 -37
  308. package/src/jobs/queue.ts +0 -67
  309. package/src/jobs/runtime.ts +0 -58
  310. package/src/jobs/slots.ts +0 -52
  311. package/src/jobs/worker.ts +0 -369
  312. package/src/lifecycle.ts +0 -49
  313. package/src/list-query.ts +0 -416
  314. package/src/manifest.ts +0 -297
  315. package/src/provision-internals.ts +0 -29
  316. package/src/provision.ts +0 -128
  317. package/src/rate-limit.ts +0 -71
  318. package/src/realtime/facade.ts +0 -55
  319. package/src/realtime/filter.ts +0 -77
  320. package/src/realtime/heartbeat.ts +0 -80
  321. package/src/realtime/publisher.ts +0 -46
  322. package/src/schema-export-pg.ts +0 -7
  323. package/src/schema-export.ts +0 -5
  324. package/src/scope.ts +0 -17
  325. package/src/standard-schema.ts +0 -59
  326. package/src/storage/buckets.ts +0 -308
  327. package/src/storage/delete.ts +0 -26
  328. package/src/storage/file-meta.ts +0 -222
  329. package/src/storage/index.ts +0 -39
  330. package/src/storage/local.ts +0 -69
  331. package/src/storage/operations.ts +0 -398
  332. package/src/storage/registry.ts +0 -40
  333. package/src/storage/s3.ts +0 -99
  334. package/src/storage/sweep.ts +0 -25
  335. package/src/storage/thumbnails.ts +0 -65
  336. package/src/testing.ts +0 -31
  337. package/src/typeid.ts +0 -116
@@ -1,29 +0,0 @@
1
- import type { Driver } from './db'
2
- // src/provision-internals.ts
3
- import type { AnyDb, Dialect } from './dialect'
4
-
5
- /**
6
- * Hidden handle connecting `createBunderstack()` to the optional
7
- * `bunderstack/provision` entry. Lives in its own module so the main entry
8
- * never imports provision code (and its drizzle-kit reference).
9
- */
10
- export const PROVISION_INTERNALS: unique symbol = Symbol.for(
11
- 'bunderstack.provision-internals',
12
- )
13
-
14
- export interface ProvisionInternals {
15
- /** Runtime db typed over the MERGED schema (user + internal tables). */
16
- db: AnyDb
17
- /** Merged schema used for push. */
18
- schema: Record<string, unknown>
19
- databaseUrl: string
20
- /** Resolved migrations folder (config `database.migrations`). */
21
- migrationsFolder: string
22
- dialect: Dialect
23
- driver: Driver
24
- adapter: import('./database/adapter').DatabaseAdapter
25
- }
26
-
27
- export interface WithProvisionInternals {
28
- [PROVISION_INTERNALS]?: ProvisionInternals
29
- }
package/src/provision.ts DELETED
@@ -1,128 +0,0 @@
1
- // src/provision.ts
2
- import { access, mkdir } from 'node:fs/promises'
3
- import { dirname, join } from 'node:path'
4
-
5
- import type { AnyDb, Dialect } from './dialect'
6
-
7
- import { detectDialect } from './dialect'
8
- import {
9
- PROVISION_INTERNALS,
10
- type WithProvisionInternals,
11
- } from './provision-internals'
12
-
13
- /** Create the local backing directory for file-based urls, per dialect. */
14
- async function ensureLocalDataDir(
15
- url: string,
16
- dialect: Dialect,
17
- ): Promise<void> {
18
- if (dialect === 'pg') {
19
- // PGlite data dir: `file:<dir>` or a bare path; server/memory urls need nothing.
20
- if (/^postgres(ql)?:\/\//.test(url)) return
21
- const raw = url.startsWith('file:') ? url.slice('file:'.length) : url
22
- if (raw === ':memory:' || raw.startsWith('memory://')) return
23
- await mkdir(raw, { recursive: true })
24
- return
25
- }
26
- const match = /^file:(.+)$/.exec(url)
27
- if (!match) return
28
- const filePath = match[1]!
29
- if (filePath === ':memory:') return
30
- await mkdir(dirname(filePath), { recursive: true })
31
- }
32
-
33
- async function exists(path: string): Promise<boolean> {
34
- try {
35
- await access(path)
36
- return true
37
- } catch {
38
- return false
39
- }
40
- }
41
-
42
- const DRIZZLE_KIT_HINT =
43
- '[bunderstack] Schema push requires drizzle-kit, which is not installed.\n' +
44
- ' Development: run `bun add -d drizzle-kit` — provision() will push schema changes to the database on startup.\n' +
45
- ' Production: generate migrations locally with `bunx drizzle-kit generate` and commit the folder — provision() applies them without drizzle-kit.'
46
-
47
- /** Push the merged schema to the database via drizzle-kit/api. */
48
- export async function provisionSchema<TSchema extends Record<string, unknown>>(
49
- db: AnyDb,
50
- schema: TSchema,
51
- options?: { force?: boolean; databaseUrl?: string },
52
- ): Promise<void> {
53
- const dialect = detectDialect(schema)
54
- if (options?.databaseUrl) {
55
- await ensureLocalDataDir(options.databaseUrl, dialect)
56
- }
57
-
58
- let kit: typeof import('drizzle-kit/api')
59
- try {
60
- kit = await import('drizzle-kit/api')
61
- } catch (cause) {
62
- throw new Error(DRIZZLE_KIT_HINT, { cause })
63
- }
64
-
65
- const result =
66
- dialect === 'pg'
67
- ? await kit.pushSchema(schema, db as never)
68
- : await kit.pushSQLiteSchema(schema, db as never)
69
-
70
- if (result.hasDataLoss && !options?.force) {
71
- throw new Error(
72
- '[bunderstack] Schema push would cause data loss. Run `bunx drizzle-kit push` or call provision(app, { force: true }).',
73
- )
74
- }
75
-
76
- if (result.warnings.length > 0) {
77
- for (const warning of result.warnings) {
78
- console.warn(`[bunderstack] ${warning}`)
79
- }
80
- }
81
-
82
- if (result.statementsToExecute.length === 0) return
83
-
84
- await result.apply()
85
-
86
- console.log(
87
- `[bunderstack] provisioned ${result.statementsToExecute.length} schema change(s)`,
88
- )
89
- }
90
-
91
- /**
92
- * Provision the database for a Bunderstack app.
93
- *
94
- * The migrations folder is the mode switch:
95
- * - `<migrations>/meta/_journal.json` exists → apply committed migrations via
96
- * drizzle-orm's migrator. Pure runtime; drizzle-kit is never imported, so a
97
- * fresh clone deploys with `bun install --production`.
98
- * - No migrations → development: push the schema straight to the database via
99
- * drizzle-kit (install it with `bun add -d drizzle-kit`).
100
- *
101
- * Once you run `bunx drizzle-kit generate` for the first time, provision stops
102
- * pushing and every schema change goes through an explicit `generate`.
103
- */
104
- export async function provision(
105
- app: object,
106
- options?: { force?: boolean },
107
- ): Promise<void> {
108
- if (process.env.BUNDERSTACK_INTROSPECT === '1') return
109
-
110
- const internals = (app as WithProvisionInternals)[PROVISION_INTERNALS]
111
- if (!internals) {
112
- throw new Error(
113
- '[bunderstack] provision() expects the app returned by createBunderstack().',
114
- )
115
- }
116
-
117
- const { db, schema, databaseUrl, migrationsFolder, dialect, adapter } =
118
- internals
119
- const journal = join(migrationsFolder, 'meta', '_journal.json')
120
-
121
- if (await exists(journal)) {
122
- await ensureLocalDataDir(databaseUrl, dialect)
123
- await adapter.migrate(db as never, migrationsFolder)
124
- return
125
- }
126
-
127
- await provisionSchema(db, schema, { force: options?.force, databaseUrl })
128
- }
package/src/rate-limit.ts DELETED
@@ -1,71 +0,0 @@
1
- export type RateLimitConfig = {
2
- windowMs?: number
3
- max?: number
4
- skip?: (req: Request) => boolean
5
- }
6
-
7
- type Bucket = {
8
- count: number
9
- resetAt: number
10
- }
11
-
12
- const buckets = new Map<string, Bucket>()
13
-
14
- function resolveConfig(
15
- config: boolean | RateLimitConfig | undefined,
16
- ): RateLimitConfig | null {
17
- if (!config) return null
18
- if (config === true) return {}
19
- return config
20
- }
21
-
22
- function clientKey(req: Request): string {
23
- const forwarded = req.headers.get('x-forwarded-for')
24
- if (forwarded) return forwarded.split(',')[0]!.trim()
25
- return req.headers.get('x-real-ip') ?? 'local'
26
- }
27
-
28
- export function createRateLimiter(
29
- config: boolean | RateLimitConfig | undefined,
30
- ): (req: Request) => Promise<Response | null> {
31
- const resolved = resolveConfig(config)
32
- if (!resolved) {
33
- return async (_req: Request) => null
34
- }
35
-
36
- const windowMs = resolved.windowMs ?? 60_000
37
- const max = resolved.max ?? 100
38
-
39
- return async (req: Request): Promise<Response | null> => {
40
- if (resolved.skip?.(req)) return null
41
-
42
- const key = `${clientKey(req)}:${new URL(req.url).pathname}`
43
- const now = Date.now()
44
- let bucket = buckets.get(key)
45
-
46
- if (!bucket || bucket.resetAt <= now) {
47
- bucket = { count: 0, resetAt: now + windowMs }
48
- buckets.set(key, bucket)
49
- }
50
-
51
- bucket.count += 1
52
- if (bucket.count > max) {
53
- const retryAfter = Math.ceil((bucket.resetAt - now) / 1000)
54
- return new Response(
55
- JSON.stringify({
56
- error: 'Too many requests',
57
- code: 'RATE_LIMITED',
58
- }),
59
- {
60
- status: 429,
61
- headers: {
62
- 'Content-Type': 'application/json',
63
- 'Retry-After': String(retryAfter),
64
- },
65
- },
66
- )
67
- }
68
-
69
- return null
70
- }
71
- }
@@ -1,55 +0,0 @@
1
- import { getTableName, type InferSelectModel, type Table } from 'drizzle-orm'
2
-
3
- import type {
4
- RealtimeAction,
5
- RealtimePublisher,
6
- } from './publisher'
7
-
8
- export type RealtimeTransport = 'disabled' | 'memory' | 'redis'
9
-
10
- export type SchemaTable<TSchema extends Record<string, unknown>> = Extract<
11
- TSchema[keyof TSchema],
12
- Table
13
- >
14
-
15
- export interface RealtimeFacade<
16
- TSchema extends Record<string, unknown> = Record<string, unknown>,
17
- > {
18
- readonly enabled: boolean
19
- readonly transport: RealtimeTransport
20
-
21
- publish<TTable extends SchemaTable<TSchema>>(
22
- table: TTable,
23
- action: RealtimeAction,
24
- record: InferSelectModel<TTable>,
25
- ): Promise<void>
26
- }
27
-
28
- export function createRealtimeFacade<TSchema extends Record<string, unknown>>(
29
- publisher?: RealtimePublisher,
30
- transport: RealtimeTransport = publisher ? 'memory' : 'disabled',
31
- ): RealtimeFacade<TSchema> {
32
- if (!publisher && transport !== 'disabled') {
33
- throw new Error(
34
- '[bunderstack] an enabled realtime transport requires a publisher',
35
- )
36
- }
37
- if (publisher && transport === 'disabled') {
38
- throw new Error(
39
- '[bunderstack] a realtime publisher cannot use the disabled transport',
40
- )
41
- }
42
-
43
- return {
44
- enabled: publisher !== undefined,
45
- transport,
46
- async publish(table, action, record) {
47
- if (!publisher) return
48
- await publisher.publish('change', {
49
- table: getTableName(table),
50
- action,
51
- record: record as unknown as Record<string, unknown>,
52
- })
53
- },
54
- }
55
- }
@@ -1,77 +0,0 @@
1
- import { getEventMeta, withEventMeta } from '@orpc/server'
2
-
3
- import type {
4
- AccessUser,
5
- ResolvedAccess,
6
- } from '../access'
7
- import type { RealtimeChange } from './publisher'
8
-
9
- import {
10
- checkAccess,
11
- rowMatchesScope,
12
- tableEntryForName,
13
- } from '../access'
14
-
15
- export interface FilterRealtimeChangesOptions {
16
- subscriptions: readonly string[]
17
- access: ResolvedAccess
18
- request: Request
19
- getSession: () => Promise<{
20
- user: AccessUser | null
21
- activeOrganizationId: string | null
22
- }>
23
- }
24
-
25
- export async function* filterRealtimeChanges(
26
- source: AsyncIterable<RealtimeChange>,
27
- options: FilterRealtimeChangesOptions,
28
- ): AsyncGenerator<RealtimeChange, void, void> {
29
- const subscriptions = new Set(options.subscriptions)
30
- let sessionPromise:
31
- | ReturnType<FilterRealtimeChangesOptions['getSession']>
32
- | undefined
33
- const getSession = () => (sessionPromise ??= options.getSession())
34
-
35
- for await (const change of source) {
36
- const entry = tableEntryForName(options.access, change.table)
37
- if (!entry?.enabled) continue
38
-
39
- const recordId = change.record.id
40
- if (
41
- !subscriptions.has(change.table) &&
42
- (recordId == null ||
43
- !subscriptions.has(`${change.table}/${String(recordId)}`))
44
- ) {
45
- continue
46
- }
47
- if (entry.get === 'deny') continue
48
-
49
- const needsSession = entry.get !== 'public' || entry.readScope !== undefined
50
- const session = needsSession
51
- ? await getSession()
52
- : { user: null, activeOrganizationId: null }
53
- const context = {
54
- request: options.request,
55
- user: session.user,
56
- row: change.record,
57
- session: { activeOrganizationId: session.activeOrganizationId },
58
- }
59
- if (!(await checkAccess(entry.get, context, entry.ownerColumn)).allowed) {
60
- continue
61
- }
62
- if (
63
- entry.readScope &&
64
- !rowMatchesScope(change.record, entry.readScope(context))
65
- ) {
66
- continue
67
- }
68
-
69
- const projected: RealtimeChange = {
70
- table: change.table,
71
- action: change.action,
72
- record: change.record,
73
- }
74
- const meta = getEventMeta(change)
75
- yield meta ? withEventMeta(projected, meta) : projected
76
- }
77
- }
@@ -1,80 +0,0 @@
1
- export const REALTIME_HEARTBEAT_INTERVAL_MS = 5_000
2
-
3
- export type RealtimeHeartbeat = { type: 'heartbeat' }
4
-
5
- type SourceState<T> =
6
- | { status: 'pending' }
7
- | { status: 'ready'; result: IteratorResult<T> }
8
- | { status: 'error'; error: unknown }
9
-
10
- /**
11
- * Emits a transport-only event whenever the source has been idle for an
12
- * interval. Heartbeats are deliberately not published, persisted, or assigned
13
- * event IDs, so they do not affect replay and resume semantics.
14
- */
15
- export async function* withRealtimeHeartbeat<T>(
16
- source: AsyncIterable<T>,
17
- options: { intervalMs?: number; signal?: AbortSignal },
18
- ): AsyncGenerator<T | RealtimeHeartbeat, void, void> {
19
- const intervalMs = Math.max(
20
- 1,
21
- options.intervalMs ?? REALTIME_HEARTBEAT_INTERVAL_MS,
22
- )
23
- const iterator = source[Symbol.asyncIterator]()
24
- let state: SourceState<T> = { status: 'pending' }
25
- let wake: (() => void) | undefined
26
- const getState = (): SourceState<T> => state
27
-
28
- const requestNext = () => {
29
- state = { status: 'pending' }
30
- void iterator.next().then(
31
- (result) => {
32
- state = { status: 'ready', result }
33
- wake?.()
34
- },
35
- (error: unknown) => {
36
- state = { status: 'error', error }
37
- wake?.()
38
- },
39
- )
40
- }
41
-
42
- requestNext()
43
- try {
44
- while (!options.signal?.aborted) {
45
- let current = getState()
46
- if (current.status === 'pending') {
47
- await new Promise<void>((resolve) => {
48
- let settled = false
49
- const finish = () => {
50
- if (settled) return
51
- settled = true
52
- clearTimeout(timer)
53
- options.signal?.removeEventListener('abort', finish)
54
- resolve()
55
- }
56
- const timer = setTimeout(finish, intervalMs)
57
- options.signal?.addEventListener('abort', finish, { once: true })
58
- wake = finish
59
- })
60
- wake = undefined
61
-
62
- if (options.signal?.aborted) break
63
- current = getState()
64
- if (current.status === 'pending') {
65
- yield { type: 'heartbeat' }
66
- continue
67
- }
68
- }
69
-
70
- if (current.status === 'error') throw current.error
71
- if (current.result.done) break
72
-
73
- yield current.result.value
74
- requestNext()
75
- }
76
- } finally {
77
- wake = undefined
78
- await iterator.return?.()
79
- }
80
- }
@@ -1,46 +0,0 @@
1
- import type { Publisher } from '@orpc/publisher'
2
- import type { RedisClient } from 'bun'
3
-
4
- import { BunRedisPublisher } from '@orpc/bun'
5
- import { MemoryPublisher } from '@orpc/publisher/memory'
6
-
7
- export type RealtimeAction = 'create' | 'update' | 'delete'
8
-
9
- export interface RealtimeChange {
10
- table: string
11
- action: RealtimeAction
12
- record: Record<string, unknown>
13
- }
14
-
15
- export interface RealtimeEvents extends Record<string, object> {
16
- change: RealtimeChange
17
- }
18
-
19
- export type RealtimePublisher = Publisher<RealtimeEvents>
20
-
21
- export interface RealtimePublisherOptions {
22
- maxBufferedEvents?: number
23
- resumeSeconds?: number
24
- }
25
-
26
- export function createMemoryRealtimePublisher(
27
- options: RealtimePublisherOptions = {},
28
- ): RealtimePublisher {
29
- return new MemoryPublisher<RealtimeEvents>({
30
- maxBufferedEvents: options.maxBufferedEvents,
31
- resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
32
- })
33
- }
34
-
35
- export function createRedisRealtimePublisher(
36
- redis: RedisClient,
37
- subscriber: RedisClient | Promise<RedisClient>,
38
- options: RealtimePublisherOptions & { prefix?: string } = {},
39
- ): RealtimePublisher {
40
- return new BunRedisPublisher<RealtimeEvents>(redis, {
41
- subscriber,
42
- prefix: options.prefix ?? 'bunderstack:',
43
- maxBufferedEvents: options.maxBufferedEvents,
44
- resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
45
- })
46
- }
@@ -1,7 +0,0 @@
1
- // src/schema-export-pg.ts — pg twins under the same names bunderstack/schema
2
- // uses, so `export * from 'bunderstack/schema/pg'` mirrors the sqlite setup.
3
- export {
4
- bunderstackFilesPg as bunderstackFiles,
5
- bunderstackIdempotencyPg as bunderstackIdempotency,
6
- bunderstackJobsPg as bunderstackJobs,
7
- } from './internal-tables-pg'
@@ -1,5 +0,0 @@
1
- export {
2
- bunderstackFiles,
3
- bunderstackIdempotency,
4
- bunderstackJobs,
5
- } from './internal-tables'
package/src/scope.ts DELETED
@@ -1,17 +0,0 @@
1
- import { and, eq, getTableColumns, inArray, type SQL } from 'drizzle-orm'
2
-
3
- import type { ScopeMap } from './access'
4
-
5
- export function buildScopeWhere(
6
- table: Parameters<typeof getTableColumns>[0],
7
- scope: ScopeMap,
8
- ): SQL | undefined {
9
- const columns = getTableColumns(table)
10
- const conditions: SQL[] = []
11
- for (const [name, value] of Object.entries(scope)) {
12
- const col = columns[name]
13
- if (!col) continue
14
- conditions.push(Array.isArray(value) ? inArray(col, value) : eq(col, value))
15
- }
16
- return conditions.length ? and(...conditions) : undefined
17
- }
@@ -1,59 +0,0 @@
1
- import type { StandardSchemaV1 } from '@standard-schema/spec'
2
-
3
- export type StandardSchema = StandardSchemaV1
4
- export type InferStandardOutput<TSchema extends StandardSchemaV1> =
5
- StandardSchemaV1.InferOutput<TSchema>
6
-
7
- export type StandardSchemaIssue = {
8
- path: PropertyKey[]
9
- message: string
10
- }
11
-
12
- export class StandardSchemaValidationError extends Error {
13
- readonly issues: StandardSchemaIssue[]
14
-
15
- constructor(label: string, issues: readonly StandardSchemaV1.Issue[]) {
16
- const normalized = issues.map((issue) => ({
17
- path: [...(issue.path ?? [])].map((segment) =>
18
- typeof segment === 'object' ? segment.key : segment,
19
- ),
20
- message: issue.message,
21
- }))
22
- super(
23
- normalized
24
- .map((issue) => {
25
- const path = issue.path.map(String).join('.')
26
- return `${label}${path ? `.${path}` : ''}: ${issue.message}`
27
- })
28
- .join('\n'),
29
- )
30
- this.name = 'StandardSchemaValidationError'
31
- this.issues = normalized
32
- }
33
- }
34
-
35
- function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
36
- return (
37
- typeof value === 'object' &&
38
- value !== null &&
39
- 'then' in value &&
40
- typeof value.then === 'function'
41
- )
42
- }
43
-
44
- export function validateStandardSchema<TSchema extends StandardSchemaV1>(
45
- schema: TSchema,
46
- value: unknown,
47
- label: string,
48
- ): StandardSchemaV1.InferOutput<TSchema> {
49
- const result = schema['~standard'].validate(value)
50
- if (isPromiseLike(result)) {
51
- throw new Error(
52
- `[bunderstack] ${label} schema validation must be synchronous`,
53
- )
54
- }
55
- if (result.issues) {
56
- throw new StandardSchemaValidationError(label, result.issues)
57
- }
58
- return result.value
59
- }