@mpen/routekit 0.1.0 → 0.1.2

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 (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
@@ -1,33 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { HttpMethod } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { acceptCtx } from './accept-ctx'
6
-
7
- describe(acceptCtx.name, () => {
8
- it('adds parsed Accept header values to the context', async () => {
9
- const router = new Router()
10
-
11
- router.use(acceptCtx())
12
- router.add({
13
- method: HttpMethod.GET,
14
- path: '/',
15
- handler: ({ accept }) => new Response(JSON.stringify(accept)),
16
- })
17
-
18
- const request = new Request('https://example.com/', {
19
- headers: {
20
- accept: 'text/plain;q=0.5, application/json, text/html;q=0.9,application/yaml;q=1',
21
- },
22
- })
23
-
24
- const response = await router.fetch(request)
25
-
26
- expect(await response.json()).toEqual([
27
- { type: 'application/json', q: 1 },
28
- { type: 'application/yaml', q: 1 },
29
- { type: 'text/html', q: 0.9 },
30
- { type: 'text/plain', q: 0.5 },
31
- ])
32
- })
33
- })
@@ -1,12 +0,0 @@
1
- import type { AcceptMediaRange, ContextMiddleware } from '../types'
2
- import { parseAcceptHeader } from '../lib/media-type'
3
-
4
- /**
5
- * Attach parsed Accept header values to the request context.
6
- *
7
- * @returns Middleware that adds `accept` to the request context.
8
- */
9
- export const acceptCtx = (): ContextMiddleware<{ accept: AcceptMediaRange[] }> => (ctx) => {
10
- const header = ctx.request.headers.get('accept')
11
- ctx.accept = parseAcceptHeader(header ?? '*/*')
12
- }
@@ -1,112 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it, mock } from 'bun:test'
3
- import { HttpMethod, HttpStatus } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { bodyLimit } from './body-limit'
6
-
7
- const encoder = new TextEncoder()
8
-
9
- function makeStream(chunks: string[]): ReadableStream<Uint8Array> {
10
- return new ReadableStream<Uint8Array>({
11
- start(controller) {
12
- for (const chunk of chunks) {
13
- controller.enqueue(encoder.encode(chunk))
14
- }
15
- controller.close()
16
- },
17
- })
18
- }
19
-
20
- describe(bodyLimit.name, () => {
21
- it('rejects immediately when Content-Length exceeds maxSize', async () => {
22
- const router = new Router()
23
- const handler = mock(() => new Response('ok'))
24
-
25
- router.use(bodyLimit({ maxSize: 9 }))
26
- router.add({ method: HttpMethod.POST, path: '/upload', handler })
27
-
28
- const request = new Request('https://example.com/upload', {
29
- method: HttpMethod.POST,
30
- headers: { 'content-length': '10' },
31
- body: makeStream(['ok']),
32
- })
33
-
34
- const response = await router.fetch(request)
35
-
36
- expect(response.status).toBe(HttpStatus.PAYLOAD_TOO_LARGE)
37
- expect(handler).not.toHaveBeenCalled()
38
- })
39
-
40
- it('rejects when the streamed body exceeds maxSize', async () => {
41
- const router = new Router()
42
-
43
- router.use(bodyLimit({ maxSize: 4 }))
44
- router.add({
45
- method: HttpMethod.POST,
46
- path: '/upload',
47
- handler: async ({ request }) => new Response(await request.body.text()),
48
- })
49
-
50
- const request = new Request('https://example.com/upload', {
51
- method: HttpMethod.POST,
52
- body: makeStream(['1234', '5']),
53
- })
54
-
55
- const response = await router.fetch(request)
56
-
57
- expect(response.status).toBe(HttpStatus.PAYLOAD_TOO_LARGE)
58
- })
59
-
60
- it('rejects when Content-Length does not match the received bytes', async () => {
61
- const router = new Router()
62
-
63
- router.use(bodyLimit({ maxSize: 10 }))
64
- router.add({
65
- method: HttpMethod.POST,
66
- path: '/upload',
67
- handler: async ({ request }) => new Response(await request.body.text()),
68
- })
69
-
70
- const request = new Request('https://example.com/upload', {
71
- method: HttpMethod.POST,
72
- headers: { 'content-length': '4' },
73
- body: makeStream(['abc']),
74
- })
75
-
76
- const response = await router.fetch(request)
77
-
78
- expect(response.status).toBe(HttpStatus.BAD_REQUEST)
79
- })
80
-
81
- it('allows requests within the limit and matching Content-Length', async () => {
82
- const router = new Router()
83
-
84
- router.use(bodyLimit({ maxSize: 10 }))
85
- router.add({
86
- method: HttpMethod.POST,
87
- path: '/upload',
88
- handler: async ({ request }) => new Response(await request.body.text()),
89
- })
90
-
91
- const request = new Request('https://example.com/upload', {
92
- method: HttpMethod.POST,
93
- headers: { 'content-length': '5' },
94
- body: makeStream(['hello']),
95
- })
96
-
97
- const response = await router.fetch(request)
98
-
99
- expect(response.status).toBe(HttpStatus.OK)
100
- expect(await response.text()).toBe('hello')
101
- })
102
-
103
- it('declares its rejection responses on affected routes', () => {
104
- const router = new Router().use(bodyLimit({ maxSize: 10 }))
105
- router.add({ method: HttpMethod.POST, path: '/upload', handler: () => new Response() })
106
-
107
- expect(router.getRoutes()[0]?.schema?.response?.body).toEqual({
108
- [HttpStatus.BAD_REQUEST]: { type: 'string' },
109
- [HttpStatus.PAYLOAD_TOO_LARGE]: { type: 'string' },
110
- })
111
- })
112
- })
@@ -1,121 +0,0 @@
1
- import { HttpStatus } from '@mpen/http'
2
- import { RequestBodyLengthMismatchError, RequestBodyTooLargeError } from '../request'
3
- import { text } from '../response/formats/content'
4
- import { defineMiddleware, type DeclaredMiddleware } from './define'
5
- import type { AnyContext } from '../types'
6
-
7
- export interface MaxContentSizeOptions {
8
- maxSize: number
9
- }
10
-
11
- const utf8encoder = new TextEncoder()
12
-
13
- function parseContentLength(value: string | null): number | null {
14
- if (!value) return null
15
- const trimmed = value.trim()
16
- if (!trimmed) return null
17
- const parsed = Number(trimmed)
18
- if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 0) return null
19
- return parsed
20
- }
21
-
22
- function chunkByteLength(chunk: Uint8Array | string): number {
23
- if (typeof chunk === 'string') return utf8encoder.encode(chunk).length
24
- return chunk.byteLength
25
- }
26
-
27
- /**
28
- * Enforce a maximum request body size while preserving access to the incoming stream.
29
- *
30
- * @example
31
- * ```ts
32
- * router.use(bodyLimit({maxSize: 1024 * 1024}))
33
- * ```
34
- *
35
- * @param options - Configuration for maximum request body size enforcement.
36
- * @returns Middleware that rejects oversized bodies and mismatched Content-Length values.
37
- */
38
- export function bodyLimit<Ctx extends object = AnyContext>(
39
- options: MaxContentSizeOptions,
40
- ): DeclaredMiddleware<{}, Ctx> {
41
- const textResponse = {
42
- schema: { type: 'string' },
43
- parse(value: unknown): string {
44
- if (typeof value !== 'string') {
45
- throw new TypeError('Body limit responses must contain a string body.')
46
- }
47
- return value
48
- },
49
- }
50
-
51
- return defineMiddleware({
52
- responses: {
53
- [HttpStatus.BAD_REQUEST]: textResponse,
54
- [HttpStatus.PAYLOAD_TOO_LARGE]: textResponse,
55
- },
56
- async run(ctx, { next, forward, respond }) {
57
- const maxSize = options.maxSize
58
- const contentLength = parseContentLength(ctx.request.headers.get('content-length'))
59
-
60
- if (contentLength != null && contentLength > maxSize) {
61
- void ctx.request.body.stream()?.cancel()
62
- return respond(text('Payload Too Large', { status: HttpStatus.PAYLOAD_TOO_LARGE }))
63
- }
64
-
65
- const bodyStream = ctx.request.body.stream()
66
- if (!bodyStream) {
67
- if (contentLength != null && contentLength !== 0) {
68
- return respond(text('Bad Request', { status: HttpStatus.BAD_REQUEST }))
69
- }
70
- return forward(await next())
71
- }
72
-
73
- const reader = bodyStream.getReader()
74
- let bytesRead = 0
75
-
76
- const monitoredBody = new ReadableStream<Uint8Array>({
77
- async pull(controller) {
78
- const result = await reader.read()
79
- if (result.done) {
80
- if (contentLength != null && bytesRead !== contentLength) {
81
- controller.error(
82
- new RequestBodyLengthMismatchError(contentLength, bytesRead),
83
- )
84
- return
85
- }
86
- controller.close()
87
- return
88
- }
89
-
90
- const value = result.value
91
- bytesRead += chunkByteLength(value)
92
- if (bytesRead > maxSize) {
93
- await reader.cancel()
94
- controller.error(new RequestBodyTooLargeError(maxSize, bytesRead))
95
- return
96
- }
97
- controller.enqueue(value)
98
- },
99
- cancel(reason) {
100
- return reader.cancel(reason)
101
- },
102
- })
103
-
104
- ctx.request = ctx.request.withBody(ctx.request.body.withStream(monitoredBody))
105
-
106
- try {
107
- return forward(await next())
108
- } catch (err) {
109
- if (err instanceof RequestBodyTooLargeError) {
110
- return respond(
111
- text('Payload Too Large', { status: HttpStatus.PAYLOAD_TOO_LARGE }),
112
- )
113
- }
114
- if (err instanceof RequestBodyLengthMismatchError) {
115
- return respond(text('Bad Request', { status: HttpStatus.BAD_REQUEST }))
116
- }
117
- throw err
118
- }
119
- },
120
- })
121
- }
File without changes
@@ -1,269 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it, mock } from 'bun:test'
3
- import { HttpMethod, HttpStatus } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { cors } from './cors'
6
-
7
- describe(cors.name, () => {
8
- it('adds wildcard allow-origin by default', async () => {
9
- const router = new Router()
10
- router.use(cors({ origin: '*' }))
11
- router.add({
12
- method: HttpMethod.GET,
13
- path: '/data',
14
- handler: () => new Response('ok'),
15
- })
16
-
17
- const response = await router.fetch(
18
- new Request('https://api.example.com/data', {
19
- headers: { origin: 'https://app.example.com' },
20
- }),
21
- )
22
-
23
- expect(response.headers.get('access-control-allow-origin')).toBe('*')
24
- })
25
-
26
- it('adds CORS headers to logical response bodies', async () => {
27
- const router = new Router()
28
- router.use(cors({ origin: '*' }))
29
- router.add({
30
- method: HttpMethod.GET,
31
- path: '/data',
32
- handler: () => ({ ok: true }),
33
- })
34
-
35
- const response = await router.fetch(
36
- new Request('https://api.example.com/data', {
37
- headers: { origin: 'https://app.example.com' },
38
- }),
39
- )
40
-
41
- expect(response.headers.get('access-control-allow-origin')).toBe('*')
42
- expect(await response.json()).toEqual({ ok: true })
43
- })
44
-
45
- it('supports an origin resolver function', async () => {
46
- const router = new Router()
47
- router.use(
48
- cors({
49
- origin: (origin) => (origin?.endsWith('.example.com') ? origin : null),
50
- }),
51
- )
52
- router.add({
53
- method: HttpMethod.GET,
54
- path: '/data',
55
- handler: () => new Response('ok'),
56
- })
57
-
58
- const allowed = await router.fetch(
59
- new Request('https://api.example.com/data', {
60
- headers: { origin: 'https://app.example.com' },
61
- }),
62
- )
63
-
64
- const denied = await router.fetch(
65
- new Request('https://api.example.com/data', {
66
- headers: { origin: 'https://evil.example' },
67
- }),
68
- )
69
-
70
- expect(allowed.headers.get('access-control-allow-origin')).toBe('https://app.example.com')
71
- expect(denied.headers.has('access-control-allow-origin')).toBe(false)
72
- })
73
-
74
- it('echoes the origin when credentials are enabled', async () => {
75
- const router = new Router()
76
- router.use(cors({ origin: '*', credentials: true }))
77
- router.add({
78
- method: HttpMethod.GET,
79
- path: '/data',
80
- handler: () => new Response('ok'),
81
- })
82
-
83
- const response = await router.fetch(
84
- new Request('https://api.example.com/data', {
85
- headers: { origin: 'https://app.example.com' },
86
- }),
87
- )
88
-
89
- expect(response.headers.get('access-control-allow-origin')).toBe('https://app.example.com')
90
- expect(response.headers.get('access-control-allow-credentials')).toBe('true')
91
- expect(response.headers.get('vary')).toContain('Origin')
92
- })
93
-
94
- it('exposes response headers when configured', async () => {
95
- const router = new Router()
96
- router.use(cors({ origin: '*', exposeHeaders: ['x-trace', 'x-request-id'] }))
97
- router.add({
98
- method: HttpMethod.GET,
99
- path: '/data',
100
- handler: () => new Response('ok'),
101
- })
102
-
103
- const response = await router.fetch(
104
- new Request('https://api.example.com/data', {
105
- headers: { origin: 'https://app.example.com' },
106
- }),
107
- )
108
-
109
- expect(response.headers.get('access-control-expose-headers')).toBe('x-trace, x-request-id')
110
- })
111
-
112
- it('accepts a static allowMethods list', async () => {
113
- const router = new Router()
114
- router.use(cors({ origin: '*', allowMethods: ['GET', 'POST'] }))
115
- router.add({
116
- method: HttpMethod.OPTIONS,
117
- path: '/widgets',
118
- handler: () => new Response('ok'),
119
- })
120
-
121
- const response = await router.fetch(
122
- new Request('https://api.example.com/widgets', {
123
- method: HttpMethod.OPTIONS,
124
- headers: {
125
- origin: 'https://app.example.com',
126
- 'access-control-request-method': 'POST',
127
- },
128
- }),
129
- )
130
-
131
- expect(response.headers.get('access-control-allow-methods')).toBe('GET, POST')
132
- })
133
-
134
- it('accepts a dynamic allowMethods resolver', async () => {
135
- const router = new Router()
136
- router.use(
137
- cors({
138
- origin: '*',
139
- allowMethods: (origin) =>
140
- origin === 'https://app.example.com' ? ['GET'] : ['POST'],
141
- }),
142
- )
143
- router.add({
144
- method: HttpMethod.OPTIONS,
145
- path: '/widgets',
146
- handler: () => new Response('ok'),
147
- })
148
-
149
- const response = await router.fetch(
150
- new Request('https://api.example.com/widgets', {
151
- method: HttpMethod.OPTIONS,
152
- headers: {
153
- origin: 'https://app.example.com',
154
- 'access-control-request-method': 'POST',
155
- },
156
- }),
157
- )
158
-
159
- expect(response.headers.get('access-control-allow-methods')).toBe('GET')
160
- })
161
-
162
- it('allows localhost origins when dev is enabled', async () => {
163
- const router = new Router()
164
- router.use(cors({ origin: 'https://app.example.com', dev: true }))
165
- router.add({
166
- method: HttpMethod.GET,
167
- path: '/data',
168
- handler: () => new Response('ok'),
169
- })
170
-
171
- const response = await router.fetch(
172
- new Request('https://api.example.com/data', {
173
- headers: { origin: 'http://localhost:3000' },
174
- }),
175
- )
176
-
177
- expect(response.headers.get('access-control-allow-origin')).toBe('http://localhost:3000')
178
- })
179
-
180
- it('allows localhost origins when allowLocalhost is enabled', async () => {
181
- const router = new Router()
182
- router.use(cors({ origin: 'https://app.example.com', allowLocalhost: true }))
183
- router.add({
184
- method: HttpMethod.GET,
185
- path: '/data',
186
- handler: () => new Response('ok'),
187
- })
188
-
189
- const response = await router.fetch(
190
- new Request('https://api.example.com/data', {
191
- headers: { origin: 'http://127.0.0.1:3000' },
192
- }),
193
- )
194
-
195
- expect(response.headers.get('access-control-allow-origin')).toBe('http://127.0.0.1:3000')
196
- })
197
-
198
- it('handles CORS preflight requests', async () => {
199
- const router = new Router()
200
- const handler = mock(() => new Response('ok'))
201
- router.use(cors({ origin: '*', maxAge: 600 }))
202
- router.add({
203
- method: HttpMethod.OPTIONS,
204
- path: '/widgets',
205
- handler,
206
- })
207
-
208
- const response = await router.fetch(
209
- new Request('https://api.example.com/widgets', {
210
- method: HttpMethod.OPTIONS,
211
- headers: {
212
- origin: 'https://app.example.com',
213
- 'access-control-request-method': 'POST',
214
- 'access-control-request-headers': 'x-test, content-type',
215
- },
216
- }),
217
- )
218
-
219
- expect(response.status).toBe(HttpStatus.NO_CONTENT)
220
- expect(handler).not.toHaveBeenCalled()
221
- expect(response.headers.get('access-control-allow-origin')).toBe('*')
222
- expect(response.headers.get('access-control-allow-methods')).toBe(
223
- 'GET, HEAD, PUT, POST, DELETE, PATCH',
224
- )
225
- expect(response.headers.get('access-control-allow-headers')).toBe('x-test, content-type')
226
- expect(response.headers.get('access-control-max-age')).toBe('600')
227
- })
228
-
229
- it('uses custom allowHeaders and preflightStatus', async () => {
230
- const router = new Router()
231
- router.use(
232
- cors({
233
- origin: '*',
234
- allowHeaders: ['x-custom', 'content-type'],
235
- preflightStatus: HttpStatus.OK,
236
- }),
237
- )
238
- router.add({
239
- method: HttpMethod.OPTIONS,
240
- path: '/widgets',
241
- handler: () => new Response('ok'),
242
- })
243
-
244
- const response = await router.fetch(
245
- new Request('https://api.example.com/widgets', {
246
- method: HttpMethod.OPTIONS,
247
- headers: {
248
- origin: 'https://app.example.com',
249
- 'access-control-request-method': 'POST',
250
- },
251
- }),
252
- )
253
-
254
- expect(response.status).toBe(HttpStatus.OK)
255
- expect(response.headers.get('access-control-allow-headers')).toBe('x-custom, content-type')
256
- })
257
-
258
- it('declares only applicable preflight route responses', () => {
259
- const router = new Router().use(cors({ origin: '*' }))
260
- router.add({ method: HttpMethod.GET, path: '/widgets', handler: () => new Response() })
261
- router.add({ method: HttpMethod.OPTIONS, path: '/widgets', handler: () => new Response() })
262
-
263
- const [getRoute, optionsRoute] = router.getRoutes()
264
- expect(getRoute?.schema).toBeUndefined()
265
- expect(optionsRoute?.schema?.response?.body).toEqual({
266
- [HttpStatus.NO_CONTENT]: { type: 'null' },
267
- })
268
- })
269
- })