bunderstack 0.15.2 → 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 +22 -14
- package/src/access.ts +24 -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 +73 -77
- package/src/cron.ts +2 -1
- package/src/crud-operations.ts +488 -0
- package/src/dialect.ts +1 -1
- package/src/env.ts +28 -21
- package/src/errors.ts +90 -23
- package/src/handler.ts +16 -44
- package/src/index.ts +283 -294
- package/src/internal-tables-pg.ts +1 -17
- package/src/internal-tables.ts +0 -31
- package/src/jobs/define.ts +75 -21
- package/src/jobs/index.ts +3 -9
- package/src/jobs/queue.ts +14 -6
- package/src/jobs/slots.ts +52 -0
- package/src/jobs/worker.ts +142 -42
- package/src/manifest.ts +84 -93
- 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 -408
- package/src/jobs/cron-auth.ts +0 -28
- package/src/jobs/cron-router.ts +0 -135
- package/src/jobs/cron-runner.ts +0 -224
- package/src/jobs/local-cron.ts +0 -78
- package/src/realtime/index.ts +0 -250
- package/src/realtime/redis.ts +0 -228
- package/src/storage/router.ts +0 -531
- package/src/trpc.ts +0 -57
package/src/errors.ts
CHANGED
|
@@ -1,10 +1,98 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import
|
|
1
|
+
import { ORPCError, os, type ErrorMap } from '@orpc/server'
|
|
2
|
+
import * as v from 'valibot'
|
|
3
3
|
|
|
4
|
+
import { StandardSchemaValidationError } from './standard-schema'
|
|
5
|
+
|
|
6
|
+
export const BUNDERSTACK_ERROR_CODES = [
|
|
7
|
+
'VALIDATION_ERROR',
|
|
8
|
+
'UNAUTHORIZED',
|
|
9
|
+
'FORBIDDEN',
|
|
10
|
+
'NOT_FOUND',
|
|
11
|
+
'CONFLICT',
|
|
12
|
+
'PAYLOAD_TOO_LARGE',
|
|
13
|
+
'RATE_LIMITED',
|
|
14
|
+
] as const
|
|
15
|
+
|
|
16
|
+
export type BunderstackErrorCode = (typeof BUNDERSTACK_ERROR_CODES)[number]
|
|
17
|
+
|
|
18
|
+
export const BUNDERSTACK_ERROR_STATUS_MAP = {
|
|
19
|
+
VALIDATION_ERROR: 400,
|
|
20
|
+
UNAUTHORIZED: 401,
|
|
21
|
+
FORBIDDEN: 403,
|
|
22
|
+
NOT_FOUND: 404,
|
|
23
|
+
CONFLICT: 409,
|
|
24
|
+
PAYLOAD_TOO_LARGE: 413,
|
|
25
|
+
RATE_LIMITED: 429,
|
|
26
|
+
} as const satisfies Record<BunderstackErrorCode, number>
|
|
27
|
+
|
|
28
|
+
function errorDataSchema<const TCode extends BunderstackErrorCode>(
|
|
29
|
+
code: TCode,
|
|
30
|
+
) {
|
|
31
|
+
return v.strictObject({
|
|
32
|
+
code: v.literal(code),
|
|
33
|
+
details: v.optional(v.unknown()),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const BUNDERSTACK_ERRORS = {
|
|
38
|
+
VALIDATION_ERROR: { data: errorDataSchema('VALIDATION_ERROR') },
|
|
39
|
+
UNAUTHORIZED: { data: errorDataSchema('UNAUTHORIZED') },
|
|
40
|
+
FORBIDDEN: { data: errorDataSchema('FORBIDDEN') },
|
|
41
|
+
NOT_FOUND: { data: errorDataSchema('NOT_FOUND') },
|
|
42
|
+
CONFLICT: { data: errorDataSchema('CONFLICT') },
|
|
43
|
+
PAYLOAD_TOO_LARGE: { data: errorDataSchema('PAYLOAD_TOO_LARGE') },
|
|
44
|
+
RATE_LIMITED: { data: errorDataSchema('RATE_LIMITED') },
|
|
45
|
+
} as const satisfies ErrorMap
|
|
46
|
+
|
|
47
|
+
export class BunderstackError extends Error {
|
|
48
|
+
readonly status: number
|
|
49
|
+
|
|
50
|
+
constructor(
|
|
51
|
+
readonly code: BunderstackErrorCode,
|
|
52
|
+
message: string,
|
|
53
|
+
readonly details?: unknown,
|
|
54
|
+
options?: ErrorOptions,
|
|
55
|
+
) {
|
|
56
|
+
super(message, options)
|
|
57
|
+
this.name = 'BunderstackError'
|
|
58
|
+
this.status = BUNDERSTACK_ERROR_STATUS_MAP[code]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const mapBunderstackErrors = os
|
|
63
|
+
.errors(BUNDERSTACK_ERRORS)
|
|
64
|
+
.middleware(async ({ next }) => {
|
|
65
|
+
try {
|
|
66
|
+
return await next()
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const mapped =
|
|
69
|
+
error instanceof StandardSchemaValidationError
|
|
70
|
+
? new BunderstackError(
|
|
71
|
+
'VALIDATION_ERROR',
|
|
72
|
+
error.message,
|
|
73
|
+
error.issues,
|
|
74
|
+
{ cause: error },
|
|
75
|
+
)
|
|
76
|
+
: error
|
|
77
|
+
if (!(mapped instanceof BunderstackError)) throw error
|
|
78
|
+
|
|
79
|
+
throw new ORPCError(mapped.code, {
|
|
80
|
+
message: mapped.message,
|
|
81
|
+
data:
|
|
82
|
+
mapped.details === undefined
|
|
83
|
+
? { code: mapped.code }
|
|
84
|
+
: { code: mapped.code, details: mapped.details },
|
|
85
|
+
cause: mapped,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
/** @deprecated Legacy internal error codes retained for list-query compatibility. */
|
|
4
91
|
export const ErrorCode = {
|
|
5
92
|
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
6
93
|
FORBIDDEN: 'FORBIDDEN',
|
|
7
94
|
NOT_FOUND: 'NOT_FOUND',
|
|
95
|
+
CONFLICT: 'CONFLICT',
|
|
8
96
|
INVALID_CURSOR: 'INVALID_CURSOR',
|
|
9
97
|
RATE_LIMITED: 'RATE_LIMITED',
|
|
10
98
|
IDEMPOTENCY_REPLAY: 'IDEMPOTENCY_REPLAY',
|
|
@@ -13,27 +101,6 @@ export const ErrorCode = {
|
|
|
13
101
|
|
|
14
102
|
export type ErrorCodeValue = (typeof ErrorCode)[keyof typeof ErrorCode]
|
|
15
103
|
|
|
16
|
-
export type ApiErrorBody = {
|
|
17
|
-
error: string
|
|
18
|
-
code?: ErrorCodeValue
|
|
19
|
-
details?: unknown
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function apiError(
|
|
23
|
-
c: Context,
|
|
24
|
-
code: ErrorCodeValue,
|
|
25
|
-
message: string,
|
|
26
|
-
status: ContentfulStatusCode,
|
|
27
|
-
details?: unknown,
|
|
28
|
-
) {
|
|
29
|
-
const body: ApiErrorBody = {
|
|
30
|
-
error: message,
|
|
31
|
-
code,
|
|
32
|
-
...(details ? { details } : {}),
|
|
33
|
-
}
|
|
34
|
-
return c.json(body, status)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
104
|
export class ListQueryError extends Error {
|
|
38
105
|
readonly code: ErrorCodeValue
|
|
39
106
|
readonly details?: unknown
|
package/src/handler.ts
CHANGED
|
@@ -1,58 +1,30 @@
|
|
|
1
1
|
// src/handler.ts
|
|
2
|
-
import { Hono } from 'hono'
|
|
3
|
-
|
|
4
2
|
import { createRateLimiter, type RateLimitConfig } from './rate-limit'
|
|
5
3
|
|
|
6
4
|
interface HandlerParts {
|
|
7
|
-
crudRouter: Hono
|
|
8
5
|
authHandler?: (req: Request) => Promise<Response>
|
|
9
|
-
|
|
10
|
-
realtimeRouter?: Hono
|
|
11
|
-
cronRouter?: Hono
|
|
12
|
-
trpcHandler?: (req: Request) => Promise<Response>
|
|
6
|
+
apiHandler?: (req: Request) => Promise<Response | null>
|
|
13
7
|
rateLimit?: boolean | RateLimitConfig
|
|
14
8
|
}
|
|
15
9
|
|
|
16
|
-
export function buildHandler(parts: HandlerParts): {
|
|
17
|
-
handler: (req: Request) => Promise<Response>
|
|
18
|
-
router: Hono
|
|
19
|
-
} {
|
|
20
|
-
const app = new Hono()
|
|
10
|
+
export function buildHandler(parts: HandlerParts): (req: Request) => Promise<Response> {
|
|
21
11
|
const checkRateLimit = createRateLimiter(parts.rateLimit)
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
c.json({ status: 'ok' })
|
|
25
|
-
app.get('/health', health)
|
|
26
|
-
app.get('/api/health', health)
|
|
27
|
-
app.route('/api', parts.crudRouter)
|
|
28
|
-
|
|
29
|
-
if (parts.cronRouter) {
|
|
30
|
-
app.route('/api/_bunderstack', parts.cronRouter)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (parts.authHandler) {
|
|
34
|
-
app.all('/api/auth/*', (c) => parts.authHandler!(c.req.raw))
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (parts.storageRouter) {
|
|
38
|
-
app.route('/api/files', parts.storageRouter)
|
|
39
|
-
app.route('/files', parts.storageRouter)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (parts.realtimeRouter) {
|
|
43
|
-
app.route('/api', parts.realtimeRouter)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (parts.trpcHandler) {
|
|
47
|
-
app.all('/api/trpc/*', (c) => parts.trpcHandler!(c.req.raw))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const inner = (req: Request): Promise<Response> =>
|
|
51
|
-
Promise.resolve(app.fetch(req))
|
|
52
|
-
const handler = async (req: Request): Promise<Response> => {
|
|
13
|
+
return async (req: Request): Promise<Response> => {
|
|
53
14
|
const limited = await checkRateLimit(req)
|
|
54
15
|
if (limited) return limited
|
|
55
|
-
|
|
16
|
+
|
|
17
|
+
const pathname = new URL(req.url).pathname
|
|
18
|
+
if (
|
|
19
|
+
parts.authHandler &&
|
|
20
|
+
(pathname === '/api/auth' || pathname.startsWith('/api/auth/'))
|
|
21
|
+
) {
|
|
22
|
+
return parts.authHandler(req)
|
|
23
|
+
}
|
|
24
|
+
if (parts.apiHandler) {
|
|
25
|
+
const apiRes = await parts.apiHandler(req)
|
|
26
|
+
if (apiRes) return apiRes
|
|
27
|
+
}
|
|
28
|
+
return new Response('Not Found', { status: 404 })
|
|
56
29
|
}
|
|
57
|
-
return { handler, router: app }
|
|
58
30
|
}
|