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.
Files changed (98) hide show
  1. package/README.md +107 -4
  2. package/dist/_node-server-unsupported.js +1 -0
  3. package/dist/_node-server-unsupported.js.map +1 -0
  4. package/dist/_node-server.d.ts.map +1 -1
  5. package/dist/_node-server.js +2 -2
  6. package/dist/_node-server.js.map +1 -0
  7. package/dist/client/errors.js +1 -0
  8. package/dist/client/errors.js.map +1 -0
  9. package/dist/client/index.d.ts +2 -2
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +7 -4
  12. package/dist/client/index.js.map +1 -0
  13. package/dist/client/types.js +1 -0
  14. package/dist/client/types.js.map +1 -0
  15. package/dist/client/utils.js +1 -0
  16. package/dist/client/utils.js.map +1 -0
  17. package/dist/client.test.js +1 -0
  18. package/dist/client.test.js.map +1 -0
  19. package/dist/context.d.ts +6 -3
  20. package/dist/context.d.ts.map +1 -1
  21. package/dist/context.js +1 -0
  22. package/dist/context.js.map +1 -0
  23. package/dist/cors.js +1 -0
  24. package/dist/cors.js.map +1 -0
  25. package/dist/cors.test.js +1 -0
  26. package/dist/cors.test.js.map +1 -0
  27. package/dist/error.js +1 -0
  28. package/dist/error.js.map +1 -0
  29. package/dist/index.d.ts +1 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mcp-client-transport.d.ts +35 -0
  34. package/dist/mcp-client-transport.d.ts.map +1 -0
  35. package/dist/mcp-client-transport.js +147 -0
  36. package/dist/mcp-client-transport.js.map +1 -0
  37. package/dist/mcp-transport.js +1 -0
  38. package/dist/mcp-transport.js.map +1 -0
  39. package/dist/mcp.d.ts +18 -1
  40. package/dist/mcp.d.ts.map +1 -1
  41. package/dist/mcp.js +43 -224
  42. package/dist/mcp.js.map +1 -0
  43. package/dist/middleware.test.js +1 -0
  44. package/dist/middleware.test.js.map +1 -0
  45. package/dist/openapi-to-mcp.d.ts +38 -0
  46. package/dist/openapi-to-mcp.d.ts.map +1 -0
  47. package/dist/openapi-to-mcp.js +367 -0
  48. package/dist/openapi-to-mcp.js.map +1 -0
  49. package/dist/openapi.d.ts.map +1 -1
  50. package/dist/openapi.js +7 -2
  51. package/dist/openapi.js.map +1 -0
  52. package/dist/openapi.test.js +32 -31
  53. package/dist/openapi.test.js.map +1 -0
  54. package/dist/simple.benchmark.js +1 -0
  55. package/dist/simple.benchmark.js.map +1 -0
  56. package/dist/spiceflow.d.ts +5 -2
  57. package/dist/spiceflow.d.ts.map +1 -1
  58. package/dist/spiceflow.js +26 -6
  59. package/dist/spiceflow.js.map +1 -0
  60. package/dist/spiceflow.test.js +15 -3
  61. package/dist/spiceflow.test.js.map +1 -0
  62. package/dist/static-node.js +1 -0
  63. package/dist/static-node.js.map +1 -0
  64. package/dist/static.benchmark.js +1 -0
  65. package/dist/static.benchmark.js.map +1 -0
  66. package/dist/static.js +1 -0
  67. package/dist/static.js.map +1 -0
  68. package/dist/stream.test.js +1 -0
  69. package/dist/stream.test.js.map +1 -0
  70. package/dist/types.js +1 -0
  71. package/dist/types.js.map +1 -0
  72. package/dist/types.test.js +1 -0
  73. package/dist/types.test.js.map +1 -0
  74. package/dist/utils.js +1 -0
  75. package/dist/utils.js.map +1 -0
  76. package/dist/waitUntil.test.d.ts +2 -0
  77. package/dist/waitUntil.test.d.ts.map +1 -0
  78. package/dist/waitUntil.test.js +142 -0
  79. package/dist/waitUntil.test.js.map +1 -0
  80. package/dist/zod.test.js +1 -0
  81. package/dist/zod.test.js.map +1 -0
  82. package/package.json +4 -3
  83. package/src/_node-server.ts +1 -2
  84. package/src/client/index.ts +9 -7
  85. package/src/context.ts +6 -3
  86. package/src/index.ts +1 -1
  87. package/src/mcp-client-transport.ts +184 -0
  88. package/src/mcp.ts +49 -307
  89. package/src/openapi-to-mcp.ts +510 -0
  90. package/src/openapi.test.ts +31 -31
  91. package/src/openapi.ts +9 -3
  92. package/src/spiceflow.test.ts +18 -4
  93. package/src/spiceflow.ts +42 -15
  94. package/src/waitUntil.test.ts +168 -0
  95. package/dist/serialize.d.ts +0 -2
  96. package/dist/serialize.d.ts.map +0 -1
  97. package/dist/serialize.js +0 -9
  98. package/src/serialize.ts +0 -10
@@ -0,0 +1,510 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js'
2
+ // import deref from 'dereference-json-schema'
3
+
4
+ import {
5
+ CallToolRequestSchema,
6
+ ListResourcesRequestSchema,
7
+ ListToolsRequestSchema,
8
+ ReadResourceRequestSchema,
9
+ } from '@modelcontextprotocol/sdk/types.js'
10
+ import { OpenAPIV3 } from 'openapi-types'
11
+
12
+ function getOperationRequestBody(
13
+ operation: OpenAPIV3.OperationObject,
14
+ ): OpenAPIV3.SchemaObject | undefined {
15
+ if (!operation.requestBody) return undefined
16
+
17
+ const requestBody = operation.requestBody as OpenAPIV3.RequestBodyObject
18
+ const content = requestBody.content['application/json']
19
+ return content?.schema as OpenAPIV3.SchemaObject
20
+ }
21
+
22
+ function getOperationParameters(operation: OpenAPIV3.OperationObject): {
23
+ queryParams?: OpenAPIV3.SchemaObject
24
+ pathParams?: OpenAPIV3.SchemaObject
25
+ headerParams?: OpenAPIV3.SchemaObject
26
+ cookieParams?: OpenAPIV3.SchemaObject
27
+ } {
28
+ if (!operation.parameters) return {}
29
+
30
+ const queryProperties: Record<string, OpenAPIV3.SchemaObject> = {}
31
+ const pathProperties: Record<string, OpenAPIV3.SchemaObject> = {}
32
+ const headerProperties: Record<string, OpenAPIV3.SchemaObject> = {}
33
+ const cookieProperties: Record<string, OpenAPIV3.SchemaObject> = {}
34
+ const queryRequired: string[] = []
35
+ const pathRequired: string[] = []
36
+ const headerRequired: string[] = []
37
+ const cookieRequired: string[] = []
38
+
39
+ operation.parameters.forEach((param) => {
40
+ const paramObj = param as OpenAPIV3.ParameterObject
41
+ if (paramObj.in === 'query') {
42
+ queryProperties[paramObj.name] = paramObj.schema as OpenAPIV3.SchemaObject
43
+ if (paramObj.required) queryRequired.push(paramObj.name)
44
+ } else if (paramObj.in === 'path') {
45
+ pathProperties[paramObj.name] = paramObj.schema as OpenAPIV3.SchemaObject
46
+ if (paramObj.required) pathRequired.push(paramObj.name)
47
+ } else if (paramObj.in === 'header') {
48
+ headerProperties[paramObj.name] =
49
+ paramObj.schema as OpenAPIV3.SchemaObject
50
+ if (paramObj.required) headerRequired.push(paramObj.name)
51
+ } else if (paramObj.in === 'cookie') {
52
+ cookieProperties[paramObj.name] =
53
+ paramObj.schema as OpenAPIV3.SchemaObject
54
+ if (paramObj.required) cookieRequired.push(paramObj.name)
55
+ }
56
+ })
57
+
58
+ const result: {
59
+ queryParams?: OpenAPIV3.SchemaObject
60
+ pathParams?: OpenAPIV3.SchemaObject
61
+ headerParams?: OpenAPIV3.SchemaObject
62
+ cookieParams?: OpenAPIV3.SchemaObject
63
+ } = {}
64
+
65
+ if (Object.keys(queryProperties).length > 0) {
66
+ result.queryParams = {
67
+ type: 'object',
68
+ properties: queryProperties,
69
+ required: queryRequired.length > 0 ? queryRequired : undefined,
70
+ }
71
+ }
72
+
73
+ if (Object.keys(pathProperties).length > 0) {
74
+ result.pathParams = {
75
+ type: 'object',
76
+ properties: pathProperties,
77
+ required: pathRequired.length > 0 ? pathRequired : undefined,
78
+ }
79
+ }
80
+
81
+ if (Object.keys(headerProperties).length > 0) {
82
+ result.headerParams = {
83
+ type: 'object',
84
+ properties: headerProperties,
85
+ required: headerRequired.length > 0 ? headerRequired : undefined,
86
+ }
87
+ }
88
+
89
+ if (Object.keys(cookieProperties).length > 0) {
90
+ result.cookieParams = {
91
+ type: 'object',
92
+ properties: cookieProperties,
93
+ required: cookieRequired.length > 0 ? cookieRequired : undefined,
94
+ }
95
+ }
96
+
97
+ return result
98
+ }
99
+ function extractApiFromBaseUrl(openapi: OpenAPIV3.Document): string {
100
+ if (openapi.servers && openapi.servers.length > 0) {
101
+ return openapi.servers[0].url
102
+ }
103
+ return ''
104
+ }
105
+
106
+ function getAuthHeaders(
107
+ openapi: OpenAPIV3.Document,
108
+ operation?: OpenAPIV3.OperationObject,
109
+ ): Record<string, string> {
110
+ const headers: Record<string, string> = {}
111
+ const token = process.env.API_TOKEN
112
+
113
+ if (!token || !openapi.components?.securitySchemes) {
114
+ return headers
115
+ }
116
+
117
+ const securitySchemes = openapi.components.securitySchemes
118
+ let selectedScheme: OpenAPIV3.SecuritySchemeObject | null = null
119
+
120
+ // Check for operation-specific security requirements first
121
+ if (operation?.security && operation.security.length > 0) {
122
+ const firstSecurityReq = operation.security[0]
123
+ const operationSchemeNames = Object.keys(firstSecurityReq)
124
+
125
+ for (const schemeName of operationSchemeNames) {
126
+ const scheme = securitySchemes[schemeName]
127
+ if (scheme) {
128
+ selectedScheme = scheme as OpenAPIV3.SecuritySchemeObject
129
+ break
130
+ }
131
+ }
132
+ }
133
+
134
+ // If no operation-specific scheme found, use the first available scheme
135
+ if (!selectedScheme) {
136
+ const schemes = Object.values(securitySchemes)
137
+ if (schemes.length > 0) {
138
+ selectedScheme = schemes[0] as OpenAPIV3.SecuritySchemeObject
139
+ }
140
+ }
141
+
142
+ if (!selectedScheme) {
143
+ return headers
144
+ }
145
+
146
+ // Set headers based on scheme type
147
+ switch (selectedScheme.type) {
148
+ case 'http':
149
+ if (selectedScheme.scheme === 'bearer') {
150
+ headers['Authorization'] = `Bearer ${token}`
151
+ } else if (selectedScheme.scheme === 'basic') {
152
+ headers['Authorization'] = `Basic ${token}`
153
+ }
154
+ break
155
+ case 'apiKey':
156
+ if (selectedScheme.in === 'header') {
157
+ headers[selectedScheme.name] = token
158
+ }
159
+ break
160
+ case 'oauth2':
161
+ headers['Authorization'] = `Bearer ${token}`
162
+ break
163
+ }
164
+
165
+ return headers
166
+ }
167
+
168
+ type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
169
+ const defaultFetch = fetch
170
+
171
+ export function createMCPServer({
172
+ name = 'spiceflow',
173
+ version = '1.0.0',
174
+ openapi,
175
+
176
+ fetch = defaultFetch,
177
+ paths,
178
+ ignorePaths,
179
+ baseUrl = '',
180
+ }: {
181
+ name?: string
182
+ version?: string
183
+
184
+ fetch?: Fetch
185
+ openapi: OpenAPIV3.Document
186
+ paths?: string[]
187
+ ignorePaths?: string[]
188
+ baseUrl?: string
189
+ }) {
190
+ const server = new Server(
191
+ { name, version },
192
+ {
193
+ capabilities: {
194
+ tools: {},
195
+ resources: {},
196
+ },
197
+ },
198
+ )
199
+ // openapi = deref.dereferenceSync(openapi)
200
+ if (!baseUrl) {
201
+ baseUrl = extractApiFromBaseUrl(openapi)
202
+ }
203
+ if (baseUrl.endsWith('/')) {
204
+ baseUrl = baseUrl.slice(0, -1)
205
+ }
206
+
207
+ async function fetchWithBaseServerAndAuth(
208
+ u: string,
209
+ options: RequestInit,
210
+ operation?: OpenAPIV3.OperationObject,
211
+ userHeaders?: Record<string, string>,
212
+ userCookies?: Record<string, string>,
213
+ ) {
214
+ const authHeaders = getAuthHeaders(openapi, operation)
215
+
216
+ // Build cookie string from userCookies
217
+ let cookieHeader = ''
218
+ if (userCookies && Object.keys(userCookies).length > 0) {
219
+ cookieHeader = Object.entries(userCookies)
220
+ .map(
221
+ ([key, value]) =>
222
+ `${encodeURIComponent(key)}=${encodeURIComponent(value)}`,
223
+ )
224
+ .join('; ')
225
+ }
226
+
227
+ const commonHeaders: Record<string, string> = {}
228
+
229
+ const finalHeaders: Record<string, string> = {
230
+ ...commonHeaders,
231
+ ...(userHeaders || {}),
232
+ ...authHeaders,
233
+ ...((options?.headers as Record<string, string>) || {}),
234
+ }
235
+
236
+ if (cookieHeader) {
237
+ // Merge with existing Cookie header if present
238
+ const existingCookie = finalHeaders['Cookie'] || finalHeaders['cookie']
239
+ finalHeaders['Cookie'] = existingCookie
240
+ ? `${existingCookie}; ${cookieHeader}`
241
+ : cookieHeader
242
+ }
243
+
244
+ console.error(`using headers ${JSON.stringify(finalHeaders, null, 2)}`)
245
+
246
+ return await fetch!(new URL(u, baseUrl), {
247
+ ...options,
248
+ headers: finalHeaders,
249
+ })
250
+ }
251
+
252
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
253
+ const filteredPaths = Object.entries(openapi.paths).filter(([path]) => {
254
+ if (ignorePaths?.includes(path)) return false
255
+ if (paths && paths.length > 0) {
256
+ return paths.some((filterPath) => path.startsWith(filterPath))
257
+ }
258
+ return true
259
+ })
260
+
261
+ const tools = filteredPaths.flatMap(([path, pathObj]) =>
262
+ Object.entries(pathObj || {})
263
+ .filter(([method]) => method !== 'parameters')
264
+ .map(([method, operation]) => {
265
+ const properties: Record<string, any> = {}
266
+ const required: string[] = []
267
+
268
+ const requestBody = getOperationRequestBody(
269
+ operation as OpenAPIV3.OperationObject,
270
+ )
271
+ if (requestBody) {
272
+ properties.body = requestBody
273
+ required.push('body')
274
+ }
275
+
276
+ const { queryParams, pathParams, headerParams, cookieParams } =
277
+ getOperationParameters(operation as OpenAPIV3.OperationObject)
278
+ if (queryParams) {
279
+ properties.query = queryParams
280
+ }
281
+ if (pathParams) {
282
+ properties.params = pathParams
283
+ }
284
+ if (headerParams) {
285
+ properties.headers = headerParams
286
+ }
287
+ if (cookieParams) {
288
+ properties.cookies = cookieParams
289
+ }
290
+ let description = `${method.toUpperCase()} route for ${baseUrl}${path}`
291
+ let moreDescription =
292
+ (operation as OpenAPIV3.OperationObject).description ||
293
+ (operation as OpenAPIV3.OperationObject).summary
294
+ if (moreDescription) {
295
+ description += '. '
296
+ description += moreDescription
297
+ }
298
+
299
+ return {
300
+ name: getRouteName({ method, path }),
301
+ description,
302
+ inputSchema: {
303
+ type: 'object',
304
+ properties,
305
+ required: required.length > 0 ? required : undefined,
306
+ },
307
+ }
308
+ }),
309
+ )
310
+
311
+ return { tools }
312
+ })
313
+
314
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
315
+ const toolName = request.params.name
316
+ let { path, method } = getPathFromToolName(toolName)
317
+
318
+ const pathObj = openapi.paths[path]
319
+ if (!pathObj || !pathObj[method.toLowerCase()]) {
320
+ return {
321
+ content: [{ type: 'text', text: `Tool ${toolName} not found` }],
322
+ isError: true,
323
+ }
324
+ }
325
+
326
+ try {
327
+ const args = request.params.arguments || {}
328
+ const { body, query, params } = args
329
+ const userHeaders = args.headers as Record<string, string> | undefined
330
+ const userCookies = args.cookies as Record<string, string> | undefined
331
+ const operation = pathObj[
332
+ method.toLowerCase()
333
+ ] as OpenAPIV3.OperationObject
334
+
335
+ if (params) {
336
+ Object.entries(params).forEach(([key, value]) => {
337
+ path = path.replace(`{${key}}`, encodeURIComponent(String(value)))
338
+ })
339
+ }
340
+
341
+ if (query) {
342
+ const searchParams = new URLSearchParams()
343
+ Object.entries(query).forEach(([key, value]) => {
344
+ searchParams.set(key, String(value))
345
+ })
346
+ path += `?${searchParams.toString()}`
347
+ }
348
+
349
+ const response = await fetchWithBaseServerAndAuth(
350
+ path,
351
+ {
352
+ method,
353
+ headers: {
354
+ 'content-type': 'application/json',
355
+ },
356
+ body: body
357
+ ? typeof body === 'string'
358
+ ? body
359
+ : JSON.stringify(body)
360
+ : undefined,
361
+ },
362
+ operation,
363
+ userHeaders,
364
+ userCookies,
365
+ )
366
+
367
+ const isError = !response.ok
368
+ const contentType = response.headers.get('content-type')
369
+
370
+ if (contentType?.includes('application/json')) {
371
+ const json = await response.json()
372
+ return {
373
+ isError,
374
+ content: [{ type: 'text', text: JSON.stringify(json, null, 2) }],
375
+ }
376
+ }
377
+
378
+ const text = await response.text()
379
+ return {
380
+ isError,
381
+ content: [{ type: 'text', text }],
382
+ }
383
+ } catch (error: any) {
384
+ return {
385
+ content: [{ type: 'text', text: error.message || 'Unknown error' }],
386
+ isError: true,
387
+ }
388
+ }
389
+ })
390
+
391
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
392
+ const resources: { uri: string; mimeType: string; name: string }[] = []
393
+ for (const [path, pathObj] of Object.entries(openapi.paths)) {
394
+ if (path.startsWith('/mcp')) {
395
+ continue
396
+ }
397
+ const getOperation = pathObj?.get as OpenAPIV3.OperationObject
398
+ if (getOperation && !path.includes('{')) {
399
+ const { queryParams, headerParams, cookieParams } =
400
+ getOperationParameters(getOperation)
401
+ const hasRequiredQuery =
402
+ queryParams?.required && queryParams.required.length > 0
403
+ const hasRequiredHeaders =
404
+ headerParams?.required && headerParams.required.length > 0
405
+ const hasRequiredCookies =
406
+ cookieParams?.required && cookieParams.required.length > 0
407
+
408
+ if (!hasRequiredQuery && !hasRequiredHeaders && !hasRequiredCookies) {
409
+ resources.push({
410
+ uri: path,
411
+ mimeType: 'application/json',
412
+ name: `GET ${path}`,
413
+ })
414
+ }
415
+ }
416
+ }
417
+ return { resources: [] }
418
+ })
419
+
420
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
421
+ throw new Error('Resources are not supported - use tools instead')
422
+ })
423
+
424
+ return { server }
425
+ }
426
+
427
+ function getRouteName({
428
+ method,
429
+ path,
430
+ }: {
431
+ method: string
432
+ path: string
433
+ }): string {
434
+ return formatToolName(`${method.toUpperCase()} ${path}`, method, path)
435
+ }
436
+
437
+ const toolNameToPath = new Map<string, { method: string; path: string }>()
438
+
439
+ function getPathFromToolName(toolName: string): {
440
+ path: string
441
+ method: string
442
+ } {
443
+ const cached = toolNameToPath.get(toolName)
444
+ if (cached) {
445
+ return cached
446
+ }
447
+ throw new Error(
448
+ `Tool name '${toolName}' not found. It might not have been registered or was invalid.`,
449
+ )
450
+ }
451
+
452
+ function formatToolName(
453
+ nameToFormat: string,
454
+ method: string,
455
+ pathForMap: string,
456
+ ): string {
457
+ if (!nameToFormat || nameToFormat.trim() === '') {
458
+ throw new Error('Original tool name for formatting cannot be empty')
459
+ }
460
+
461
+ // Replace spaces and other invalid characters with underscores
462
+ let formatted = nameToFormat
463
+ .replace(/[^a-zA-Z0-9_-]/g, '_')
464
+ .replace(/_+/g, '_') // Replace multiple underscores with single underscore
465
+ .replace(/^_+|_+$/g, '') // Remove leading/trailing underscores
466
+
467
+ // Truncate to 64 characters if necessary
468
+ if (formatted.length > 64) {
469
+ formatted = formatted.substring(0, 64)
470
+ }
471
+
472
+ // Remove trailing underscores again in case truncation created them or they persisted
473
+ formatted = formatted.replace(/_+$/, '')
474
+
475
+ if (formatted === '') {
476
+ throw new Error(
477
+ `Tool name results in empty string after formatting (from original: '${nameToFormat}')`,
478
+ )
479
+ }
480
+
481
+ // Validate against regex
482
+ const regex = /^[a-zA-Z0-9_-]{1,64}$/
483
+ if (!regex.test(formatted)) {
484
+ throw new Error(
485
+ `Formatted tool name "${formatted}" (from original: '${nameToFormat}') does not match required pattern: ^[a-zA-Z0-9_-]{1,64}$`,
486
+ )
487
+ }
488
+
489
+ // Check for duplicates: if this formatted name already exists and belongs to a DIFFERENT tool (method/path), it's a collision.
490
+ const existingEntry = toolNameToPath.get(formatted)
491
+ if (
492
+ existingEntry &&
493
+ (existingEntry.method !== method || existingEntry.path !== pathForMap)
494
+ ) {
495
+ console.error(
496
+ new Error(
497
+ `Duplicate tool name generated: '${formatted}'. ` +
498
+ `This name was generated for original: '${nameToFormat}' (method: '${method}', path: '${pathForMap}'). ` +
499
+ `It conflicts with an existing tool that also maps to '${formatted}', originally from (method: '${existingEntry.method}', path: '${existingEntry.path}'). ` +
500
+ `Ensure operationIds or path/method combinations in your OpenAPI spec are sufficiently unique to avoid naming collisions after formatting.`,
501
+ ),
502
+ )
503
+ }
504
+
505
+ // Register the name with its method and path.
506
+ // If the same tool (method/path) is formatted again to the same name, this just overwrites with identical values.
507
+ toolNameToPath.set(formatted, { method, path: pathForMap })
508
+
509
+ return formatted
510
+ }
@@ -5,7 +5,7 @@ import { openapi } from './openapi.ts'
5
5
  import { Spiceflow } from './spiceflow.ts'
6
6
 
7
7
  test('openapi response', async () => {
8
- const app = new Spiceflow()
8
+ const app = new Spiceflow({ basePath: '/base' })
9
9
  .use(
10
10
  openapi({
11
11
  info: {
@@ -156,73 +156,73 @@ test('openapi response', async () => {
156
156
  ),
157
157
  )
158
158
  const openapiSchema = await app
159
- .handle(new Request('http://localhost/openapi'))
159
+ .handle(new Request('http://localhost/base/openapi'))
160
160
  .then((x) => x.json())
161
161
  expect(openapiSchema).toMatchInlineSnapshot(`
162
162
  {
163
163
  "__superjsonMeta": {
164
164
  "values": {
165
- "paths./addBody.patch.responses.200.content.application/json.schema.items": [
165
+ "paths./base/addBody.patch.responses.200.content.application/json.schema.items": [
166
166
  "undefined",
167
167
  ],
168
- "paths./addBody.patch.responses.200.content.application/json.schema.patternProperties": [
168
+ "paths./base/addBody.patch.responses.200.content.application/json.schema.patternProperties": [
169
169
  "undefined",
170
170
  ],
171
- "paths./addBody.patch.responses.200.content.application/json.schema.required": [
171
+ "paths./base/addBody.patch.responses.200.content.application/json.schema.required": [
172
172
  "undefined",
173
173
  ],
174
- "paths./formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.items": [
174
+ "paths./base/formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.items": [
175
175
  "undefined",
176
176
  ],
177
- "paths./formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.patternProperties": [
177
+ "paths./base/formWithSchemaForm.post.responses.200.content.multipart/form-data.schema.patternProperties": [
178
178
  "undefined",
179
179
  ],
180
- "paths./one/ids/{id}.get.parameters.0.description": [
180
+ "paths./base/one/ids/{id}.get.parameters.0.description": [
181
181
  "undefined",
182
182
  ],
183
- "paths./one/ids/{id}.get.parameters.0.examples": [
183
+ "paths./base/one/ids/{id}.get.parameters.0.examples": [
184
184
  "undefined",
185
185
  ],
186
- "paths./one/ids/{id}.get.responses.404.content.application/json.schema.items": [
186
+ "paths./base/one/ids/{id}.get.responses.404.content.application/json.schema.items": [
187
187
  "undefined",
188
188
  ],
189
- "paths./one/ids/{id}.get.responses.404.content.application/json.schema.patternProperties": [
189
+ "paths./base/one/ids/{id}.get.responses.404.content.application/json.schema.patternProperties": [
190
190
  "undefined",
191
191
  ],
192
- "paths./queryParams.get.parameters.0.description": [
192
+ "paths./base/queryParams.get.parameters.0.description": [
193
193
  "undefined",
194
194
  ],
195
- "paths./queryParams.get.parameters.0.examples": [
195
+ "paths./base/queryParams.get.parameters.0.examples": [
196
196
  "undefined",
197
197
  ],
198
- "paths./queryParams.get.responses.200.content.application/json.schema.items": [
198
+ "paths./base/queryParams.get.responses.200.content.application/json.schema.items": [
199
199
  "undefined",
200
200
  ],
201
- "paths./queryParams.get.responses.200.content.application/json.schema.patternProperties": [
201
+ "paths./base/queryParams.get.responses.200.content.application/json.schema.patternProperties": [
202
202
  "undefined",
203
203
  ],
204
- "paths./queryParams.get.responses.200.content.application/json.schema.required": [
204
+ "paths./base/queryParams.get.responses.200.content.application/json.schema.required": [
205
205
  "undefined",
206
206
  ],
207
- "paths./queryParams.post.responses.200.content.application/json.schema.items": [
207
+ "paths./base/queryParams.post.responses.200.content.application/json.schema.items": [
208
208
  "undefined",
209
209
  ],
210
- "paths./queryParams.post.responses.200.content.application/json.schema.patternProperties": [
210
+ "paths./base/queryParams.post.responses.200.content.application/json.schema.patternProperties": [
211
211
  "undefined",
212
212
  ],
213
- "paths./queryParams.post.responses.200.content.application/json.schema.required": [
213
+ "paths./base/queryParams.post.responses.200.content.application/json.schema.required": [
214
214
  "undefined",
215
215
  ],
216
- "paths./streamWithSchema.get.responses.200.content.application/json.schema.items": [
216
+ "paths./base/streamWithSchema.get.responses.200.content.application/json.schema.items": [
217
217
  "undefined",
218
218
  ],
219
- "paths./streamWithSchema.get.responses.200.content.application/json.schema.patternProperties": [
219
+ "paths./base/streamWithSchema.get.responses.200.content.application/json.schema.patternProperties": [
220
220
  "undefined",
221
221
  ],
222
- "paths./two/ids/{id}.get.parameters.0.description": [
222
+ "paths./base/two/ids/{id}.get.parameters.0.description": [
223
223
  "undefined",
224
224
  ],
225
- "paths./two/ids/{id}.get.parameters.0.examples": [
225
+ "paths./base/two/ids/{id}.get.parameters.0.examples": [
226
226
  "undefined",
227
227
  ],
228
228
  },
@@ -237,7 +237,7 @@ test('openapi response', async () => {
237
237
  },
238
238
  "openapi": "3.1.3",
239
239
  "paths": {
240
- "/addBody": {
240
+ "/base/addBody": {
241
241
  "patch": {
242
242
  "parameters": [],
243
243
  "requestBody": {
@@ -288,7 +288,7 @@ test('openapi response', async () => {
288
288
  },
289
289
  },
290
290
  },
291
- "/formWithSchemaForm": {
291
+ "/base/formWithSchemaForm": {
292
292
  "post": {
293
293
  "description": "This returns form data with schema",
294
294
  "responses": {
@@ -327,7 +327,7 @@ test('openapi response', async () => {
327
327
  },
328
328
  },
329
329
  },
330
- "/one/ids/{id}": {
330
+ "/base/one/ids/{id}": {
331
331
  "get": {
332
332
  "parameters": [
333
333
  {
@@ -383,7 +383,7 @@ test('openapi response', async () => {
383
383
  },
384
384
  },
385
385
  },
386
- "/openapi": {
386
+ "/base/openapi": {
387
387
  "get": {
388
388
  "responses": {
389
389
  "200": {
@@ -405,7 +405,7 @@ test('openapi response', async () => {
405
405
  },
406
406
  },
407
407
  },
408
- "/queryParams": {
408
+ "/base/queryParams": {
409
409
  "get": {
410
410
  "parameters": [
411
411
  {
@@ -501,7 +501,7 @@ test('openapi response', async () => {
501
501
  },
502
502
  },
503
503
  },
504
- "/stream": {
504
+ "/base/stream": {
505
505
  "get": {
506
506
  "description": "This is a stream",
507
507
  "responses": {
@@ -527,7 +527,7 @@ test('openapi response', async () => {
527
527
  },
528
528
  },
529
529
  },
530
- "/streamWithSchema": {
530
+ "/base/streamWithSchema": {
531
531
  "get": {
532
532
  "description": "This is a stream with schema",
533
533
  "responses": {
@@ -565,7 +565,7 @@ test('openapi response', async () => {
565
565
  },
566
566
  },
567
567
  },
568
- "/two/ids/{id}": {
568
+ "/base/two/ids/{id}": {
569
569
  "get": {
570
570
  "parameters": [
571
571
  {