bunderstack 0.16.0 → 0.17.0-beta.0
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/README.md +25 -138
- package/package.json +21 -14
- package/src/access.ts +6 -1
- package/src/api/api-types.types.ts +106 -0
- package/src/api/builder.ts +52 -0
- package/src/api/context.ts +83 -0
- package/src/api/crud-router.ts +321 -0
- package/src/api/openapi.ts +184 -0
- package/src/api/realtime-router.ts +75 -0
- package/src/api/registry.ts +338 -0
- package/src/api/router.ts +34 -0
- package/src/api/storage-router.ts +224 -0
- package/src/api/types.ts +84 -0
- package/src/auth.ts +5 -0
- package/src/blueprint.ts +88 -105
- package/src/config.ts +57 -39
- package/src/crud-operations.ts +488 -0
- package/src/dialect.ts +1 -1
- package/src/env.ts +19 -13
- package/src/errors.ts +90 -23
- package/src/handler.ts +16 -44
- package/src/index.ts +235 -246
- package/src/jobs/define.ts +8 -10
- package/src/jobs/queue.ts +6 -2
- package/src/jobs/worker.ts +4 -1
- package/src/manifest.ts +84 -87
- package/src/realtime/facade.ts +16 -13
- package/src/realtime/filter.ts +77 -0
- package/src/realtime/heartbeat.ts +80 -0
- package/src/realtime/publisher.ts +46 -0
- package/src/standard-schema.ts +59 -0
- package/src/storage/index.ts +8 -0
- package/src/storage/operations.ts +398 -0
- package/src/crud.ts +0 -400
- package/src/realtime/index.ts +0 -242
- package/src/realtime/redis.ts +0 -219
- package/src/routes.ts +0 -137
- package/src/storage/router.ts +0 -531
- package/src/trpc.ts +0 -57
package/src/index.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import type { AnyRouter as AnyORPCRouter } from '@orpc/server'
|
|
1
2
|
// src/index.ts
|
|
2
|
-
import type { AnyRouter } from '@trpc/server'
|
|
3
|
-
import type { Hono as HonoType } from 'hono'
|
|
4
3
|
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import {
|
|
4
|
+
import { OpenAPIGenerator, OpenAPIGeneratorError } from '@orpc/openapi'
|
|
5
|
+
import { OpenAPIHandler } from '@orpc/openapi/fetch'
|
|
6
|
+
import { RPCHandler } from '@orpc/server/fetch'
|
|
7
|
+
import { ValibotToJsonSchemaConverter } from '@orpc/valibot'
|
|
8
8
|
|
|
9
9
|
import type { TableAccessInput } from './access'
|
|
10
|
+
import type {
|
|
11
|
+
CrudApiRouterFor,
|
|
12
|
+
ExposedApiTables,
|
|
13
|
+
MergeApiRouterTypes,
|
|
14
|
+
UnifiedApiRouter,
|
|
15
|
+
} from './api/types'
|
|
16
|
+
import type { RealtimeApiRouter } from './api/realtime-router'
|
|
10
17
|
import type { DbFor } from './db'
|
|
11
18
|
import type {
|
|
12
19
|
BunderstackJobsBuilder,
|
|
@@ -19,11 +26,20 @@ import type {
|
|
|
19
26
|
import type { StorageConfigInput } from './storage/buckets'
|
|
20
27
|
import type { StorageAdapter } from './storage/index'
|
|
21
28
|
|
|
29
|
+
import { validateAndResolveAccess } from './access'
|
|
30
|
+
import { createApiBuilder } from './api/builder'
|
|
31
|
+
import { createApiContext } from './api/context'
|
|
32
|
+
import { buildCrudApiRouter } from './api/crud-router'
|
|
33
|
+
import { mergeOpenAPISpecs } from './api/openapi'
|
|
34
|
+
import { buildRealtimeApiRouter } from './api/realtime-router'
|
|
35
|
+
import { buildApiRouter } from './api/router'
|
|
36
|
+
import { buildStorageApiRouter } from './api/storage-router'
|
|
22
37
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
38
|
+
buildApiRegistry,
|
|
39
|
+
mergeApiRoutersStrict,
|
|
40
|
+
normalizeApiPath,
|
|
41
|
+
normalizeForeignOpenAPISpec,
|
|
42
|
+
} from './api/registry'
|
|
27
43
|
import {
|
|
28
44
|
createAuth,
|
|
29
45
|
toAuthSessionResolver,
|
|
@@ -31,11 +47,11 @@ import {
|
|
|
31
47
|
} from './auth'
|
|
32
48
|
import { resolveConfig, type BunderstackConfig } from './config'
|
|
33
49
|
import { resolveRealtimeRedisUrl } from './config'
|
|
34
|
-
import { buildCrudRouter } from './crud'
|
|
35
50
|
import { createDb } from './db'
|
|
36
51
|
import { detectDialect } from './dialect'
|
|
37
52
|
import { createEmail, emailProviderTag, type EmailFacade } from './email'
|
|
38
53
|
import { validateEnv, type EnvConfigInput, type ValidatedEnv } from './env'
|
|
54
|
+
import { BUNDERSTACK_ERROR_STATUS_MAP } from './errors'
|
|
39
55
|
import { buildHandler } from './handler'
|
|
40
56
|
import { withInternalTables } from './internal-tables'
|
|
41
57
|
import {
|
|
@@ -56,19 +72,18 @@ import {
|
|
|
56
72
|
type RealtimeFacade,
|
|
57
73
|
type RealtimeTransport,
|
|
58
74
|
} from './realtime/facade'
|
|
59
|
-
import {
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
import {
|
|
76
|
+
createMemoryRealtimePublisher,
|
|
77
|
+
createRedisRealtimePublisher,
|
|
78
|
+
} from './realtime/publisher'
|
|
62
79
|
import { deleteFileWithDerivatives } from './storage/delete'
|
|
63
80
|
import { deleteFileMetaRow, insertReadyFile } from './storage/file-meta'
|
|
64
81
|
import { createBucketStorages } from './storage/registry'
|
|
65
|
-
import {
|
|
82
|
+
import { createStorageOperations } from './storage/operations'
|
|
66
83
|
import { sweepOrphans } from './storage/sweep'
|
|
67
|
-
import { createTRPC, type BunderstackTRPC } from './trpc'
|
|
68
84
|
|
|
69
85
|
export type AuthInstance = ReturnType<typeof createAuth>
|
|
70
86
|
|
|
71
|
-
|
|
72
87
|
function waitForWorkerShutdown(
|
|
73
88
|
signal: AbortSignal,
|
|
74
89
|
installProcessListeners: boolean,
|
|
@@ -153,16 +168,13 @@ export type BunderstackApp<
|
|
|
153
168
|
TAccess extends Record<string, TableAccessInput> | undefined = undefined,
|
|
154
169
|
TBuckets extends string = string,
|
|
155
170
|
TEnv extends EnvConfigInput | undefined = undefined,
|
|
156
|
-
TRouter = undefined,
|
|
157
171
|
TJobsDefs extends JobsDefs | undefined = undefined,
|
|
172
|
+
TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
|
|
158
173
|
> = {
|
|
159
174
|
handler: (req: Request) => Promise<Response>
|
|
160
175
|
db: DbFor<TSchema>
|
|
161
176
|
auth: AuthInstance
|
|
162
177
|
storage: StorageFacade
|
|
163
|
-
router: HonoType
|
|
164
|
-
/** Raw tRPC router when the config declared one — escape hatch. */
|
|
165
|
-
trpcRouter?: AnyRouter
|
|
166
178
|
/** Validated env: bunderstack's base vars plus the config's `env` extension. */
|
|
167
179
|
env: ValidatedEnv<TEnv>
|
|
168
180
|
/** Email facade; always present — send() throws when email isn't configured. */
|
|
@@ -191,129 +203,40 @@ export type BunderstackApp<
|
|
|
191
203
|
schema: TSchema
|
|
192
204
|
access: TAccess
|
|
193
205
|
buckets: TBuckets
|
|
194
|
-
|
|
206
|
+
api: MergeApiRouterTypes<
|
|
207
|
+
UnifiedApiRouter<CrudApiRouterFor<TSchema, TAccess>, TCustomApiRouter>,
|
|
208
|
+
RealtimeApiRouter
|
|
209
|
+
>
|
|
195
210
|
}
|
|
196
211
|
}
|
|
197
212
|
|
|
198
|
-
// Overloads: the builder-callback form and the prebuilt-router/none form are
|
|
199
|
-
// separate signatures so the callback's `t` parameter gets contextual typing
|
|
200
|
-
// and the router type lands on `$inferClient` without conditional-type
|
|
201
|
-
// inference (which breaks under contextual return types). `jobs` needs the
|
|
202
|
-
// same split against BOTH trpc forms — a union parameter type (`TJobsDefs |
|
|
203
|
-
// (callback => TJobsDefs)`) defeats inference (TS widens TJobsDefs to its
|
|
204
|
-
// constraint when a function literal could match either union arm) — hence
|
|
205
|
-
// four overloads covering the trpc × jobs option cross product.
|
|
206
|
-
export function createBunderstack<
|
|
207
|
-
TSchema extends Record<string, unknown>,
|
|
208
|
-
const TAccess extends Record<string, TableAccessInput> | undefined =
|
|
209
|
-
undefined,
|
|
210
|
-
const TStorage extends StorageConfigInput | undefined = undefined,
|
|
211
|
-
const TEnv extends EnvConfigInput | undefined = undefined,
|
|
212
|
-
TRouter extends AnyRouter = AnyRouter,
|
|
213
|
-
const TJobsDefs extends JobsDefs | undefined = undefined,
|
|
214
|
-
>(
|
|
215
|
-
options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv> & {
|
|
216
|
-
/** Builder callback receiving the pre-wired `t` instance. */
|
|
217
|
-
trpc: (t: BunderstackTRPC<TSchema, ValidatedEnv<TEnv>>) => TRouter
|
|
218
|
-
/** Builder callback receiving the pre-wired `j` instance. */
|
|
219
|
-
jobs: (j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => TJobsDefs
|
|
220
|
-
},
|
|
221
|
-
): Promise<
|
|
222
|
-
BunderstackApp<
|
|
223
|
-
TSchema,
|
|
224
|
-
TAccess,
|
|
225
|
-
BucketNamesOf<TStorage>,
|
|
226
|
-
TEnv,
|
|
227
|
-
TRouter,
|
|
228
|
-
TJobsDefs
|
|
229
|
-
>
|
|
230
|
-
>
|
|
231
|
-
export function createBunderstack<
|
|
232
|
-
TSchema extends Record<string, unknown>,
|
|
233
|
-
const TAccess extends Record<string, TableAccessInput> | undefined =
|
|
234
|
-
undefined,
|
|
235
|
-
const TStorage extends StorageConfigInput | undefined = undefined,
|
|
236
|
-
const TEnv extends EnvConfigInput | undefined = undefined,
|
|
237
|
-
TRouter extends AnyRouter = AnyRouter,
|
|
238
|
-
const TJobsDefs extends JobsDefs | undefined = undefined,
|
|
239
|
-
>(
|
|
240
|
-
options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv> & {
|
|
241
|
-
/** Builder callback receiving the pre-wired `t` instance. */
|
|
242
|
-
trpc: (t: BunderstackTRPC<TSchema, ValidatedEnv<TEnv>>) => TRouter
|
|
243
|
-
/** Prebuilt job definitions (escape hatch for multi-file setups). */
|
|
244
|
-
jobs?: TJobsDefs
|
|
245
|
-
},
|
|
246
|
-
): Promise<
|
|
247
|
-
BunderstackApp<
|
|
248
|
-
TSchema,
|
|
249
|
-
TAccess,
|
|
250
|
-
BucketNamesOf<TStorage>,
|
|
251
|
-
TEnv,
|
|
252
|
-
TRouter,
|
|
253
|
-
TJobsDefs
|
|
254
|
-
>
|
|
255
|
-
>
|
|
256
213
|
export function createBunderstack<
|
|
257
214
|
TSchema extends Record<string, unknown>,
|
|
258
|
-
const TAccess extends Record<string, TableAccessInput> | undefined =
|
|
259
|
-
undefined,
|
|
215
|
+
const TAccess extends Record<string, TableAccessInput> | undefined = undefined,
|
|
260
216
|
const TStorage extends StorageConfigInput | undefined = undefined,
|
|
261
217
|
const TEnv extends EnvConfigInput | undefined = undefined,
|
|
262
|
-
TRouter extends AnyRouter | undefined = undefined,
|
|
263
218
|
const TJobsDefs extends JobsDefs | undefined = undefined,
|
|
219
|
+
TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
|
|
264
220
|
>(
|
|
265
|
-
options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv> & {
|
|
266
|
-
|
|
267
|
-
trpc?: TRouter
|
|
268
|
-
/** Builder callback receiving the pre-wired `j` instance. */
|
|
269
|
-
jobs: (j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => TJobsDefs
|
|
221
|
+
options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv, TCustomApiRouter> & {
|
|
222
|
+
jobs?: TJobsDefs | ((j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => TJobsDefs)
|
|
270
223
|
},
|
|
271
|
-
): Promise<
|
|
272
|
-
|
|
273
|
-
TSchema,
|
|
274
|
-
TAccess,
|
|
275
|
-
BucketNamesOf<TStorage>,
|
|
276
|
-
TEnv,
|
|
277
|
-
TRouter,
|
|
278
|
-
TJobsDefs
|
|
279
|
-
>
|
|
280
|
-
>
|
|
281
|
-
export function createBunderstack<
|
|
224
|
+
): Promise<BunderstackApp<TSchema, TAccess, BucketNamesOf<TStorage>, TEnv, TJobsDefs, TCustomApiRouter>>
|
|
225
|
+
export async function createBunderstack<
|
|
282
226
|
TSchema extends Record<string, unknown>,
|
|
283
227
|
const TAccess extends Record<string, TableAccessInput> | undefined =
|
|
284
228
|
undefined,
|
|
285
229
|
const TStorage extends StorageConfigInput | undefined = undefined,
|
|
286
230
|
const TEnv extends EnvConfigInput | undefined = undefined,
|
|
287
|
-
|
|
288
|
-
const TJobsDefs extends JobsDefs | undefined = undefined,
|
|
231
|
+
TCustomApiRouter extends AnyORPCRouter | undefined = undefined,
|
|
289
232
|
>(
|
|
290
|
-
options: BunderstackConfig<
|
|
291
|
-
/** Prebuilt tRPC router (escape hatch for multi-file setups). */
|
|
292
|
-
trpc?: TRouter
|
|
293
|
-
/** Prebuilt job definitions (escape hatch for multi-file setups). */
|
|
294
|
-
jobs?: TJobsDefs
|
|
295
|
-
},
|
|
296
|
-
): Promise<
|
|
297
|
-
BunderstackApp<
|
|
233
|
+
options: BunderstackConfig<
|
|
298
234
|
TSchema,
|
|
299
235
|
TAccess,
|
|
300
|
-
|
|
236
|
+
TStorage,
|
|
301
237
|
TEnv,
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
>
|
|
305
|
-
>
|
|
306
|
-
export async function createBunderstack<
|
|
307
|
-
TSchema extends Record<string, unknown>,
|
|
308
|
-
const TAccess extends Record<string, TableAccessInput> | undefined =
|
|
309
|
-
undefined,
|
|
310
|
-
const TStorage extends StorageConfigInput | undefined = undefined,
|
|
311
|
-
const TEnv extends EnvConfigInput | undefined = undefined,
|
|
312
|
-
>(
|
|
313
|
-
options: BunderstackConfig<TSchema, TAccess, TStorage, TEnv> & {
|
|
314
|
-
trpc?:
|
|
315
|
-
| AnyRouter
|
|
316
|
-
| ((t: BunderstackTRPC<TSchema, ValidatedEnv<TEnv>>) => AnyRouter)
|
|
238
|
+
TCustomApiRouter
|
|
239
|
+
> & {
|
|
317
240
|
jobs?:
|
|
318
241
|
| JobsDefs
|
|
319
242
|
| ((j: BunderstackJobsBuilder<TSchema, ValidatedEnv<TEnv>>) => JobsDefs)
|
|
@@ -324,8 +247,8 @@ export async function createBunderstack<
|
|
|
324
247
|
TAccess,
|
|
325
248
|
BucketNamesOf<TStorage>,
|
|
326
249
|
TEnv,
|
|
327
|
-
|
|
328
|
-
|
|
250
|
+
JobsDefs | undefined,
|
|
251
|
+
TCustomApiRouter
|
|
329
252
|
>
|
|
330
253
|
> {
|
|
331
254
|
const dialect = detectDialect(options.schema)
|
|
@@ -336,7 +259,7 @@ export async function createBunderstack<
|
|
|
336
259
|
: undefined
|
|
337
260
|
if (jobsDefs) validateJobsDefs(jobsDefs)
|
|
338
261
|
// Env is validated FIRST: the app refuses to boot on missing/invalid vars,
|
|
339
|
-
// and everything downstream
|
|
262
|
+
// and everything downstream consumes the result.
|
|
340
263
|
const env = validateEnv(options.env, {
|
|
341
264
|
emailProvider: emailProviderTag(options.email),
|
|
342
265
|
defaultDatabaseUrl:
|
|
@@ -388,81 +311,51 @@ export async function createBunderstack<
|
|
|
388
311
|
typeof config.realtime === 'object'
|
|
389
312
|
? config.realtime.bufferSize
|
|
390
313
|
: undefined
|
|
314
|
+
const realtimeResumeSeconds =
|
|
315
|
+
typeof config.realtime === 'object'
|
|
316
|
+
? config.realtime.resumeSeconds
|
|
317
|
+
: undefined
|
|
391
318
|
const configuredRedisUrl = config.realtime
|
|
392
319
|
? resolveRealtimeRedisUrl(config.realtime, env)
|
|
393
320
|
: undefined
|
|
394
|
-
const configuredRealtimeTransport: RealtimeTransport = !config.realtime
|
|
395
|
-
? 'disabled'
|
|
396
|
-
: configuredRedisUrl
|
|
397
|
-
? 'redis'
|
|
398
|
-
: 'memory'
|
|
399
321
|
const redisUrl = introspect ? undefined : configuredRedisUrl
|
|
400
|
-
const
|
|
322
|
+
const publisher = config.realtime
|
|
401
323
|
? redisUrl
|
|
402
|
-
?
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
cmdClient.ltrim(key, start, stop),
|
|
420
|
-
lrange: (key: string, start: number, stop: number) =>
|
|
421
|
-
cmdClient.lrange(key, start, stop),
|
|
422
|
-
close: () => {
|
|
423
|
-
cmdClient.close()
|
|
424
|
-
subClient.close()
|
|
425
|
-
},
|
|
426
|
-
}
|
|
427
|
-
},
|
|
428
|
-
bufferSize: realtimeBufferSize,
|
|
429
|
-
})
|
|
430
|
-
: createRealtimeBroker({
|
|
431
|
-
access: resolvedAccess,
|
|
432
|
-
bufferSize: realtimeBufferSize,
|
|
324
|
+
? (() => {
|
|
325
|
+
const redis = new Bun.RedisClient(redisUrl)
|
|
326
|
+
const subscriber = redis.duplicate()
|
|
327
|
+
lifecycle.add(async () => {
|
|
328
|
+
redis.close()
|
|
329
|
+
;(await subscriber).close()
|
|
330
|
+
})
|
|
331
|
+
return createRedisRealtimePublisher(redis, subscriber, {
|
|
332
|
+
prefix:
|
|
333
|
+
process.env.BUNDERSTACK_REALTIME_PREFIX ?? 'bunderstack:',
|
|
334
|
+
maxBufferedEvents: realtimeBufferSize,
|
|
335
|
+
resumeSeconds: realtimeResumeSeconds,
|
|
336
|
+
})
|
|
337
|
+
})()
|
|
338
|
+
: createMemoryRealtimePublisher({
|
|
339
|
+
maxBufferedEvents: realtimeBufferSize,
|
|
340
|
+
resumeSeconds: realtimeResumeSeconds,
|
|
433
341
|
})
|
|
434
342
|
: undefined
|
|
435
|
-
const runtimeRealtimeTransport: RealtimeTransport = !
|
|
343
|
+
const runtimeRealtimeTransport: RealtimeTransport = !publisher
|
|
436
344
|
? 'disabled'
|
|
437
345
|
: redisUrl
|
|
438
346
|
? 'redis'
|
|
439
347
|
: 'memory'
|
|
440
348
|
const realtime = createRealtimeFacade<TSchema>(
|
|
441
|
-
|
|
349
|
+
publisher,
|
|
442
350
|
runtimeRealtimeTransport,
|
|
443
351
|
)
|
|
444
|
-
const crudRouter = buildCrudRouter(options.schema, userDb, {
|
|
445
|
-
auth: authResolver,
|
|
446
|
-
access: resolvedAccess,
|
|
447
|
-
idempotency: options.idempotency,
|
|
448
|
-
realtime,
|
|
449
|
-
})
|
|
450
|
-
const realtimeRouter = broker
|
|
451
|
-
? buildRealtimeRouter(broker, {
|
|
452
|
-
auth: authResolver,
|
|
453
|
-
keepaliveMs:
|
|
454
|
-
typeof config.realtime === 'object'
|
|
455
|
-
? config.realtime.keepaliveMs
|
|
456
|
-
: undefined,
|
|
457
|
-
})
|
|
458
|
-
: undefined
|
|
459
352
|
const registry = createBucketStorages(config.storage)
|
|
460
|
-
|
|
461
|
-
const storageRouter = buildBucketStorageRouter({
|
|
353
|
+
const storageOperations = createStorageOperations({
|
|
462
354
|
registry,
|
|
463
355
|
db,
|
|
464
|
-
auth: authResolver,
|
|
465
356
|
})
|
|
357
|
+
const storageApiRouter = buildStorageApiRouter(registry, storageOperations)
|
|
358
|
+
const realtimeApiRouter = buildRealtimeApiRouter(publisher, resolvedAccess)
|
|
466
359
|
const storage: StorageFacade = {
|
|
467
360
|
async delete(fileId) {
|
|
468
361
|
const bucketName = fileId.split('/')[0] ?? ''
|
|
@@ -546,7 +439,9 @@ export async function createBunderstack<
|
|
|
546
439
|
return result
|
|
547
440
|
},
|
|
548
441
|
tick(now?: number) {
|
|
549
|
-
return jobRunner
|
|
442
|
+
return jobRunner
|
|
443
|
+
? jobRunner.tick(now)
|
|
444
|
+
: Promise.resolve({ claimed: 0, ran: 0, failed: 0 })
|
|
550
445
|
},
|
|
551
446
|
}
|
|
552
447
|
if (jobRunner) jobRunner.setJobsFacade(jobs)
|
|
@@ -605,58 +500,144 @@ export async function createBunderstack<
|
|
|
605
500
|
await lifecycle.close()
|
|
606
501
|
}
|
|
607
502
|
}
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
req,
|
|
617
|
-
router: trpcRouter,
|
|
618
|
-
createContext: async () => ({
|
|
619
|
-
db: userDb,
|
|
620
|
-
user: await resolveAccessUser(authResolver, req.headers),
|
|
621
|
-
env,
|
|
622
|
-
email,
|
|
623
|
-
jobs,
|
|
624
|
-
realtime,
|
|
625
|
-
storage,
|
|
626
|
-
req,
|
|
627
|
-
}),
|
|
628
|
-
})
|
|
503
|
+
const crudApiRouter = buildCrudApiRouter(options.schema, userDb, {
|
|
504
|
+
access: resolvedAccess,
|
|
505
|
+
idempotency: options.idempotency,
|
|
506
|
+
realtime,
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
const customApiRouter = options.api
|
|
510
|
+
? options.api(createApiBuilder<TSchema, ValidatedEnv<TEnv>>())
|
|
629
511
|
: undefined
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
512
|
+
|
|
513
|
+
const nativeRouter = buildApiRouter({
|
|
514
|
+
crud: crudApiRouter as Record<string, unknown>,
|
|
515
|
+
storage: storageApiRouter as Record<string, unknown>,
|
|
516
|
+
realtime: realtimeApiRouter as Record<string, unknown> | undefined,
|
|
517
|
+
custom: customApiRouter as Record<string, unknown> | undefined,
|
|
518
|
+
}) as any
|
|
519
|
+
|
|
520
|
+
const authOpenAPISpecRaw =
|
|
521
|
+
options.openapi &&
|
|
522
|
+
auth.api &&
|
|
523
|
+
'generateOpenAPISchema' in auth.api &&
|
|
524
|
+
typeof auth.api.generateOpenAPISchema === 'function'
|
|
525
|
+
? await auth.api.generateOpenAPISchema()
|
|
526
|
+
: undefined
|
|
527
|
+
|
|
528
|
+
const authOpenAPISpec = authOpenAPISpecRaw
|
|
529
|
+
? normalizeForeignOpenAPISpec(authOpenAPISpecRaw, {
|
|
530
|
+
prefix: '/api/auth',
|
|
531
|
+
source: 'auth',
|
|
532
|
+
})
|
|
533
|
+
: undefined
|
|
534
|
+
|
|
535
|
+
await buildApiRegistry({
|
|
536
|
+
nativeRouter,
|
|
537
|
+
foreignSpecs: authOpenAPISpec ? [authOpenAPISpec] : [],
|
|
538
|
+
reservedCoreHandles: new Set([
|
|
539
|
+
'health',
|
|
540
|
+
...(publisher ? ['realtime.changes'] : []),
|
|
541
|
+
...[...registry.keys()].flatMap((name) =>
|
|
542
|
+
['prepareUpload', 'upload', 'confirmUpload', 'download', 'delete'].map(
|
|
543
|
+
(operation) => `files.${name}.${operation}`,
|
|
544
|
+
),
|
|
545
|
+
),
|
|
546
|
+
]),
|
|
547
|
+
})
|
|
548
|
+
|
|
549
|
+
const combinedOpenAPISpec = options.openapi
|
|
550
|
+
? mergeOpenAPISpecs({
|
|
551
|
+
nativeSpec: await new OpenAPIGenerator({
|
|
552
|
+
converters: [
|
|
553
|
+
new ValibotToJsonSchemaConverter(),
|
|
554
|
+
{
|
|
555
|
+
condition: (schema: any) =>
|
|
556
|
+
Boolean(
|
|
557
|
+
schema?.['~standard'] &&
|
|
558
|
+
!schema['~standard'].jsonSchema,
|
|
559
|
+
),
|
|
560
|
+
convert: (schema: any) => {
|
|
561
|
+
const vendor = schema?.['~standard']?.vendor ?? 'unknown'
|
|
562
|
+
throw new OpenAPIGeneratorError(
|
|
563
|
+
`No JSON Schema converter is configured for Standard Schema vendor "${vendor}"`,
|
|
564
|
+
)
|
|
565
|
+
},
|
|
566
|
+
},
|
|
567
|
+
],
|
|
568
|
+
}).generate(nativeRouter),
|
|
569
|
+
authSpec: authOpenAPISpec,
|
|
570
|
+
})
|
|
652
571
|
: undefined
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
572
|
+
|
|
573
|
+
const openapiHandler = new OpenAPIHandler(nativeRouter, {
|
|
574
|
+
errorStatusMap: BUNDERSTACK_ERROR_STATUS_MAP,
|
|
575
|
+
customErrorResponseBodyEncoder: (error: any) => ({
|
|
576
|
+
error: error.message,
|
|
577
|
+
code: error.data?.code ?? error.code,
|
|
578
|
+
...(error.data?.details !== undefined
|
|
579
|
+
? { details: error.data.details }
|
|
580
|
+
: {}),
|
|
581
|
+
}),
|
|
582
|
+
fetchInterceptors: [
|
|
583
|
+
async (options) => {
|
|
584
|
+
const res = await options.next()
|
|
585
|
+
if (res.matched && options.context.resHeaders) {
|
|
586
|
+
options.context.resHeaders.forEach((v: string, k: string) =>
|
|
587
|
+
res.response.headers.set(k, v),
|
|
588
|
+
)
|
|
589
|
+
}
|
|
590
|
+
return res
|
|
591
|
+
},
|
|
592
|
+
],
|
|
593
|
+
})
|
|
594
|
+
const rpcHandler = new RPCHandler(nativeRouter)
|
|
595
|
+
|
|
596
|
+
const apiHandler = async (req: Request): Promise<Response | null> => {
|
|
597
|
+
const urlString = typeof req === 'string' ? (req as string) : req.url
|
|
598
|
+
if (!urlString) return null
|
|
599
|
+
const url = new URL(urlString, 'http://localhost')
|
|
600
|
+
if (
|
|
601
|
+
combinedOpenAPISpec &&
|
|
602
|
+
url.pathname === '/api/openapi.json' &&
|
|
603
|
+
req.method === 'GET'
|
|
604
|
+
) {
|
|
605
|
+
return new Response(JSON.stringify(combinedOpenAPISpec), {
|
|
606
|
+
headers: { 'Content-Type': 'application/json' },
|
|
607
|
+
})
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const apiCtx = createApiContext(
|
|
611
|
+
{
|
|
612
|
+
db: userDb,
|
|
613
|
+
env,
|
|
614
|
+
storage,
|
|
615
|
+
email,
|
|
616
|
+
jobs,
|
|
617
|
+
realtime,
|
|
618
|
+
auth,
|
|
619
|
+
authResolver,
|
|
620
|
+
},
|
|
621
|
+
req,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
if (url.pathname.startsWith('/api/rpc')) {
|
|
625
|
+
const res = await rpcHandler.handle(req, {
|
|
626
|
+
prefix: '/api/rpc',
|
|
627
|
+
context: apiCtx,
|
|
628
|
+
})
|
|
629
|
+
if (res.matched) return res.response
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const openapiRes = await openapiHandler.handle(req, { context: apiCtx })
|
|
633
|
+
if (openapiRes.matched) return openapiRes.response
|
|
634
|
+
|
|
635
|
+
return null
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const handler = buildHandler({
|
|
656
639
|
authHandler: (req) => auth.handler(req),
|
|
657
|
-
|
|
658
|
-
realtimeRouter,
|
|
659
|
-
trpcHandler,
|
|
640
|
+
apiHandler,
|
|
660
641
|
rateLimit: options.rateLimit,
|
|
661
642
|
})
|
|
662
643
|
|
|
@@ -678,15 +659,14 @@ export async function createBunderstack<
|
|
|
678
659
|
TAccess,
|
|
679
660
|
BucketNamesOf<TStorage>,
|
|
680
661
|
TEnv,
|
|
681
|
-
|
|
682
|
-
|
|
662
|
+
JobsDefs | undefined,
|
|
663
|
+
TCustomApiRouter
|
|
683
664
|
> = {
|
|
684
665
|
handler,
|
|
685
666
|
// Internal tables live on the runtime db but stay out of the public type.
|
|
686
667
|
db: userDb,
|
|
687
668
|
auth,
|
|
688
669
|
storage,
|
|
689
|
-
router,
|
|
690
670
|
env,
|
|
691
671
|
email,
|
|
692
672
|
realtime,
|
|
@@ -702,7 +682,6 @@ export async function createBunderstack<
|
|
|
702
682
|
return lifecycle.status
|
|
703
683
|
},
|
|
704
684
|
signal: lifecycle.signal,
|
|
705
|
-
trpcRouter,
|
|
706
685
|
manifest: buildManifest({
|
|
707
686
|
schema: options.schema,
|
|
708
687
|
dialect,
|
|
@@ -743,6 +722,8 @@ export async function createBunderstack<
|
|
|
743
722
|
}
|
|
744
723
|
|
|
745
724
|
export { MAX_LIST_LIMIT } from './list-query'
|
|
725
|
+
export { BunderstackError } from './errors'
|
|
726
|
+
export type { BunderstackErrorCode } from './errors'
|
|
746
727
|
export { resolveConfig } from './config'
|
|
747
728
|
export type {
|
|
748
729
|
BetterAuthConfig,
|
|
@@ -760,8 +741,6 @@ export type {
|
|
|
760
741
|
EmailConfigInput,
|
|
761
742
|
EmailFacade,
|
|
762
743
|
} from './email'
|
|
763
|
-
export { createTRPC } from './trpc'
|
|
764
|
-
export type { BunderstackTRPC, TRPCContext } from './trpc'
|
|
765
744
|
export { createJobsBuilder } from './jobs/index'
|
|
766
745
|
export type {
|
|
767
746
|
BunderstackJobContext,
|
|
@@ -817,7 +796,7 @@ export type {
|
|
|
817
796
|
export type { TransformSpec } from './storage/thumbnails'
|
|
818
797
|
export { mockAuthSession } from './testing'
|
|
819
798
|
|
|
820
|
-
export type { RealtimeAction } from './realtime/
|
|
799
|
+
export type { RealtimeAction } from './realtime/publisher'
|
|
821
800
|
export { createRealtimeFacade } from './realtime/facade'
|
|
822
801
|
export type {
|
|
823
802
|
RealtimeFacade,
|
|
@@ -825,9 +804,19 @@ export type {
|
|
|
825
804
|
SchemaTable,
|
|
826
805
|
} from './realtime/facade'
|
|
827
806
|
|
|
807
|
+
export { createApiBuilder } from './api/builder'
|
|
808
|
+
export type { BunderstackApiBuilder, ApiFactory } from './api/builder'
|
|
828
809
|
export type {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
810
|
+
CrudApiRouterFor,
|
|
811
|
+
ExposedApiTables,
|
|
812
|
+
MergeApiRouterTypes,
|
|
813
|
+
UnifiedApiRouter,
|
|
814
|
+
} from './api/types'
|
|
815
|
+
export type { TableCrudProcedures } from './api/crud-router'
|
|
816
|
+
export {
|
|
817
|
+
buildApiRegistry,
|
|
818
|
+
mergeApiRoutersStrict,
|
|
819
|
+
normalizeApiPath,
|
|
820
|
+
normalizeForeignOpenAPISpec,
|
|
821
|
+
} from './api/registry'
|
|
822
|
+
export { mergeOpenAPISpecs } from './api/openapi'
|