@kubb/adapter-oas 5.0.0-beta.62 → 5.0.0-beta.63
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 +216 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +216 -94
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +1 -1
- package/src/constants.ts +13 -14
- package/src/dedupe.ts +237 -0
- package/src/dialect.ts +2 -20
- package/src/discriminator.ts +1 -1
- package/src/factory.ts +1 -1
- package/src/mime.ts +2 -3
- package/src/operation.ts +2 -2
- package/src/parser.ts +16 -22
- package/src/refs.ts +4 -3
- package/src/resolvers.ts +13 -5
- package/src/stream.ts +16 -59
- package/src/types.ts +1 -1
package/src/stream.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findCircularSchemas } from '@kubb/ast/utils'
|
|
2
2
|
import { ast } from '@kubb/core'
|
|
3
|
-
import {
|
|
3
|
+
import type { Plan } from './dedupe.ts'
|
|
4
|
+
import { oasDialect } from './dialect.ts'
|
|
4
5
|
import { buildDiscriminatorChildMap, patchDiscriminatorNode } from './discriminator.ts'
|
|
5
6
|
import { getOperations } from './operation.ts'
|
|
6
7
|
import type { SchemaParser } from './parser.ts'
|
|
@@ -14,7 +15,7 @@ export type PreScanResult = {
|
|
|
14
15
|
enumNames: Array<string>
|
|
15
16
|
circularNames: Array<string>
|
|
16
17
|
discriminatorChildMap: Map<string, DiscriminatorTarget> | null
|
|
17
|
-
dedupePlan:
|
|
18
|
+
dedupePlan: Plan | null
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
@@ -107,7 +108,7 @@ export function preScan({
|
|
|
107
108
|
const circularNames = [...findCircularSchemas(allNodes)]
|
|
108
109
|
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null
|
|
109
110
|
|
|
110
|
-
let dedupePlan:
|
|
111
|
+
let dedupePlan: Plan | null = null
|
|
111
112
|
if (dedupe) {
|
|
112
113
|
// One extra parse pass over operations so duplicates in request/response bodies are seen.
|
|
113
114
|
// Reuses the already-parsed `allNodes` for schemas, no second schema parse.
|
|
@@ -117,44 +118,19 @@ export function preScan({
|
|
|
117
118
|
if (operationNode) operationNodes.push(operationNode)
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
// Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
121
|
-
// rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
122
|
-
// name (collision-resolved against existing component names). Shapes without a name stay inline.
|
|
123
121
|
const circularSchemas = new Set(circularNames)
|
|
124
122
|
const usedNames = new Set(Object.keys(schemas))
|
|
125
123
|
|
|
126
|
-
dedupePlan =
|
|
127
|
-
isCandidate: (node) => {
|
|
128
|
-
if (node.type === 'enum') return true
|
|
129
|
-
if (node.type !== 'object') return false
|
|
130
|
-
// Skip object shapes that are part of a circular chain, hoisting them would break the cycle.
|
|
131
|
-
if (node.name && circularSchemas.has(node.name)) return false
|
|
132
|
-
return !containsCircularRef(node, { circularSchemas })
|
|
133
|
-
},
|
|
134
|
-
nameFor: (node) => {
|
|
135
|
-
const base = node.name
|
|
136
|
-
if (!base) return null
|
|
124
|
+
dedupePlan = oasDialect.dedupe.plan([...allNodes, ...operationNodes], { circularSchemas, usedNames })
|
|
137
125
|
|
|
138
|
-
|
|
139
|
-
let counter = 2
|
|
140
|
-
while (usedNames.has(name)) {
|
|
141
|
-
name = `${base}${counter++}`
|
|
142
|
-
}
|
|
143
|
-
usedNames.add(name)
|
|
144
|
-
return name
|
|
145
|
-
},
|
|
146
|
-
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`,
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
for (const definition of dedupePlan.hoisted) {
|
|
126
|
+
for (const definition of dedupePlan.extracted) {
|
|
150
127
|
if (definition.type === 'enum' && definition.name) enumNames.push(definition.name)
|
|
151
128
|
}
|
|
152
129
|
}
|
|
153
130
|
|
|
154
131
|
// Enum names that duplicate an earlier schema's content are never emitted, so they are not
|
|
155
132
|
// advertised to plugins either.
|
|
156
|
-
const
|
|
157
|
-
const emittedEnumNames = aliasNames && aliasNames.size > 0 ? enumNames.filter((name) => !aliasNames.has(name)) : enumNames
|
|
133
|
+
const emittedEnumNames = dedupePlan ? enumNames.filter((name) => !dedupePlan.isAlias(name)) : enumNames
|
|
158
134
|
|
|
159
135
|
return { refAliasMap, enumNames: emittedEnumNames, circularNames, discriminatorChildMap, dedupePlan }
|
|
160
136
|
}
|
|
@@ -195,55 +171,36 @@ export function createInputStream({
|
|
|
195
171
|
parserOptions: ast.ParserOptions
|
|
196
172
|
refAliasMap: Map<string, ast.SchemaNode>
|
|
197
173
|
discriminatorChildMap: Map<string, DiscriminatorTarget> | null
|
|
198
|
-
dedupePlan:
|
|
174
|
+
dedupePlan: Plan | null
|
|
199
175
|
meta: ast.InputMeta
|
|
200
176
|
}): ast.InputNode<true> {
|
|
201
|
-
// Rewrites a top-level schema against the dedupe plan: a structurally identical sibling
|
|
202
|
-
// becomes a `ref` alias to the canonical one (keeping its own name). Otherwise nested
|
|
203
|
-
// duplicates are collapsed while the schema's own root is preserved.
|
|
204
|
-
const rewriteTopLevelSchema = (node: ast.SchemaNode): ast.SchemaNode => {
|
|
205
|
-
if (!dedupePlan) return node
|
|
206
|
-
|
|
207
|
-
const canonical = dedupePlan.canonicalBySignature.get(ast.signatureOf(node))
|
|
208
|
-
if (canonical && canonical.name !== node.name) {
|
|
209
|
-
return ast.factory.createSchema({
|
|
210
|
-
type: 'ref',
|
|
211
|
-
name: node.name ?? null,
|
|
212
|
-
ref: canonical.ref,
|
|
213
|
-
description: node.description,
|
|
214
|
-
deprecated: node.deprecated,
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return ast.applyDedupe(node, dedupePlan, true)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
177
|
const schemasIterable: AsyncIterable<ast.SchemaNode> = {
|
|
222
178
|
[Symbol.asyncIterator]() {
|
|
223
179
|
return (async function* () {
|
|
224
|
-
//
|
|
180
|
+
// Extracted shared definitions are emitted first so the schema list owns the shared shapes.
|
|
225
181
|
if (dedupePlan) {
|
|
226
|
-
for (const definition of dedupePlan.
|
|
182
|
+
for (const definition of dedupePlan.extracted) yield definition
|
|
227
183
|
}
|
|
228
184
|
|
|
229
185
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
230
186
|
// A top-level schema whose content duplicates an earlier one is not emitted: every
|
|
231
187
|
// ref to it is repointed at the first schema with that content, so its model would
|
|
232
188
|
// be dead code.
|
|
233
|
-
if (dedupePlan?.
|
|
189
|
+
if (dedupePlan?.isAlias(name)) continue
|
|
234
190
|
|
|
235
191
|
// Inline ref aliases: replace the alias entry with its target's parsed node
|
|
236
192
|
// (keeping the alias name). Skip the first parse entirely for alias entries
|
|
237
193
|
// since that result is never used.
|
|
238
194
|
const alias = refAliasMap.get(name)
|
|
239
195
|
if (alias?.name && schemas[alias.name]) {
|
|
240
|
-
|
|
196
|
+
const aliasNode = { ...parseSchema({ schema: schemas[alias.name]!, name: alias.name }, parserOptions), name }
|
|
197
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(aliasNode) : aliasNode
|
|
241
198
|
continue
|
|
242
199
|
}
|
|
243
200
|
|
|
244
201
|
const parsed = parseSchema({ schema, name }, parserOptions)
|
|
245
202
|
const node = discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)!) : parsed
|
|
246
|
-
yield
|
|
203
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(node) : node
|
|
247
204
|
}
|
|
248
205
|
})()
|
|
249
206
|
},
|
|
@@ -254,7 +211,7 @@ export function createInputStream({
|
|
|
254
211
|
return (async function* () {
|
|
255
212
|
for (const operation of getOperations(document)) {
|
|
256
213
|
const node = parseOperation(parserOptions, operation)
|
|
257
|
-
if (node) yield dedupePlan ?
|
|
214
|
+
if (node) yield dedupePlan ? dedupePlan.apply(node) : node
|
|
258
215
|
}
|
|
259
216
|
})()
|
|
260
217
|
},
|
package/src/types.ts
CHANGED
|
@@ -218,7 +218,7 @@ export type AdapterOasResolvedOptions = {
|
|
|
218
218
|
enumSuffix: AdapterOasOptions['enumSuffix']
|
|
219
219
|
/**
|
|
220
220
|
* Map from original `$ref` paths to their collision-resolved schema names.
|
|
221
|
-
* Populated
|
|
221
|
+
* Populated once the adapter resolves a spec's schemas, on the first `stream()` or `parse()`.
|
|
222
222
|
*
|
|
223
223
|
* @example
|
|
224
224
|
* ```ts
|