@kubb/adapter-oas 5.0.0-beta.11 → 5.0.0-beta.13
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/dist/index.cjs +26 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +26 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/adapter.bench.ts +35 -0
- package/src/adapter.ts +1 -6
- package/src/parser.ts +27 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kubb/adapter-oas",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.13",
|
|
4
4
|
"description": "OpenAPI and Swagger adapter for Kubb. Parses and validates OAS 2.0/3.x specifications into a @kubb/ast RootNode for downstream code generation plugins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"oas": "^32.1.18",
|
|
48
48
|
"oas-normalize": "^16.0.4",
|
|
49
49
|
"swagger2openapi": "^7.0.8",
|
|
50
|
-
"@kubb/core": "5.0.0-beta.
|
|
50
|
+
"@kubb/core": "5.0.0-beta.13"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/swagger2openapi": "^7.0.4",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"release": "pnpm publish --no-git-check",
|
|
69
69
|
"release:canary": "bash ../../.github/canary.sh && node ../../scripts/build.js canary && pnpm publish --no-git-check",
|
|
70
70
|
"start": "tsdown --watch",
|
|
71
|
+
"bench": "vitest bench",
|
|
71
72
|
"test": "vitest --passWithNoTests",
|
|
72
73
|
"typecheck": "tsc -p ./tsconfig.json --noEmit --emitDeclarationOnly false"
|
|
73
74
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { bench, describe } from 'vitest'
|
|
4
|
+
import { parseDocument } from './factory.ts'
|
|
5
|
+
import { parseOas } from './parser.ts'
|
|
6
|
+
import type { Document } from './types.ts'
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
9
|
+
const __dirname = path.dirname(__filename)
|
|
10
|
+
|
|
11
|
+
// TODO: replace with stripe.json once the parser handles its circular-reference
|
|
12
|
+
// expansion without exhausting heap (the full Stripe spec triggers exponential
|
|
13
|
+
// sub-tree duplication because resolvingRefs only prevents recursion within a
|
|
14
|
+
// single resolution chain, not repeated resolution of the same schema).
|
|
15
|
+
const petStorePath = path.resolve(__dirname, '../mocks/petStore.yaml')
|
|
16
|
+
|
|
17
|
+
let document: Document | undefined
|
|
18
|
+
|
|
19
|
+
async function getDocument(): Promise<Document> {
|
|
20
|
+
if (!document) {
|
|
21
|
+
document = await parseDocument(petStorePath)
|
|
22
|
+
}
|
|
23
|
+
return document
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('parseOas() performance', () => {
|
|
27
|
+
bench(
|
|
28
|
+
'petStore spec',
|
|
29
|
+
async () => {
|
|
30
|
+
const doc = await getDocument()
|
|
31
|
+
parseOas(doc)
|
|
32
|
+
},
|
|
33
|
+
{ iterations: 5, warmupIterations: 1 },
|
|
34
|
+
)
|
|
35
|
+
})
|
package/src/adapter.ts
CHANGED
|
@@ -44,10 +44,8 @@ export const adapterOas = createAdapter<AdapterOas>((options) => {
|
|
|
44
44
|
emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType,
|
|
45
45
|
} = options
|
|
46
46
|
|
|
47
|
-
// Let-binding so parse() can replace it with a simple reassignment (no clear+loop).
|
|
48
47
|
let nameMapping = new Map<string, string>()
|
|
49
48
|
let parsedDocument: Document | null
|
|
50
|
-
let inputNode: ast.InputNode | null
|
|
51
49
|
|
|
52
50
|
return {
|
|
53
51
|
name: adapterOasName,
|
|
@@ -69,9 +67,6 @@ export const adapterOas = createAdapter<AdapterOas>((options) => {
|
|
|
69
67
|
get document() {
|
|
70
68
|
return parsedDocument
|
|
71
69
|
},
|
|
72
|
-
get inputNode() {
|
|
73
|
-
return inputNode
|
|
74
|
-
},
|
|
75
70
|
async validate(input, options) {
|
|
76
71
|
const document = await parseDocument(input)
|
|
77
72
|
await validateDocument(document, options)
|
|
@@ -114,7 +109,7 @@ export const adapterOas = createAdapter<AdapterOas>((options) => {
|
|
|
114
109
|
// Expose the raw document so consumers (e.g. plugin-redoc) can access it.
|
|
115
110
|
parsedDocument = document
|
|
116
111
|
|
|
117
|
-
inputNode = ast.createInput({
|
|
112
|
+
const inputNode = ast.createInput({
|
|
118
113
|
...node,
|
|
119
114
|
meta: {
|
|
120
115
|
title: document.info?.title,
|
package/src/parser.ts
CHANGED
|
@@ -89,6 +89,20 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
89
89
|
*/
|
|
90
90
|
const resolvingRefs = new Set<string>()
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Cache of already-resolved `$ref` schemas within this parser instance.
|
|
94
|
+
*
|
|
95
|
+
* Without this, the same referenced schema (e.g. `customer`) is fully re-expanded
|
|
96
|
+
* every time it appears as a `$ref` in a different parent schema. In heavily
|
|
97
|
+
* cross-referenced specs like Stripe (~1 400 schemas), this causes exponential
|
|
98
|
+
* blowup — `customer` alone may be referenced from dozens of top-level schemas,
|
|
99
|
+
* each triggering a fresh recursive expansion of its entire sub-tree.
|
|
100
|
+
*
|
|
101
|
+
* Memoising by `$ref` path reduces the overall work from O(2^depth) to O(N)
|
|
102
|
+
* where N is the number of unique schema names.
|
|
103
|
+
*/
|
|
104
|
+
const resolvedRefCache = new Map<string, ast.SchemaNode | undefined>()
|
|
105
|
+
|
|
92
106
|
/**
|
|
93
107
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
94
108
|
*
|
|
@@ -101,15 +115,20 @@ function createSchemaParser(ctx: OasParserContext) {
|
|
|
101
115
|
let resolvedSchema: ast.SchemaNode | undefined
|
|
102
116
|
const refPath = schema.$ref
|
|
103
117
|
if (refPath && !resolvingRefs.has(refPath)) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
118
|
+
if (resolvedRefCache.has(refPath)) {
|
|
119
|
+
resolvedSchema = resolvedRefCache.get(refPath)
|
|
120
|
+
} else {
|
|
121
|
+
try {
|
|
122
|
+
const referenced = resolveRef<SchemaObject>(document, refPath)
|
|
123
|
+
if (referenced) {
|
|
124
|
+
resolvingRefs.add(refPath)
|
|
125
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions)
|
|
126
|
+
resolvingRefs.delete(refPath)
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
// Ref cannot be resolved in this document (e.g. unit tests with minimal documents).
|
|
110
130
|
}
|
|
111
|
-
|
|
112
|
-
// Ref cannot be resolved in this document (e.g. unit tests with minimal documents).
|
|
131
|
+
resolvedRefCache.set(refPath, resolvedSchema)
|
|
113
132
|
}
|
|
114
133
|
}
|
|
115
134
|
|