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.
Files changed (47) hide show
  1. package/README.md +25 -138
  2. package/package.json +22 -14
  3. package/src/access.ts +24 -1
  4. package/src/api/api-types.types.ts +106 -0
  5. package/src/api/builder.ts +52 -0
  6. package/src/api/context.ts +83 -0
  7. package/src/api/crud-router.ts +321 -0
  8. package/src/api/openapi.ts +184 -0
  9. package/src/api/realtime-router.ts +75 -0
  10. package/src/api/registry.ts +338 -0
  11. package/src/api/router.ts +34 -0
  12. package/src/api/storage-router.ts +224 -0
  13. package/src/api/types.ts +84 -0
  14. package/src/auth.ts +5 -0
  15. package/src/blueprint.ts +88 -105
  16. package/src/config.ts +73 -77
  17. package/src/cron.ts +2 -1
  18. package/src/crud-operations.ts +488 -0
  19. package/src/dialect.ts +1 -1
  20. package/src/env.ts +28 -21
  21. package/src/errors.ts +90 -23
  22. package/src/handler.ts +16 -44
  23. package/src/index.ts +283 -294
  24. package/src/internal-tables-pg.ts +1 -17
  25. package/src/internal-tables.ts +0 -31
  26. package/src/jobs/define.ts +75 -21
  27. package/src/jobs/index.ts +3 -9
  28. package/src/jobs/queue.ts +14 -6
  29. package/src/jobs/slots.ts +52 -0
  30. package/src/jobs/worker.ts +142 -42
  31. package/src/manifest.ts +84 -93
  32. package/src/realtime/facade.ts +16 -13
  33. package/src/realtime/filter.ts +77 -0
  34. package/src/realtime/heartbeat.ts +80 -0
  35. package/src/realtime/publisher.ts +46 -0
  36. package/src/standard-schema.ts +59 -0
  37. package/src/storage/index.ts +8 -0
  38. package/src/storage/operations.ts +398 -0
  39. package/src/crud.ts +0 -408
  40. package/src/jobs/cron-auth.ts +0 -28
  41. package/src/jobs/cron-router.ts +0 -135
  42. package/src/jobs/cron-runner.ts +0 -224
  43. package/src/jobs/local-cron.ts +0 -78
  44. package/src/realtime/index.ts +0 -250
  45. package/src/realtime/redis.ts +0 -228
  46. package/src/storage/router.ts +0 -531
  47. package/src/trpc.ts +0 -57
package/src/errors.ts CHANGED
@@ -1,10 +1,98 @@
1
- import type { Context } from 'hono'
2
- import type { ContentfulStatusCode } from 'hono/utils/http-status'
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
- storageRouter?: Hono
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
- const health = (c: { json: (data: unknown) => Response }) =>
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
- return inner(req)
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
  }