bunderstack 0.17.0-beta.2 → 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 +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/config.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// src/config.ts
|
|
2
|
-
import { betterAuth } from 'better-auth'
|
|
3
|
-
import * as v from 'valibot'
|
|
4
|
-
|
|
5
|
-
import type { AnyRouter } from '@orpc/server'
|
|
6
|
-
import type { AuthSessionResolver, TableAccessInput } from './access'
|
|
7
|
-
import type { BunderstackApiBuilder } from './api/builder'
|
|
8
|
-
import type { DatabaseAdapter } from './database/adapter'
|
|
9
|
-
import type { EmailConfigInput } from './email'
|
|
10
|
-
import type { IdempotencyConfig } from './idempotency'
|
|
11
|
-
import type { RateLimitConfig } from './rate-limit'
|
|
12
|
-
|
|
13
|
-
import { validateEnv, type BaseEnv, type EnvConfigInput, type ValidatedEnv } from './env'
|
|
14
|
-
import {
|
|
15
|
-
resolveBuckets,
|
|
16
|
-
type ResolvedStorageBuckets,
|
|
17
|
-
type StorageConfigInput,
|
|
18
|
-
} from './storage/buckets'
|
|
19
|
-
|
|
20
|
-
export type BetterAuthConfig = Omit<
|
|
21
|
-
NonNullable<Parameters<typeof betterAuth>[0]>,
|
|
22
|
-
'database'
|
|
23
|
-
>
|
|
24
|
-
|
|
25
|
-
// Only the union-shaped options need runtime validation: they are the ones a
|
|
26
|
-
// JavaScript caller can plausibly get wrong in a way that fails confusingly
|
|
27
|
-
// downstream. Everything else is either typed-only or read raw from `options`.
|
|
28
|
-
const RuntimeOptionsSchema = v.object({
|
|
29
|
-
rateLimit: v.optional(
|
|
30
|
-
v.union([
|
|
31
|
-
v.boolean(),
|
|
32
|
-
v.object({
|
|
33
|
-
windowMs: v.optional(v.number()),
|
|
34
|
-
max: v.optional(v.number()),
|
|
35
|
-
}),
|
|
36
|
-
]),
|
|
37
|
-
),
|
|
38
|
-
idempotency: v.optional(
|
|
39
|
-
v.union([v.boolean(), v.object({ ttlMs: v.optional(v.number()) })]),
|
|
40
|
-
),
|
|
41
|
-
realtime: v.optional(
|
|
42
|
-
v.union([
|
|
43
|
-
v.boolean(),
|
|
44
|
-
v.object({
|
|
45
|
-
bufferSize: v.optional(v.number()),
|
|
46
|
-
resumeSeconds: v.optional(v.number()),
|
|
47
|
-
redis: v.optional(
|
|
48
|
-
v.union([
|
|
49
|
-
v.string(),
|
|
50
|
-
v.object({ url: v.string(), token: v.optional(v.string()) }),
|
|
51
|
-
]),
|
|
52
|
-
),
|
|
53
|
-
}),
|
|
54
|
-
]),
|
|
55
|
-
),
|
|
56
|
-
openapi: v.optional(v.boolean()),
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
export type BunderstackConfig<
|
|
60
|
-
TSchema extends Record<string, unknown>,
|
|
61
|
-
TAccess extends Record<string, TableAccessInput> | undefined =
|
|
62
|
-
| Record<string, TableAccessInput>
|
|
63
|
-
| undefined,
|
|
64
|
-
TStorage extends StorageConfigInput | undefined =
|
|
65
|
-
| StorageConfigInput
|
|
66
|
-
| undefined,
|
|
67
|
-
TEnv extends EnvConfigInput | undefined = EnvConfigInput | undefined,
|
|
68
|
-
TCustomApiRouter extends AnyRouter | undefined = AnyRouter | undefined,
|
|
69
|
-
> = {
|
|
70
|
-
schema: TSchema
|
|
71
|
-
access?: TAccess
|
|
72
|
-
database: {
|
|
73
|
-
adapter: DatabaseAdapter
|
|
74
|
-
url?: string
|
|
75
|
-
authToken?: string
|
|
76
|
-
migrations?: string
|
|
77
|
-
}
|
|
78
|
-
auth?: BetterAuthConfig
|
|
79
|
-
/**
|
|
80
|
-
* Reuse an application-owned session reader for the unified API while
|
|
81
|
-
* keeping Bunderstack's auth handler available.
|
|
82
|
-
*/
|
|
83
|
-
authResolver?: AuthSessionResolver
|
|
84
|
-
storage?: TStorage
|
|
85
|
-
env?: TEnv
|
|
86
|
-
/**
|
|
87
|
-
* Stand-in for `process.env`. Feeds both env validation and platform
|
|
88
|
-
* overrides, so tests and embedders have one injection point instead of
|
|
89
|
-
* three.
|
|
90
|
-
*/
|
|
91
|
-
processEnv?: Record<string, string | undefined>
|
|
92
|
-
background?: { autoStart?: boolean }
|
|
93
|
-
email?: EmailConfigInput
|
|
94
|
-
/**
|
|
95
|
-
* Unified oRPC API builder callback.
|
|
96
|
-
*/
|
|
97
|
-
api?: (
|
|
98
|
-
builder: BunderstackApiBuilder<TSchema, ValidatedEnv<TEnv>>,
|
|
99
|
-
) => TCustomApiRouter
|
|
100
|
-
rateLimit?: boolean | RateLimitConfig
|
|
101
|
-
idempotency?: boolean | IdempotencyConfig
|
|
102
|
-
/** Generate and serve `/api/openapi.json`. Disabled by default. */
|
|
103
|
-
openapi?: boolean
|
|
104
|
-
realtime?:
|
|
105
|
-
| boolean
|
|
106
|
-
| {
|
|
107
|
-
bufferSize?: number
|
|
108
|
-
resumeSeconds?: number
|
|
109
|
-
redis?: string | { url: string; token?: string }
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export type ResolvedConfig = {
|
|
114
|
-
database: {
|
|
115
|
-
adapter: DatabaseAdapter
|
|
116
|
-
url: string
|
|
117
|
-
authToken?: string
|
|
118
|
-
migrations: string
|
|
119
|
-
}
|
|
120
|
-
auth: BetterAuthConfig
|
|
121
|
-
storage: ResolvedStorageBuckets
|
|
122
|
-
realtime?:
|
|
123
|
-
| boolean
|
|
124
|
-
| {
|
|
125
|
-
bufferSize?: number
|
|
126
|
-
resumeSeconds?: number
|
|
127
|
-
redis?: string | { url: string; token?: string }
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export function resolveConfig<
|
|
132
|
-
TSchema extends Record<string, unknown>,
|
|
133
|
-
TAccess extends Record<string, TableAccessInput> | undefined = undefined,
|
|
134
|
-
TStorage extends StorageConfigInput | undefined = undefined,
|
|
135
|
-
TEnv extends EnvConfigInput | undefined = undefined,
|
|
136
|
-
TCustomApiRouter extends AnyRouter | undefined = undefined,
|
|
137
|
-
>(
|
|
138
|
-
options: BunderstackConfig<
|
|
139
|
-
TSchema,
|
|
140
|
-
TAccess,
|
|
141
|
-
TStorage,
|
|
142
|
-
TEnv,
|
|
143
|
-
TCustomApiRouter
|
|
144
|
-
>,
|
|
145
|
-
env?: BaseEnv,
|
|
146
|
-
// Platform-injected overrides (Bunderhost & co.) beat code-level config so
|
|
147
|
-
// apps with hardcoded local urls deploy unchanged.
|
|
148
|
-
platformSource: Record<string, string | undefined> = process.env as Record<
|
|
149
|
-
string,
|
|
150
|
-
string | undefined
|
|
151
|
-
>,
|
|
152
|
-
): ResolvedConfig {
|
|
153
|
-
const parsed = v.parse(RuntimeOptionsSchema, options)
|
|
154
|
-
// Self-validate when the caller didn't pass a pre-validated env, so
|
|
155
|
-
// resolveConfig stays usable standalone.
|
|
156
|
-
const resolvedEnv =
|
|
157
|
-
env ?? validateEnv(options.env as EnvConfigInput | undefined)
|
|
158
|
-
|
|
159
|
-
const adapter = options.database?.adapter
|
|
160
|
-
if (!adapter) {
|
|
161
|
-
throw new Error('[bunderstack] database.adapter is required')
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const defaultUrl =
|
|
165
|
-
adapter.dialect === 'sqlite' ? 'file:./data.db' : 'file:./data.pglite'
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
database: {
|
|
169
|
-
adapter,
|
|
170
|
-
url:
|
|
171
|
-
platformSource['BUNDERSTACK_DATABASE_URL'] ??
|
|
172
|
-
options.database?.url ??
|
|
173
|
-
resolvedEnv.DATABASE_URL ??
|
|
174
|
-
defaultUrl,
|
|
175
|
-
authToken:
|
|
176
|
-
platformSource['BUNDERSTACK_DATABASE_AUTH_TOKEN'] ??
|
|
177
|
-
options.database?.authToken ??
|
|
178
|
-
resolvedEnv.DATABASE_AUTH_TOKEN,
|
|
179
|
-
migrations: options.database?.migrations ?? './migrations',
|
|
180
|
-
},
|
|
181
|
-
auth: (() => {
|
|
182
|
-
const authInput = options.auth ?? {}
|
|
183
|
-
return {
|
|
184
|
-
...authInput,
|
|
185
|
-
secret: authInput.secret ?? resolvedEnv.AUTH_SECRET,
|
|
186
|
-
}
|
|
187
|
-
})(),
|
|
188
|
-
storage: resolveBuckets(options.storage, platformSource),
|
|
189
|
-
realtime: parsed.realtime,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export function resolveRealtimeRedisUrl(
|
|
194
|
-
realtime: ResolvedConfig['realtime'],
|
|
195
|
-
env?: BaseEnv,
|
|
196
|
-
platformSource: Record<string, string | undefined> = process.env as Record<
|
|
197
|
-
string,
|
|
198
|
-
string | undefined
|
|
199
|
-
>,
|
|
200
|
-
): string | undefined {
|
|
201
|
-
const platformRedis = platformSource['REDIS_URL']
|
|
202
|
-
if (platformRedis) return platformRedis
|
|
203
|
-
|
|
204
|
-
const envRedis = env?.REDIS_URL
|
|
205
|
-
if (envRedis) return envRedis
|
|
206
|
-
|
|
207
|
-
const fromConfig =
|
|
208
|
-
typeof realtime === 'object' && realtime.redis
|
|
209
|
-
? typeof realtime.redis === 'string'
|
|
210
|
-
? realtime.redis
|
|
211
|
-
: realtime.redis.url
|
|
212
|
-
: undefined
|
|
213
|
-
|
|
214
|
-
return fromConfig ?? undefined
|
|
215
|
-
}
|
package/src/cron.ts
DELETED
package/src/crud-operations.ts
DELETED
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
eq,
|
|
3
|
-
getTableColumns,
|
|
4
|
-
getTableName,
|
|
5
|
-
isTable,
|
|
6
|
-
type Table,
|
|
7
|
-
} from 'drizzle-orm'
|
|
8
|
-
|
|
9
|
-
import type { AnyDb } from './dialect'
|
|
10
|
-
import type { RealtimeFacade } from './realtime/facade'
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
checkAccess,
|
|
14
|
-
rowMatchesScope,
|
|
15
|
-
sanitizeWriteBody,
|
|
16
|
-
stampScope,
|
|
17
|
-
tableEntryForName,
|
|
18
|
-
type AccessUser,
|
|
19
|
-
type ResolvedAccess,
|
|
20
|
-
type ScopeMap,
|
|
21
|
-
type ScopeResolver,
|
|
22
|
-
} from './access'
|
|
23
|
-
import {
|
|
24
|
-
BunderstackError,
|
|
25
|
-
ErrorCode,
|
|
26
|
-
ListQueryError,
|
|
27
|
-
type BunderstackErrorCode,
|
|
28
|
-
type ErrorCodeValue,
|
|
29
|
-
} from './errors'
|
|
30
|
-
import {
|
|
31
|
-
lookupIdempotency,
|
|
32
|
-
resolveIdempotencyConfig,
|
|
33
|
-
storeIdempotency,
|
|
34
|
-
type IdempotencyConfig,
|
|
35
|
-
} from './idempotency'
|
|
36
|
-
import {
|
|
37
|
-
executeList,
|
|
38
|
-
resolveListParams,
|
|
39
|
-
type ListParamsInput,
|
|
40
|
-
type ListResult,
|
|
41
|
-
} from './list-query'
|
|
42
|
-
import { buildScopeWhere } from './scope'
|
|
43
|
-
|
|
44
|
-
export interface CrudExecutionContext {
|
|
45
|
-
request: Request
|
|
46
|
-
user: AccessUser | null
|
|
47
|
-
session: { activeOrganizationId: string | null }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function errorCodeForStatus(status: number): BunderstackErrorCode {
|
|
51
|
-
if (status === 401) return 'UNAUTHORIZED'
|
|
52
|
-
if (status === 403) return 'FORBIDDEN'
|
|
53
|
-
if (status === 404) return 'NOT_FOUND'
|
|
54
|
-
if (status === 409) return 'CONFLICT'
|
|
55
|
-
if (status === 413) return 'PAYLOAD_TOO_LARGE'
|
|
56
|
-
if (status === 429) return 'TOO_MANY_REQUESTS'
|
|
57
|
-
return 'BAD_REQUEST'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export class CrudOperationError extends BunderstackError {
|
|
61
|
-
constructor(
|
|
62
|
-
status: number,
|
|
63
|
-
readonly legacyCode: ErrorCodeValue,
|
|
64
|
-
message: string,
|
|
65
|
-
details?: unknown,
|
|
66
|
-
) {
|
|
67
|
-
const code = errorCodeForStatus(status)
|
|
68
|
-
super(
|
|
69
|
-
code,
|
|
70
|
-
message,
|
|
71
|
-
legacyCode === code
|
|
72
|
-
? details
|
|
73
|
-
: {
|
|
74
|
-
code: legacyCode,
|
|
75
|
-
...(details === undefined ? {} : { details }),
|
|
76
|
-
},
|
|
77
|
-
)
|
|
78
|
-
this.name = 'CrudOperationError'
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export type CrudOperationsDeps<
|
|
83
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
84
|
-
> = {
|
|
85
|
-
schema: TSchema
|
|
86
|
-
db: AnyDb
|
|
87
|
-
access: ResolvedAccess
|
|
88
|
-
idempotency?: boolean | IdempotencyConfig
|
|
89
|
-
realtime?: RealtimeFacade<TSchema>
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export type CreateResult =
|
|
93
|
-
| { type: 'created'; status: 201; record: Record<string, unknown> }
|
|
94
|
-
| {
|
|
95
|
-
type: 'replay'
|
|
96
|
-
status: number
|
|
97
|
-
body: string
|
|
98
|
-
record: Record<string, unknown>
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
102
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function isUniqueConstraintError(error: unknown): boolean {
|
|
106
|
-
const seen = new Set<unknown>()
|
|
107
|
-
let current = error
|
|
108
|
-
|
|
109
|
-
while (isRecord(current) && !seen.has(current)) {
|
|
110
|
-
seen.add(current)
|
|
111
|
-
const code = current['code']
|
|
112
|
-
if (
|
|
113
|
-
code === '23505' ||
|
|
114
|
-
code === 'SQLITE_CONSTRAINT_UNIQUE' ||
|
|
115
|
-
code === 'SQLITE_CONSTRAINT_PRIMARYKEY'
|
|
116
|
-
) {
|
|
117
|
-
return true
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const message = current['message']
|
|
121
|
-
if (
|
|
122
|
-
typeof message === 'string' &&
|
|
123
|
-
(/duplicate key value violates unique constraint/i.test(message) ||
|
|
124
|
-
/unique constraint failed/i.test(message))
|
|
125
|
-
) {
|
|
126
|
-
return true
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
current = current['cause']
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return false
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function coerceId(rawId: string | number): string | number {
|
|
136
|
-
if (typeof rawId === 'number') return rawId
|
|
137
|
-
return isNaN(Number(rawId)) ? rawId : Number(rawId)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function createCrudOperations<
|
|
141
|
-
TSchema extends Record<string, unknown> = Record<string, unknown>,
|
|
142
|
-
>(deps: CrudOperationsDeps<TSchema>) {
|
|
143
|
-
const { schema, db, access, realtime } = deps
|
|
144
|
-
const idempotency = resolveIdempotencyConfig(deps.idempotency)
|
|
145
|
-
|
|
146
|
-
const scopeFor = (
|
|
147
|
-
resolver: ScopeResolver | undefined,
|
|
148
|
-
ctx: {
|
|
149
|
-
user: AccessUser | null
|
|
150
|
-
session: { activeOrganizationId: string | null }
|
|
151
|
-
request: Request
|
|
152
|
-
row?: Record<string, unknown>
|
|
153
|
-
body?: Record<string, unknown>
|
|
154
|
-
},
|
|
155
|
-
): ScopeMap | undefined => (resolver ? resolver(ctx) : undefined)
|
|
156
|
-
|
|
157
|
-
function resolveTable(tableName: string) {
|
|
158
|
-
const table = Object.values(schema).find(
|
|
159
|
-
(t) => isTable(t) && getTableName(t) === tableName,
|
|
160
|
-
) as Table | undefined
|
|
161
|
-
if (!table) {
|
|
162
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
163
|
-
}
|
|
164
|
-
const tableAccess = tableEntryForName(access, tableName)
|
|
165
|
-
if (!tableAccess || !tableAccess.enabled) {
|
|
166
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
167
|
-
}
|
|
168
|
-
const idCol = getTableColumns(table)['id']
|
|
169
|
-
if (!idCol) {
|
|
170
|
-
throw new CrudOperationError(
|
|
171
|
-
400,
|
|
172
|
-
ErrorCode.BAD_REQUEST,
|
|
173
|
-
`Table ${tableName} has no id column`,
|
|
174
|
-
)
|
|
175
|
-
}
|
|
176
|
-
return { table, tableAccess, idCol }
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
async list(
|
|
181
|
-
tableName: string,
|
|
182
|
-
params: ListParamsInput | undefined,
|
|
183
|
-
ctx: CrudExecutionContext,
|
|
184
|
-
): Promise<ListResult<Record<string, unknown>>> {
|
|
185
|
-
const { table, tableAccess, idCol } = resolveTable(tableName)
|
|
186
|
-
|
|
187
|
-
const denied = await checkAccess(
|
|
188
|
-
tableAccess.list,
|
|
189
|
-
ctx,
|
|
190
|
-
tableAccess.ownerColumn,
|
|
191
|
-
)
|
|
192
|
-
if (!denied.allowed) {
|
|
193
|
-
throw new CrudOperationError(
|
|
194
|
-
denied.status === 401 ? 401 : 403,
|
|
195
|
-
ErrorCode.FORBIDDEN,
|
|
196
|
-
'Forbidden',
|
|
197
|
-
)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const resolved = resolveListParams(params ?? {}, tableAccess)
|
|
202
|
-
const scope = scopeFor(tableAccess.readScope, ctx)
|
|
203
|
-
const scopeWhere = scope ? buildScopeWhere(table, scope) : undefined
|
|
204
|
-
return await executeList(
|
|
205
|
-
db,
|
|
206
|
-
table,
|
|
207
|
-
tableAccess,
|
|
208
|
-
resolved,
|
|
209
|
-
idCol,
|
|
210
|
-
scopeWhere,
|
|
211
|
-
)
|
|
212
|
-
} catch (err) {
|
|
213
|
-
if (err instanceof ListQueryError) {
|
|
214
|
-
throw new CrudOperationError(400, err.code, err.message, err.details)
|
|
215
|
-
}
|
|
216
|
-
throw err
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
|
|
220
|
-
async get(
|
|
221
|
-
tableName: string,
|
|
222
|
-
rawId: string | number,
|
|
223
|
-
ctx: CrudExecutionContext,
|
|
224
|
-
): Promise<Record<string, unknown>> {
|
|
225
|
-
const { table, tableAccess, idCol } = resolveTable(tableName)
|
|
226
|
-
const id = coerceId(rawId)
|
|
227
|
-
|
|
228
|
-
const rows = await db
|
|
229
|
-
.select()
|
|
230
|
-
.from(table)
|
|
231
|
-
.where(eq(idCol, id))
|
|
232
|
-
if (!rows[0]) {
|
|
233
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
234
|
-
}
|
|
235
|
-
const row = rows[0] as Record<string, unknown>
|
|
236
|
-
|
|
237
|
-
const denied = await checkAccess(
|
|
238
|
-
tableAccess.get,
|
|
239
|
-
{ ...ctx, row },
|
|
240
|
-
tableAccess.ownerColumn,
|
|
241
|
-
)
|
|
242
|
-
if (!denied.allowed) {
|
|
243
|
-
throw new CrudOperationError(
|
|
244
|
-
denied.status === 401 ? 401 : 403,
|
|
245
|
-
ErrorCode.FORBIDDEN,
|
|
246
|
-
'Forbidden',
|
|
247
|
-
)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const scope = scopeFor(tableAccess.readScope, ctx)
|
|
251
|
-
if (scope && !rowMatchesScope(row, scope)) {
|
|
252
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return row
|
|
256
|
-
},
|
|
257
|
-
|
|
258
|
-
async create(
|
|
259
|
-
tableName: string,
|
|
260
|
-
body: unknown,
|
|
261
|
-
rawBody: string | undefined,
|
|
262
|
-
idempotencyKey: string | undefined,
|
|
263
|
-
ctx: CrudExecutionContext,
|
|
264
|
-
): Promise<CreateResult> {
|
|
265
|
-
const { table, tableAccess } = resolveTable(tableName)
|
|
266
|
-
|
|
267
|
-
const denied = await checkAccess(
|
|
268
|
-
tableAccess.create,
|
|
269
|
-
ctx,
|
|
270
|
-
tableAccess.ownerColumn,
|
|
271
|
-
)
|
|
272
|
-
if (!denied.allowed) {
|
|
273
|
-
throw new CrudOperationError(
|
|
274
|
-
denied.status === 401 ? 401 : 403,
|
|
275
|
-
ErrorCode.FORBIDDEN,
|
|
276
|
-
'Forbidden',
|
|
277
|
-
)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (!isRecord(body)) {
|
|
281
|
-
throw new CrudOperationError(
|
|
282
|
-
400,
|
|
283
|
-
ErrorCode.BAD_REQUEST,
|
|
284
|
-
'Invalid JSON body',
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const trimmedKey = idempotencyKey?.trim()
|
|
289
|
-
const effectiveRawBody = rawBody ?? JSON.stringify(body)
|
|
290
|
-
|
|
291
|
-
if (idempotency && trimmedKey) {
|
|
292
|
-
const lookup = await lookupIdempotency(
|
|
293
|
-
db,
|
|
294
|
-
tableName,
|
|
295
|
-
trimmedKey,
|
|
296
|
-
effectiveRawBody,
|
|
297
|
-
idempotency,
|
|
298
|
-
)
|
|
299
|
-
if (lookup.type === 'conflict') {
|
|
300
|
-
throw new CrudOperationError(
|
|
301
|
-
409,
|
|
302
|
-
ErrorCode.IDEMPOTENCY_CONFLICT,
|
|
303
|
-
'Idempotency key reused with different body',
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
if (lookup.type === 'replay') {
|
|
307
|
-
return {
|
|
308
|
-
type: 'replay',
|
|
309
|
-
status: lookup.status,
|
|
310
|
-
body: lookup.response,
|
|
311
|
-
record: JSON.parse(lookup.response) as Record<string, unknown>,
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const values = sanitizeWriteBody(
|
|
317
|
-
body,
|
|
318
|
-
tableAccess,
|
|
319
|
-
'create',
|
|
320
|
-
ctx.user?.id ?? null,
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
const scope = scopeFor(tableAccess.writeScope, { ...ctx, body })
|
|
324
|
-
const stamped = scope ? stampScope(values, scope) : values
|
|
325
|
-
|
|
326
|
-
let rows: Record<string, unknown>[]
|
|
327
|
-
try {
|
|
328
|
-
rows = await db.insert(table).values(stamped).returning()
|
|
329
|
-
} catch (error) {
|
|
330
|
-
if (isUniqueConstraintError(error)) {
|
|
331
|
-
throw new CrudOperationError(
|
|
332
|
-
409,
|
|
333
|
-
ErrorCode.CONFLICT,
|
|
334
|
-
'Record already exists',
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
throw error
|
|
338
|
-
}
|
|
339
|
-
const created = rows[0] as Record<string, unknown>
|
|
340
|
-
void realtime?.publish(table as never, 'create', created as never)
|
|
341
|
-
|
|
342
|
-
if (idempotency && trimmedKey) {
|
|
343
|
-
await storeIdempotency(
|
|
344
|
-
db,
|
|
345
|
-
tableName,
|
|
346
|
-
trimmedKey,
|
|
347
|
-
effectiveRawBody,
|
|
348
|
-
201,
|
|
349
|
-
created,
|
|
350
|
-
idempotency,
|
|
351
|
-
)
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
type: 'created',
|
|
356
|
-
status: 201,
|
|
357
|
-
record: created,
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
|
|
361
|
-
async update(
|
|
362
|
-
tableName: string,
|
|
363
|
-
rawId: string | number,
|
|
364
|
-
body: unknown,
|
|
365
|
-
ctx: CrudExecutionContext,
|
|
366
|
-
): Promise<Record<string, unknown>> {
|
|
367
|
-
const { table, tableAccess, idCol } = resolveTable(tableName)
|
|
368
|
-
const id = coerceId(rawId)
|
|
369
|
-
|
|
370
|
-
const existing = await db
|
|
371
|
-
.select()
|
|
372
|
-
.from(table)
|
|
373
|
-
.where(eq(idCol, id))
|
|
374
|
-
if (!existing[0]) {
|
|
375
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
376
|
-
}
|
|
377
|
-
const existingRow = existing[0] as Record<string, unknown>
|
|
378
|
-
|
|
379
|
-
const readScope = scopeFor(tableAccess.readScope, ctx)
|
|
380
|
-
if (readScope && !rowMatchesScope(existingRow, readScope)) {
|
|
381
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const denied = await checkAccess(
|
|
385
|
-
tableAccess.update,
|
|
386
|
-
{ ...ctx, row: existingRow },
|
|
387
|
-
tableAccess.ownerColumn,
|
|
388
|
-
)
|
|
389
|
-
if (!denied.allowed) {
|
|
390
|
-
throw new CrudOperationError(
|
|
391
|
-
denied.status === 401 ? 401 : 403,
|
|
392
|
-
ErrorCode.FORBIDDEN,
|
|
393
|
-
'Forbidden',
|
|
394
|
-
)
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!isRecord(body)) {
|
|
398
|
-
throw new CrudOperationError(
|
|
399
|
-
400,
|
|
400
|
-
ErrorCode.BAD_REQUEST,
|
|
401
|
-
'Invalid JSON body',
|
|
402
|
-
)
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
const values = sanitizeWriteBody(
|
|
406
|
-
body,
|
|
407
|
-
tableAccess,
|
|
408
|
-
'update',
|
|
409
|
-
ctx.user?.id ?? null,
|
|
410
|
-
)
|
|
411
|
-
|
|
412
|
-
if (Object.keys(values).length === 0) {
|
|
413
|
-
throw new CrudOperationError(
|
|
414
|
-
400,
|
|
415
|
-
ErrorCode.BAD_REQUEST,
|
|
416
|
-
'No writable fields to update',
|
|
417
|
-
)
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const writeScope = scopeFor(tableAccess.writeScope, { ...ctx, body })
|
|
421
|
-
const stamped = writeScope ? stampScope(values, writeScope) : values
|
|
422
|
-
|
|
423
|
-
const rows = await db
|
|
424
|
-
.update(table)
|
|
425
|
-
.set(stamped)
|
|
426
|
-
.where(eq(idCol, id))
|
|
427
|
-
.returning()
|
|
428
|
-
|
|
429
|
-
if (!rows[0]) {
|
|
430
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
431
|
-
}
|
|
432
|
-
const updated = rows[0] as Record<string, unknown>
|
|
433
|
-
void realtime?.publish(table as never, 'update', updated as never)
|
|
434
|
-
return updated
|
|
435
|
-
},
|
|
436
|
-
|
|
437
|
-
async delete(
|
|
438
|
-
tableName: string,
|
|
439
|
-
rawId: string | number,
|
|
440
|
-
ctx: CrudExecutionContext,
|
|
441
|
-
): Promise<void> {
|
|
442
|
-
const { table, tableAccess, idCol } = resolveTable(tableName)
|
|
443
|
-
const id = coerceId(rawId)
|
|
444
|
-
|
|
445
|
-
const existing = await db
|
|
446
|
-
.select()
|
|
447
|
-
.from(table)
|
|
448
|
-
.where(eq(idCol, id))
|
|
449
|
-
if (!existing[0]) {
|
|
450
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
451
|
-
}
|
|
452
|
-
const existingRow = existing[0] as Record<string, unknown>
|
|
453
|
-
|
|
454
|
-
const readScope = scopeFor(tableAccess.readScope, ctx)
|
|
455
|
-
if (readScope && !rowMatchesScope(existingRow, readScope)) {
|
|
456
|
-
throw new CrudOperationError(404, ErrorCode.NOT_FOUND, 'Not found')
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const denied = await checkAccess(
|
|
460
|
-
tableAccess.delete,
|
|
461
|
-
{ ...ctx, row: existingRow },
|
|
462
|
-
tableAccess.ownerColumn,
|
|
463
|
-
)
|
|
464
|
-
if (!denied.allowed) {
|
|
465
|
-
throw new CrudOperationError(
|
|
466
|
-
denied.status === 401 ? 401 : 403,
|
|
467
|
-
ErrorCode.FORBIDDEN,
|
|
468
|
-
'Forbidden',
|
|
469
|
-
)
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
await db.delete(table).where(eq(idCol, id))
|
|
473
|
-
void realtime?.publish(table as never, 'delete', existingRow as never)
|
|
474
|
-
},
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
export type CrudOperations = ReturnType<typeof createCrudOperations>
|