spiceflow 1.12.1 → 1.13.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 +107 -4
- package/dist/_node-server-unsupported.js +1 -0
- package/dist/_node-server-unsupported.js.map +1 -0
- package/dist/_node-server.d.ts.map +1 -1
- package/dist/_node-server.js +2 -2
- package/dist/_node-server.js.map +1 -0
- package/dist/client/errors.js +1 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +7 -4
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.js +1 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/utils.js +1 -0
- package/dist/client/utils.js.map +1 -0
- package/dist/client.test.js +1 -0
- package/dist/client.test.js.map +1 -0
- package/dist/context.d.ts +6 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +1 -0
- package/dist/context.js.map +1 -0
- package/dist/cors.js +1 -0
- package/dist/cors.js.map +1 -0
- package/dist/cors.test.js +1 -0
- package/dist/cors.test.js.map +1 -0
- package/dist/error.js +1 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-client-transport.d.ts +35 -0
- package/dist/mcp-client-transport.d.ts.map +1 -0
- package/dist/mcp-client-transport.js +147 -0
- package/dist/mcp-client-transport.js.map +1 -0
- package/dist/mcp-transport.js +1 -0
- package/dist/mcp-transport.js.map +1 -0
- package/dist/mcp.d.ts +18 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +43 -224
- package/dist/mcp.js.map +1 -0
- package/dist/middleware.test.js +1 -0
- package/dist/middleware.test.js.map +1 -0
- package/dist/openapi-to-mcp.d.ts +38 -0
- package/dist/openapi-to-mcp.d.ts.map +1 -0
- package/dist/openapi-to-mcp.js +367 -0
- package/dist/openapi-to-mcp.js.map +1 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +7 -2
- package/dist/openapi.js.map +1 -0
- package/dist/openapi.test.js +32 -31
- package/dist/openapi.test.js.map +1 -0
- package/dist/simple.benchmark.js +1 -0
- package/dist/simple.benchmark.js.map +1 -0
- package/dist/spiceflow.d.ts +5 -2
- package/dist/spiceflow.d.ts.map +1 -1
- package/dist/spiceflow.js +26 -6
- package/dist/spiceflow.js.map +1 -0
- package/dist/spiceflow.test.js +15 -3
- package/dist/spiceflow.test.js.map +1 -0
- package/dist/static-node.js +1 -0
- package/dist/static-node.js.map +1 -0
- package/dist/static.benchmark.js +1 -0
- package/dist/static.benchmark.js.map +1 -0
- package/dist/static.js +1 -0
- package/dist/static.js.map +1 -0
- package/dist/stream.test.js +1 -0
- package/dist/stream.test.js.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/types.test.js +1 -0
- package/dist/types.test.js.map +1 -0
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -0
- package/dist/waitUntil.test.d.ts +2 -0
- package/dist/waitUntil.test.d.ts.map +1 -0
- package/dist/waitUntil.test.js +142 -0
- package/dist/waitUntil.test.js.map +1 -0
- package/dist/zod.test.js +1 -0
- package/dist/zod.test.js.map +1 -0
- package/package.json +4 -3
- package/src/_node-server.ts +1 -2
- package/src/client/index.ts +9 -7
- package/src/context.ts +6 -3
- package/src/index.ts +1 -1
- package/src/mcp-client-transport.ts +184 -0
- package/src/mcp.ts +49 -307
- package/src/openapi-to-mcp.ts +510 -0
- package/src/openapi.test.ts +31 -31
- package/src/openapi.ts +9 -3
- package/src/spiceflow.test.ts +18 -4
- package/src/spiceflow.ts +42 -15
- package/src/waitUntil.test.ts +168 -0
- package/dist/serialize.d.ts +0 -2
- package/dist/serialize.d.ts.map +0 -1
- package/dist/serialize.js +0 -9
- package/src/serialize.ts +0 -10
package/src/openapi.ts
CHANGED
|
@@ -121,8 +121,10 @@ const registerSchemaPath = ({
|
|
|
121
121
|
schema,
|
|
122
122
|
route,
|
|
123
123
|
models,
|
|
124
|
+
basePath,
|
|
124
125
|
}: {
|
|
125
126
|
schema: Partial<OpenAPIV3.PathsObject>
|
|
127
|
+
basePath: string
|
|
126
128
|
route: InternalRoute
|
|
127
129
|
models: Record<string, TypeSchema>
|
|
128
130
|
}) => {
|
|
@@ -377,6 +379,7 @@ export const openapi = <Path extends string = '/openapi'>({
|
|
|
377
379
|
const relativePath = path.startsWith('/') ? path.slice(1) : path
|
|
378
380
|
|
|
379
381
|
const app = new Spiceflow({ name: 'openapi' }).get(path, ({}) => {
|
|
382
|
+
const basePath = app.topLevelApp!.basePath // TODO this does not work
|
|
380
383
|
let routes = app.getAllRoutes()
|
|
381
384
|
if (routes.length !== totalRoutes) {
|
|
382
385
|
const ALLOWED_METHODS = [
|
|
@@ -392,6 +395,7 @@ export const openapi = <Path extends string = '/openapi'>({
|
|
|
392
395
|
totalRoutes = routes.length
|
|
393
396
|
|
|
394
397
|
routes.forEach((route: InternalRoute) => {
|
|
398
|
+
if (route.path.startsWith('_mcp_')) return
|
|
395
399
|
if (route.hooks?.detail?.hide === true) return
|
|
396
400
|
// TODO: route.hooks?.detail?.hide !== false add ability to hide: false to prevent excluding
|
|
397
401
|
if (excludeMethods.includes(route.method)) return
|
|
@@ -404,9 +408,10 @@ export const openapi = <Path extends string = '/openapi'>({
|
|
|
404
408
|
if (route.method === 'ALL') {
|
|
405
409
|
ALLOWED_METHODS.forEach((method) => {
|
|
406
410
|
registerSchemaPath({
|
|
411
|
+
basePath,
|
|
407
412
|
schema,
|
|
408
413
|
route: { ...route, method },
|
|
409
|
-
|
|
414
|
+
|
|
410
415
|
models: app.definitions?.type,
|
|
411
416
|
})
|
|
412
417
|
})
|
|
@@ -414,6 +419,7 @@ export const openapi = <Path extends string = '/openapi'>({
|
|
|
414
419
|
}
|
|
415
420
|
|
|
416
421
|
registerSchemaPath({
|
|
422
|
+
basePath,
|
|
417
423
|
schema,
|
|
418
424
|
route,
|
|
419
425
|
// @ts-ignore
|
|
@@ -473,9 +479,9 @@ function getJsonSchema(schema: TypeSchema) {
|
|
|
473
479
|
return rest as any
|
|
474
480
|
}
|
|
475
481
|
if (isZodSchema(schema)) {
|
|
476
|
-
let jsonSchema = zodToJsonSchema(schema, {
|
|
482
|
+
let jsonSchema = zodToJsonSchema(schema as any, {
|
|
477
483
|
removeAdditionalStrategy: 'strict',
|
|
478
|
-
})
|
|
484
|
+
}) as any
|
|
479
485
|
const { $schema, ...rest } = jsonSchema
|
|
480
486
|
return rest as any
|
|
481
487
|
}
|
package/src/spiceflow.test.ts
CHANGED
|
@@ -36,6 +36,18 @@ test('* param is a path without front slash', async () => {
|
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
+
test('should error if passing .request option to .route with method GET', () => {
|
|
40
|
+
new Spiceflow().route({
|
|
41
|
+
method: 'GET',
|
|
42
|
+
path: '/abc',
|
|
43
|
+
handler: () => 'ok',
|
|
44
|
+
// @ts-expect-error .request is not allowed for GET routes
|
|
45
|
+
request: z.object({
|
|
46
|
+
abc: z.string(),
|
|
47
|
+
}),
|
|
48
|
+
})
|
|
49
|
+
})
|
|
50
|
+
|
|
39
51
|
test('this works to reference app in handler', async () => {
|
|
40
52
|
const res = await new Spiceflow()
|
|
41
53
|
.route({
|
|
@@ -600,7 +612,7 @@ test('getRouteAndParents', async () => {
|
|
|
600
612
|
let routers = bfs(app)
|
|
601
613
|
let last = routers[routers.length - 1]
|
|
602
614
|
|
|
603
|
-
expect(app['getAppAndParents'](last).map((x) => x.
|
|
615
|
+
expect(app['getAppAndParents'](last).map((x) => x.basePath))
|
|
604
616
|
.toMatchInlineSnapshot(`
|
|
605
617
|
[
|
|
606
618
|
"/one",
|
|
@@ -626,7 +638,7 @@ test('getAppsInScope include all parent apps', async () => {
|
|
|
626
638
|
let routers = bfs(app)
|
|
627
639
|
let secondLast = routers[routers.length - 2]
|
|
628
640
|
|
|
629
|
-
expect(app['getAppsInScope'](secondLast).map((x) => x.
|
|
641
|
+
expect(app['getAppsInScope'](secondLast).map((x) => x.basePath))
|
|
630
642
|
.toMatchInlineSnapshot(`
|
|
631
643
|
[
|
|
632
644
|
"/one",
|
|
@@ -652,7 +664,7 @@ test('getAppsInScope include all parent apps and non scoped apps', async () => {
|
|
|
652
664
|
let routers = bfs(app)
|
|
653
665
|
let secondLast = routers[routers.length - 2]
|
|
654
666
|
|
|
655
|
-
expect(app['getAppsInScope'](secondLast).map((x) => x.
|
|
667
|
+
expect(app['getAppsInScope'](secondLast).map((x) => x.basePath))
|
|
656
668
|
.toMatchInlineSnapshot(`
|
|
657
669
|
[
|
|
658
670
|
"/one",
|
|
@@ -932,7 +944,9 @@ describe('safePath', () => {
|
|
|
932
944
|
expect(app.safePath('/posts')).toBe('/posts')
|
|
933
945
|
// @ts-expect-error
|
|
934
946
|
app.safePath('/posts/*')
|
|
935
|
-
expect(app.safePath('/posts/*', { '*': 'some/key' })).toBe(
|
|
947
|
+
expect(app.safePath('/posts/*', { '*': 'some/key' })).toBe(
|
|
948
|
+
'/posts/some/key',
|
|
949
|
+
)
|
|
936
950
|
})
|
|
937
951
|
|
|
938
952
|
test('handles paths with required parameters', () => {
|
package/src/spiceflow.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { copy } from 'copy-anything'
|
|
2
|
+
import superjson from 'superjson'
|
|
2
3
|
|
|
3
4
|
import { SpiceflowFetchError } from './client/errors.ts'
|
|
4
5
|
import { ValidationError } from './error.ts'
|
|
@@ -34,7 +35,7 @@ import { StandardSchemaV1 } from '@standard-schema/spec'
|
|
|
34
35
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
35
36
|
import { handleForNode, listenForNode } from 'spiceflow/_node-server'
|
|
36
37
|
import { MiddlewareContext } from './context.ts'
|
|
37
|
-
|
|
38
|
+
|
|
38
39
|
import { isAsyncIterable, isResponse, redirect } from './utils.ts'
|
|
39
40
|
|
|
40
41
|
let globalIndex = 0
|
|
@@ -46,6 +47,8 @@ export type SpiceflowServerError =
|
|
|
46
47
|
| SpiceflowFetchError<number, any>
|
|
47
48
|
| Error
|
|
48
49
|
|
|
50
|
+
export type WaitUntil = (promise: Promise<any>) => void
|
|
51
|
+
|
|
49
52
|
type OnError = (x: {
|
|
50
53
|
error: SpiceflowServerError
|
|
51
54
|
request: Request
|
|
@@ -104,7 +107,8 @@ export class Spiceflow<
|
|
|
104
107
|
private onErrorHandlers: OnError[] = []
|
|
105
108
|
private routes: InternalRoute[] = []
|
|
106
109
|
private defaultState: Record<any, any> = {}
|
|
107
|
-
topLevelApp?: AnySpiceflow
|
|
110
|
+
topLevelApp?: AnySpiceflow = this
|
|
111
|
+
private waitUntilFn: WaitUntil
|
|
108
112
|
|
|
109
113
|
_types = {
|
|
110
114
|
Prefix: '' as BasePath,
|
|
@@ -117,7 +121,7 @@ export class Spiceflow<
|
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
/** @internal */
|
|
120
|
-
|
|
124
|
+
basePath?: string = ''
|
|
121
125
|
|
|
122
126
|
/** @internal */
|
|
123
127
|
childrenApps: AnySpiceflow[] = []
|
|
@@ -128,7 +132,7 @@ export class Spiceflow<
|
|
|
128
132
|
const allApps = bfs(root) || []
|
|
129
133
|
const allRoutes = allApps.flatMap((x) => {
|
|
130
134
|
const prefix = this.getAppAndParents(x)
|
|
131
|
-
.map((x) => x.
|
|
135
|
+
.map((x) => x.basePath)
|
|
132
136
|
.join('')
|
|
133
137
|
|
|
134
138
|
return x.routes.map((x) => ({ ...x, path: prefix + x.path }))
|
|
@@ -181,7 +185,7 @@ export class Spiceflow<
|
|
|
181
185
|
const result = bfsFind(this, (app) => {
|
|
182
186
|
app.topLevelApp = root
|
|
183
187
|
let prefix = this.getAppAndParents(app)
|
|
184
|
-
.map((x) => x.
|
|
188
|
+
.map((x) => x.basePath)
|
|
185
189
|
.join('')
|
|
186
190
|
.replace(/\/$/, '')
|
|
187
191
|
if (prefix && !path.startsWith(prefix)) {
|
|
@@ -283,13 +287,22 @@ export class Spiceflow<
|
|
|
283
287
|
options: {
|
|
284
288
|
name?: string
|
|
285
289
|
scoped?: Scoped
|
|
286
|
-
|
|
290
|
+
waitUntil?: WaitUntil
|
|
287
291
|
basePath?: BasePath
|
|
288
292
|
} = {},
|
|
289
293
|
) {
|
|
290
294
|
this.scoped = options.scoped
|
|
291
|
-
|
|
292
|
-
|
|
295
|
+
|
|
296
|
+
// Set up waitUntil function - use provided one, global one, or noop
|
|
297
|
+
this.waitUntilFn = options.waitUntil ||
|
|
298
|
+
(typeof globalThis !== 'undefined' && 'waitUntil' in globalThis
|
|
299
|
+
? (globalThis as any).waitUntil
|
|
300
|
+
: () => {})
|
|
301
|
+
|
|
302
|
+
this.basePath = options.basePath || ''
|
|
303
|
+
if (this.basePath === '/') {
|
|
304
|
+
this.basePath = ''
|
|
305
|
+
}
|
|
293
306
|
}
|
|
294
307
|
|
|
295
308
|
post<
|
|
@@ -454,13 +467,16 @@ export class Spiceflow<
|
|
|
454
467
|
path: Path
|
|
455
468
|
method: Method
|
|
456
469
|
handler: Handle
|
|
457
|
-
} &
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
470
|
+
} & Omit<
|
|
471
|
+
LocalHook<
|
|
472
|
+
LocalSchema,
|
|
473
|
+
Schema,
|
|
474
|
+
Singleton,
|
|
475
|
+
Definitions['error'],
|
|
476
|
+
Metadata['macro'],
|
|
477
|
+
JoinPath<BasePath, Path>
|
|
478
|
+
>,
|
|
479
|
+
Method extends 'GET' | 'HEAD' ? 'request' : never
|
|
464
480
|
>,
|
|
465
481
|
): Spiceflow<
|
|
466
482
|
BasePath,
|
|
@@ -778,6 +794,7 @@ export class Spiceflow<
|
|
|
778
794
|
|
|
779
795
|
use(appOrHandler) {
|
|
780
796
|
if (appOrHandler instanceof Spiceflow) {
|
|
797
|
+
appOrHandler.topLevelApp = this
|
|
781
798
|
this.childrenApps.push(appOrHandler)
|
|
782
799
|
} else if (typeof appOrHandler === 'function') {
|
|
783
800
|
this.middlewares ??= []
|
|
@@ -843,6 +860,7 @@ export class Spiceflow<
|
|
|
843
860
|
query: parseQuery((u.search || '').slice(1)),
|
|
844
861
|
params: _params,
|
|
845
862
|
redirect,
|
|
863
|
+
waitUntil: this.waitUntilFn,
|
|
846
864
|
} satisfies MiddlewareContext<any>
|
|
847
865
|
let handlerResponse: Response | undefined
|
|
848
866
|
async function getResForError(err: any) {
|
|
@@ -1422,3 +1440,12 @@ function parseQuery(queryString: string) {
|
|
|
1422
1440
|
export function cloneDeep(x) {
|
|
1423
1441
|
return copy(x)
|
|
1424
1442
|
}
|
|
1443
|
+
|
|
1444
|
+
function superjsonSerialize(value: any, indent = false) {
|
|
1445
|
+
// return JSON.stringify(value)
|
|
1446
|
+
const { json, meta } = superjson.serialize(value)
|
|
1447
|
+
if (json && meta) {
|
|
1448
|
+
json['__superjsonMeta'] = meta
|
|
1449
|
+
}
|
|
1450
|
+
return JSON.stringify(json ?? null, null, indent ? 2 : undefined)
|
|
1451
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { describe, test, expect, vi } from 'vitest'
|
|
2
|
+
import { Spiceflow } from './spiceflow.ts'
|
|
3
|
+
|
|
4
|
+
describe('waitUntil', () => {
|
|
5
|
+
test('waitUntil is available in handler context', async () => {
|
|
6
|
+
let waitUntilCalled = false
|
|
7
|
+
let waitUntilPromise: Promise<any> | null = null
|
|
8
|
+
|
|
9
|
+
const mockWaitUntil = vi.fn((promise: Promise<any>) => {
|
|
10
|
+
waitUntilCalled = true
|
|
11
|
+
waitUntilPromise = promise
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const app = new Spiceflow({
|
|
15
|
+
waitUntil: mockWaitUntil
|
|
16
|
+
}).route({
|
|
17
|
+
method: 'GET',
|
|
18
|
+
path: '/test',
|
|
19
|
+
handler({ waitUntil }) {
|
|
20
|
+
expect(typeof waitUntil).toBe('function')
|
|
21
|
+
|
|
22
|
+
const backgroundTask = Promise.resolve('background work done')
|
|
23
|
+
waitUntil(backgroundTask)
|
|
24
|
+
|
|
25
|
+
return { success: true }
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const response = await app.handle(new Request('http://localhost/test'))
|
|
30
|
+
const data = await response.json()
|
|
31
|
+
|
|
32
|
+
expect(data).toEqual({ success: true })
|
|
33
|
+
expect(waitUntilCalled).toBe(true)
|
|
34
|
+
expect(mockWaitUntil).toHaveBeenCalledTimes(1)
|
|
35
|
+
expect(waitUntilPromise).toBeInstanceOf(Promise)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('waitUntil defaults to noop when not provided', async () => {
|
|
39
|
+
const app = new Spiceflow().route({
|
|
40
|
+
method: 'GET',
|
|
41
|
+
path: '/test',
|
|
42
|
+
handler({ waitUntil }) {
|
|
43
|
+
expect(typeof waitUntil).toBe('function')
|
|
44
|
+
|
|
45
|
+
// Should not throw when called
|
|
46
|
+
waitUntil(Promise.resolve('test'))
|
|
47
|
+
|
|
48
|
+
return { success: true }
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const response = await app.handle(new Request('http://localhost/test'))
|
|
53
|
+
const data = await response.json()
|
|
54
|
+
|
|
55
|
+
expect(data).toEqual({ success: true })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test('waitUntil can be called multiple times', async () => {
|
|
59
|
+
const mockWaitUntil = vi.fn()
|
|
60
|
+
|
|
61
|
+
const app = new Spiceflow({
|
|
62
|
+
waitUntil: mockWaitUntil
|
|
63
|
+
}).route({
|
|
64
|
+
method: 'POST',
|
|
65
|
+
path: '/multi',
|
|
66
|
+
handler({ waitUntil }) {
|
|
67
|
+
waitUntil(Promise.resolve('task 1'))
|
|
68
|
+
waitUntil(Promise.resolve('task 2'))
|
|
69
|
+
waitUntil(Promise.resolve('task 3'))
|
|
70
|
+
|
|
71
|
+
return { taskCount: 3 }
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const response = await app.handle(new Request('http://localhost/multi', {
|
|
76
|
+
method: 'POST'
|
|
77
|
+
}))
|
|
78
|
+
const data = await response.json()
|
|
79
|
+
|
|
80
|
+
expect(data).toEqual({ taskCount: 3 })
|
|
81
|
+
expect(mockWaitUntil).toHaveBeenCalledTimes(3)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('waitUntil works with middleware context', async () => {
|
|
85
|
+
const mockWaitUntil = vi.fn()
|
|
86
|
+
|
|
87
|
+
const app = new Spiceflow({
|
|
88
|
+
waitUntil: mockWaitUntil
|
|
89
|
+
})
|
|
90
|
+
.use(({ waitUntil }, next) => {
|
|
91
|
+
expect(typeof waitUntil).toBe('function')
|
|
92
|
+
waitUntil(Promise.resolve('middleware task'))
|
|
93
|
+
return next()
|
|
94
|
+
})
|
|
95
|
+
.route({
|
|
96
|
+
method: 'GET',
|
|
97
|
+
path: '/middleware',
|
|
98
|
+
handler({ waitUntil }) {
|
|
99
|
+
waitUntil(Promise.resolve('handler task'))
|
|
100
|
+
return { success: true }
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
const response = await app.handle(new Request('http://localhost/middleware'))
|
|
105
|
+
const data = await response.json()
|
|
106
|
+
|
|
107
|
+
expect(data).toEqual({ success: true })
|
|
108
|
+
expect(mockWaitUntil).toHaveBeenCalledTimes(2)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('waitUntil uses global waitUntil when available', async () => {
|
|
112
|
+
// Mock global waitUntil
|
|
113
|
+
const originalGlobal = globalThis as any
|
|
114
|
+
const mockGlobalWaitUntil = vi.fn()
|
|
115
|
+
originalGlobal.waitUntil = mockGlobalWaitUntil
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const app = new Spiceflow().route({
|
|
119
|
+
method: 'GET',
|
|
120
|
+
path: '/global',
|
|
121
|
+
handler({ waitUntil }) {
|
|
122
|
+
waitUntil(Promise.resolve('using global'))
|
|
123
|
+
return { usingGlobal: true }
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const response = await app.handle(new Request('http://localhost/global'))
|
|
128
|
+
const data = await response.json()
|
|
129
|
+
|
|
130
|
+
expect(data).toEqual({ usingGlobal: true })
|
|
131
|
+
expect(mockGlobalWaitUntil).toHaveBeenCalledTimes(1)
|
|
132
|
+
} finally {
|
|
133
|
+
// Clean up
|
|
134
|
+
delete originalGlobal.waitUntil
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('custom waitUntil overrides global waitUntil', async () => {
|
|
139
|
+
// Mock global waitUntil
|
|
140
|
+
const originalGlobal = globalThis as any
|
|
141
|
+
const mockGlobalWaitUntil = vi.fn()
|
|
142
|
+
const mockCustomWaitUntil = vi.fn()
|
|
143
|
+
originalGlobal.waitUntil = mockGlobalWaitUntil
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const app = new Spiceflow({
|
|
147
|
+
waitUntil: mockCustomWaitUntil
|
|
148
|
+
}).route({
|
|
149
|
+
method: 'GET',
|
|
150
|
+
path: '/custom',
|
|
151
|
+
handler({ waitUntil }) {
|
|
152
|
+
waitUntil(Promise.resolve('using custom'))
|
|
153
|
+
return { usingCustom: true }
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const response = await app.handle(new Request('http://localhost/custom'))
|
|
158
|
+
const data = await response.json()
|
|
159
|
+
|
|
160
|
+
expect(data).toEqual({ usingCustom: true })
|
|
161
|
+
expect(mockCustomWaitUntil).toHaveBeenCalledTimes(1)
|
|
162
|
+
expect(mockGlobalWaitUntil).not.toHaveBeenCalled()
|
|
163
|
+
} finally {
|
|
164
|
+
// Clean up
|
|
165
|
+
delete originalGlobal.waitUntil
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
})
|
package/dist/serialize.d.ts
DELETED
package/dist/serialize.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../src/serialize.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,UAAQ,UAO5D"}
|
package/dist/serialize.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import superjson from 'superjson';
|
|
2
|
-
export function superjsonSerialize(value, indent = false) {
|
|
3
|
-
// return JSON.stringify(value)
|
|
4
|
-
const { json, meta } = superjson.serialize(value);
|
|
5
|
-
if (json && meta) {
|
|
6
|
-
json['__superjsonMeta'] = meta;
|
|
7
|
-
}
|
|
8
|
-
return JSON.stringify(json ?? null, null, indent ? 2 : undefined);
|
|
9
|
-
}
|
package/src/serialize.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import superjson from 'superjson'
|
|
2
|
-
|
|
3
|
-
export function superjsonSerialize(value: any, indent = false) {
|
|
4
|
-
// return JSON.stringify(value)
|
|
5
|
-
const { json, meta } = superjson.serialize(value)
|
|
6
|
-
if (json && meta) {
|
|
7
|
-
json['__superjsonMeta'] = meta
|
|
8
|
-
}
|
|
9
|
-
return JSON.stringify(json ?? null, null, indent ? 2 : undefined)
|
|
10
|
-
}
|