spiceflow 1.1.2 → 1.1.4
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 +54 -23
- package/dist/benchmark.benchmark.d.ts +2 -0
- package/dist/benchmark.benchmark.d.ts.map +1 -0
- package/dist/benchmark.benchmark.js +13 -0
- package/dist/benchmark.benchmark.js.map +1 -0
- package/dist/context.d.ts +9 -26
- package/dist/context.d.ts.map +1 -1
- package/dist/cors.d.ts +22 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +76 -0
- package/dist/cors.js.map +1 -0
- package/dist/cors.test.d.ts +2 -0
- package/dist/cors.test.d.ts.map +1 -0
- package/dist/cors.test.js +39 -0
- package/dist/cors.test.js.map +1 -0
- package/dist/error.d.ts +0 -4
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +0 -7
- package/dist/error.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware.test.js +64 -0
- package/dist/middleware.test.js.map +1 -1
- package/dist/openapi.d.ts +1 -1
- package/dist/spiceflow.d.ts +5 -16
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +70 -55
- package/dist/spiceflow.js.map +1 -1
- package/dist/spiceflow.test.js +60 -4
- package/dist/spiceflow.test.js.map +1 -1
- package/dist/types.d.ts +23 -53
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +23 -1
- package/src/benchmark.benchmark.ts +16 -0
- package/src/context.ts +10 -28
- package/src/cors.test.ts +44 -0
- package/src/cors.ts +115 -0
- package/src/error.ts +0 -8
- package/src/index.ts +1 -0
- package/src/middleware.test.ts +68 -0
- package/src/spiceflow.test.ts +81 -4
- package/src/spiceflow.ts +83 -109
- package/src/types.test.ts +1 -0
- package/src/types.ts +30 -55
- package/dist/benchmark.test.d.ts +0 -2
- package/dist/benchmark.test.d.ts.map +0 -1
- package/dist/benchmark.test.js +0 -8
- package/dist/benchmark.test.js.map +0 -1
- package/src/benchmark.test.ts +0 -8
package/src/middleware.test.ts
CHANGED
|
@@ -23,6 +23,22 @@ test('middleware with next changes the response', async () => {
|
|
|
23
23
|
expect(await res.json()).toEqual('hi')
|
|
24
24
|
expect(res.headers.get('x-test')).toBe('ok')
|
|
25
25
|
})
|
|
26
|
+
test('middleware next returns a response even for 404, if there are no routes', async () => {
|
|
27
|
+
const res = await new Spiceflow()
|
|
28
|
+
.use(async ({ request }, next) => {
|
|
29
|
+
expect(request.method).toBe('GET')
|
|
30
|
+
const res = await next()
|
|
31
|
+
expect(res).toBeInstanceOf(Response)
|
|
32
|
+
if (res) {
|
|
33
|
+
res.headers.set('x-test', 'ok')
|
|
34
|
+
}
|
|
35
|
+
return res
|
|
36
|
+
})
|
|
37
|
+
.handle(new Request('http://localhost/non-existent', { method: 'GET' }))
|
|
38
|
+
expect(res.status).toBe(404)
|
|
39
|
+
expect(res.headers.get('x-test')).toBe('ok')
|
|
40
|
+
expect(await res.text()).toContain('Not Found')
|
|
41
|
+
})
|
|
26
42
|
|
|
27
43
|
test('middleware without next runs the next middleware and handler', async () => {
|
|
28
44
|
let middlewaresCalled = [] as string[]
|
|
@@ -82,6 +98,29 @@ test('middleware stops other middlewares', async () => {
|
|
|
82
98
|
expect(await res.text()).toEqual('ok')
|
|
83
99
|
})
|
|
84
100
|
|
|
101
|
+
test('calling next and then returning a new response works', async () => {
|
|
102
|
+
let middlewaresCalled = [] as string[]
|
|
103
|
+
const res = await new Spiceflow()
|
|
104
|
+
.use(async (ctx, next) => {
|
|
105
|
+
middlewaresCalled.push('first')
|
|
106
|
+
await next()
|
|
107
|
+
return new Response('middleware response')
|
|
108
|
+
})
|
|
109
|
+
.use(async (ctx, next) => {
|
|
110
|
+
middlewaresCalled.push('second')
|
|
111
|
+
return next()
|
|
112
|
+
})
|
|
113
|
+
.get('/ids/:id', () => {
|
|
114
|
+
middlewaresCalled.push('handler')
|
|
115
|
+
return 'handler response'
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
.handle(new Request('http://localhost/ids/xxx', { method: 'GET' }))
|
|
119
|
+
expect(res.status).toBe(200)
|
|
120
|
+
expect(middlewaresCalled).toEqual(['first', 'second', 'handler'])
|
|
121
|
+
expect(await res.text()).toEqual('middleware response')
|
|
122
|
+
})
|
|
123
|
+
|
|
85
124
|
test('middleware changes handler response body', async () => {
|
|
86
125
|
let middlewaresCalled = [] as string[]
|
|
87
126
|
const res = await new Spiceflow()
|
|
@@ -105,3 +144,32 @@ test('middleware changes handler response body', async () => {
|
|
|
105
144
|
expect(middlewaresCalled).toEqual(['first', 'second'])
|
|
106
145
|
expect(await res.json()).toEqual('HELLO WORLD')
|
|
107
146
|
})
|
|
147
|
+
|
|
148
|
+
test('mutating response returned by next without returning it works', async () => {
|
|
149
|
+
let handlerCalledTimes = 0
|
|
150
|
+
const res = await new Spiceflow()
|
|
151
|
+
.use(async (ctx, next) => {
|
|
152
|
+
const response = await next()
|
|
153
|
+
if (response) {
|
|
154
|
+
response.headers.set('X-Custom-Header', 'Modified')
|
|
155
|
+
}
|
|
156
|
+
// Not returning the response, letting it pass through
|
|
157
|
+
})
|
|
158
|
+
.use(async (ctx, next) => {
|
|
159
|
+
const response = await next()
|
|
160
|
+
if (response) {
|
|
161
|
+
response.headers.set('X-Another-Header', 'Added')
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
.get('/test', () => {
|
|
165
|
+
handlerCalledTimes++
|
|
166
|
+
return 'hello world'
|
|
167
|
+
})
|
|
168
|
+
.handle(new Request('http://localhost/test'))
|
|
169
|
+
|
|
170
|
+
expect(res.status).toBe(200)
|
|
171
|
+
expect(handlerCalledTimes).toBe(1)
|
|
172
|
+
expect(await res.json()).toBe('hello world')
|
|
173
|
+
expect(res.headers.get('X-Custom-Header')).toBe('Modified')
|
|
174
|
+
expect(res.headers.get('X-Another-Header')).toBe('Added')
|
|
175
|
+
})
|
package/src/spiceflow.test.ts
CHANGED
|
@@ -28,6 +28,83 @@ test('GET dynamic route', async () => {
|
|
|
28
28
|
expect(await res.json()).toEqual('hi')
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
+
test('onError does not fire on 404', async () => {
|
|
32
|
+
let errorFired = false
|
|
33
|
+
const app = new Spiceflow()
|
|
34
|
+
.get('/test', () => 'Hello')
|
|
35
|
+
.onError(() => {
|
|
36
|
+
errorFired = true
|
|
37
|
+
return new Response('Error', { status: 500 })
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const res = await app.handle(
|
|
41
|
+
new Request('http://localhost/non-existent', { method: 'GET' }),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
expect(res.status).toBe(404)
|
|
45
|
+
expect(errorFired).toBe(false)
|
|
46
|
+
expect(await res.text()).toBe('Not Found')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('onError fires on validation errors', async () => {
|
|
50
|
+
let errorMessage = ''
|
|
51
|
+
const app = new Spiceflow()
|
|
52
|
+
.post(
|
|
53
|
+
'/test',
|
|
54
|
+
async ({ request }) => {
|
|
55
|
+
await request.json()
|
|
56
|
+
return 'Success'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
body: z.object({
|
|
60
|
+
name: z.string(),
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
.onError(({ error }) => {
|
|
65
|
+
errorMessage = error.message
|
|
66
|
+
return new Response('Error', { status: 400 })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const res = await app.handle(
|
|
70
|
+
new Request('http://localhost/test', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: {
|
|
73
|
+
'Content-Type': 'application/json',
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify({ name: 1 }), // Invalid type for 'name'
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
expect(res.status).toBe(400)
|
|
80
|
+
expect(errorMessage).toContain('data/name must be string')
|
|
81
|
+
expect(await res.text()).toBe('Error')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test.todo('HEAD uses GET route, does not add body', async () => {
|
|
85
|
+
const app = new Spiceflow().get('/ids/:id', () => {
|
|
86
|
+
console.trace('GET')
|
|
87
|
+
return {
|
|
88
|
+
message: 'hi',
|
|
89
|
+
length: 10,
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const res = await app.handle(
|
|
94
|
+
new Request('http://localhost/ids/xxx', { method: 'HEAD' }),
|
|
95
|
+
)
|
|
96
|
+
expect(res.status).toBe(200)
|
|
97
|
+
// expect(res.headers.get('Content-Length')).toBe('10')
|
|
98
|
+
expect(await res.text()).toBe('')
|
|
99
|
+
|
|
100
|
+
// Compare with GET to ensure HEAD is using GET route
|
|
101
|
+
const getRes = await app.handle(
|
|
102
|
+
new Request('http://localhost/ids/xxx', { method: 'GET' }),
|
|
103
|
+
)
|
|
104
|
+
expect(getRes.status).toBe(200)
|
|
105
|
+
expect(await getRes.json()).toEqual({ message: 'hi', length: 10 })
|
|
106
|
+
})
|
|
107
|
+
|
|
31
108
|
test('GET with query, untyped', async () => {
|
|
32
109
|
const res = await new Spiceflow()
|
|
33
110
|
.get('/query', ({ query }) => {
|
|
@@ -118,11 +195,11 @@ test('missing route is not found', async () => {
|
|
|
118
195
|
test('state works', async () => {
|
|
119
196
|
const res = await new Spiceflow()
|
|
120
197
|
.state('id', '')
|
|
121
|
-
.use(({
|
|
122
|
-
|
|
198
|
+
.use(({ state, request }) => {
|
|
199
|
+
state.id = 'xxx'
|
|
123
200
|
})
|
|
124
|
-
.get('/get', ({
|
|
125
|
-
expect(
|
|
201
|
+
.get('/get', ({ state: state }) => {
|
|
202
|
+
expect(state.id).toBe('xxx')
|
|
126
203
|
})
|
|
127
204
|
.handle(new Request('http://localhost/get'))
|
|
128
205
|
expect(res.status).toBe(200)
|
package/src/spiceflow.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
JoinPath,
|
|
18
18
|
LocalHook,
|
|
19
19
|
MaybeArray,
|
|
20
|
-
MergeSchema,
|
|
21
20
|
MetadataBase,
|
|
22
21
|
MiddlewareHandler,
|
|
23
22
|
Reconcile,
|
|
@@ -30,14 +29,12 @@ import {
|
|
|
30
29
|
} from './types.js'
|
|
31
30
|
let globalIndex = 0
|
|
32
31
|
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
34
|
-
// @ts-ignore
|
|
35
32
|
import OriginalRouter from '@medley/router'
|
|
36
33
|
import Ajv, { ValidateFunction } from 'ajv'
|
|
37
34
|
import { z, ZodType } from 'zod'
|
|
38
35
|
import { zodToJsonSchema } from 'zod-to-json-schema'
|
|
39
36
|
import { Context, MiddlewareContext } from './context.js'
|
|
40
|
-
import { isProduction,
|
|
37
|
+
import { isProduction, ValidationError } from './error.js'
|
|
41
38
|
import { isAsyncIterable, redirect } from './utils.js'
|
|
42
39
|
|
|
43
40
|
const ajv = (addFormats.default || addFormats)(
|
|
@@ -74,9 +71,7 @@ export type InternalRoute = {
|
|
|
74
71
|
validateBody?: ValidateFunction
|
|
75
72
|
validateQuery?: ValidateFunction
|
|
76
73
|
validateParams?: ValidateFunction
|
|
77
|
-
prefix: string
|
|
78
|
-
|
|
79
|
-
// store: Record<any, any>
|
|
74
|
+
// prefix: string
|
|
80
75
|
}
|
|
81
76
|
|
|
82
77
|
type MedleyRouter = {
|
|
@@ -88,14 +83,16 @@ type MedleyRouter = {
|
|
|
88
83
|
| undefined
|
|
89
84
|
register: (path: string | undefined) => Record<string, InternalRoute>
|
|
90
85
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
|
|
87
|
+
const notFoundHandler = (c) => {
|
|
88
|
+
return new Response('Not Found', { status: 404 })
|
|
89
|
+
}
|
|
90
|
+
|
|
94
91
|
export class Spiceflow<
|
|
95
92
|
const in out BasePath extends string = '',
|
|
96
93
|
const in out Scoped extends boolean = true,
|
|
97
94
|
const in out Singleton extends SingletonBase = {
|
|
98
|
-
|
|
95
|
+
state: {}
|
|
99
96
|
},
|
|
100
97
|
const in out Definitions extends DefinitionBase = {
|
|
101
98
|
type: {}
|
|
@@ -113,7 +110,7 @@ export class Spiceflow<
|
|
|
113
110
|
private middlewares: Function[] = []
|
|
114
111
|
private onErrorHandlers: OnError[] = []
|
|
115
112
|
private routes: InternalRoute[] = []
|
|
116
|
-
private
|
|
113
|
+
private defaultState: Record<any, any> = {}
|
|
117
114
|
private topLevelApp?: AnySpiceflow
|
|
118
115
|
|
|
119
116
|
/** @internal */
|
|
@@ -151,7 +148,6 @@ export class Spiceflow<
|
|
|
151
148
|
const store = this.router.register(path)
|
|
152
149
|
let route: InternalRoute = {
|
|
153
150
|
...rest,
|
|
154
|
-
prefix: this.prefix || '',
|
|
155
151
|
method: (method || '') as any,
|
|
156
152
|
path: path || '',
|
|
157
153
|
handler: handler!,
|
|
@@ -166,6 +162,7 @@ export class Spiceflow<
|
|
|
166
162
|
|
|
167
163
|
private match(method: string, path: string) {
|
|
168
164
|
let root = this
|
|
165
|
+
let foundApp: AnySpiceflow | undefined
|
|
169
166
|
const result = bfsFind(this, (app) => {
|
|
170
167
|
app.topLevelApp = root
|
|
171
168
|
let prefix = this.getAppAndParents(app)
|
|
@@ -180,10 +177,12 @@ export class Spiceflow<
|
|
|
180
177
|
}
|
|
181
178
|
const medleyRoute = app.router.find(pathWithoutPrefix)
|
|
182
179
|
if (!medleyRoute) {
|
|
180
|
+
foundApp = app
|
|
183
181
|
return
|
|
184
182
|
}
|
|
185
183
|
|
|
186
184
|
let internalRoute: InternalRoute = medleyRoute.store[method]
|
|
185
|
+
|
|
187
186
|
if (internalRoute) {
|
|
188
187
|
const params = medleyRoute.params || {}
|
|
189
188
|
|
|
@@ -194,9 +193,46 @@ export class Spiceflow<
|
|
|
194
193
|
}
|
|
195
194
|
return res
|
|
196
195
|
}
|
|
196
|
+
if (method === 'HEAD') {
|
|
197
|
+
let internalRouteGet: InternalRoute = medleyRoute.store['GET']
|
|
198
|
+
if (!internalRouteGet?.handler) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
app,
|
|
203
|
+
internalRoute: {
|
|
204
|
+
hooks: {},
|
|
205
|
+
handler: async (c) => {
|
|
206
|
+
const response = await internalRouteGet.handler(c)
|
|
207
|
+
if (response instanceof Response) {
|
|
208
|
+
return new Response('', {
|
|
209
|
+
status: response.status,
|
|
210
|
+
statusText: response.statusText,
|
|
211
|
+
headers: response.headers,
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
return new Response(null, { status: 200 })
|
|
215
|
+
},
|
|
216
|
+
method,
|
|
217
|
+
path,
|
|
218
|
+
} as InternalRoute,
|
|
219
|
+
params: medleyRoute.params,
|
|
220
|
+
}
|
|
221
|
+
}
|
|
197
222
|
})
|
|
198
223
|
|
|
199
|
-
return
|
|
224
|
+
return (
|
|
225
|
+
result || {
|
|
226
|
+
app: foundApp || root,
|
|
227
|
+
internalRoute: {
|
|
228
|
+
hooks: {},
|
|
229
|
+
handler: notFoundHandler,
|
|
230
|
+
method,
|
|
231
|
+
path,
|
|
232
|
+
} as InternalRoute,
|
|
233
|
+
params: {},
|
|
234
|
+
}
|
|
235
|
+
)
|
|
200
236
|
}
|
|
201
237
|
|
|
202
238
|
state<const Name extends string | number | symbol, Value>(
|
|
@@ -206,8 +242,8 @@ export class Spiceflow<
|
|
|
206
242
|
BasePath,
|
|
207
243
|
Scoped,
|
|
208
244
|
{
|
|
209
|
-
|
|
210
|
-
Singleton['
|
|
245
|
+
state: Reconcile<
|
|
246
|
+
Singleton['state'],
|
|
211
247
|
{
|
|
212
248
|
[name in Name]: Value
|
|
213
249
|
}
|
|
@@ -217,7 +253,7 @@ export class Spiceflow<
|
|
|
217
253
|
Metadata,
|
|
218
254
|
Routes
|
|
219
255
|
> {
|
|
220
|
-
this.
|
|
256
|
+
this.defaultState[name] = value
|
|
221
257
|
return this as any
|
|
222
258
|
}
|
|
223
259
|
|
|
@@ -667,45 +703,6 @@ export class Spiceflow<
|
|
|
667
703
|
|
|
668
704
|
private scoped?: Scoped = true as Scoped
|
|
669
705
|
|
|
670
|
-
// group is not needed, you can add another prefixed app instead
|
|
671
|
-
// group<
|
|
672
|
-
// const Prefix extends string,
|
|
673
|
-
// const NewSpiceflow extends Spiceflow<any, any, any, any, any, any, any, any>
|
|
674
|
-
// >(
|
|
675
|
-
// prefix: Prefix,
|
|
676
|
-
// run: (
|
|
677
|
-
// group: Spiceflow<
|
|
678
|
-
// `${BasePath}${Prefix}`,
|
|
679
|
-
// Scoped,
|
|
680
|
-
// Singleton,
|
|
681
|
-
// Definitions,
|
|
682
|
-
// Metadata,
|
|
683
|
-
// {},
|
|
684
|
-
// Ephemeral,
|
|
685
|
-
// Volatile
|
|
686
|
-
// >
|
|
687
|
-
// ) => NewSpiceflow
|
|
688
|
-
// ): Spiceflow<
|
|
689
|
-
// BasePath,
|
|
690
|
-
// Scoped,
|
|
691
|
-
// Singleton,
|
|
692
|
-
// Definitions,
|
|
693
|
-
// Metadata,
|
|
694
|
-
// Prettify<Routes & NewSpiceflow['_routes']>,
|
|
695
|
-
// Ephemeral,
|
|
696
|
-
// Volatile
|
|
697
|
-
// > {
|
|
698
|
-
// let thisRouter = this.routers[0]
|
|
699
|
-
// this.routers.push(
|
|
700
|
-
// ...instance.routers.map((r) => ({
|
|
701
|
-
// ...r,
|
|
702
|
-
// prefix: (thisRouter.prefix || '') + r.prefix
|
|
703
|
-
// }))
|
|
704
|
-
// )
|
|
705
|
-
|
|
706
|
-
// return this
|
|
707
|
-
// }
|
|
708
|
-
|
|
709
706
|
use<const NewSpiceflow extends AnySpiceflow>(
|
|
710
707
|
instance: NewSpiceflow,
|
|
711
708
|
): IsAny<NewSpiceflow> extends true
|
|
@@ -725,7 +722,7 @@ export class Spiceflow<
|
|
|
725
722
|
MiddlewareHandler<
|
|
726
723
|
Schema,
|
|
727
724
|
{
|
|
728
|
-
|
|
725
|
+
state: Singleton['state']
|
|
729
726
|
}
|
|
730
727
|
>
|
|
731
728
|
>,
|
|
@@ -752,12 +749,6 @@ export class Spiceflow<
|
|
|
752
749
|
return this
|
|
753
750
|
}
|
|
754
751
|
|
|
755
|
-
/**
|
|
756
|
-
* Pass a request through all matching route handles and return a response
|
|
757
|
-
* @param request The `Request`
|
|
758
|
-
* @param platform Platform specific context {@link Platform}
|
|
759
|
-
* @returns The final `Response`
|
|
760
|
-
*/
|
|
761
752
|
async handle(request: Request): Promise<Response> {
|
|
762
753
|
let u = new URL(request.url, 'http://localhost')
|
|
763
754
|
let path = u.pathname + u.search
|
|
@@ -773,39 +764,27 @@ export class Spiceflow<
|
|
|
773
764
|
|
|
774
765
|
const route = this.match(request.method, path)
|
|
775
766
|
|
|
776
|
-
if (!route) {
|
|
777
|
-
const error = new NotFoundError(`${path} not found`)
|
|
778
|
-
const res = await this.runErrorHandlers({
|
|
779
|
-
onErrorHandlers,
|
|
780
|
-
error,
|
|
781
|
-
request,
|
|
782
|
-
})
|
|
783
|
-
if (res) return res
|
|
784
|
-
return new Response(`Not Found`, {
|
|
785
|
-
status: 404,
|
|
786
|
-
})
|
|
787
|
-
}
|
|
788
767
|
onErrorHandlers = this.getAppsInScope(route.app).flatMap(
|
|
789
768
|
(x) => x.onErrorHandlers,
|
|
790
769
|
)
|
|
791
770
|
let {
|
|
792
771
|
params,
|
|
793
|
-
app: { defaultStore },
|
|
772
|
+
app: { defaultState: defaultStore },
|
|
794
773
|
} = route
|
|
795
774
|
const middlewares = this.getAppsInScope(route.app).flatMap(
|
|
796
775
|
(x) => x.middlewares,
|
|
797
776
|
)
|
|
798
777
|
// console.log({ onReqHandlers })
|
|
799
|
-
let
|
|
778
|
+
let state = { ...defaultStore }
|
|
800
779
|
|
|
801
780
|
let content = route?.internalRoute?.hooks?.content
|
|
802
781
|
|
|
803
782
|
if (route.internalRoute?.validateBody) {
|
|
804
783
|
// TODO don't clone the request
|
|
805
784
|
let typedRequest =
|
|
806
|
-
request instanceof
|
|
785
|
+
request instanceof SpiceflowRequest
|
|
807
786
|
? request
|
|
808
|
-
: new
|
|
787
|
+
: new SpiceflowRequest(request)
|
|
809
788
|
typedRequest.validateBody = route.internalRoute?.validateBody
|
|
810
789
|
request = typedRequest
|
|
811
790
|
}
|
|
@@ -813,27 +792,31 @@ export class Spiceflow<
|
|
|
813
792
|
let query = parseQuery.parse((u.search || '').slice(1))
|
|
814
793
|
|
|
815
794
|
let index = 0
|
|
816
|
-
|
|
795
|
+
let context = {
|
|
796
|
+
...defaultContext,
|
|
797
|
+
request,
|
|
798
|
+
state,
|
|
799
|
+
path,
|
|
800
|
+
query,
|
|
801
|
+
params,
|
|
802
|
+
redirect,
|
|
803
|
+
} satisfies MiddlewareContext<any>
|
|
804
|
+
let handlerResponse: Response | undefined
|
|
817
805
|
const next = async () => {
|
|
818
806
|
if (index < middlewares.length) {
|
|
819
807
|
const middleware = middlewares[index]
|
|
820
808
|
index++
|
|
821
|
-
|
|
822
|
-
request,
|
|
823
|
-
store,
|
|
824
|
-
path,
|
|
825
|
-
query,
|
|
826
|
-
params,
|
|
827
|
-
redirect,
|
|
828
|
-
} satisfies MiddlewareContext<any>
|
|
809
|
+
|
|
829
810
|
const result = await middleware(context, next)
|
|
830
811
|
|
|
831
812
|
if (!result && index < middlewares.length) {
|
|
832
|
-
return await next()
|
|
833
813
|
} else if (result) {
|
|
834
814
|
return await turnHandlerResultIntoResponse(result)
|
|
835
815
|
}
|
|
836
816
|
}
|
|
817
|
+
if (handlerResponse) {
|
|
818
|
+
return handlerResponse
|
|
819
|
+
}
|
|
837
820
|
|
|
838
821
|
query = runValidation(query, route.internalRoute?.validateQuery)
|
|
839
822
|
params = runValidation(
|
|
@@ -841,29 +824,18 @@ export class Spiceflow<
|
|
|
841
824
|
route.internalRoute?.validateParams,
|
|
842
825
|
)
|
|
843
826
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
const res = route.internalRoute?.handler({
|
|
847
|
-
...defaultContext,
|
|
848
|
-
request,
|
|
849
|
-
params: params as any,
|
|
850
|
-
redirect,
|
|
851
|
-
store,
|
|
852
|
-
query,
|
|
853
|
-
// body,
|
|
854
|
-
path,
|
|
855
|
-
|
|
856
|
-
// platform
|
|
857
|
-
} satisfies Context<any, any, any>)
|
|
827
|
+
const res = route.internalRoute?.handler(context)
|
|
858
828
|
if (isAsyncIterable(res)) {
|
|
859
|
-
|
|
829
|
+
handlerResponse = await this.handleStream({
|
|
860
830
|
generator: res,
|
|
861
831
|
request,
|
|
862
832
|
onErrorHandlers,
|
|
863
833
|
})
|
|
834
|
+
return handlerResponse
|
|
864
835
|
}
|
|
865
836
|
|
|
866
|
-
|
|
837
|
+
handlerResponse = await turnHandlerResultIntoResponse(res)
|
|
838
|
+
return handlerResponse
|
|
867
839
|
}
|
|
868
840
|
const response = await next()
|
|
869
841
|
|
|
@@ -875,11 +847,13 @@ export class Spiceflow<
|
|
|
875
847
|
error: err,
|
|
876
848
|
request,
|
|
877
849
|
})
|
|
878
|
-
if (res) return res
|
|
850
|
+
if (res instanceof Response) return res
|
|
851
|
+
|
|
879
852
|
let status = err?.status ?? 500
|
|
880
|
-
|
|
853
|
+
res ||= new Response(err?.message || 'Internal Server Error', {
|
|
881
854
|
status,
|
|
882
855
|
})
|
|
856
|
+
return res
|
|
883
857
|
}
|
|
884
858
|
}
|
|
885
859
|
|
|
@@ -1003,7 +977,7 @@ export class Spiceflow<
|
|
|
1003
977
|
req.url || '',
|
|
1004
978
|
`http://${req.headers.host || hostname || 'localhost'}`,
|
|
1005
979
|
)
|
|
1006
|
-
const typedRequest = new
|
|
980
|
+
const typedRequest = new SpiceflowRequest(url.toString(), {
|
|
1007
981
|
method: req.method,
|
|
1008
982
|
headers: req.headers as HeadersInit,
|
|
1009
983
|
body:
|
|
@@ -1223,7 +1197,7 @@ function bfsFind<T>(
|
|
|
1223
1197
|
}
|
|
1224
1198
|
return
|
|
1225
1199
|
}
|
|
1226
|
-
export class
|
|
1200
|
+
export class SpiceflowRequest<T = any> extends Request {
|
|
1227
1201
|
validateBody?: ValidateFunction
|
|
1228
1202
|
|
|
1229
1203
|
async json(): Promise<T> {
|
package/src/types.test.ts
CHANGED