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/list-query.ts DELETED
@@ -1,333 +0,0 @@
1
- import {
2
- and,
3
- asc,
4
- desc,
5
- eq,
6
- getTableColumns,
7
- gt,
8
- ilike,
9
- inArray,
10
- is,
11
- like,
12
- lt,
13
- or,
14
- sql,
15
- type SQL,
16
- } from 'drizzle-orm'
17
- import { PgTable } from 'drizzle-orm/pg-core'
18
-
19
- import type { ResolvedTableAccess, SortOrder } from './access'
20
- import type { AnyDb } from './dialect'
21
-
22
- import { ErrorCode, ListQueryError } from './errors'
23
-
24
- /** Caps both `?limit=` and the number of values in an `IN` filter. */
25
- export const MAX_LIST_LIMIT = 200
26
-
27
- /** Default page size when a request omits `limit`. */
28
- export const DEFAULT_LIST_LIMIT = 20
29
-
30
- /**
31
- * What a list procedure accepts. Shape and column types are enforced by the
32
- * generated input schema, so by the time these params arrive they are already
33
- * validated and coerced — this module only applies policy (defaults, the limit
34
- * cap, cursor rules).
35
- */
36
- export type ListParamsInput = {
37
- limit?: number
38
- offset?: number
39
- cursor?: string
40
- sort?: string
41
- order?: SortOrder
42
- q?: string
43
- count?: boolean
44
- filters?: Record<string, unknown>
45
- }
46
-
47
- export type ParsedListParams = {
48
- limit: number
49
- offset?: number
50
- sort: string
51
- order: SortOrder
52
- q: string
53
- cursor?: string
54
- count: boolean
55
- filters: Record<string, unknown>
56
- }
57
-
58
- export type ListResult<T> = {
59
- items: T[]
60
- limit: number
61
- offset?: number
62
- cursor?: string
63
- nextCursor?: string
64
- hasMore: boolean
65
- total?: number
66
- q?: string
67
- sort: string
68
- order: SortOrder
69
- }
70
-
71
- type CursorPayload = {
72
- sort: string
73
- order: SortOrder
74
- v: string | number | null
75
- id: string | number
76
- }
77
-
78
- function isRecord(value: unknown): value is Record<string, unknown> {
79
- return value !== null && typeof value === 'object' && !Array.isArray(value)
80
- }
81
-
82
- function isCursorPayload(value: unknown): value is CursorPayload {
83
- if (!isRecord(value)) return false
84
- return (
85
- typeof value.sort === 'string' &&
86
- (value.order === 'asc' || value.order === 'desc') &&
87
- (value.v === null ||
88
- typeof value.v === 'string' ||
89
- typeof value.v === 'number') &&
90
- (typeof value.id === 'string' || typeof value.id === 'number')
91
- )
92
- }
93
-
94
- /**
95
- * Applies list policy to already-validated input: defaults, the limit cap, and
96
- * the rules a schema cannot express (cursor excludes offset, and a cursor must
97
- * agree with the sort it was minted for).
98
- */
99
- export function resolveListParams(
100
- input: ListParamsInput,
101
- access: ResolvedTableAccess,
102
- ): ParsedListParams {
103
- const cursor = input.cursor?.trim() || undefined
104
- if (cursor && input.offset !== undefined) {
105
- throw new ListQueryError('cursor and offset cannot be used together')
106
- }
107
-
108
- const sort = input.sort ?? access.defaultSort.column
109
- const order = input.order ?? (input.sort ? 'asc' : access.defaultSort.order)
110
-
111
- if (cursor) {
112
- const decoded = decodeCursor(cursor)
113
- if (decoded.sort !== sort || decoded.order !== order) {
114
- throw new ListQueryError(
115
- 'cursor does not match sort and order parameters',
116
- ErrorCode.INVALID_CURSOR,
117
- )
118
- }
119
- }
120
-
121
- return {
122
- limit: Math.min(input.limit ?? DEFAULT_LIST_LIMIT, MAX_LIST_LIMIT),
123
- offset: cursor ? undefined : (input.offset ?? 0),
124
- sort,
125
- order,
126
- q: input.q?.trim() ?? '',
127
- cursor,
128
- count: input.count ?? false,
129
- filters: input.filters ?? {},
130
- }
131
- }
132
-
133
- function buildSearchWhere(
134
- table: Parameters<typeof getTableColumns>[0],
135
- searchableColumns: string[] | undefined,
136
- q: string,
137
- ): SQL | undefined {
138
- if (!q || !searchableColumns?.length) return undefined
139
- const columns = getTableColumns(table)
140
- const pattern = `%${q.replace(/[%_\\]/g, (ch) => `\\${ch}`)}%`
141
- // LIKE is case-insensitive in SQLite but case-sensitive in Postgres; use
142
- // ilike there so search behaves identically across dialects.
143
- const likeOp = is(table, PgTable) ? ilike : like
144
- const conditions = searchableColumns
145
- .filter((name) => name in columns)
146
- .map((name) => likeOp(columns[name]!, pattern))
147
- return conditions.length ? or(...conditions) : undefined
148
- }
149
-
150
- /**
151
- * Cursors carry their sort value as JSON, so a date column arrives as a string
152
- * and has to be rebuilt. Filter values need no such repair — the generated
153
- * input schema already types them.
154
- */
155
- function coerceCursorValue(
156
- table: Parameters<typeof getTableColumns>[0],
157
- columnName: string,
158
- raw: string | number | null,
159
- ): unknown {
160
- if (raw === null) return null
161
- const col = getTableColumns(table)[columnName]
162
- if (col?.dataType === 'date') return new Date(raw)
163
- return raw
164
- }
165
-
166
- function buildFilterWhere(
167
- table: Parameters<typeof getTableColumns>[0],
168
- filters: Record<string, unknown>,
169
- ): SQL | undefined {
170
- const columns = getTableColumns(table)
171
- const conditions: SQL[] = []
172
-
173
- for (const [name, value] of Object.entries(filters)) {
174
- const col = columns[name]
175
- if (!col || value === undefined) continue
176
-
177
- if (value === null) {
178
- conditions.push(sql`${col} IS NULL`)
179
- } else if (Array.isArray(value)) {
180
- if (value.length) conditions.push(inArray(col, value))
181
- } else {
182
- conditions.push(eq(col, value))
183
- }
184
- }
185
-
186
- return conditions.length ? and(...conditions) : undefined
187
- }
188
-
189
- function serializeCursorValue(value: unknown): string | number | null {
190
- if (value == null) return null
191
- if (value instanceof Date) return value.toISOString()
192
- if (typeof value === 'number' || typeof value === 'string') return value
193
- return String(value)
194
- }
195
-
196
- export function encodeCursor(payload: CursorPayload): string {
197
- return Buffer.from(JSON.stringify(payload)).toString('base64url')
198
- }
199
-
200
- export function decodeCursor(cursor: string): CursorPayload {
201
- try {
202
- const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))
203
- if (!isCursorPayload(parsed)) {
204
- throw new Error('invalid cursor shape')
205
- }
206
- return parsed
207
- } catch {
208
- throw new ListQueryError('invalid cursor', ErrorCode.INVALID_CURSOR)
209
- }
210
- }
211
-
212
- function buildCursorWhere(
213
- table: Parameters<typeof getTableColumns>[0],
214
- sortColName: string,
215
- order: SortOrder,
216
- cursor: CursorPayload,
217
- idCol: unknown,
218
- ): SQL {
219
- const columns = getTableColumns(table)
220
- const sortCol = columns[sortColName]!
221
- const sortValue = coerceCursorValue(table, sortColName, cursor.v)
222
-
223
- if (order === 'desc') {
224
- return or(
225
- lt(sortCol, sortValue),
226
- and(eq(sortCol, sortValue), lt(idCol as never, cursor.id)),
227
- )!
228
- }
229
- return or(
230
- gt(sortCol, sortValue),
231
- and(eq(sortCol, sortValue), gt(idCol as never, cursor.id)),
232
- )!
233
- }
234
-
235
- function buildOrderBy(
236
- table: Parameters<typeof getTableColumns>[0],
237
- sortColName: string,
238
- order: SortOrder,
239
- idCol: unknown,
240
- ) {
241
- const columns = getTableColumns(table)
242
- const sortCol = columns[sortColName]!
243
- const idOrder = order === 'asc' ? asc(idCol as never) : desc(idCol as never)
244
- return order === 'asc' ? [asc(sortCol), idOrder] : [desc(sortCol), idOrder]
245
- }
246
-
247
- export async function executeList<T extends Record<string, unknown>>(
248
- db: AnyDb,
249
- table: Parameters<typeof getTableColumns>[0],
250
- access: ResolvedTableAccess,
251
- params: ParsedListParams,
252
- idCol: unknown,
253
- scopeWhere?: SQL,
254
- ): Promise<ListResult<T>> {
255
- const searchWhere = buildSearchWhere(
256
- table,
257
- access.searchableColumns,
258
- params.q,
259
- )
260
- const filterWhere = buildFilterWhere(table, params.filters)
261
- let where = and(
262
- ...(searchWhere ? [searchWhere] : []),
263
- ...(filterWhere ? [filterWhere] : []),
264
- ...(scopeWhere ? [scopeWhere] : []),
265
- )
266
-
267
- if (params.cursor) {
268
- const cursorPayload = decodeCursor(params.cursor)
269
- const cursorWhere = buildCursorWhere(
270
- table,
271
- params.sort,
272
- params.order,
273
- cursorPayload,
274
- idCol,
275
- )
276
- where = where ? and(where, cursorWhere) : cursorWhere
277
- }
278
-
279
- const orderBy = buildOrderBy(table, params.sort, params.order, idCol)
280
-
281
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
282
- let query = (db as any).select().from(table)
283
- if (where) query = query.where(where)
284
- query = query.orderBy(...orderBy)
285
-
286
- if (params.offset !== undefined) {
287
- query = query.limit(params.limit).offset(params.offset)
288
- } else {
289
- query = query.limit(params.limit)
290
- }
291
-
292
- const items = (await query) as T[]
293
-
294
- let total: number | undefined
295
- if (params.count) {
296
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
297
- let countQuery = (db as any)
298
- .select({ count: sql<number>`count(*)` })
299
- .from(table)
300
- if (where) countQuery = countQuery.where(where)
301
- const [row] = await countQuery
302
- total = Number(row?.count ?? 0)
303
- }
304
-
305
- const hasMore =
306
- total !== undefined && params.offset !== undefined
307
- ? params.offset + items.length < total
308
- : items.length === params.limit
309
-
310
- let nextCursor: string | undefined
311
- if (items.length === params.limit) {
312
- const last = items[items.length - 1]!
313
- nextCursor = encodeCursor({
314
- sort: params.sort,
315
- order: params.order,
316
- v: serializeCursorValue(last[params.sort]),
317
- id: last.id as string | number,
318
- })
319
- }
320
-
321
- return {
322
- items,
323
- limit: params.limit,
324
- ...(params.offset !== undefined ? { offset: params.offset } : {}),
325
- ...(params.cursor ? { cursor: params.cursor } : {}),
326
- ...(nextCursor ? { nextCursor } : {}),
327
- hasMore,
328
- ...(total !== undefined ? { total } : {}),
329
- ...(params.q ? { q: params.q } : {}),
330
- sort: params.sort,
331
- order: params.order,
332
- }
333
- }
package/src/manifest.ts DELETED
@@ -1,297 +0,0 @@
1
- import type { StandardSchemaV1 } from '@standard-schema/spec'
2
-
3
- import { getTableName, isTable } from 'drizzle-orm'
4
- import * as v from 'valibot'
5
-
6
- import type { Dialect } from './dialect'
7
- import type { EnvConfigInput } from './env'
8
- import type { JobsDefs } from './jobs/define'
9
- import type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'
10
-
11
- import {
12
- bunderstackFiles,
13
- bunderstackIdempotency,
14
- bunderstackJobs,
15
- } from './internal-tables'
16
- import { parseCron } from './jobs/cron'
17
- import {
18
- StandardSchemaValidationError,
19
- validateStandardSchema,
20
- } from './standard-schema'
21
-
22
- export type ManifestEnvVar = {
23
- key: string
24
- required: boolean
25
- scope: 'server' | 'client'
26
- }
27
-
28
- export type BunderstackManifest = {
29
- version: 3
30
- database: {
31
- dialect: Dialect
32
- migrationsDirectory: string
33
- tables: { exportName: string; physicalName: string; system: boolean }[]
34
- }
35
- storage: {
36
- defaultBucket: string
37
- buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]
38
- }
39
- realtime: { required: boolean }
40
- environment: ManifestEnvVar[]
41
- background: {
42
- jobs: { name: string }[]
43
- cron: { name: string; schedule: string; timezone: 'UTC' }[]
44
- maintenance: {
45
- name: 'storage-sweep'
46
- schedule: string
47
- timezone: 'UTC'
48
- }[]
49
- }
50
- }
51
-
52
- const nonEmpty = v.pipe(v.string(), v.minLength(1))
53
- const migrationDirectory = v.pipe(
54
- nonEmpty,
55
- v.check(
56
- (value) =>
57
- value.startsWith('/') ||
58
- (!value.includes('\\') &&
59
- value.split('/').every((part) => part !== '..' && part !== '')),
60
- 'migrationsDirectory must be an absolute path or a relative path without traversal',
61
- ),
62
- )
63
- const cronSchedule = v.pipe(
64
- nonEmpty,
65
- v.check((value) => {
66
- try {
67
- parseCron(value)
68
- return true
69
- } catch {
70
- return false
71
- }
72
- }, 'invalid cron schedule'),
73
- )
74
-
75
- const manifestSchema = v.strictObject({
76
- version: v.literal(3),
77
- database: v.strictObject({
78
- dialect: v.picklist(['sqlite', 'pg']),
79
- migrationsDirectory: migrationDirectory,
80
- tables: v.array(
81
- v.strictObject({
82
- exportName: nonEmpty,
83
- physicalName: nonEmpty,
84
- system: v.boolean(),
85
- }),
86
- ),
87
- }),
88
- storage: v.strictObject({
89
- defaultBucket: nonEmpty,
90
- buckets: v.array(
91
- v.strictObject({
92
- name: nonEmpty,
93
- visibility: v.picklist(['public', 'private']),
94
- }),
95
- ),
96
- }),
97
- realtime: v.strictObject({ required: v.boolean() }),
98
- environment: v.array(
99
- v.strictObject({
100
- key: nonEmpty,
101
- required: v.boolean(),
102
- scope: v.picklist(['server', 'client']),
103
- }),
104
- ),
105
- background: v.strictObject({
106
- jobs: v.array(v.strictObject({ name: nonEmpty })),
107
- cron: v.array(
108
- v.strictObject({
109
- name: nonEmpty,
110
- schedule: cronSchedule,
111
- timezone: v.literal('UTC'),
112
- }),
113
- ),
114
- maintenance: v.array(
115
- v.strictObject({
116
- name: v.literal('storage-sweep'),
117
- schedule: cronSchedule,
118
- timezone: v.literal('UTC'),
119
- }),
120
- ),
121
- }),
122
- })
123
-
124
- function sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {
125
- return [...entries].sort((left, right) => key(left).localeCompare(key(right)))
126
- }
127
-
128
- function rejectDuplicates(collection: string, values: readonly string[]): void {
129
- const seen = new Set<string>()
130
- for (const value of values) {
131
- if (seen.has(value))
132
- throw new Error(`[bunderstack] duplicate ${collection} "${value}"`)
133
- seen.add(value)
134
- }
135
- }
136
-
137
- function describeTables(schema: Record<string, unknown>) {
138
- const systemNames = new Set<string>(
139
- systemTables().map((table) => table.physicalName),
140
- )
141
- return sortBy(
142
- Object.entries(schema).flatMap(([exportName, value]) =>
143
- isTable(value) && !systemNames.has(getTableName(value))
144
- ? [{ exportName, physicalName: getTableName(value), system: false }]
145
- : [],
146
- ),
147
- (entry) => entry.physicalName,
148
- )
149
- }
150
-
151
- function describeSection(
152
- section: Record<string, StandardSchemaV1> | undefined,
153
- scope: ManifestEnvVar['scope'],
154
- ): ManifestEnvVar[] {
155
- return Object.entries(section ?? {}).map(([key, schema]) => {
156
- let required = false
157
- try {
158
- validateStandardSchema(schema, undefined, 'env')
159
- } catch (error) {
160
- if (!(error instanceof StandardSchemaValidationError)) throw error
161
- required = true
162
- }
163
- return { key, required, scope }
164
- })
165
- }
166
-
167
- function systemTables() {
168
- return [
169
- {
170
- exportName: '_system.files',
171
- physicalName: getTableName(bunderstackFiles),
172
- system: true,
173
- },
174
- {
175
- exportName: '_system.idempotency',
176
- physicalName: getTableName(bunderstackIdempotency),
177
- system: true,
178
- },
179
- {
180
- exportName: '_system.jobs',
181
- physicalName: getTableName(bunderstackJobs),
182
- system: true,
183
- },
184
- ]
185
- }
186
-
187
- export function parseManifest(value: unknown): BunderstackManifest {
188
- const manifest = validateStandardSchema(
189
- manifestSchema,
190
- value,
191
- 'manifest',
192
- ) as BunderstackManifest
193
- rejectDuplicates(
194
- 'database physical table',
195
- manifest.database.tables.map((entry) => entry.physicalName),
196
- )
197
- rejectDuplicates(
198
- 'database export table',
199
- manifest.database.tables.map((entry) => entry.exportName),
200
- )
201
- rejectDuplicates(
202
- 'storage bucket',
203
- manifest.storage.buckets.map((entry) => entry.name),
204
- )
205
- rejectDuplicates(
206
- 'environment key',
207
- manifest.environment.map((entry) => entry.key),
208
- )
209
- rejectDuplicates(
210
- 'background job',
211
- manifest.background.jobs.map((entry) => entry.name),
212
- )
213
- rejectDuplicates(
214
- 'background cron',
215
- manifest.background.cron.map((entry) => entry.name),
216
- )
217
- rejectDuplicates(
218
- 'background maintenance',
219
- manifest.background.maintenance.map((entry) => entry.name),
220
- )
221
- return manifest
222
- }
223
-
224
- export function buildManifest(args: {
225
- schema: Record<string, unknown>
226
- dialect: Dialect
227
- migrationsDirectory: string
228
- storage: ResolvedStorageBuckets
229
- envConfig: EnvConfigInput | undefined
230
- emailProvider: string | undefined
231
- realtime: boolean
232
- jobs: JobsDefs | undefined
233
- }): BunderstackManifest {
234
- const environment = [
235
- ...describeSection(args.envConfig?.server, 'server'),
236
- ...describeSection(args.envConfig?.client, 'client'),
237
- ...(args.emailProvider === 'resend'
238
- ? [{ key: 'RESEND_API_KEY', required: true, scope: 'server' as const }]
239
- : []),
240
- ...(args.emailProvider === 'smtp'
241
- ? [{ key: 'SMTP_URL', required: true, scope: 'server' as const }]
242
- : []),
243
- ]
244
- rejectDuplicates(
245
- 'environment key',
246
- environment.map((entry) => entry.key),
247
- )
248
-
249
- return parseManifest({
250
- version: 3,
251
- database: {
252
- dialect: args.dialect,
253
- migrationsDirectory: args.migrationsDirectory,
254
- tables: sortBy(
255
- [...systemTables(), ...describeTables(args.schema)],
256
- (entry) => entry.physicalName,
257
- ),
258
- },
259
- storage: {
260
- defaultBucket: args.storage.defaultBucket,
261
- buckets: sortBy(
262
- [...args.storage.buckets.values()].map((bucket) => ({
263
- name: bucket.name,
264
- visibility: bucket.visibility,
265
- })),
266
- (bucket) => bucket.name,
267
- ),
268
- },
269
- realtime: { required: args.realtime },
270
- environment: sortBy(environment, (entry) => entry.key),
271
- background: {
272
- jobs: sortBy(
273
- Object.entries(args.jobs ?? {})
274
- .filter(([, def]) => def.kind === 'job')
275
- .map(([name]) => ({ name })),
276
- (entry) => entry.name,
277
- ),
278
- cron: sortBy(
279
- Object.entries(args.jobs ?? {})
280
- .filter(([, def]) => def.kind === 'cron')
281
- .map(([name, def]) => ({
282
- name,
283
- schedule: def.kind === 'cron' ? def.schedule : '',
284
- timezone: 'UTC' as const,
285
- })),
286
- (entry) => entry.name,
287
- ),
288
- maintenance: [
289
- {
290
- name: 'storage-sweep',
291
- schedule: '0 4 * * *',
292
- timezone: 'UTC' as const,
293
- },
294
- ],
295
- },
296
- })
297
- }
@@ -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
- }