@kubb/core 5.0.0-beta.4 → 5.0.0-beta.41

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 (57) hide show
  1. package/README.md +25 -158
  2. package/dist/diagnostics-Ba-FcsPo.d.ts +2970 -0
  3. package/dist/index.cjs +760 -1193
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts +165 -171
  6. package/dist/index.js +745 -1188
  7. package/dist/index.js.map +1 -1
  8. package/dist/memoryStorage-DA1bnMte.js +2860 -0
  9. package/dist/memoryStorage-DA1bnMte.js.map +1 -0
  10. package/dist/memoryStorage-DZqlEW7H.cjs +2993 -0
  11. package/dist/memoryStorage-DZqlEW7H.cjs.map +1 -0
  12. package/dist/mocks.cjs +80 -18
  13. package/dist/mocks.cjs.map +1 -1
  14. package/dist/mocks.d.ts +35 -8
  15. package/dist/mocks.js +81 -21
  16. package/dist/mocks.js.map +1 -1
  17. package/package.json +8 -19
  18. package/src/FileManager.ts +85 -63
  19. package/src/FileProcessor.ts +171 -43
  20. package/src/HookRegistry.ts +45 -0
  21. package/src/KubbDriver.ts +906 -0
  22. package/src/Telemetry.ts +278 -0
  23. package/src/Transform.ts +58 -0
  24. package/src/constants.ts +111 -19
  25. package/src/createAdapter.ts +113 -17
  26. package/src/createKubb.ts +944 -492
  27. package/src/createRenderer.ts +58 -27
  28. package/src/createReporter.ts +147 -0
  29. package/src/createStorage.ts +36 -23
  30. package/src/defineGenerator.ts +129 -17
  31. package/src/defineLogger.ts +58 -5
  32. package/src/defineMiddleware.ts +19 -17
  33. package/src/defineParser.ts +30 -13
  34. package/src/definePlugin.ts +320 -17
  35. package/src/defineResolver.ts +381 -179
  36. package/src/diagnostics.ts +660 -0
  37. package/src/index.ts +8 -6
  38. package/src/mocks.ts +92 -19
  39. package/src/reporters/cliReporter.ts +90 -0
  40. package/src/reporters/fileReporter.ts +103 -0
  41. package/src/reporters/jsonReporter.ts +20 -0
  42. package/src/reporters/report.ts +85 -0
  43. package/src/storages/fsStorage.ts +13 -37
  44. package/src/types.ts +60 -1297
  45. package/dist/PluginDriver-Ds-Us-e4.cjs +0 -1036
  46. package/dist/PluginDriver-Ds-Us-e4.cjs.map +0 -1
  47. package/dist/PluginDriver-Wi34Pegx.js +0 -945
  48. package/dist/PluginDriver-Wi34Pegx.js.map +0 -1
  49. package/dist/types-Cd0jhNmx.d.ts +0 -2153
  50. package/src/Kubb.ts +0 -300
  51. package/src/PluginDriver.ts +0 -424
  52. package/src/devtools.ts +0 -59
  53. package/src/renderNode.ts +0 -35
  54. package/src/utils/diagnostics.ts +0 -18
  55. package/src/utils/isInputPath.ts +0 -10
  56. package/src/utils/packageJSON.ts +0 -99
  57. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -0,0 +1,278 @@
1
+ import { randomBytes } from 'node:crypto'
2
+ import os from 'node:os'
3
+ import process from 'node:process'
4
+ import { executeIfOnline, isCIEnvironment } from '@internals/utils'
5
+ import { OTLP_ENDPOINT } from './constants.ts'
6
+
7
+ // OpenTelemetry OTLP JSON types
8
+ // https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
9
+ // https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto
10
+
11
+ type OtlpStringValue = { stringValue: string }
12
+ type OtlpBoolValue = { boolValue: boolean }
13
+ type OtlpIntValue = { intValue: number }
14
+ type OtlpDoubleValue = { doubleValue: number }
15
+ type OtlpBytesValue = { bytesValue: string }
16
+ type OtlpArrayValue = { arrayValue: { values: Array<OtlpAnyValue> } }
17
+ type OtlpKvListValue = { kvlistValue: { values: Array<OtlpKeyValue> } }
18
+
19
+ type OtlpAnyValue = OtlpStringValue | OtlpBoolValue | OtlpIntValue | OtlpDoubleValue | OtlpBytesValue | OtlpArrayValue | OtlpKvListValue
20
+
21
+ type OtlpKeyValue = {
22
+ key: string
23
+ value: OtlpAnyValue
24
+ }
25
+
26
+ type OtlpResource = {
27
+ attributes: Array<OtlpKeyValue>
28
+ droppedAttributesCount?: number
29
+ }
30
+
31
+ type OtlpInstrumentationScope = {
32
+ name: string
33
+ version?: string
34
+ attributes?: Array<OtlpKeyValue>
35
+ droppedAttributesCount?: number
36
+ }
37
+
38
+ /** https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto#L103 */
39
+ type OtlpSpanKind = 0 | 1 | 2 | 3 | 4 | 5
40
+
41
+ /** 0 = STATUS_CODE_UNSET, 1 = STATUS_CODE_OK, 2 = STATUS_CODE_ERROR */
42
+ type OtlpStatusCode = 0 | 1 | 2
43
+
44
+ type OtlpStatus = {
45
+ code: OtlpStatusCode
46
+ message?: string
47
+ }
48
+
49
+ type OtlpSpan = {
50
+ traceId: string
51
+ spanId: string
52
+ traceState?: string
53
+ parentSpanId?: string
54
+ name: string
55
+ kind: OtlpSpanKind
56
+ startTimeUnixNano: string
57
+ endTimeUnixNano: string
58
+ attributes?: Array<OtlpKeyValue>
59
+ droppedAttributesCount?: number
60
+ events?: Array<OtlpSpanEvent>
61
+ droppedEventsCount?: number
62
+ links?: Array<OtlpSpanLink>
63
+ droppedLinksCount?: number
64
+ status?: OtlpStatus
65
+ }
66
+
67
+ type OtlpSpanEvent = {
68
+ timeUnixNano: string
69
+ name: string
70
+ attributes?: Array<OtlpKeyValue>
71
+ droppedAttributesCount?: number
72
+ }
73
+
74
+ type OtlpSpanLink = {
75
+ traceId: string
76
+ spanId: string
77
+ traceState?: string
78
+ attributes?: Array<OtlpKeyValue>
79
+ droppedAttributesCount?: number
80
+ }
81
+
82
+ type OtlpScopeSpans = {
83
+ scope: OtlpInstrumentationScope
84
+ spans: Array<OtlpSpan>
85
+ schemaUrl?: string
86
+ }
87
+
88
+ type OtlpResourceSpans = {
89
+ resource: OtlpResource
90
+ scopeSpans: Array<OtlpScopeSpans>
91
+ schemaUrl?: string
92
+ }
93
+
94
+ /** Root payload sent to POST /v1/traces */
95
+ type OtlpExportTraceServiceRequest = {
96
+ resourceSpans: Array<OtlpResourceSpans>
97
+ }
98
+
99
+ /**
100
+ * Anonymous plugin name and options snapshot sent with each telemetry event.
101
+ */
102
+ export type TelemetryPlugin = {
103
+ /**
104
+ * Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.
105
+ */
106
+ name: string
107
+ /**
108
+ * anonymized plugin options snapshot, values are included but cannot be traced back to the user.
109
+ */
110
+ options: Record<string, unknown>
111
+ }
112
+
113
+ export type TelemetryEvent = {
114
+ command: string
115
+ kubbVersion: string
116
+ nodeVersion: string
117
+ platform: string
118
+ ci: boolean
119
+ plugins: Array<TelemetryPlugin>
120
+ duration: number
121
+ filesCreated: number
122
+ status: 'success' | 'failed'
123
+ }
124
+
125
+ /**
126
+ * Anonymous OTLP usage telemetry for the Kubb run. All methods are static, so call them as
127
+ * `Telemetry.build(...)`, `Telemetry.send(...)`, and `Telemetry.isDisabled()`. No file paths,
128
+ * OpenAPI specs, or secrets are ever included, and sending fails silently to never break a run.
129
+ */
130
+ export class Telemetry {
131
+ /**
132
+ * Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.
133
+ */
134
+ static isDisabled(): boolean {
135
+ return (
136
+ process.env['DO_NOT_TRACK'] === '1' ||
137
+ process.env['DO_NOT_TRACK'] === 'true' ||
138
+ process.env['KUBB_DISABLE_TELEMETRY'] === '1' ||
139
+ process.env['KUBB_DISABLE_TELEMETRY'] === 'true'
140
+ )
141
+ }
142
+
143
+ /**
144
+ * Build an anonymous telemetry payload from a completed generation run.
145
+ */
146
+ static build(options: {
147
+ command: 'generate' | 'mcp' | 'validate' | 'agent'
148
+ kubbVersion: string
149
+ plugins?: Array<TelemetryPlugin>
150
+ hrStart: [number, number]
151
+ filesCreated?: number
152
+ status: 'success' | 'failed'
153
+ }): TelemetryEvent {
154
+ const [seconds, nanoseconds] = process.hrtime(options.hrStart)
155
+ const duration = Math.round(seconds * 1000 + nanoseconds / 1e6)
156
+
157
+ return {
158
+ command: options.command,
159
+ kubbVersion: options.kubbVersion,
160
+ nodeVersion: process.versions.node.split('.')[0] as string,
161
+ platform: os.platform(),
162
+ ci: isCIEnvironment(),
163
+ plugins: options.plugins ?? [],
164
+ duration,
165
+ filesCreated: options.filesCreated ?? 0,
166
+ status: options.status,
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Convert a {@link TelemetryEvent} into an OTLP-compatible JSON trace payload.
172
+ * See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
173
+ */
174
+ static buildOtlpPayload(event: TelemetryEvent): OtlpExportTraceServiceRequest {
175
+ const traceId = randomBytes(16).toString('hex')
176
+ const spanId = randomBytes(8).toString('hex')
177
+ const endTimeNs = BigInt(Date.now()) * 1_000_000n
178
+ const startTimeNs = endTimeNs - BigInt(event.duration) * 1_000_000n
179
+
180
+ const attributes: Array<OtlpKeyValue> = [
181
+ { key: 'kubb.command', value: { stringValue: event.command } },
182
+ { key: 'kubb.version', value: { stringValue: event.kubbVersion } },
183
+ { key: 'kubb.node_version', value: { stringValue: event.nodeVersion } },
184
+ { key: 'kubb.platform', value: { stringValue: event.platform } },
185
+ { key: 'kubb.ci', value: { boolValue: event.ci } },
186
+ { key: 'kubb.files_created', value: { intValue: event.filesCreated } },
187
+ { key: 'kubb.status', value: { stringValue: event.status } },
188
+ {
189
+ key: 'kubb.plugins',
190
+ value: {
191
+ arrayValue: {
192
+ values: event.plugins.map(
193
+ (p): OtlpKvListValue => ({
194
+ kvlistValue: {
195
+ values: [
196
+ { key: 'name', value: { stringValue: p.name } },
197
+ {
198
+ key: 'options',
199
+ value: {
200
+ stringValue: JSON.stringify({
201
+ ...p.options,
202
+ usedEnumNames: undefined,
203
+ }),
204
+ },
205
+ },
206
+ ],
207
+ },
208
+ }),
209
+ ),
210
+ },
211
+ },
212
+ },
213
+ ]
214
+
215
+ return {
216
+ resourceSpans: [
217
+ {
218
+ resource: {
219
+ attributes: [
220
+ { key: 'service.name', value: { stringValue: 'kubb-core' } },
221
+ {
222
+ key: 'service.version',
223
+ value: { stringValue: event.kubbVersion },
224
+ },
225
+ { key: 'telemetry.sdk.language', value: { stringValue: 'nodejs' } },
226
+ ],
227
+ },
228
+ scopeSpans: [
229
+ {
230
+ scope: { name: 'kubb-core', version: event.kubbVersion },
231
+ spans: [
232
+ {
233
+ traceId,
234
+ spanId,
235
+ name: event.command,
236
+ kind: 1 satisfies OtlpSpanKind,
237
+ startTimeUnixNano: String(startTimeNs),
238
+ endTimeUnixNano: String(endTimeNs),
239
+ attributes,
240
+ status: {
241
+ code: (event.status === 'success' ? 1 : 2) satisfies OtlpStatusCode,
242
+ },
243
+ },
244
+ ],
245
+ },
246
+ ],
247
+ },
248
+ ],
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Send an anonymous telemetry event to the Kubb OTLP endpoint. Respects `DO_NOT_TRACK` and
254
+ * `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run.
255
+ */
256
+ static async send(event: TelemetryEvent): Promise<void> {
257
+ if (Telemetry.isDisabled()) {
258
+ return
259
+ }
260
+
261
+ await executeIfOnline(async () => {
262
+ try {
263
+ await fetch(`${OTLP_ENDPOINT}/v1/traces`, {
264
+ method: 'POST',
265
+ headers: {
266
+ 'Content-Type': 'application/json',
267
+ 'Kubb-Telemetry-Version': '1',
268
+ 'Kubb-Telemetry-Source': 'kubb-core',
269
+ },
270
+ body: JSON.stringify(Telemetry.buildOtlpPayload(event)),
271
+ signal: AbortSignal.timeout(5_000),
272
+ })
273
+ } catch (_e) {
274
+ // Fail silently, telemetry must never break the run
275
+ }
276
+ })
277
+ }
278
+ }
@@ -0,0 +1,58 @@
1
+ import type { OperationNode, SchemaNode, Visitor } from '@kubb/ast'
2
+ import { transform } from '@kubb/ast'
3
+
4
+ /**
5
+ * Holds one `Visitor` per plugin, keyed by plugin name. Each plugin's transformer runs in
6
+ * isolation on the original adapter node. `applyTo` is a lookup, not a chain, so plugin A's
7
+ * visitor never sees plugin B's output. When no transformer is registered, `applyTo` returns
8
+ * the original node reference, and the `@kubb/ast` `transform` primitive does the same when
9
+ * its visitor leaves the tree untouched. Callers can compare by identity to detect a no-op.
10
+ *
11
+ * Registration order matches the order setup hooks fire, which the driver has already sorted
12
+ * by `enforce` and dependency edges. The registry does not re-order anything.
13
+ */
14
+ export class Transform {
15
+ readonly #visitors = new Map<string, Visitor>()
16
+
17
+ /**
18
+ * Number of plugins with a registered transformer.
19
+ */
20
+ get size(): number {
21
+ return this.#visitors.size
22
+ }
23
+
24
+ /**
25
+ * Records `visitor` as the transformer for `pluginName`. A second call for the same plugin
26
+ * replaces the first.
27
+ */
28
+ register(pluginName: string, visitor: Visitor): void {
29
+ this.#visitors.set(pluginName, visitor)
30
+ }
31
+
32
+ /**
33
+ * Looks up the transformer for `pluginName`. The generator context uses this so plugins can
34
+ * read their own visitor through `ctx.transformer`.
35
+ */
36
+ get(pluginName: string): Visitor | undefined {
37
+ return this.#visitors.get(pluginName)
38
+ }
39
+
40
+ /**
41
+ * Runs the plugin's transformer on `node`. Returns the original node reference when the
42
+ * plugin has no transformer, so callers can compare by identity to detect a no-op.
43
+ */
44
+ applyTo<TNode extends SchemaNode | OperationNode>(pluginName: string, node: TNode): TNode {
45
+ const visitor = this.#visitors.get(pluginName)
46
+ if (!visitor) return node
47
+
48
+ return transform(node, visitor) as TNode
49
+ }
50
+
51
+ /**
52
+ * Clears every registration. Called from the driver's `dispose()` so visitors do not leak
53
+ * across builds.
54
+ */
55
+ dispose(): void {
56
+ this.#visitors.clear()
57
+ }
58
+ }
package/src/constants.ts CHANGED
@@ -1,35 +1,127 @@
1
- import type { FileNode } from '@kubb/ast'
1
+ /**
2
+ * Number of file writes to batch in parallel during `flushPendingFiles`.
3
+ */
4
+ export const STREAM_FLUSH_EVERY = 50
5
+
6
+ /**
7
+ * OpenTelemetry ingestion endpoint for anonymous usage telemetry.
8
+ */
9
+ export const OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const
2
10
 
3
11
  /**
4
- * Base URL for the Kubb Studio web app.
12
+ * Maximum number of characters in a plugin timing bar.
5
13
  */
6
- export const DEFAULT_STUDIO_URL = 'https://studio.kubb.dev' as const
14
+ export const SUMMARY_MAX_BAR_LENGTH = 10 as const
7
15
 
8
16
  /**
9
- * Maximum number of files processed in parallel by FileProcessor.
17
+ * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).
10
18
  */
11
- export const PARALLEL_CONCURRENCY_LIMIT = 100
19
+ export const SUMMARY_TIME_SCALE_DIVISOR = 100 as const
12
20
 
13
21
  /**
14
- * Default banner style written at the top of every generated file.
22
+ * Number of schema/operation nodes to dispatch concurrently during generation.
15
23
  */
16
- export const DEFAULT_BANNER = 'simple' as const
24
+ export const SCHEMA_PARALLEL = 8
17
25
 
18
26
  /**
19
- * Default file-extension mapping used when no explicit mapping is configured.
27
+ * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,
28
+ * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's
29
+ * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.
20
30
  */
21
- export const DEFAULT_EXTENSION: Record<FileNode['extname'], FileNode['extname'] | ''> = { '.ts': '.ts' }
31
+ export const HOOK_LISTENERS_PER_PLUGIN = 4
22
32
 
23
33
  /**
24
- * Numeric log-level thresholds used internally to compare verbosity.
25
- *
26
- * Higher numbers are more verbose.
34
+ * Plugin `include` filter types that select operations directly. When one of these is set
35
+ * without a `schemaName` include, the generate phase pre-scans operations to compute the set
36
+ * of schemas they reach, so unreachable schemas can be pruned for that plugin.
27
37
  */
28
- export const logLevel = {
29
- silent: Number.NEGATIVE_INFINITY,
30
- error: 0,
31
- warn: 1,
32
- info: 3,
33
- verbose: 4,
34
- debug: 5,
38
+ export const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
39
+
40
+ /**
41
+ * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
42
+ * and stays stable so it can be referenced in tooling and (later) docs. Reference
43
+ * these instead of inlining the string at a throw site.
44
+ */
45
+ export const diagnosticCode = {
46
+ /**
47
+ * Fallback for an unstructured error with no specific code.
48
+ */
49
+ unknown: 'KUBB_UNKNOWN',
50
+ /**
51
+ * The `input.path` file or URL could not be read.
52
+ */
53
+ inputNotFound: 'KUBB_INPUT_NOT_FOUND',
54
+ /**
55
+ * An adapter was configured without an `input`.
56
+ */
57
+ inputRequired: 'KUBB_INPUT_REQUIRED',
58
+ /**
59
+ * A `$ref` (or equivalent reference) could not be resolved in the source document.
60
+ */
61
+ refNotFound: 'KUBB_REF_NOT_FOUND',
62
+ /**
63
+ * A server variable value is not allowed by its `enum`.
64
+ */
65
+ invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',
66
+ /**
67
+ * A required plugin is missing from the config.
68
+ */
69
+ pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',
70
+ /**
71
+ * A plugin threw while generating.
72
+ */
73
+ pluginFailed: 'KUBB_PLUGIN_FAILED',
74
+ /**
75
+ * A plugin reported a non-fatal warning through `ctx.warn`.
76
+ */
77
+ pluginWarning: 'KUBB_PLUGIN_WARNING',
78
+ /**
79
+ * A plugin reported an informational message through `ctx.info`.
80
+ */
81
+ pluginInfo: 'KUBB_PLUGIN_INFO',
82
+ /**
83
+ * A schema uses a `format` Kubb does not map to a specific type. Reserved for
84
+ * adapters to emit as a `warning`.
85
+ */
86
+ unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',
87
+ /**
88
+ * A referenced schema or operation is marked `deprecated`. Reserved for adapters
89
+ * to emit as an `info`.
90
+ */
91
+ deprecated: 'KUBB_DEPRECATED',
92
+ /**
93
+ * An adapter is required but the config has none. The build cannot read the input
94
+ * without one.
95
+ */
96
+ adapterRequired: 'KUBB_ADAPTER_REQUIRED',
97
+ /**
98
+ * A resolved output path escapes the output directory, which can stem from a path
99
+ * traversal in the spec or a misconfigured `group.name`.
100
+ */
101
+ pathTraversal: 'KUBB_PATH_TRAVERSAL',
102
+ /**
103
+ * A post-generate shell hook (`hooks.done`) exited with a failure.
104
+ */
105
+ hookFailed: 'KUBB_HOOK_FAILED',
106
+ /**
107
+ * The formatter pass over the generated files failed.
108
+ */
109
+ formatFailed: 'KUBB_FORMAT_FAILED',
110
+ /**
111
+ * The linter pass over the generated files failed.
112
+ */
113
+ lintFailed: 'KUBB_LINT_FAILED',
114
+ /**
115
+ * Not a failure. Carries a plugin's elapsed time, summed into the run total.
116
+ */
117
+ performance: 'KUBB_PERFORMANCE',
118
+ /**
119
+ * Not a failure. A newer Kubb version is available on npm.
120
+ */
121
+ updateAvailable: 'KUBB_UPDATE_AVAILABLE',
35
122
  } as const
123
+
124
+ /**
125
+ * Union of the stable {@link diagnosticCode} values.
126
+ */
127
+ export type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]
@@ -1,30 +1,126 @@
1
- import type { Adapter, AdapterFactoryOptions } from './types.ts'
1
+ import type { PossiblePromise } from '@internals/utils'
2
+ import type { ImportNode, InputNode, InputStreamNode, SchemaNode } from '@kubb/ast'
2
3
 
3
- type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>
4
+ /**
5
+ * Source data handed to an adapter's `parse` function. Mirrors the config
6
+ * input shape with paths resolved to absolute.
7
+ *
8
+ * - `{ type: 'path' }`: single file on disk.
9
+ * - `{ type: 'paths' }`: multiple files (e.g. split spec).
10
+ * - `{ type: 'data' }`: raw string or parsed object provided inline.
11
+ */
12
+ export type AdapterSource = { type: 'path'; path: string } | { type: 'data'; data: string | unknown } | { type: 'paths'; paths: Array<string> }
4
13
 
5
14
  /**
6
- * Factory for implementing custom adapters that translate non-OpenAPI specs into Kubb's AST.
15
+ * Generic parameters used by `createAdapter` and the resulting `Adapter` type.
7
16
  *
8
- * Use this to support GraphQL schemas, gRPC definitions, AsyncAPI, or custom domain-specific languages.
9
- * Built-in adapters include `@kubb/adapter-oas` for OpenAPI and Swagger documents.
17
+ * - `TName`: unique adapter identifier (`'oas'`, `'asyncapi'`, ...).
18
+ * - `TOptions`: user-facing options accepted by the adapter factory.
19
+ * - `TResolvedOptions`: options after defaults are applied.
20
+ * - `TDocument`: type of the parsed source document.
21
+ */
22
+ export type AdapterFactoryOptions<
23
+ TName extends string = string,
24
+ TOptions extends object = object,
25
+ TResolvedOptions extends object = TOptions,
26
+ TDocument = unknown,
27
+ > = {
28
+ name: TName
29
+ options: TOptions
30
+ resolvedOptions: TResolvedOptions
31
+ document: TDocument
32
+ }
33
+
34
+ /**
35
+ * Converts input files or inline data into Kubb's universal AST `InputNode`.
10
36
  *
11
- * @note Adapters must parse their input format to Kubb's `InputNode` structure.
37
+ * Adapters live between the spec format and the plugins. The built-in
38
+ * `@kubb/adapter-oas` handles OpenAPI 2.0, 3.0, and 3.1; custom adapters can
39
+ * support GraphQL, gRPC, AsyncAPI, or any domain-specific schema language.
12
40
  *
13
41
  * @example
14
42
  * ```ts
15
- * export const myAdapter = createAdapter<MyAdapter>((options) => {
16
- * return {
17
- * name: 'my-adapter',
18
- * options,
19
- * async parse(source) {
20
- * // Transform source format to InputNode
21
- * return { ... }
22
- * },
23
- * }
43
+ * import { defineConfig } from 'kubb'
44
+ * import { adapterOas } from '@kubb/adapter-oas'
45
+ * import { pluginTs } from '@kubb/plugin-ts'
46
+ *
47
+ * export default defineConfig({
48
+ * input: { path: './petStore.yaml' },
49
+ * output: { path: './src/gen' },
50
+ * adapter: adapterOas(),
51
+ * plugins: [pluginTs()],
24
52
  * })
53
+ * ```
54
+ */
55
+ export type Adapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions> = {
56
+ /**
57
+ * Human-readable adapter identifier (e.g. `'oas'`, `'asyncapi'`).
58
+ */
59
+ name: TOptions['name']
60
+ /**
61
+ * Resolved adapter options after defaults have been applied.
62
+ */
63
+ options: TOptions['resolvedOptions']
64
+ /**
65
+ * Parsed source document after the first `parse()` call. `null` before parsing.
66
+ */
67
+ document: TOptions['document'] | null
68
+ /**
69
+ * Parse the source into a universal `InputNode`.
70
+ */
71
+ parse: (source: AdapterSource) => PossiblePromise<InputNode>
72
+ /**
73
+ * Extract `ImportNode` entries for a schema tree.
74
+ * Returns an empty array before the first `parse()` call.
75
+ *
76
+ * The `resolve` callback receives the collision-corrected schema name and must
77
+ * return `{ name, path }` for the import, or `undefined` to skip it.
78
+ */
79
+ getImports: (node: SchemaNode, resolve: (schemaName: string) => { name: string; path: string }) => Array<ImportNode>
80
+ /**
81
+ * Validate the document at the given path or URL.
82
+ */
83
+ validate: (input: string, options?: { throwOnError?: boolean }) => Promise<void>
84
+ /**
85
+ * Memory-efficient streaming variant of `parse()`.
86
+ *
87
+ * Returns an `InputStreamNode` whose `schemas` and `operations` are `AsyncIterable`.
88
+ * Each `for await` loop creates a fresh parse pass over the cached in-memory document.
89
+ * No pre-built arrays are held in memory.
90
+ */
91
+ stream?: (source: AdapterSource) => Promise<InputStreamNode>
92
+ }
93
+
94
+ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) => Adapter<T>
95
+
96
+ /**
97
+ * Defines a custom adapter that translates a spec format into Kubb's universal
98
+ * AST. Use this when you need to consume GraphQL, gRPC, AsyncAPI, or another
99
+ * domain-specific schema. Built-in adapters: `@kubb/adapter-oas` for
100
+ * OpenAPI/Swagger documents.
101
+ *
102
+ * Adapters must return an `InputNode` from `parse`. That node is what every
103
+ * plugin in the build consumes.
104
+ *
105
+ * @example
106
+ * ```ts
107
+ * import { createAdapter, ast, type AdapterFactoryOptions } from '@kubb/core'
108
+ *
109
+ * type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
25
110
  *
26
- * // Instantiate:
27
- * const adapter = myAdapter({ validate: true })
111
+ * export const myAdapter = createAdapter<MyAdapter>((options) => ({
112
+ * name: 'my-adapter',
113
+ * options,
114
+ * document: null,
115
+ * async parse(_source) {
116
+ * // Convert `source` (path or inline data) into an InputNode.
117
+ * return ast.createInput()
118
+ * },
119
+ * getImports: () => [],
120
+ * async validate() {
121
+ * // Throw or call ctx.error here when the spec is invalid.
122
+ * },
123
+ * }))
28
124
  * ```
29
125
  */
30
126
  export function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T> {