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/auth.ts DELETED
@@ -1,117 +0,0 @@
1
- // src/auth.ts
2
- import { betterAuth } from 'better-auth'
3
- import { drizzleAdapter } from 'better-auth/adapters/drizzle'
4
- import { openAPI } from 'better-auth/plugins'
5
-
6
- import type { AuthSessionResolver } from './access'
7
- import type { BetterAuthConfig } from './config'
8
- import type { AnyDb, Dialect } from './dialect'
9
- import type { EmailFacade } from './email'
10
-
11
- export function createAuth(
12
- db: AnyDb,
13
- cfg: BetterAuthConfig,
14
- dialect: Dialect,
15
- userSchema?: Record<string, unknown>,
16
- ) {
17
- const hasOpenApi = cfg.plugins?.some((p: any) => p.id === 'open-api')
18
- const plugins = hasOpenApi ? cfg.plugins : [...(cfg.plugins || []), openAPI()]
19
-
20
- return betterAuth({
21
- ...cfg,
22
- plugins,
23
- database: drizzleAdapter(db as Parameters<typeof drizzleAdapter>[0], {
24
- provider: dialect === 'pg' ? 'pg' : 'sqlite',
25
- ...(userSchema ? { schema: userSchema } : {}),
26
- }),
27
- })
28
- }
29
-
30
- /**
31
- * Adapt the raw better-auth instance to our internal {@link AuthSessionResolver}
32
- * contract. better-auth's `getSession` has a union return (a bare session, or a
33
- * `{ headers, response }` wrapper when `returnHeaders` is set); we only ever
34
- * call the bare form, so we narrow on `'user' in result` and map to our shape.
35
- * Keeping this adapter here means internal modules never depend on better-auth's
36
- * evolving types.
37
- */
38
- export function toAuthSessionResolver(
39
- auth: ReturnType<typeof createAuth>,
40
- ): AuthSessionResolver {
41
- return {
42
- api: {
43
- async getSession({ headers }) {
44
- const result = await auth.api.getSession({ headers })
45
- if (result && 'user' in result && result.user) {
46
- const session = 'session' in result ? result.session : null
47
- const activeOrganizationId =
48
- session &&
49
- 'activeOrganizationId' in session &&
50
- typeof session.activeOrganizationId === 'string'
51
- ? session.activeOrganizationId
52
- : null
53
- const role =
54
- 'role' in result.user && typeof result.user.role === 'string'
55
- ? result.user.role
56
- : undefined
57
- return {
58
- user: {
59
- id: result.user.id,
60
- email: result.user.email,
61
- name: result.user.name,
62
- ...(role ? { role } : {}),
63
- },
64
- session: session ? { activeOrganizationId } : null,
65
- }
66
- }
67
- return null
68
- },
69
- },
70
- }
71
- }
72
-
73
- /**
74
- * Fill better-auth's email hooks from the bunderstack email facade. Only fills
75
- * gaps: user-supplied handlers always win, and nothing is injected when email
76
- * isn't configured. emailAndPassword is only touched when the user enabled it
77
- * (injecting it unasked would enable the feature).
78
- */
79
- export function withEmailAuthDefaults(
80
- cfg: BetterAuthConfig,
81
- email: EmailFacade,
82
- emailConfigured: boolean,
83
- ): BetterAuthConfig {
84
- if (!emailConfigured) return cfg
85
- const out: BetterAuthConfig = { ...cfg }
86
-
87
- if (
88
- cfg.emailAndPassword?.enabled &&
89
- !cfg.emailAndPassword.sendResetPassword
90
- ) {
91
- out.emailAndPassword = {
92
- ...cfg.emailAndPassword,
93
- sendResetPassword: async ({ user, url }) => {
94
- await email.send({
95
- to: user.email,
96
- subject: 'Reset your password',
97
- text: `Click the link to reset your password:\n\n${url}\n\nIf you didn't request this, you can ignore this email.`,
98
- })
99
- },
100
- }
101
- }
102
-
103
- if (!cfg.emailVerification?.sendVerificationEmail) {
104
- out.emailVerification = {
105
- ...cfg.emailVerification,
106
- sendVerificationEmail: async ({ user, url }) => {
107
- await email.send({
108
- to: user.email,
109
- subject: 'Verify your email',
110
- text: `Click the link to verify your email address:\n\n${url}`,
111
- })
112
- },
113
- }
114
- }
115
-
116
- return out
117
- }
@@ -1,203 +0,0 @@
1
- import {
2
- mkdir,
3
- readFile,
4
- realpath,
5
- rename,
6
- rm,
7
- writeFile,
8
- } from 'node:fs/promises'
9
- import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
10
- import { pathToFileURL } from 'node:url'
11
-
12
- import {
13
- blueprintFromManifest,
14
- serializeBlueprint,
15
- type BunderstackBlueprint,
16
- } from './blueprint'
17
- import { parseManifest } from './manifest'
18
-
19
- export type GenerateBlueprintOptions = {
20
- directory: string
21
- entry?: string
22
- output?: string
23
- check?: boolean
24
- }
25
-
26
- export type GenerateBlueprintResult = {
27
- path: string
28
- blueprint: BunderstackBlueprint
29
- source: string
30
- changed: boolean
31
- }
32
-
33
- export class BlueprintCheckError extends Error {
34
- constructor() {
35
- super(
36
- 'bunderstack.blueprint.yaml is missing or stale; run `bunderstack blueprint`',
37
- )
38
- this.name = 'BlueprintCheckError'
39
- }
40
- }
41
-
42
- type AppPackage = {
43
- scripts?: Record<string, unknown>
44
- dependencies?: Record<string, unknown>
45
- devDependencies?: Record<string, unknown>
46
- bunderstack?: { entry?: unknown }
47
- }
48
-
49
- function requireRelativePath(value: string, label: string): string {
50
- const normalized = value.replaceAll('\\', '/')
51
- if (
52
- !normalized ||
53
- isAbsolute(normalized) ||
54
- normalized.split('/').some((part) => !part || part === '..')
55
- ) {
56
- throw new Error(
57
- `[bunderstack] ${label} must be a relative path without traversal`,
58
- )
59
- }
60
- return normalized
61
- }
62
-
63
- function resolveWithin(root: string, value: string, label: string): string {
64
- const path = resolve(root, requireRelativePath(value, label))
65
- const pathFromRoot = relative(root, path)
66
- if (
67
- pathFromRoot === '..' ||
68
- pathFromRoot.startsWith('../') ||
69
- isAbsolute(pathFromRoot)
70
- ) {
71
- throw new Error(
72
- `[bunderstack] ${label} must stay within the application directory`,
73
- )
74
- }
75
- return path
76
- }
77
-
78
- function normalizeProjectPath(
79
- root: string,
80
- value: string,
81
- label: string,
82
- ): string {
83
- if (!isAbsolute(value)) return requireRelativePath(value, label)
84
- const pathFromRoot = relative(root, resolve(value)) || '.'
85
- return requireRelativePath(pathFromRoot, label)
86
- }
87
-
88
- function requireScript(
89
- pkg: AppPackage,
90
- name: 'build' | 'start' | 'worker',
91
- required: boolean,
92
- ): boolean {
93
- const value = pkg.scripts?.[name]
94
- if (typeof value === 'string' && value.trim()) return true
95
- if (required)
96
- throw new Error(
97
- `[bunderstack] package.json requires a non-empty "${name}" script`,
98
- )
99
- return false
100
- }
101
-
102
- async function packageVersion(): Promise<string> {
103
- const pkg = (await Bun.file(
104
- new URL('../package.json', import.meta.url),
105
- ).json()) as { version: string }
106
- return pkg.version
107
- }
108
-
109
- export async function generateBlueprint(
110
- options: GenerateBlueprintOptions,
111
- ): Promise<GenerateBlueprintResult> {
112
- const directory = await realpath(resolve(options.directory))
113
- const packagePath = join(directory, 'package.json')
114
- const pkg = JSON.parse(await readFile(packagePath, 'utf8')) as AppPackage
115
- const allDependencies = { ...pkg.dependencies, ...pkg.devDependencies }
116
- if (typeof allDependencies['@tanstack/react-start'] !== 'string') {
117
- throw new Error(
118
- '[bunderstack] package.json must depend on @tanstack/react-start',
119
- )
120
- }
121
- requireScript(pkg, 'build', true)
122
- requireScript(pkg, 'start', true)
123
-
124
- const configuredEntry = pkg.bunderstack?.entry
125
- const entry = requireRelativePath(
126
- options.entry ??
127
- (typeof configuredEntry === 'string'
128
- ? configuredEntry
129
- : 'src/bunderstack.ts'),
130
- 'entry',
131
- )
132
- const entryPath = resolveWithin(directory, entry, 'entry')
133
- if (!(await Bun.file(entryPath).exists())) {
134
- throw new Error(`[bunderstack] entry does not exist: ${entry}`)
135
- }
136
- const output = requireRelativePath(
137
- options.output ?? 'bunderstack.blueprint.yaml',
138
- 'output',
139
- )
140
- const outputPath = resolveWithin(directory, output, 'output')
141
-
142
- const previousIntrospection = process.env.BUNDERSTACK_INTROSPECT
143
- process.env.BUNDERSTACK_INTROSPECT = '1'
144
- let app: { manifest?: unknown; close?: () => Promise<void> } | undefined
145
- try {
146
- const module = (await import(
147
- `${pathToFileURL(entryPath).href}?blueprint=${Date.now()}`
148
- )) as {
149
- app?: typeof app
150
- }
151
- app = module.app
152
- if (!app) throw new Error(`[bunderstack] ${entry} must export app`)
153
- const manifest = parseManifest(app.manifest)
154
- const workerRequired = manifest.background.jobs.length > 0
155
- requireScript(pkg, 'worker', workerRequired)
156
- const migrationsDirectory = normalizeProjectPath(
157
- directory,
158
- manifest.database.migrationsDirectory,
159
- 'migrationsDirectory',
160
- )
161
- const migrationJournal = join(
162
- resolveWithin(directory, migrationsDirectory, 'migrationsDirectory'),
163
- 'meta',
164
- '_journal.json',
165
- )
166
- const migrationMode = (await Bun.file(migrationJournal).exists())
167
- ? 'migrations'
168
- : 'push'
169
- const blueprint = blueprintFromManifest({
170
- manifest: {
171
- ...manifest,
172
- database: { ...manifest.database, migrationsDirectory },
173
- },
174
- generatorVersion: await packageVersion(),
175
- entry,
176
- migrationMode,
177
- })
178
- const source = serializeBlueprint(blueprint)
179
- const existing = (await Bun.file(outputPath).exists())
180
- ? await Bun.file(outputPath).text()
181
- : undefined
182
- if (options.check) {
183
- if (existing !== source) throw new BlueprintCheckError()
184
- return { path: outputPath, blueprint, source, changed: false }
185
- }
186
- if (existing === source)
187
- return { path: outputPath, blueprint, source, changed: false }
188
- await mkdir(dirname(outputPath), { recursive: true })
189
- const temporary = `${outputPath}.${process.pid}.${Date.now()}.tmp`
190
- try {
191
- await writeFile(temporary, source, { mode: 0o600 })
192
- await rename(temporary, outputPath)
193
- } finally {
194
- await rm(temporary, { force: true })
195
- }
196
- return { path: outputPath, blueprint, source, changed: true }
197
- } finally {
198
- await app?.close?.()
199
- if (previousIntrospection === undefined)
200
- delete process.env.BUNDERSTACK_INTROSPECT
201
- else process.env.BUNDERSTACK_INTROSPECT = previousIntrospection
202
- }
203
- }
package/src/blueprint.ts DELETED
@@ -1,264 +0,0 @@
1
- import * as v from 'valibot'
2
- import { parse, stringify } from 'yaml'
3
-
4
- import type { BunderstackManifest } from './manifest'
5
-
6
- import { parseCron } from './jobs/cron'
7
- import { validateStandardSchema } from './standard-schema'
8
-
9
- export type MigrationMode = 'migrations' | 'push'
10
-
11
- export type BunderstackBlueprint = {
12
- version: 1
13
- generator: { name: 'bunderstack'; version: string }
14
- application: {
15
- framework: 'tanstack-start'
16
- scripts: { build: 'build'; start: 'start'; worker?: 'worker' }
17
- }
18
- bunderstack: { entry: string; manifestVersion: 3 }
19
- resources: {
20
- database: BunderstackManifest['database'] & { migrationMode: MigrationMode }
21
- storage: BunderstackManifest['storage']
22
- realtime?: { required: true }
23
- }
24
- environment: BunderstackManifest['environment']
25
- background: BunderstackManifest['background'] & {
26
- worker: { required: boolean }
27
- }
28
- }
29
-
30
- const nonEmpty = v.pipe(v.string(), v.minLength(1))
31
- const relativePath = v.pipe(
32
- nonEmpty,
33
- v.check(
34
- (value) =>
35
- !value.startsWith('/') &&
36
- !value.includes('\\') &&
37
- value.split('/').every((part) => part !== '' && part !== '..'),
38
- 'entry must be a relative path without traversal',
39
- ),
40
- )
41
- const cronSchedule = v.pipe(
42
- nonEmpty,
43
- v.check((value) => {
44
- try {
45
- parseCron(value)
46
- return true
47
- } catch {
48
- return false
49
- }
50
- }, 'invalid cron schedule'),
51
- )
52
-
53
- const blueprintSchema = v.strictObject({
54
- version: v.literal(1),
55
- generator: v.strictObject({
56
- name: v.literal('bunderstack'),
57
- version: nonEmpty,
58
- }),
59
- application: v.strictObject({
60
- framework: v.literal('tanstack-start'),
61
- scripts: v.strictObject({
62
- build: v.literal('build'),
63
- start: v.literal('start'),
64
- worker: v.optional(v.literal('worker')),
65
- }),
66
- }),
67
- bunderstack: v.strictObject({
68
- entry: relativePath,
69
- manifestVersion: v.literal(3),
70
- }),
71
- resources: v.strictObject({
72
- database: v.strictObject({
73
- dialect: v.picklist(['sqlite', 'pg']),
74
- migrationsDirectory: relativePath,
75
- migrationMode: v.picklist(['migrations', 'push']),
76
- tables: v.array(
77
- v.strictObject({
78
- exportName: nonEmpty,
79
- physicalName: nonEmpty,
80
- system: v.boolean(),
81
- }),
82
- ),
83
- }),
84
- storage: v.strictObject({
85
- defaultBucket: nonEmpty,
86
- buckets: v.array(
87
- v.strictObject({
88
- name: nonEmpty,
89
- visibility: v.picklist(['public', 'private']),
90
- }),
91
- ),
92
- }),
93
- realtime: v.optional(v.strictObject({ required: v.literal(true) })),
94
- }),
95
- environment: v.array(
96
- v.strictObject({
97
- key: nonEmpty,
98
- required: v.boolean(),
99
- scope: v.picklist(['server', 'client']),
100
- }),
101
- ),
102
- background: v.strictObject({
103
- worker: v.strictObject({ required: v.boolean() }),
104
- jobs: v.array(v.strictObject({ name: nonEmpty })),
105
- cron: v.array(
106
- v.strictObject({
107
- name: nonEmpty,
108
- schedule: cronSchedule,
109
- timezone: v.literal('UTC'),
110
- }),
111
- ),
112
- maintenance: v.array(
113
- v.strictObject({
114
- name: v.literal('storage-sweep'),
115
- schedule: cronSchedule,
116
- timezone: v.literal('UTC'),
117
- }),
118
- ),
119
- }),
120
- })
121
-
122
- function sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {
123
- return [...entries].sort((left, right) => key(left).localeCompare(key(right)))
124
- }
125
-
126
- function rejectDuplicates(collection: string, values: readonly string[]): void {
127
- const seen = new Set<string>()
128
- for (const value of values) {
129
- if (seen.has(value))
130
- throw new Error(`[bunderstack] duplicate ${collection} "${value}"`)
131
- seen.add(value)
132
- }
133
- }
134
-
135
- export function parseBlueprint(value: unknown): BunderstackBlueprint {
136
- const blueprint = validateStandardSchema(
137
- blueprintSchema,
138
- value,
139
- 'blueprint',
140
- ) as BunderstackBlueprint
141
- rejectDuplicates(
142
- 'database physical table',
143
- blueprint.resources.database.tables.map((entry) => entry.physicalName),
144
- )
145
- rejectDuplicates(
146
- 'database export table',
147
- blueprint.resources.database.tables.map((entry) => entry.exportName),
148
- )
149
- rejectDuplicates(
150
- 'storage bucket',
151
- blueprint.resources.storage.buckets.map((entry) => entry.name),
152
- )
153
- rejectDuplicates(
154
- 'environment key',
155
- blueprint.environment.map((entry) => entry.key),
156
- )
157
- rejectDuplicates(
158
- 'background job',
159
- blueprint.background.jobs.map((entry) => entry.name),
160
- )
161
- rejectDuplicates(
162
- 'background cron',
163
- blueprint.background.cron.map((entry) => entry.name),
164
- )
165
- rejectDuplicates(
166
- 'background maintenance',
167
- blueprint.background.maintenance.map((entry) => entry.name),
168
- )
169
- if (
170
- !blueprint.resources.storage.buckets.some(
171
- (bucket) => bucket.name === blueprint.resources.storage.defaultBucket,
172
- )
173
- ) {
174
- throw new Error(
175
- '[bunderstack] storage defaultBucket must be declared in storage buckets',
176
- )
177
- }
178
- const workerRequired = blueprint.background.jobs.length > 0
179
- if (blueprint.background.worker.required !== workerRequired) {
180
- throw new Error(
181
- '[bunderstack] background worker.required must match declared queue jobs',
182
- )
183
- }
184
- if (Boolean(blueprint.application.scripts.worker) !== workerRequired) {
185
- throw new Error(
186
- '[bunderstack] application worker script must match declared queue jobs',
187
- )
188
- }
189
- return blueprint
190
- }
191
-
192
- export function blueprintFromManifest(args: {
193
- manifest: BunderstackManifest
194
- generatorVersion: string
195
- entry: string
196
- migrationMode: MigrationMode
197
- }): BunderstackBlueprint {
198
- const workerRequired = args.manifest.background.jobs.length > 0
199
- return parseBlueprint({
200
- version: 1,
201
- generator: { name: 'bunderstack', version: args.generatorVersion },
202
- application: {
203
- framework: 'tanstack-start',
204
- scripts: {
205
- build: 'build',
206
- start: 'start',
207
- ...(workerRequired ? { worker: 'worker' } : {}),
208
- },
209
- },
210
- bunderstack: { entry: args.entry, manifestVersion: 3 },
211
- resources: {
212
- database: {
213
- ...args.manifest.database,
214
- migrationMode: args.migrationMode,
215
- tables: sortBy(
216
- args.manifest.database.tables,
217
- (entry) => entry.physicalName,
218
- ),
219
- },
220
- storage: {
221
- ...args.manifest.storage,
222
- buckets: sortBy(args.manifest.storage.buckets, (entry) => entry.name),
223
- },
224
- ...(args.manifest.realtime.required
225
- ? { realtime: { required: true } }
226
- : {}),
227
- },
228
- environment: sortBy(args.manifest.environment, (entry) => entry.key),
229
- background: {
230
- worker: { required: workerRequired },
231
- jobs: sortBy(args.manifest.background.jobs, (entry) => entry.name),
232
- cron: sortBy(args.manifest.background.cron, (entry) => entry.name),
233
- maintenance: sortBy(
234
- args.manifest.background.maintenance,
235
- (entry) => entry.name,
236
- ),
237
- },
238
- })
239
- }
240
-
241
- export function parseBlueprintYaml(source: string): BunderstackBlueprint {
242
- return parseBlueprint(parse(source) as unknown)
243
- }
244
-
245
- export function serializeBlueprint(value: BunderstackBlueprint): string {
246
- const blueprint = parseBlueprint(value)
247
- const options = {
248
- aliasDuplicateObjects: false,
249
- defaultKeyType: 'PLAIN',
250
- defaultStringType: 'PLAIN',
251
- lineWidth: 0,
252
- } as const
253
- const source = stringify(
254
- parseBlueprintYaml(stringify(blueprint, options)),
255
- options,
256
- )
257
- return source.replace(
258
- /^(\s*schedule: )(.+)$/gm,
259
- (_match, prefix: string, value: string) => {
260
- const schedule = value.startsWith('"') ? JSON.parse(value) : value
261
- return `${prefix}${JSON.stringify(schedule)}`
262
- },
263
- )
264
- }
package/src/cli.ts DELETED
@@ -1,90 +0,0 @@
1
- #!/usr/bin/env bun
2
- import {
3
- BlueprintCheckError,
4
- generateBlueprint,
5
- type GenerateBlueprintOptions,
6
- } from './blueprint-generator'
7
-
8
- export type CliIo = {
9
- stdout(message: string): void
10
- stderr(message: string): void
11
- }
12
-
13
- const help = `Usage: bunderstack blueprint [directory] [--entry <path>] [--output <path>] [--check]
14
-
15
- Generate a committed deployment declaration for a TanStack Start application.
16
- Entry precedence: --entry, package.json#bunderstack.entry, src/bunderstack.ts.`
17
-
18
- export async function runCli(
19
- args: string[],
20
- io: CliIo,
21
- generate: typeof generateBlueprint = generateBlueprint,
22
- ): Promise<number> {
23
- if (args[0] === '--help' || args[0] === '-h') {
24
- io.stdout(help)
25
- return 0
26
- }
27
- if (args[0] === '--version') {
28
- io.stdout(
29
- (
30
- (await Bun.file(
31
- new URL('../package.json', import.meta.url),
32
- ).json()) as { version: string }
33
- ).version,
34
- )
35
- return 0
36
- }
37
- if (args[0] !== 'blueprint') {
38
- io.stderr(
39
- 'Usage: bunderstack blueprint [directory] [--entry <path>] [--output <path>] [--check]',
40
- )
41
- return 2
42
- }
43
- const options: GenerateBlueprintOptions = { directory: process.cwd() }
44
- for (let index = 1; index < args.length; index++) {
45
- const argument = args[index]!
46
- if (argument === '--check') {
47
- options.check = true
48
- continue
49
- }
50
- if (argument === '--entry' || argument === '--output') {
51
- const value = args[++index]
52
- if (!value || value.startsWith('--')) {
53
- io.stderr(`[bunderstack] missing value for ${argument}`)
54
- return 2
55
- }
56
- if (argument === '--entry') options.entry = value
57
- else options.output = value
58
- continue
59
- }
60
- if (argument.startsWith('-')) {
61
- io.stderr(`[bunderstack] unknown option: ${argument}`)
62
- return 2
63
- }
64
- if (options.directory !== process.cwd()) {
65
- io.stderr('[bunderstack] only one application directory is allowed')
66
- return 2
67
- }
68
- options.directory = argument
69
- }
70
- try {
71
- const result = await generate(options)
72
- io.stdout(
73
- options.check || !result.changed
74
- ? 'bunderstack.blueprint.yaml is current'
75
- : 'Generated bunderstack.blueprint.yaml',
76
- )
77
- return 0
78
- } catch (error) {
79
- io.stderr(error instanceof Error ? error.message : String(error))
80
- return error instanceof BlueprintCheckError ? 1 : 1
81
- }
82
- }
83
-
84
- if (import.meta.main) {
85
- const exitCode = await runCli(process.argv.slice(2), {
86
- stdout: (message) => console.log(message),
87
- stderr: (message) => console.error(message),
88
- })
89
- process.exit(exitCode)
90
- }