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.
- package/CHANGELOG.md +233 -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 +108 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +90 -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 +480 -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 +77 -28
- package/src/access.ts +0 -532
- package/src/api/builder.ts +0 -52
- package/src/api/context.ts +0 -83
- package/src/api/crud-router.ts +0 -386
- 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 -105
- 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 -478
- 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 -123
- package/src/handler.ts +0 -30
- package/src/idempotency.ts +0 -97
- package/src/index.ts +0 -834
- 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 -333
- 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 -74
- package/src/realtime/filter.ts +0 -81
- 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/access.ts
DELETED
|
@@ -1,532 +0,0 @@
|
|
|
1
|
-
import { getTableColumns, getTableName, isTable } from 'drizzle-orm'
|
|
2
|
-
|
|
3
|
-
import { INTERNAL_TABLE_NAMES } from './internal-tables'
|
|
4
|
-
|
|
5
|
-
export const AUTH_TABLE_NAMES = new Set([
|
|
6
|
-
'user',
|
|
7
|
-
'session',
|
|
8
|
-
'account',
|
|
9
|
-
'verification',
|
|
10
|
-
])
|
|
11
|
-
|
|
12
|
-
const EXPOSEABLE_AUTH_TABLES = new Set(['user'])
|
|
13
|
-
|
|
14
|
-
export type AccessUser = {
|
|
15
|
-
id: string
|
|
16
|
-
email: string
|
|
17
|
-
name?: string
|
|
18
|
-
role?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type AccessContext = {
|
|
22
|
-
user: AccessUser | null
|
|
23
|
-
request: Request
|
|
24
|
-
row?: Record<string, unknown>
|
|
25
|
-
body?: Record<string, unknown>
|
|
26
|
-
session?: { activeOrganizationId: string | null } | null
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type OperationRule =
|
|
30
|
-
| 'public'
|
|
31
|
-
| 'authenticated'
|
|
32
|
-
| 'owner'
|
|
33
|
-
| 'deny'
|
|
34
|
-
| ((ctx: AccessContext) => boolean | Promise<boolean>)
|
|
35
|
-
|
|
36
|
-
export type ScopeMap = Record<string, string | string[]>
|
|
37
|
-
export type ScopeResolver = (ctx: AccessContext) => ScopeMap
|
|
38
|
-
|
|
39
|
-
export type CrudOperation = 'list' | 'get' | 'create' | 'update' | 'delete'
|
|
40
|
-
|
|
41
|
-
export type SortOrder = 'asc' | 'desc'
|
|
42
|
-
|
|
43
|
-
export type DefaultSort = {
|
|
44
|
-
column: string
|
|
45
|
-
order: SortOrder
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type TableAccessInput = {
|
|
49
|
-
crud?: boolean
|
|
50
|
-
/** Opt the BetterAuth `user` table into auto-CRUD (read profiles, owner-update image). */
|
|
51
|
-
exposeAuthTable?: boolean
|
|
52
|
-
ownerColumn?: string
|
|
53
|
-
list?: OperationRule
|
|
54
|
-
get?: OperationRule
|
|
55
|
-
create?: OperationRule
|
|
56
|
-
update?: OperationRule
|
|
57
|
-
delete?: OperationRule
|
|
58
|
-
/** Explicit write allowlist. Entries override matching system-readonly
|
|
59
|
-
* defaults such as `updatedAt`; `id` remains immutable on update. */
|
|
60
|
-
writableColumns?: string[]
|
|
61
|
-
readonlyColumns?: string[]
|
|
62
|
-
/** Columns matched by `?q=` on list — opt-in; omitted columns are never searched. */
|
|
63
|
-
searchableColumns?: string[]
|
|
64
|
-
/** Columns filterable via flat `?column=value` query params. */
|
|
65
|
-
filterableColumns?: string[]
|
|
66
|
-
/** Columns allowed in `?sort=`. Defaults to `['id']`. */
|
|
67
|
-
sortableColumns?: string[]
|
|
68
|
-
/** Default list ordering when `?sort` is omitted. Defaults to `{ column: 'id', order: 'desc' }`. */
|
|
69
|
-
defaultSort?: DefaultSort
|
|
70
|
-
scope?: {
|
|
71
|
-
read?: ScopeResolver
|
|
72
|
-
write?: ScopeResolver
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type ResolvedTableAccess = {
|
|
77
|
-
tableKey: string
|
|
78
|
-
tableName: string
|
|
79
|
-
enabled: boolean
|
|
80
|
-
ownerColumn?: string
|
|
81
|
-
list: OperationRule
|
|
82
|
-
get: OperationRule
|
|
83
|
-
create: OperationRule
|
|
84
|
-
update: OperationRule
|
|
85
|
-
delete: OperationRule
|
|
86
|
-
writableColumns?: string[]
|
|
87
|
-
readonlyColumns: string[]
|
|
88
|
-
searchableColumns?: string[]
|
|
89
|
-
filterableColumns: string[]
|
|
90
|
-
sortableColumns: string[]
|
|
91
|
-
defaultSort: DefaultSort
|
|
92
|
-
readScope?: ScopeResolver
|
|
93
|
-
writeScope?: ScopeResolver
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export type ResolvedAccess = Map<string, ResolvedTableAccess>
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Look up a table's resolved access by its physical table name.
|
|
100
|
-
*
|
|
101
|
-
* `ResolvedAccess` is keyed by schema export name, not table name, so every
|
|
102
|
-
* consumer that starts from a physical name needs this scan. It lives here so
|
|
103
|
-
* CRUD, realtime, and route validation cannot drift apart on which tables they
|
|
104
|
-
* consider enabled.
|
|
105
|
-
*/
|
|
106
|
-
export function tableEntryForName(
|
|
107
|
-
access: ResolvedAccess,
|
|
108
|
-
tableName: string,
|
|
109
|
-
): ResolvedTableAccess | undefined {
|
|
110
|
-
for (const entry of access.values()) {
|
|
111
|
-
if (entry.tableName === tableName) return entry
|
|
112
|
-
}
|
|
113
|
-
return undefined
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const DEFAULT_READONLY = [
|
|
117
|
-
'id',
|
|
118
|
-
'createdAt',
|
|
119
|
-
'updatedAt',
|
|
120
|
-
'created_at',
|
|
121
|
-
'updated_at',
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
function getSchemaTables<TSchema extends Record<string, unknown>>(
|
|
125
|
-
schema: TSchema,
|
|
126
|
-
) {
|
|
127
|
-
const tables: {
|
|
128
|
-
key: string
|
|
129
|
-
table: Parameters<typeof getTableName>[0]
|
|
130
|
-
name: string
|
|
131
|
-
columns: string[]
|
|
132
|
-
}[] = []
|
|
133
|
-
for (const [key, value] of Object.entries(schema)) {
|
|
134
|
-
if (!isTable(value)) continue
|
|
135
|
-
const name = getTableName(value)
|
|
136
|
-
const columns = Object.keys(getTableColumns(value))
|
|
137
|
-
tables.push({ key, table: value, name, columns })
|
|
138
|
-
}
|
|
139
|
-
return tables
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function resolveListAccess(
|
|
143
|
-
input: TableAccessInput,
|
|
144
|
-
columns: string[],
|
|
145
|
-
): Pick<
|
|
146
|
-
ResolvedTableAccess,
|
|
147
|
-
'filterableColumns' | 'sortableColumns' | 'defaultSort'
|
|
148
|
-
> {
|
|
149
|
-
const sortableColumns =
|
|
150
|
-
input.sortableColumns ?? (columns.includes('id') ? ['id'] : [])
|
|
151
|
-
const defaultSort: DefaultSort = input.defaultSort ?? {
|
|
152
|
-
column: sortableColumns[0] ?? 'id',
|
|
153
|
-
order: 'desc',
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!columns.includes(defaultSort.column)) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`[bunderstack] defaultSort.column "${defaultSort.column}" is not a column on this table`,
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
if (!sortableColumns.includes(defaultSort.column)) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
`[bunderstack] defaultSort.column "${defaultSort.column}" must be listed in sortableColumns`,
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const filterableColumns = input.filterableColumns ?? []
|
|
168
|
-
for (const col of filterableColumns) {
|
|
169
|
-
if (!columns.includes(col)) {
|
|
170
|
-
throw new Error(
|
|
171
|
-
`[bunderstack] filterableColumns references unknown column "${col}"`,
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (const col of sortableColumns) {
|
|
177
|
-
if (!columns.includes(col)) {
|
|
178
|
-
throw new Error(
|
|
179
|
-
`[bunderstack] sortableColumns references unknown column "${col}"`,
|
|
180
|
-
)
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { filterableColumns, sortableColumns, defaultSort }
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function resolveDefaults(
|
|
188
|
-
input: TableAccessInput,
|
|
189
|
-
ownerColumn: string | undefined,
|
|
190
|
-
columns: string[],
|
|
191
|
-
): Omit<ResolvedTableAccess, 'tableKey' | 'tableName' | 'enabled'> {
|
|
192
|
-
const listAccess = resolveListAccess(input, columns)
|
|
193
|
-
const explicitlyWritable = new Set(input.writableColumns ?? [])
|
|
194
|
-
return {
|
|
195
|
-
ownerColumn,
|
|
196
|
-
list: input.list ?? 'public',
|
|
197
|
-
get: input.get ?? 'public',
|
|
198
|
-
create: input.create ?? 'public',
|
|
199
|
-
update: input.update ?? (ownerColumn ? 'owner' : 'deny'),
|
|
200
|
-
delete: input.delete ?? (ownerColumn ? 'owner' : 'deny'),
|
|
201
|
-
writableColumns: input.writableColumns,
|
|
202
|
-
readonlyColumns: [
|
|
203
|
-
...DEFAULT_READONLY.filter(
|
|
204
|
-
(column) => column === 'id' || !explicitlyWritable.has(column),
|
|
205
|
-
),
|
|
206
|
-
...(input.readonlyColumns ?? []),
|
|
207
|
-
...(ownerColumn ? [ownerColumn] : []),
|
|
208
|
-
],
|
|
209
|
-
searchableColumns: input.searchableColumns,
|
|
210
|
-
readScope: input.scope?.read,
|
|
211
|
-
writeScope: input.scope?.write,
|
|
212
|
-
...listAccess,
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function detectOwnerColumn(
|
|
217
|
-
columns: string[],
|
|
218
|
-
input?: TableAccessInput,
|
|
219
|
-
): string | undefined {
|
|
220
|
-
if (input?.ownerColumn) return input.ownerColumn
|
|
221
|
-
if (columns.includes('userId')) return 'userId'
|
|
222
|
-
return undefined
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function validateAndResolveAccess<
|
|
226
|
-
TSchema extends Record<string, unknown>,
|
|
227
|
-
>(
|
|
228
|
-
schema: TSchema,
|
|
229
|
-
accessInput?: Record<string, TableAccessInput>,
|
|
230
|
-
): ResolvedAccess {
|
|
231
|
-
const tables = getSchemaTables(schema)
|
|
232
|
-
const tableByKey = new Map(tables.map((t) => [t.key, t]))
|
|
233
|
-
const resolved: ResolvedAccess = new Map()
|
|
234
|
-
|
|
235
|
-
if (accessInput) {
|
|
236
|
-
for (const key of Object.keys(accessInput)) {
|
|
237
|
-
if (!tableByKey.has(key)) {
|
|
238
|
-
throw new Error(
|
|
239
|
-
`[bunderstack] access.${key} does not match any table in schema`,
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
const tableName = tableByKey.get(key)!.name
|
|
243
|
-
const input = accessInput[key]
|
|
244
|
-
if (AUTH_TABLE_NAMES.has(tableName)) {
|
|
245
|
-
if (input?.crud === false) continue
|
|
246
|
-
if (!EXPOSEABLE_AUTH_TABLES.has(tableName) || !input?.exposeAuthTable) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`[bunderstack] access.${key} cannot target auth table "${tableName}" — use { crud: false } to silence, or exposeAuthTable on user`,
|
|
249
|
-
)
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
for (const { key, name, columns } of tables) {
|
|
256
|
-
const input = accessInput?.[key]
|
|
257
|
-
if (input?.crud === false) continue
|
|
258
|
-
if (INTERNAL_TABLE_NAMES.has(name)) continue
|
|
259
|
-
|
|
260
|
-
if (AUTH_TABLE_NAMES.has(name)) {
|
|
261
|
-
if (!input?.exposeAuthTable || !EXPOSEABLE_AUTH_TABLES.has(name)) continue
|
|
262
|
-
|
|
263
|
-
const ownerColumn = input.ownerColumn ?? 'id'
|
|
264
|
-
if (!columns.includes(ownerColumn)) {
|
|
265
|
-
throw new Error(
|
|
266
|
-
`[bunderstack] access.${key}.ownerColumn "${ownerColumn}" is not a column on table "${name}"`,
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const defaults = resolveDefaults(
|
|
271
|
-
{
|
|
272
|
-
...input,
|
|
273
|
-
create: input.create ?? 'deny',
|
|
274
|
-
delete: input.delete ?? 'deny',
|
|
275
|
-
},
|
|
276
|
-
ownerColumn,
|
|
277
|
-
columns,
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
const existingReadonly = defaults.readonlyColumns.filter((col) =>
|
|
281
|
-
columns.includes(col),
|
|
282
|
-
)
|
|
283
|
-
const resolvedReadonly = [...new Set(existingReadonly)]
|
|
284
|
-
|
|
285
|
-
for (const col of [
|
|
286
|
-
...(defaults.writableColumns ?? []),
|
|
287
|
-
...(defaults.searchableColumns ?? []),
|
|
288
|
-
...defaults.filterableColumns,
|
|
289
|
-
...defaults.sortableColumns,
|
|
290
|
-
...resolvedReadonly,
|
|
291
|
-
]) {
|
|
292
|
-
if (!columns.includes(col)) {
|
|
293
|
-
throw new Error(
|
|
294
|
-
`[bunderstack] access.${key} references unknown column "${col}" on table "${name}"`,
|
|
295
|
-
)
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
resolved.set(key, {
|
|
300
|
-
tableKey: key,
|
|
301
|
-
tableName: name,
|
|
302
|
-
enabled: true,
|
|
303
|
-
...defaults,
|
|
304
|
-
readonlyColumns: resolvedReadonly,
|
|
305
|
-
})
|
|
306
|
-
continue
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const ownerColumn = detectOwnerColumn(columns, input)
|
|
310
|
-
const hasExplicitRules = input !== undefined
|
|
311
|
-
const hasConventionOwner =
|
|
312
|
-
ownerColumn !== undefined &&
|
|
313
|
-
input?.ownerColumn === undefined &&
|
|
314
|
-
columns.includes('userId')
|
|
315
|
-
|
|
316
|
-
if (!hasExplicitRules && !hasConventionOwner) continue
|
|
317
|
-
if (
|
|
318
|
-
!ownerColumn &&
|
|
319
|
-
input?.crud !== true &&
|
|
320
|
-
!input?.scope?.read &&
|
|
321
|
-
!input?.scope?.write
|
|
322
|
-
)
|
|
323
|
-
continue
|
|
324
|
-
|
|
325
|
-
if (input?.ownerColumn && !columns.includes(input.ownerColumn)) {
|
|
326
|
-
throw new Error(
|
|
327
|
-
`[bunderstack] access.${key}.ownerColumn "${input.ownerColumn}" is not a column on table "${name}"`,
|
|
328
|
-
)
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
const defaults = resolveDefaults(input ?? {}, ownerColumn, columns)
|
|
332
|
-
const existingReadonly = defaults.readonlyColumns.filter((col) =>
|
|
333
|
-
columns.includes(col),
|
|
334
|
-
)
|
|
335
|
-
const resolvedReadonly = [...new Set(existingReadonly)]
|
|
336
|
-
|
|
337
|
-
for (const col of [
|
|
338
|
-
...(defaults.writableColumns ?? []),
|
|
339
|
-
...(defaults.searchableColumns ?? []),
|
|
340
|
-
...defaults.filterableColumns,
|
|
341
|
-
...defaults.sortableColumns,
|
|
342
|
-
...resolvedReadonly,
|
|
343
|
-
]) {
|
|
344
|
-
if (!columns.includes(col)) {
|
|
345
|
-
throw new Error(
|
|
346
|
-
`[bunderstack] access.${key} references unknown column "${col}" on table "${name}"`,
|
|
347
|
-
)
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const needsOwner = [defaults.update, defaults.delete].some(
|
|
352
|
-
(r) => r === 'owner',
|
|
353
|
-
)
|
|
354
|
-
if (needsOwner && !ownerColumn) {
|
|
355
|
-
throw new Error(
|
|
356
|
-
`[bunderstack] access.${key} requires ownerColumn for owner-based update/delete rules`,
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
resolved.set(key, {
|
|
361
|
-
tableKey: key,
|
|
362
|
-
tableName: name,
|
|
363
|
-
enabled: true,
|
|
364
|
-
...defaults,
|
|
365
|
-
readonlyColumns: resolvedReadonly,
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
return resolved
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
export function defineAccess<
|
|
373
|
-
TSchema extends Record<string, unknown>,
|
|
374
|
-
const TRules extends Record<string, TableAccessInput>,
|
|
375
|
-
>(schema: TSchema, rules: TRules): TRules {
|
|
376
|
-
validateAndResolveAccess(schema, rules)
|
|
377
|
-
return rules
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
export function rowMatchesScope(
|
|
381
|
-
row: Record<string, unknown>,
|
|
382
|
-
scope: ScopeMap,
|
|
383
|
-
): boolean {
|
|
384
|
-
for (const [col, expected] of Object.entries(scope)) {
|
|
385
|
-
const actual = row[col]
|
|
386
|
-
if (actual == null) return false
|
|
387
|
-
if (Array.isArray(expected)) {
|
|
388
|
-
if (!expected.map(String).includes(String(actual))) return false
|
|
389
|
-
} else if (String(actual) !== String(expected)) {
|
|
390
|
-
return false
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return true
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
export function stampScope(
|
|
397
|
-
values: Record<string, unknown>,
|
|
398
|
-
scope: ScopeMap,
|
|
399
|
-
): Record<string, unknown> {
|
|
400
|
-
const out = { ...values }
|
|
401
|
-
for (const [col, expected] of Object.entries(scope)) {
|
|
402
|
-
if (!Array.isArray(expected)) out[col] = expected
|
|
403
|
-
}
|
|
404
|
-
return out
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
export function checkAccessSync(
|
|
408
|
-
rule: Exclude<
|
|
409
|
-
OperationRule,
|
|
410
|
-
(ctx: AccessContext) => boolean | Promise<boolean>
|
|
411
|
-
>,
|
|
412
|
-
ctx: AccessContext,
|
|
413
|
-
ownerColumn?: string,
|
|
414
|
-
): { allowed: boolean } {
|
|
415
|
-
if (rule === 'deny') return { allowed: false }
|
|
416
|
-
if (rule === 'public') return { allowed: true }
|
|
417
|
-
if (!ctx.user) return { allowed: false }
|
|
418
|
-
if (rule === 'authenticated') return { allowed: true }
|
|
419
|
-
if (rule === 'owner') {
|
|
420
|
-
if (!ownerColumn) return { allowed: false }
|
|
421
|
-
const owner = ctx.row?.[ownerColumn]
|
|
422
|
-
return { allowed: owner != null && String(owner) === ctx.user.id }
|
|
423
|
-
}
|
|
424
|
-
return { allowed: false }
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export async function checkAccess(
|
|
428
|
-
rule: OperationRule,
|
|
429
|
-
ctx: AccessContext,
|
|
430
|
-
ownerColumn?: string,
|
|
431
|
-
): Promise<{ allowed: boolean; status: 401 | 403 }> {
|
|
432
|
-
if (rule === 'deny') return { allowed: false, status: 403 }
|
|
433
|
-
|
|
434
|
-
if (typeof rule === 'function') {
|
|
435
|
-
return (await rule(ctx))
|
|
436
|
-
? { allowed: true, status: 403 }
|
|
437
|
-
: { allowed: false, status: 403 }
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (rule === 'public') return { allowed: true, status: 403 }
|
|
441
|
-
|
|
442
|
-
if (!ctx.user) return { allowed: false, status: 401 }
|
|
443
|
-
|
|
444
|
-
if (rule === 'authenticated') return { allowed: true, status: 403 }
|
|
445
|
-
|
|
446
|
-
if (rule === 'owner') {
|
|
447
|
-
if (!ownerColumn) return { allowed: false, status: 403 }
|
|
448
|
-
const rowOwner = ctx.row?.[ownerColumn] ?? ctx.body?.[ownerColumn]
|
|
449
|
-
if (rowOwner == null) return { allowed: false, status: 403 }
|
|
450
|
-
if (String(rowOwner) !== ctx.user.id) return { allowed: false, status: 403 }
|
|
451
|
-
return { allowed: true, status: 403 }
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return { allowed: false, status: 403 }
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export function sanitizeWriteBody(
|
|
458
|
-
body: Record<string, unknown>,
|
|
459
|
-
access: ResolvedTableAccess,
|
|
460
|
-
mode: 'create' | 'update',
|
|
461
|
-
userId: string | null,
|
|
462
|
-
): Record<string, unknown> {
|
|
463
|
-
const out: Record<string, unknown> = {}
|
|
464
|
-
const readonly = new Set(access.readonlyColumns)
|
|
465
|
-
|
|
466
|
-
for (const [key, value] of Object.entries(body)) {
|
|
467
|
-
// On create, allow client-supplied `id` even if it appears in readonlyColumns,
|
|
468
|
-
// but still respect an explicit writableColumns allowlist if provided.
|
|
469
|
-
if (mode === 'create' && key === 'id') {
|
|
470
|
-
if (access.writableColumns && !access.writableColumns.includes(key))
|
|
471
|
-
continue
|
|
472
|
-
out[key] = value
|
|
473
|
-
continue
|
|
474
|
-
}
|
|
475
|
-
if (readonly.has(key)) continue
|
|
476
|
-
if (access.writableColumns && !access.writableColumns.includes(key))
|
|
477
|
-
continue
|
|
478
|
-
out[key] = value
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (mode === 'create' && access.ownerColumn) {
|
|
482
|
-
out[access.ownerColumn] = userId ?? null
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
if (mode === 'update' && access.ownerColumn) {
|
|
486
|
-
delete out[access.ownerColumn]
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
return out
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export type AuthSessionResolver = {
|
|
493
|
-
api: {
|
|
494
|
-
getSession: (opts: { headers: Headers }) => Promise<{
|
|
495
|
-
user: { id: string; email: string; name?: string; role?: string } | null
|
|
496
|
-
session?: { activeOrganizationId?: string | null } | null
|
|
497
|
-
} | null>
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
export async function resolveAccessUser(
|
|
502
|
-
auth: AuthSessionResolver | undefined,
|
|
503
|
-
headers: Headers,
|
|
504
|
-
): Promise<AccessUser | null> {
|
|
505
|
-
if (!auth) return null
|
|
506
|
-
const session = await auth.api.getSession({ headers })
|
|
507
|
-
if (!session?.user) return null
|
|
508
|
-
return {
|
|
509
|
-
id: session.user.id,
|
|
510
|
-
email: session.user.email,
|
|
511
|
-
name: session.user.name,
|
|
512
|
-
role: session.user.role,
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
export async function resolveSession(
|
|
517
|
-
auth: AuthSessionResolver | undefined,
|
|
518
|
-
headers: Headers,
|
|
519
|
-
): Promise<{ user: AccessUser | null; activeOrganizationId: string | null }> {
|
|
520
|
-
if (!auth) return { user: null, activeOrganizationId: null }
|
|
521
|
-
const session = await auth.api.getSession({ headers })
|
|
522
|
-
if (!session?.user) return { user: null, activeOrganizationId: null }
|
|
523
|
-
return {
|
|
524
|
-
user: {
|
|
525
|
-
id: session.user.id,
|
|
526
|
-
email: session.user.email,
|
|
527
|
-
name: session.user.name,
|
|
528
|
-
role: session.user.role,
|
|
529
|
-
},
|
|
530
|
-
activeOrganizationId: session.session?.activeOrganizationId ?? null,
|
|
531
|
-
}
|
|
532
|
-
}
|
package/src/api/builder.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { os, type AnyRouter } from '@orpc/server'
|
|
2
|
-
|
|
3
|
-
import type { ApiContext } from './context'
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
BUNDERSTACK_ERRORS,
|
|
7
|
-
BunderstackError,
|
|
8
|
-
mapBunderstackErrors,
|
|
9
|
-
} from '../errors'
|
|
10
|
-
export type { ProtectedContextAdditions } from './types'
|
|
11
|
-
|
|
12
|
-
export function createApiBuilder<
|
|
13
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
14
|
-
TEnv = Record<string, unknown>,
|
|
15
|
-
>() {
|
|
16
|
-
const base = os
|
|
17
|
-
.$context<ApiContext<TSchema, TEnv>>()
|
|
18
|
-
.errors(BUNDERSTACK_ERRORS)
|
|
19
|
-
.use(mapBunderstackErrors)
|
|
20
|
-
|
|
21
|
-
const protectedProc = base.use(async ({ context, next }) => {
|
|
22
|
-
const session = await context.getSession()
|
|
23
|
-
if (!session.user) {
|
|
24
|
-
throw new BunderstackError('UNAUTHORIZED', 'Authentication required')
|
|
25
|
-
}
|
|
26
|
-
return next({
|
|
27
|
-
context: {
|
|
28
|
-
user: session.user,
|
|
29
|
-
session: {
|
|
30
|
-
activeOrganizationId: session.activeOrganizationId,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
public: base,
|
|
38
|
-
protected: protectedProc,
|
|
39
|
-
webhook: base,
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export type BunderstackApiBuilder<
|
|
44
|
-
TSchema extends Record<string, unknown>,
|
|
45
|
-
TEnv = Record<string, unknown>,
|
|
46
|
-
> = ReturnType<typeof createApiBuilder<TSchema, TEnv>>
|
|
47
|
-
|
|
48
|
-
export type ApiFactory<
|
|
49
|
-
TSchema extends Record<string, unknown>,
|
|
50
|
-
TEnv,
|
|
51
|
-
TCustomApiRouter extends AnyRouter,
|
|
52
|
-
> = (builder: BunderstackApiBuilder<TSchema, TEnv>) => TCustomApiRouter
|
package/src/api/context.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { AccessUser, AuthSessionResolver } from '../access'
|
|
2
|
-
import type { DbFor } from '../db'
|
|
3
|
-
import type { EmailFacade } from '../email'
|
|
4
|
-
import type { AuthInstance, StorageFacade } from '../index'
|
|
5
|
-
import type { JobsRuntimeFacade } from '../jobs/define'
|
|
6
|
-
import type { RealtimeFacade } from '../realtime/facade'
|
|
7
|
-
|
|
8
|
-
import { resolveSession } from '../access'
|
|
9
|
-
|
|
10
|
-
export interface ApiContextDeps<
|
|
11
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
12
|
-
TEnv = Record<string, unknown>,
|
|
13
|
-
> {
|
|
14
|
-
db: DbFor<TSchema>
|
|
15
|
-
env: TEnv
|
|
16
|
-
storage: StorageFacade
|
|
17
|
-
email: EmailFacade
|
|
18
|
-
jobs: JobsRuntimeFacade
|
|
19
|
-
realtime: RealtimeFacade<TSchema>
|
|
20
|
-
auth: AuthInstance
|
|
21
|
-
authResolver?: AuthSessionResolver
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface ApiContext<
|
|
25
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
26
|
-
TEnv = Record<string, unknown>,
|
|
27
|
-
> {
|
|
28
|
-
db: DbFor<TSchema>
|
|
29
|
-
env: TEnv
|
|
30
|
-
storage: StorageFacade
|
|
31
|
-
email: EmailFacade
|
|
32
|
-
jobs: JobsRuntimeFacade
|
|
33
|
-
realtime: RealtimeFacade<TSchema>
|
|
34
|
-
auth: AuthInstance
|
|
35
|
-
request: Request
|
|
36
|
-
resHeaders: Headers
|
|
37
|
-
getRawBody: () => Promise<string>
|
|
38
|
-
getSession: () => Promise<{
|
|
39
|
-
user: AccessUser | null
|
|
40
|
-
activeOrganizationId: string | null
|
|
41
|
-
}>
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createApiContext<
|
|
45
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
46
|
-
TEnv = Record<string, unknown>,
|
|
47
|
-
>(
|
|
48
|
-
deps: ApiContextDeps<TSchema, TEnv>,
|
|
49
|
-
request: Request,
|
|
50
|
-
): ApiContext<TSchema, TEnv> {
|
|
51
|
-
// Reserve the body stream before a transport codec consumes `request`.
|
|
52
|
-
const rawBodyRequest = request.clone()
|
|
53
|
-
let rawBodyPromise: Promise<string> | undefined
|
|
54
|
-
let sessionPromise:
|
|
55
|
-
| Promise<{ user: AccessUser | null; activeOrganizationId: string | null }>
|
|
56
|
-
| undefined
|
|
57
|
-
|
|
58
|
-
const getSession = () => {
|
|
59
|
-
if (!sessionPromise) {
|
|
60
|
-
sessionPromise = resolveSession(deps.authResolver, request.headers)
|
|
61
|
-
}
|
|
62
|
-
return sessionPromise
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const getRawBody = () => {
|
|
66
|
-
if (!rawBodyPromise) rawBodyPromise = rawBodyRequest.text()
|
|
67
|
-
return rawBodyPromise
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
db: deps.db,
|
|
72
|
-
env: deps.env,
|
|
73
|
-
storage: deps.storage,
|
|
74
|
-
email: deps.email,
|
|
75
|
-
jobs: deps.jobs,
|
|
76
|
-
realtime: deps.realtime,
|
|
77
|
-
auth: deps.auth,
|
|
78
|
-
request,
|
|
79
|
-
resHeaders: new Headers(),
|
|
80
|
-
getRawBody,
|
|
81
|
-
getSession,
|
|
82
|
-
}
|
|
83
|
-
}
|