@kubb/adapter-oas 5.0.0-beta.63 → 5.0.0-beta.65

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/adapter-oas",
3
- "version": "5.0.0-beta.63",
3
+ "version": "5.0.0-beta.65",
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",
@@ -19,7 +19,6 @@
19
19
  "directory": "packages/adapter-oas"
20
20
  },
21
21
  "files": [
22
- "src",
23
22
  "dist",
24
23
  "!/**/**.test.**",
25
24
  "!/**/__tests__/**",
@@ -43,15 +42,14 @@
43
42
  },
44
43
  "dependencies": {
45
44
  "@readme/openapi-parser": "^6.1.3",
45
+ "@scalar/openapi-upgrader": "^0.2.9",
46
46
  "api-ref-bundler": "0.5.1",
47
- "swagger2openapi": "^7.0.8",
48
47
  "yaml": "^2.9.0",
49
- "@kubb/ast": "5.0.0-beta.63",
50
- "@kubb/core": "5.0.0-beta.63"
48
+ "@kubb/ast": "5.0.0-beta.65",
49
+ "@kubb/core": "5.0.0-beta.65"
51
50
  },
52
51
  "devDependencies": {
53
52
  "@types/json-schema": "^7.0.15",
54
- "@types/swagger2openapi": "^7.0.4",
55
53
  "openapi-types": "^12.1.3",
56
54
  "@internals/utils": "0.0.0"
57
55
  },
@@ -1,60 +0,0 @@
1
- import path from 'node:path'
2
- import { existsSync } from 'node:fs'
3
- import { bench, describe } from 'vitest'
4
- import { adapterOas } from './adapter.ts'
5
- import { parseDocument } from './factory.ts'
6
- import { parseOas } from './parser.ts'
7
- import type { Document } from './types.ts'
8
- import type { AdapterSource } from '@kubb/core'
9
-
10
- const petStorePath = path.resolve(import.meta.dirname, '../mocks/petStore.yaml')
11
- const stripeSpecPath = '/tmp/kubb-stripe-spec3.json'
12
- const hasStripe = existsSync(stripeSpecPath)
13
-
14
- let petStoreDoc: Document | undefined
15
-
16
- async function getPetStoreDocument(): Promise<Document> {
17
- if (!petStoreDoc) {
18
- petStoreDoc = await parseDocument(petStorePath)
19
- }
20
- return petStoreDoc
21
- }
22
-
23
- describe('parseOas() performance', () => {
24
- bench(
25
- 'petStore spec',
26
- async () => {
27
- const doc = await getPetStoreDocument()
28
- parseOas(doc)
29
- },
30
- { iterations: 5, warmupIterations: 1 },
31
- )
32
- })
33
-
34
- describe.skipIf(!hasStripe)('Stripe spec — batch vs streaming (1,385 schemas)', () => {
35
- const stripeSource: AdapterSource = { type: 'path', path: stripeSpecPath }
36
-
37
- bench(
38
- 'batch — adapter.parse()',
39
- async () => {
40
- const adapter = adapterOas({ validate: false })
41
- await adapter.parse(stripeSource)
42
- },
43
- { iterations: 3, warmupIterations: 1 },
44
- )
45
-
46
- bench(
47
- 'streaming — adapter.stream() drain',
48
- async () => {
49
- const adapter = adapterOas({ validate: false })
50
- const stream = await adapter.stream!(stripeSource)
51
- for await (const _ of stream.schemas) {
52
- /* drain */
53
- }
54
- for await (const _ of stream.operations) {
55
- /* drain */
56
- }
57
- },
58
- { iterations: 3, warmupIterations: 1 },
59
- )
60
- })
package/src/adapter.ts DELETED
@@ -1,207 +0,0 @@
1
- import { ast, createAdapter } from '@kubb/core'
2
- import type { AdapterSource } from '@kubb/core'
3
- import { DEFAULT_PARSER_OPTIONS } from './constants.ts'
4
- import { assertInputExists, parseDocument, parseFromConfig, validateDocument } from './factory.ts'
5
- import { createSchemaParser } from './parser.ts'
6
- import { getSchemas } from './resolvers.ts'
7
- import { createInputStream, preScan, resolveBaseUrl } from './stream.ts'
8
- import type { AdapterOas, Document } from './types.ts'
9
- import { collect, narrowSchema } from '@kubb/ast'
10
- import { extractRefName } from '@kubb/ast/utils'
11
-
12
- /**
13
- * The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
14
- */
15
- export const adapterOasName = 'oas' satisfies AdapterOas['name']
16
-
17
- /**
18
- * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
19
- * file at `input.path`, validates it, resolves the base URL, and converts every
20
- * schema and operation into the universal AST that every downstream plugin
21
- * consumes.
22
- *
23
- * Configure once on `defineConfig`. The adapter's choices (date representation,
24
- * integer width, server URL) apply to every plugin in the build.
25
- *
26
- * @example
27
- * ```ts
28
- * import { defineConfig } from 'kubb'
29
- * import { adapterOas } from '@kubb/adapter-oas'
30
- * import { pluginTs } from '@kubb/plugin-ts'
31
- *
32
- * export default defineConfig({
33
- * input: { path: './petStore.yaml' },
34
- * output: { path: './src/gen' },
35
- * adapter: adapterOas({
36
- * serverIndex: 0,
37
- * discriminator: 'inherit',
38
- * dateType: 'date',
39
- * }),
40
- * plugins: [pluginTs()],
41
- * })
42
- * ```
43
- */
44
- export const adapterOas = createAdapter<AdapterOas>((options) => {
45
- const {
46
- validate = true,
47
- contentType,
48
- serverIndex,
49
- serverVariables,
50
- discriminator = 'strict',
51
- dedupe = true,
52
- dateType = DEFAULT_PARSER_OPTIONS.dateType,
53
- integerType = DEFAULT_PARSER_OPTIONS.integerType,
54
- unknownType = DEFAULT_PARSER_OPTIONS.unknownType,
55
- enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix,
56
- emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType,
57
- } = options
58
-
59
- const parserOptions: ast.ParserOptions = {
60
- ...DEFAULT_PARSER_OPTIONS,
61
- dateType,
62
- integerType,
63
- unknownType,
64
- emptySchemaType,
65
- enumSuffix,
66
- }
67
-
68
- let nameMapping = new Map<string, string>()
69
- let parsedDocument: Document | null = null
70
-
71
- // Cache per source and per document so one adapter instance reused across a `defineConfig` array
72
- // parses each config's spec instead of replaying the first one. Keying the document by its source
73
- // object still collapses a config's concurrent `stream()` (build) and `parse()` (studio) calls,
74
- // which share one source object, onto a single parse. The document-derived caches key off the
75
- // resulting document, so distinct configs (distinct documents) stay isolated.
76
- const documentCache = new WeakMap<AdapterSource, Promise<Document>>()
77
- const schemasCache = new WeakMap<Document, Promise<ReturnType<typeof getSchemas>['schemas']>>()
78
- const schemaParserCache = new WeakMap<Document, ReturnType<typeof createSchemaParser>>()
79
- const preScanCache = new WeakMap<Document, ReturnType<typeof preScan>>()
80
-
81
- function ensureDocument(source: AdapterSource): Promise<Document> {
82
- const cached = documentCache.get(source)
83
- if (cached) return cached
84
-
85
- const promise = (async () => {
86
- const fresh = await parseFromConfig(source)
87
- if (validate) await validateDocument(fresh)
88
- parsedDocument = fresh
89
- return fresh
90
- })()
91
- documentCache.set(source, promise)
92
- return promise
93
- }
94
-
95
- function ensureSchemas(document: Document): Promise<ReturnType<typeof getSchemas>['schemas']> {
96
- const cached = schemasCache.get(document)
97
- if (cached) return cached
98
-
99
- const promise = Promise.resolve().then(() => {
100
- const result = getSchemas(document, { contentType })
101
- nameMapping = result.nameMapping
102
- return result.schemas
103
- })
104
- schemasCache.set(document, promise)
105
- return promise
106
- }
107
-
108
- function ensureSchemaParser(document: Document): ReturnType<typeof createSchemaParser> {
109
- const cached = schemaParserCache.get(document)
110
- if (cached) return cached
111
-
112
- const parser = createSchemaParser({ document, contentType })
113
- schemaParserCache.set(document, parser)
114
- return parser
115
- }
116
-
117
- function ensurePreScan(
118
- document: Document,
119
- schemas: ReturnType<typeof getSchemas>['schemas'],
120
- parseSchema: ReturnType<typeof ensureSchemaParser>['parseSchema'],
121
- parseOperation: ReturnType<typeof ensureSchemaParser>['parseOperation'],
122
- ): ReturnType<typeof preScan> {
123
- const cached = preScanCache.get(document)
124
- if (cached) return cached
125
-
126
- const result = preScan({ schemas, parseSchema, parseOperation, document, parserOptions, discriminator, dedupe })
127
- preScanCache.set(document, result)
128
- return result
129
- }
130
-
131
- async function createStream(source: AdapterSource): Promise<ast.InputNode<true>> {
132
- const document = await ensureDocument(source)
133
- const schemas = await ensureSchemas(document)
134
- const { parseSchema, parseOperation } = ensureSchemaParser(document)
135
- const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation)
136
-
137
- return createInputStream({
138
- schemas,
139
- parseSchema,
140
- parseOperation,
141
- document,
142
- parserOptions,
143
- refAliasMap,
144
- discriminatorChildMap,
145
- dedupePlan,
146
- meta: {
147
- title: document.info?.title,
148
- description: document.info?.description,
149
- version: document.info?.version,
150
- baseURL: resolveBaseUrl({ document, serverIndex, serverVariables }),
151
- circularNames,
152
- enumNames,
153
- },
154
- })
155
- }
156
-
157
- return {
158
- name: adapterOasName,
159
- get options() {
160
- return {
161
- validate,
162
- contentType,
163
- serverIndex,
164
- serverVariables,
165
- discriminator,
166
- dedupe,
167
- dateType,
168
- integerType,
169
- unknownType,
170
- emptySchemaType,
171
- enumSuffix,
172
- nameMapping,
173
- }
174
- },
175
- get document() {
176
- return parsedDocument
177
- },
178
- async validate(input, options) {
179
- await assertInputExists(input)
180
- const document = await parseDocument(input)
181
- await validateDocument(document, options)
182
- },
183
- getImports(node, resolve) {
184
- return collect(node, {
185
- schema(schemaNode) {
186
- const schemaRef = narrowSchema(schemaNode, 'ref')
187
- if (!schemaRef?.ref) return null
188
-
189
- const rawName = extractRefName(schemaRef.ref)
190
- const schemaName = nameMapping.get(rawName) ?? rawName
191
- const result = resolve(schemaName)
192
- if (!result) return null
193
-
194
- return ast.factory.createImport({ name: [result.name], path: result.path })
195
- },
196
- })
197
- },
198
- async parse(source) {
199
- const streamNode = await createStream(source)
200
-
201
- const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)])
202
-
203
- return ast.factory.createInput({ schemas, operations, meta: streamNode.meta })
204
- },
205
- stream: createStream,
206
- }
207
- })
package/src/bundler.ts DELETED
@@ -1,71 +0,0 @@
1
- import { read } from '@internals/utils'
2
- import { bundle } from 'api-ref-bundler'
3
- import { parse } from 'yaml'
4
- import type { Document } from './types.ts'
5
-
6
- const urlRegExp = /^https?:\/+/i
7
-
8
- async function readSource(sourcePath: string): Promise<string> {
9
- if (urlRegExp.test(sourcePath)) {
10
- // api-ref-bundler joins relative refs with posix normalization, collapsing `https://` to
11
- // `https:/`. The WHATWG URL parser restores the double slash.
12
- const url = new URL(sourcePath)
13
- const response = await fetch(url)
14
-
15
- if (!response.ok) {
16
- throw new Error(`Cannot fetch the OAS document at ${url.href} (HTTP ${response.status})`)
17
- }
18
-
19
- return response.text()
20
- }
21
-
22
- return read(sourcePath)
23
- }
24
-
25
- async function resolveSource(sourcePath: string): Promise<object | string> {
26
- const data = await readSource(sourcePath)
27
-
28
- if (sourcePath.toLowerCase().endsWith('.md')) {
29
- return data
30
- }
31
-
32
- return parse(data) as object
33
- }
34
-
35
- /**
36
- * Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
37
- *
38
- * External file schemas are hoisted into named `components.schemas` entries, so a property
39
- * pointing at `./schemas/User.yaml` ends up referencing `#/components/schemas/User`. Generators
40
- * can then emit a named type with an import instead of inlining the shape. Sources are read with
41
- * the Bun-aware `read` util for local YAML and JSON files, and with `fetch` for HTTP(S) URLs.
42
- *
43
- * @example Local file
44
- * `const document = await bundleDocument('./openapi.yaml')`
45
- *
46
- * @example Remote URL
47
- * `const document = await bundleDocument('https://example.com/openapi.yaml')`
48
- */
49
- export async function bundleDocument(pathOrUrl: string): Promise<Document> {
50
- const cache = new Map<string, Promise<object | string>>()
51
-
52
- const resolver = (sourcePath: string) => {
53
- // api-ref-bundler refers to the same URL as both `https://` and the posix-normalized
54
- // `https:/`, so cache on the canonical href to fetch each source once.
55
- const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath
56
- const cached = cache.get(key)
57
- if (cached) {
58
- return cached
59
- }
60
-
61
- const result = resolveSource(sourcePath)
62
- cache.set(key, result)
63
- return result
64
- }
65
-
66
- // api-ref-bundler swallows resolver errors and leaves refs unresolved, so surface an
67
- // unreadable input document as a hard error before bundling.
68
- await resolver(pathOrUrl)
69
-
70
- return (await bundle(pathOrUrl, resolver)) as Document
71
- }
package/src/constants.ts DELETED
@@ -1,135 +0,0 @@
1
- import { ast } from '@kubb/core'
2
-
3
- /**
4
- * Default parser options applied when no explicit options are provided.
5
- *
6
- * @example
7
- * ```ts
8
- * import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
9
- *
10
- * const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
11
- * ```
12
- */
13
- export const DEFAULT_PARSER_OPTIONS = {
14
- dateType: 'string',
15
- integerType: 'bigint',
16
- unknownType: 'any',
17
- emptySchemaType: 'any',
18
- enumSuffix: 'enum',
19
- } as const satisfies ast.ParserOptions
20
-
21
- /**
22
- * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
23
- *
24
- * Used when building or parsing `$ref` strings.
25
- *
26
- * @example
27
- * ```ts
28
- * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
29
- * ```
30
- */
31
- export const SCHEMA_REF_PREFIX = '#/components/schemas/' as const
32
-
33
- /**
34
- * HTTP methods that count as operations on an OpenAPI path item. Other keys
35
- * (`parameters`, `summary`, `$ref`, vendor extensions) are skipped when iterating operations.
36
- */
37
- export const SUPPORTED_METHODS: ReadonlySet<string> = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'])
38
-
39
- /**
40
- * OpenAPI version string written into the stub document created during multi-spec merges.
41
- */
42
- export const MERGE_OPENAPI_VERSION = '3.0.0' as const
43
-
44
- /**
45
- * Fallback `info.title` placed in the stub document when merging multiple API files.
46
- */
47
- export const MERGE_DEFAULT_TITLE = 'Merged API' as const
48
-
49
- /**
50
- * Fallback `info.version` placed in the stub document when merging multiple API files.
51
- */
52
- export const MERGE_DEFAULT_VERSION = '1.0.0' as const
53
-
54
- /**
55
- * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
56
- *
57
- * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
58
- * intersection member rather than being merged into the parent.
59
- *
60
- * @example
61
- * ```ts
62
- * import { structuralKeys } from '@kubb/adapter-oas'
63
- *
64
- * const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
65
- * // true when fragment has e.g. 'properties' or 'oneOf'
66
- * ```
67
- */
68
- export const structuralKeys = new Set(['properties', 'items', 'additionalProperties', 'oneOf', 'anyOf', 'allOf', 'not'] as const)
69
-
70
- /**
71
- * Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
72
- * `int64` and the date/time family. Keep this in sync with the `convertFormat`
73
- * special-cases in `parser.ts`. `isHandledFormat` reads it so the
74
- * `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
75
- */
76
- export const specialCasedFormats: ReadonlySet<string> = new Set(['int64', 'date-time', 'date', 'time'])
77
-
78
- /**
79
- * Static map from OAS `format` strings to Kubb `SchemaType` values.
80
- *
81
- * Only formats whose AST type differs from the OAS `type` field appear here.
82
- * Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
83
- * separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
84
- * and `idn-hostname` map to `'url'` as the closest generic string-format type.
85
- *
86
- * @example
87
- * ```ts
88
- * import { formatMap } from '@kubb/adapter-oas'
89
- *
90
- * formatMap['uuid'] // 'uuid'
91
- * formatMap['binary'] // 'blob'
92
- * formatMap['float'] // 'number'
93
- * ```
94
- */
95
- export const formatMap = {
96
- uuid: 'uuid',
97
- email: 'email',
98
- 'idn-email': 'email',
99
- uri: 'url',
100
- 'uri-reference': 'url',
101
- url: 'url',
102
- ipv4: 'ipv4',
103
- ipv6: 'ipv6',
104
- hostname: 'url',
105
- 'idn-hostname': 'url',
106
- binary: 'blob',
107
- byte: 'blob',
108
- // Numeric formats override the OAS `type` because format is more specific.
109
- // See https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7
110
- int32: 'integer',
111
- float: 'number',
112
- double: 'number',
113
- } as const satisfies Record<string, ast.SchemaType>
114
-
115
- /**
116
- * Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
117
- *
118
- * @example
119
- * ```ts
120
- * import { enumExtensionKeys } from '@kubb/adapter-oas'
121
- *
122
- * const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
123
- * ```
124
- */
125
- export const enumExtensionKeys = ['x-enumNames', 'x-enum-varnames'] as const
126
-
127
- /**
128
- * Maps the `unknownType`/`emptySchemaType` option string to its `ScalarSchemaType` constant.
129
- * A `Map` (over a plain object) lets callers test key membership with `.has()`.
130
- */
131
- export const typeOptionMap = new Map<'any' | 'unknown' | 'void', ast.ScalarSchemaType>([
132
- ['any', ast.schemaTypes.any],
133
- ['unknown', ast.schemaTypes.unknown],
134
- ['void', ast.schemaTypes.void],
135
- ])