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.
- package/CHANGELOG.md +227 -0
- package/README.md +11 -1
- package/dist/access.d.ts +116 -0
- package/dist/access.d.ts.map +1 -0
- package/dist/access.js +325 -0
- package/dist/access.js.map +1 -0
- package/dist/api/builder.d.ts +145 -0
- package/dist/api/builder.d.ts.map +1 -0
- package/dist/api/builder.js +28 -0
- package/dist/api/builder.js.map +1 -0
- package/dist/api/context.d.ts +34 -0
- package/dist/api/context.d.ts.map +1 -0
- package/dist/api/context.js +32 -0
- package/dist/api/context.js.map +1 -0
- package/dist/api/crud-router.d.ts +290 -0
- package/dist/api/crud-router.d.ts.map +1 -0
- package/dist/api/crud-router.js +249 -0
- package/dist/api/crud-router.js.map +1 -0
- package/dist/api/openapi.d.ts +6 -0
- package/dist/api/openapi.d.ts.map +1 -0
- package/dist/api/openapi.js +146 -0
- package/dist/api/openapi.js.map +1 -0
- package/dist/api/realtime-router.d.ts +76 -0
- package/dist/api/realtime-router.d.ts.map +1 -0
- package/dist/api/realtime-router.js +41 -0
- package/dist/api/realtime-router.js.map +1 -0
- package/dist/api/registry.d.ts +30 -0
- package/dist/api/registry.d.ts.map +1 -0
- package/dist/api/registry.js +239 -0
- package/dist/api/registry.js.map +1 -0
- package/dist/api/router.d.ts +9 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +19 -0
- package/dist/api/router.js.map +1 -0
- package/dist/api/storage-router.d.ts +279 -0
- package/dist/api/storage-router.d.ts.map +1 -0
- package/dist/api/storage-router.js +155 -0
- package/dist/api/storage-router.js.map +1 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/auth.d.ts +31 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +102 -0
- package/dist/auth.js.map +1 -0
- package/dist/blueprint-generator.d.ts +18 -0
- package/dist/blueprint-generator.d.ts.map +1 -0
- package/dist/blueprint-generator.js +125 -0
- package/dist/blueprint-generator.js.map +1 -0
- package/dist/blueprint.d.ts +46 -0
- package/dist/blueprint.d.ts.map +1 -0
- package/dist/blueprint.js +162 -0
- package/dist/blueprint.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +68 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +76 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/cron.d.ts +4 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +3 -0
- package/dist/cron.js.map +1 -0
- package/dist/crud-operations.d.ts +43 -0
- package/dist/crud-operations.d.ts.map +1 -0
- package/dist/crud-operations.js +238 -0
- package/dist/crud-operations.js.map +1 -0
- package/dist/database/adapter.d.ts +20 -0
- package/dist/database/adapter.d.ts.map +1 -0
- package/dist/database/adapter.js +2 -0
- package/dist/database/adapter.js.map +1 -0
- package/dist/database/bun-sql.d.ts +3 -0
- package/dist/database/bun-sql.d.ts.map +1 -0
- package/dist/database/bun-sql.js +21 -0
- package/dist/database/bun-sql.js.map +1 -0
- package/dist/database/libsql.d.ts +3 -0
- package/dist/database/libsql.d.ts.map +1 -0
- package/dist/database/libsql.js +22 -0
- package/dist/database/libsql.js.map +1 -0
- package/dist/database/pglite.d.ts +3 -0
- package/dist/database/pglite.d.ts.map +1 -0
- package/dist/database/pglite.js +27 -0
- package/dist/database/pglite.js.map +1 -0
- package/dist/database/postgres-js.d.ts +3 -0
- package/dist/database/postgres-js.d.ts.map +1 -0
- package/dist/database/postgres-js.js +21 -0
- package/dist/database/postgres-js.js.map +1 -0
- package/dist/db.d.ts +18 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +26 -0
- package/dist/db.js.map +1 -0
- package/dist/dialect.d.ts +17 -0
- package/dist/dialect.d.ts.map +1 -0
- package/dist/dialect.js +22 -0
- package/dist/dialect.js.map +1 -0
- package/dist/email/smtp.d.ts +14 -0
- package/dist/email/smtp.d.ts.map +1 -0
- package/dist/email/smtp.js +27 -0
- package/dist/email/smtp.js.map +1 -0
- package/dist/email.d.ts +39 -0
- package/dist/email.d.ts.map +1 -0
- package/dist/email.js +97 -0
- package/dist/email.js.map +1 -0
- package/dist/env.d.ts +47 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +99 -0
- package/dist/env.js.map +1 -0
- package/dist/errors.d.ts +125 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +92 -0
- package/dist/errors.js.map +1 -0
- package/dist/handler.d.ts +9 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +22 -0
- package/dist/handler.js.map +1 -0
- package/dist/idempotency.d.ts +17 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +65 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/internal-tables-pg.d.ts +481 -0
- package/dist/internal-tables-pg.d.ts.map +1 -0
- package/{src/internal-tables-pg.ts → dist/internal-tables-pg.js} +10 -34
- package/dist/internal-tables-pg.js.map +1 -0
- package/dist/internal-tables.d.ts +2509 -0
- package/dist/internal-tables.d.ts.map +1 -0
- package/dist/internal-tables.js +108 -0
- package/dist/internal-tables.js.map +1 -0
- package/dist/jobs/cron.d.ts +15 -0
- package/dist/jobs/cron.d.ts.map +1 -0
- package/dist/jobs/cron.js +74 -0
- package/dist/jobs/cron.js.map +1 -0
- package/dist/jobs/define.d.ts +133 -0
- package/dist/jobs/define.d.ts.map +1 -0
- package/dist/jobs/define.js +74 -0
- package/dist/jobs/define.js.map +1 -0
- package/dist/jobs/index.d.ts +11 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +8 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/jobs/queue.d.ts +6 -0
- package/dist/jobs/queue.d.ts.map +1 -0
- package/dist/jobs/queue.js +55 -0
- package/dist/jobs/queue.js.map +1 -0
- package/dist/jobs/runtime.d.ts +13 -0
- package/dist/jobs/runtime.d.ts.map +1 -0
- package/dist/jobs/runtime.js +46 -0
- package/dist/jobs/runtime.js.map +1 -0
- package/dist/jobs/slots.d.ts +25 -0
- package/dist/jobs/slots.d.ts.map +1 -0
- package/dist/jobs/slots.js +41 -0
- package/dist/jobs/slots.js.map +1 -0
- package/dist/jobs/worker.d.ts +12 -0
- package/dist/jobs/worker.d.ts.map +1 -0
- package/dist/jobs/worker.js +311 -0
- package/dist/jobs/worker.js.map +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +37 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/list-query.d.ts +62 -0
- package/dist/list-query.d.ts.map +1 -0
- package/dist/list-query.js +194 -0
- package/dist/list-query.js.map +1 -0
- package/dist/manifest.d.ts +59 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +167 -0
- package/dist/manifest.js.map +1 -0
- package/dist/provision-internals.d.ts +24 -0
- package/dist/provision-internals.d.ts.map +1 -0
- package/dist/provision-internals.js +7 -0
- package/dist/provision-internals.js.map +1 -0
- package/dist/provision.d.ts +23 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +96 -0
- package/dist/provision.js.map +1 -0
- package/dist/rate-limit.d.ts +7 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +49 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/realtime/facade.d.ts +18 -0
- package/dist/realtime/facade.d.ts.map +1 -0
- package/dist/realtime/facade.js +36 -0
- package/dist/realtime/facade.js.map +1 -0
- package/dist/realtime/filter.d.ts +13 -0
- package/dist/realtime/filter.d.ts.map +1 -0
- package/dist/realtime/filter.js +48 -0
- package/dist/realtime/filter.js.map +1 -0
- package/dist/realtime/heartbeat.d.ts +14 -0
- package/dist/realtime/heartbeat.d.ts.map +1 -0
- package/dist/realtime/heartbeat.js +64 -0
- package/dist/realtime/heartbeat.js.map +1 -0
- package/dist/realtime/publisher.d.ts +21 -0
- package/dist/realtime/publisher.d.ts.map +1 -0
- package/dist/realtime/publisher.js +17 -0
- package/dist/realtime/publisher.js.map +1 -0
- package/dist/schema-export-pg.d.ts +2 -0
- package/dist/schema-export-pg.d.ts.map +1 -0
- package/dist/schema-export-pg.js +4 -0
- package/dist/schema-export-pg.js.map +1 -0
- package/dist/schema-export.d.ts +2 -0
- package/dist/schema-export.d.ts.map +1 -0
- package/dist/schema-export.js +2 -0
- package/dist/schema-export.js.map +1 -0
- package/dist/scope.d.ts +4 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +13 -0
- package/dist/scope.js.map +1 -0
- package/dist/standard-schema.d.ts +13 -0
- package/dist/standard-schema.d.ts.map +1 -0
- package/dist/standard-schema.js +34 -0
- package/dist/standard-schema.js.map +1 -0
- package/dist/storage/buckets.d.ts +80 -0
- package/dist/storage/buckets.d.ts.map +1 -0
- package/dist/storage/buckets.js +178 -0
- package/dist/storage/buckets.js.map +1 -0
- package/dist/storage/delete.d.ts +11 -0
- package/dist/storage/delete.d.ts.map +1 -0
- package/dist/storage/delete.js +18 -0
- package/dist/storage/delete.js.map +1 -0
- package/dist/storage/file-meta.d.ts +37 -0
- package/dist/storage/file-meta.d.ts.map +1 -0
- package/dist/storage/file-meta.js +166 -0
- package/dist/storage/file-meta.js.map +1 -0
- package/dist/storage/index.d.ts +28 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/local.d.ts +15 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +56 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/operations.d.ts +54 -0
- package/dist/storage/operations.d.ts.map +1 -0
- package/dist/storage/operations.js +250 -0
- package/dist/storage/operations.js.map +1 -0
- package/dist/storage/registry.d.ts +10 -0
- package/dist/storage/registry.d.ts.map +1 -0
- package/dist/storage/registry.js +23 -0
- package/dist/storage/registry.js.map +1 -0
- package/dist/storage/s3.d.ts +28 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +71 -0
- package/dist/storage/s3.js.map +1 -0
- package/dist/storage/sweep.d.ts +9 -0
- package/dist/storage/sweep.d.ts.map +1 -0
- package/dist/storage/sweep.js +17 -0
- package/dist/storage/sweep.js.map +1 -0
- package/dist/storage/thumbnails.d.ts +11 -0
- package/dist/storage/thumbnails.d.ts.map +1 -0
- package/dist/storage/thumbnails.js +60 -0
- package/dist/storage/thumbnails.js.map +1 -0
- package/dist/testing.d.ts +27 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +11 -0
- package/dist/testing.js.map +1 -0
- package/dist/typeid-pg.d.ts +19 -0
- package/dist/typeid-pg.d.ts.map +1 -0
- package/{src/typeid-pg.ts → dist/typeid-pg.js} +10 -13
- package/dist/typeid-pg.js.map +1 -0
- package/dist/typeid.d.ts +45 -0
- package/dist/typeid.d.ts.map +1 -0
- package/dist/typeid.js +100 -0
- package/dist/typeid.js.map +1 -0
- package/package.json +81 -29
- package/src/access.ts +0 -552
- package/src/api/api-types.types.ts +0 -106
- package/src/api/builder.ts +0 -52
- package/src/api/context.ts +0 -83
- package/src/api/crud-router.ts +0 -321
- package/src/api/openapi.ts +0 -184
- package/src/api/realtime-router.ts +0 -75
- package/src/api/registry.ts +0 -338
- package/src/api/router.ts +0 -34
- package/src/api/storage-router.ts +0 -224
- package/src/api/types.ts +0 -84
- package/src/auth.ts +0 -117
- package/src/blueprint-generator.ts +0 -203
- package/src/blueprint.ts +0 -264
- package/src/cli.ts +0 -90
- package/src/config.ts +0 -215
- package/src/cron.ts +0 -3
- package/src/crud-operations.ts +0 -488
- package/src/database/adapter.ts +0 -26
- package/src/database/bun-sql.ts +0 -23
- package/src/database/libsql.ts +0 -28
- package/src/database/pglite.ts +0 -33
- package/src/database/postgres-js.ts +0 -33
- package/src/db.ts +0 -61
- package/src/dialect.ts +0 -38
- package/src/email/smtp.ts +0 -45
- package/src/email.ts +0 -154
- package/src/env.ts +0 -180
- package/src/errors.ts +0 -118
- package/src/handler.ts +0 -30
- package/src/idempotency.ts +0 -97
- package/src/index.ts +0 -822
- package/src/internal-tables.ts +0 -149
- package/src/jobs/cron.ts +0 -87
- package/src/jobs/define.ts +0 -283
- package/src/jobs/index.ts +0 -37
- package/src/jobs/queue.ts +0 -67
- package/src/jobs/runtime.ts +0 -58
- package/src/jobs/slots.ts +0 -52
- package/src/jobs/worker.ts +0 -369
- package/src/lifecycle.ts +0 -49
- package/src/list-query.ts +0 -416
- package/src/manifest.ts +0 -297
- package/src/provision-internals.ts +0 -29
- package/src/provision.ts +0 -128
- package/src/rate-limit.ts +0 -71
- package/src/realtime/facade.ts +0 -55
- package/src/realtime/filter.ts +0 -77
- package/src/realtime/heartbeat.ts +0 -80
- package/src/realtime/publisher.ts +0 -46
- package/src/schema-export-pg.ts +0 -7
- package/src/schema-export.ts +0 -5
- package/src/scope.ts +0 -17
- package/src/standard-schema.ts +0 -59
- package/src/storage/buckets.ts +0 -308
- package/src/storage/delete.ts +0 -26
- package/src/storage/file-meta.ts +0 -222
- package/src/storage/index.ts +0 -39
- package/src/storage/local.ts +0 -69
- package/src/storage/operations.ts +0 -398
- package/src/storage/registry.ts +0 -40
- package/src/storage/s3.ts +0 -99
- package/src/storage/sweep.ts +0 -25
- package/src/storage/thumbnails.ts +0 -65
- package/src/testing.ts +0 -31
- package/src/typeid.ts +0 -116
package/src/list-query.ts
DELETED
|
@@ -1,416 +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 a comma-separated `IN` filter. */
|
|
25
|
-
export const MAX_LIST_LIMIT = 200
|
|
26
|
-
|
|
27
|
-
export const RESERVED_LIST_PARAMS = new Set([
|
|
28
|
-
'limit',
|
|
29
|
-
'offset',
|
|
30
|
-
'sort',
|
|
31
|
-
'order',
|
|
32
|
-
'q',
|
|
33
|
-
'cursor',
|
|
34
|
-
'count',
|
|
35
|
-
])
|
|
36
|
-
|
|
37
|
-
export type ParsedListParams = {
|
|
38
|
-
limit: number
|
|
39
|
-
offset?: number
|
|
40
|
-
sort: string
|
|
41
|
-
order: SortOrder
|
|
42
|
-
q: string
|
|
43
|
-
cursor?: string
|
|
44
|
-
count: boolean
|
|
45
|
-
filters: Record<string, unknown>
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type ListResult<T> = {
|
|
49
|
-
items: T[]
|
|
50
|
-
limit: number
|
|
51
|
-
offset?: number
|
|
52
|
-
cursor?: string
|
|
53
|
-
nextCursor?: string
|
|
54
|
-
hasMore: boolean
|
|
55
|
-
total?: number
|
|
56
|
-
q?: string
|
|
57
|
-
sort: string
|
|
58
|
-
order: SortOrder
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
type CursorPayload = {
|
|
62
|
-
sort: string
|
|
63
|
-
order: SortOrder
|
|
64
|
-
v: string | number | null
|
|
65
|
-
id: string | number
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
69
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isCursorPayload(value: unknown): value is CursorPayload {
|
|
73
|
-
if (!isRecord(value)) return false
|
|
74
|
-
return (
|
|
75
|
-
typeof value.sort === 'string' &&
|
|
76
|
-
(value.order === 'asc' || value.order === 'desc') &&
|
|
77
|
-
(value.v === null ||
|
|
78
|
-
typeof value.v === 'string' ||
|
|
79
|
-
typeof value.v === 'number') &&
|
|
80
|
-
(typeof value.id === 'string' || typeof value.id === 'number')
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function parseBoolean(value: string | undefined): boolean {
|
|
85
|
-
if (!value) return false
|
|
86
|
-
const v = value.toLowerCase()
|
|
87
|
-
return v === 'true' || v === '1'
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function parseLimit(raw: string | undefined): number {
|
|
91
|
-
if (raw === undefined || raw === '') return 20
|
|
92
|
-
const n = Number(raw)
|
|
93
|
-
if (!Number.isInteger(n) || n < 1) {
|
|
94
|
-
throw new ListQueryError(
|
|
95
|
-
`limit must be an integer between 1 and ${MAX_LIST_LIMIT}`,
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
return Math.min(n, MAX_LIST_LIMIT)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function parseOffset(raw: string | undefined): number {
|
|
102
|
-
if (raw === undefined || raw === '') return 0
|
|
103
|
-
const n = Number(raw)
|
|
104
|
-
if (!Number.isInteger(n) || n < 0) {
|
|
105
|
-
throw new ListQueryError('offset must be a non-negative integer')
|
|
106
|
-
}
|
|
107
|
-
return n
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function parseOrder(raw: string | undefined): SortOrder {
|
|
111
|
-
if (!raw || raw === 'asc') return 'asc'
|
|
112
|
-
if (raw === 'desc') return 'desc'
|
|
113
|
-
throw new ListQueryError('order must be "asc" or "desc"')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function parseListParams(
|
|
117
|
-
url: URL,
|
|
118
|
-
access: ResolvedTableAccess,
|
|
119
|
-
): ParsedListParams {
|
|
120
|
-
const params = url.searchParams
|
|
121
|
-
const limit = parseLimit(params.get('limit') ?? undefined)
|
|
122
|
-
const cursor = params.get('cursor')?.trim() || undefined
|
|
123
|
-
const hasOffset = params.has('offset') && params.get('offset') !== ''
|
|
124
|
-
const offset = hasOffset
|
|
125
|
-
? parseOffset(params.get('offset') ?? undefined)
|
|
126
|
-
: undefined
|
|
127
|
-
|
|
128
|
-
if (cursor && hasOffset) {
|
|
129
|
-
throw new ListQueryError('cursor and offset cannot be used together')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const sort = params.get('sort')?.trim() || access.defaultSort.column
|
|
133
|
-
const order = params.has('order')
|
|
134
|
-
? parseOrder(params.get('order') ?? undefined)
|
|
135
|
-
: params.has('sort')
|
|
136
|
-
? 'asc'
|
|
137
|
-
: access.defaultSort.order
|
|
138
|
-
|
|
139
|
-
if (!access.sortableColumns.includes(sort)) {
|
|
140
|
-
throw new ListQueryError(`sort column "${sort}" is not allowed`)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const filters: Record<string, unknown> = {}
|
|
144
|
-
for (const [key, value] of params.entries()) {
|
|
145
|
-
if (RESERVED_LIST_PARAMS.has(key)) continue
|
|
146
|
-
if (!access.filterableColumns.includes(key)) {
|
|
147
|
-
throw new ListQueryError(`filter column "${key}" is not allowed`)
|
|
148
|
-
}
|
|
149
|
-
filters[key] = value === 'null' ? null : value
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const q = params.get('q')?.trim().slice(0, 100) ?? ''
|
|
153
|
-
const count = parseBoolean(params.get('count') ?? undefined)
|
|
154
|
-
|
|
155
|
-
if (cursor) {
|
|
156
|
-
const decoded = decodeCursor(cursor)
|
|
157
|
-
if (decoded.sort !== sort || decoded.order !== order) {
|
|
158
|
-
throw new ListQueryError(
|
|
159
|
-
'cursor does not match sort and order parameters',
|
|
160
|
-
ErrorCode.INVALID_CURSOR,
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return {
|
|
166
|
-
limit,
|
|
167
|
-
offset: cursor ? undefined : (offset ?? 0),
|
|
168
|
-
sort,
|
|
169
|
-
order,
|
|
170
|
-
q,
|
|
171
|
-
cursor,
|
|
172
|
-
count,
|
|
173
|
-
filters,
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function buildSearchWhere(
|
|
178
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
179
|
-
searchableColumns: string[] | undefined,
|
|
180
|
-
q: string,
|
|
181
|
-
): SQL | undefined {
|
|
182
|
-
if (!q || !searchableColumns?.length) return undefined
|
|
183
|
-
const columns = getTableColumns(table)
|
|
184
|
-
const pattern = `%${q.replace(/[%_\\]/g, (ch) => `\\${ch}`)}%`
|
|
185
|
-
// LIKE is case-insensitive in SQLite but case-sensitive in Postgres; use
|
|
186
|
-
// ilike there so search behaves identically across dialects.
|
|
187
|
-
const likeOp = is(table, PgTable) ? ilike : like
|
|
188
|
-
const conditions = searchableColumns
|
|
189
|
-
.filter((name) => name in columns)
|
|
190
|
-
.map((name) => likeOp(columns[name]!, pattern))
|
|
191
|
-
return conditions.length ? or(...conditions) : undefined
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function coerceFilterValue(
|
|
195
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
196
|
-
columnName: string,
|
|
197
|
-
raw: unknown,
|
|
198
|
-
): unknown {
|
|
199
|
-
if (raw === null) return null
|
|
200
|
-
const col = getTableColumns(table)[columnName]
|
|
201
|
-
if (!col) return raw
|
|
202
|
-
|
|
203
|
-
const dataType = col.dataType
|
|
204
|
-
if (
|
|
205
|
-
dataType === 'number' ||
|
|
206
|
-
dataType === 'integer' ||
|
|
207
|
-
dataType === 'bigint'
|
|
208
|
-
) {
|
|
209
|
-
const n = Number(raw)
|
|
210
|
-
if (Number.isNaN(n)) {
|
|
211
|
-
throw new ListQueryError(`filter "${columnName}" must be a number`)
|
|
212
|
-
}
|
|
213
|
-
return n
|
|
214
|
-
}
|
|
215
|
-
if (dataType === 'boolean') {
|
|
216
|
-
const s = String(raw).toLowerCase()
|
|
217
|
-
if (s === 'true' || s === '1') return true
|
|
218
|
-
if (s === 'false' || s === '0') return false
|
|
219
|
-
throw new ListQueryError(`filter "${columnName}" must be a boolean`)
|
|
220
|
-
}
|
|
221
|
-
if (dataType === 'date') {
|
|
222
|
-
const d = new Date(raw as string | number)
|
|
223
|
-
if (Number.isNaN(d.getTime())) {
|
|
224
|
-
throw new ListQueryError(`filter "${columnName}" must be a valid date`)
|
|
225
|
-
}
|
|
226
|
-
return d
|
|
227
|
-
}
|
|
228
|
-
return String(raw)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function buildFilterWhere(
|
|
232
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
233
|
-
filters: Record<string, unknown>,
|
|
234
|
-
): SQL | undefined {
|
|
235
|
-
const columns = getTableColumns(table)
|
|
236
|
-
const conditions: SQL[] = []
|
|
237
|
-
|
|
238
|
-
for (const [name, raw] of Object.entries(filters)) {
|
|
239
|
-
const col = columns[name]
|
|
240
|
-
if (!col) continue
|
|
241
|
-
|
|
242
|
-
if (raw === null) {
|
|
243
|
-
conditions.push(sql`${col} IS NULL`)
|
|
244
|
-
continue
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// `?column=a,b,c` — TypeIDs and other filterable values never contain
|
|
248
|
-
// commas, so this is a safe, zero-syntax way to do `column IN (...)`.
|
|
249
|
-
if (typeof raw === 'string' && raw.includes(',')) {
|
|
250
|
-
const parts = raw.split(',').filter((p) => p.length > 0)
|
|
251
|
-
if (parts.length > MAX_LIST_LIMIT) {
|
|
252
|
-
throw new ListQueryError(
|
|
253
|
-
`filter "${name}" accepts at most ${MAX_LIST_LIMIT} comma-separated values`,
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
const values = parts.map((p) => coerceFilterValue(table, name, p))
|
|
257
|
-
conditions.push(inArray(col, values))
|
|
258
|
-
continue
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const value = coerceFilterValue(table, name, raw)
|
|
262
|
-
if (value === null) {
|
|
263
|
-
conditions.push(sql`${col} IS NULL`)
|
|
264
|
-
} else {
|
|
265
|
-
conditions.push(eq(col, value))
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return conditions.length ? and(...conditions) : undefined
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function serializeCursorValue(value: unknown): string | number | null {
|
|
273
|
-
if (value == null) return null
|
|
274
|
-
if (value instanceof Date) return value.toISOString()
|
|
275
|
-
if (typeof value === 'number' || typeof value === 'string') return value
|
|
276
|
-
return String(value)
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
export function encodeCursor(payload: CursorPayload): string {
|
|
280
|
-
return Buffer.from(JSON.stringify(payload)).toString('base64url')
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
export function decodeCursor(cursor: string): CursorPayload {
|
|
284
|
-
try {
|
|
285
|
-
const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'))
|
|
286
|
-
if (!isCursorPayload(parsed)) {
|
|
287
|
-
throw new Error('invalid cursor shape')
|
|
288
|
-
}
|
|
289
|
-
return parsed
|
|
290
|
-
} catch {
|
|
291
|
-
throw new ListQueryError('invalid cursor', ErrorCode.INVALID_CURSOR)
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function buildCursorWhere(
|
|
296
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
297
|
-
sortColName: string,
|
|
298
|
-
order: SortOrder,
|
|
299
|
-
cursor: CursorPayload,
|
|
300
|
-
idCol: unknown,
|
|
301
|
-
): SQL {
|
|
302
|
-
const columns = getTableColumns(table)
|
|
303
|
-
const sortCol = columns[sortColName]!
|
|
304
|
-
const sortValue = coerceFilterValue(table, sortColName, cursor.v)
|
|
305
|
-
|
|
306
|
-
if (order === 'desc') {
|
|
307
|
-
return or(
|
|
308
|
-
lt(sortCol, sortValue),
|
|
309
|
-
and(eq(sortCol, sortValue), lt(idCol as never, cursor.id)),
|
|
310
|
-
)!
|
|
311
|
-
}
|
|
312
|
-
return or(
|
|
313
|
-
gt(sortCol, sortValue),
|
|
314
|
-
and(eq(sortCol, sortValue), gt(idCol as never, cursor.id)),
|
|
315
|
-
)!
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function buildOrderBy(
|
|
319
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
320
|
-
sortColName: string,
|
|
321
|
-
order: SortOrder,
|
|
322
|
-
idCol: unknown,
|
|
323
|
-
) {
|
|
324
|
-
const columns = getTableColumns(table)
|
|
325
|
-
const sortCol = columns[sortColName]!
|
|
326
|
-
const idOrder = order === 'asc' ? asc(idCol as never) : desc(idCol as never)
|
|
327
|
-
return order === 'asc' ? [asc(sortCol), idOrder] : [desc(sortCol), idOrder]
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
export async function executeList<T extends Record<string, unknown>>(
|
|
331
|
-
db: AnyDb,
|
|
332
|
-
table: Parameters<typeof getTableColumns>[0],
|
|
333
|
-
access: ResolvedTableAccess,
|
|
334
|
-
params: ParsedListParams,
|
|
335
|
-
idCol: unknown,
|
|
336
|
-
scopeWhere?: SQL,
|
|
337
|
-
): Promise<ListResult<T>> {
|
|
338
|
-
const searchWhere = buildSearchWhere(
|
|
339
|
-
table,
|
|
340
|
-
access.searchableColumns,
|
|
341
|
-
params.q,
|
|
342
|
-
)
|
|
343
|
-
const filterWhere = buildFilterWhere(table, params.filters)
|
|
344
|
-
let where = and(
|
|
345
|
-
...(searchWhere ? [searchWhere] : []),
|
|
346
|
-
...(filterWhere ? [filterWhere] : []),
|
|
347
|
-
...(scopeWhere ? [scopeWhere] : []),
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
if (params.cursor) {
|
|
351
|
-
const cursorPayload = decodeCursor(params.cursor)
|
|
352
|
-
const cursorWhere = buildCursorWhere(
|
|
353
|
-
table,
|
|
354
|
-
params.sort,
|
|
355
|
-
params.order,
|
|
356
|
-
cursorPayload,
|
|
357
|
-
idCol,
|
|
358
|
-
)
|
|
359
|
-
where = where ? and(where, cursorWhere) : cursorWhere
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const orderBy = buildOrderBy(table, params.sort, params.order, idCol)
|
|
363
|
-
|
|
364
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
365
|
-
let query = (db as any).select().from(table)
|
|
366
|
-
if (where) query = query.where(where)
|
|
367
|
-
query = query.orderBy(...orderBy)
|
|
368
|
-
|
|
369
|
-
if (params.offset !== undefined) {
|
|
370
|
-
query = query.limit(params.limit).offset(params.offset)
|
|
371
|
-
} else {
|
|
372
|
-
query = query.limit(params.limit)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const items = (await query) as T[]
|
|
376
|
-
|
|
377
|
-
let total: number | undefined
|
|
378
|
-
if (params.count) {
|
|
379
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
380
|
-
let countQuery = (db as any)
|
|
381
|
-
.select({ count: sql<number>`count(*)` })
|
|
382
|
-
.from(table)
|
|
383
|
-
if (where) countQuery = countQuery.where(where)
|
|
384
|
-
const [row] = await countQuery
|
|
385
|
-
total = Number(row?.count ?? 0)
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const hasMore =
|
|
389
|
-
total !== undefined && params.offset !== undefined
|
|
390
|
-
? params.offset + items.length < total
|
|
391
|
-
: items.length === params.limit
|
|
392
|
-
|
|
393
|
-
let nextCursor: string | undefined
|
|
394
|
-
if (items.length === params.limit) {
|
|
395
|
-
const last = items[items.length - 1]!
|
|
396
|
-
nextCursor = encodeCursor({
|
|
397
|
-
sort: params.sort,
|
|
398
|
-
order: params.order,
|
|
399
|
-
v: serializeCursorValue(last[params.sort]),
|
|
400
|
-
id: last.id as string | number,
|
|
401
|
-
})
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
items,
|
|
406
|
-
limit: params.limit,
|
|
407
|
-
...(params.offset !== undefined ? { offset: params.offset } : {}),
|
|
408
|
-
...(params.cursor ? { cursor: params.cursor } : {}),
|
|
409
|
-
...(nextCursor ? { nextCursor } : {}),
|
|
410
|
-
hasMore,
|
|
411
|
-
...(total !== undefined ? { total } : {}),
|
|
412
|
-
...(params.q ? { q: params.q } : {}),
|
|
413
|
-
sort: params.sort,
|
|
414
|
-
order: params.order,
|
|
415
|
-
}
|
|
416
|
-
}
|
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
|
-
}
|