@kubb/core 5.0.0-beta.6 → 5.0.0-beta.61

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 (56) hide show
  1. package/LICENSE +17 -10
  2. package/README.md +25 -158
  3. package/dist/diagnostics-DiaUv_iK.d.ts +2904 -0
  4. package/dist/index.cjs +2523 -1071
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.ts +80 -273
  7. package/dist/index.js +2513 -1067
  8. package/dist/index.js.map +1 -1
  9. package/dist/memoryStorage-CUj1hrxa.cjs +823 -0
  10. package/dist/memoryStorage-CUj1hrxa.cjs.map +1 -0
  11. package/dist/memoryStorage-CWFzAz4o.js +714 -0
  12. package/dist/memoryStorage-CWFzAz4o.js.map +1 -0
  13. package/dist/mocks.cjs +83 -23
  14. package/dist/mocks.cjs.map +1 -1
  15. package/dist/mocks.d.ts +36 -10
  16. package/dist/mocks.js +85 -27
  17. package/dist/mocks.js.map +1 -1
  18. package/package.json +8 -28
  19. package/src/FileManager.ts +86 -64
  20. package/src/FileProcessor.ts +170 -44
  21. package/src/KubbDriver.ts +909 -0
  22. package/src/Transform.ts +105 -0
  23. package/src/constants.ts +111 -20
  24. package/src/createAdapter.ts +112 -17
  25. package/src/createKubb.ts +140 -517
  26. package/src/createRenderer.ts +43 -28
  27. package/src/createReporter.ts +134 -0
  28. package/src/createStorage.ts +36 -23
  29. package/src/defineGenerator.ts +140 -17
  30. package/src/defineParser.ts +30 -12
  31. package/src/definePlugin.ts +375 -21
  32. package/src/defineResolver.ts +402 -212
  33. package/src/diagnostics.ts +662 -0
  34. package/src/index.ts +8 -8
  35. package/src/mocks.ts +97 -26
  36. package/src/reporters/cliReporter.ts +89 -0
  37. package/src/reporters/fileReporter.ts +103 -0
  38. package/src/reporters/jsonReporter.ts +20 -0
  39. package/src/reporters/report.ts +85 -0
  40. package/src/storages/fsStorage.ts +23 -55
  41. package/src/types.ts +411 -887
  42. package/dist/PluginDriver-BkTRD2H2.js +0 -946
  43. package/dist/PluginDriver-BkTRD2H2.js.map +0 -1
  44. package/dist/PluginDriver-Cadu4ORh.cjs +0 -1037
  45. package/dist/PluginDriver-Cadu4ORh.cjs.map +0 -1
  46. package/dist/types-DVPKmzw_.d.ts +0 -2159
  47. package/src/Kubb.ts +0 -300
  48. package/src/PluginDriver.ts +0 -426
  49. package/src/defineLogger.ts +0 -19
  50. package/src/defineMiddleware.ts +0 -62
  51. package/src/devtools.ts +0 -59
  52. package/src/renderNode.ts +0 -35
  53. package/src/utils/diagnostics.ts +0 -18
  54. package/src/utils/isInputPath.ts +0 -10
  55. package/src/utils/packageJSON.ts +0 -99
  56. /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
@@ -0,0 +1,105 @@
1
+ import type { Macro, OperationNode, SchemaNode, Visitor } from '@kubb/ast'
2
+ import { composeMacros, transform } from '@kubb/ast'
3
+
4
+ /**
5
+ * Holds an ordered list of macros per plugin, keyed by plugin name. Each plugin's macros run in
6
+ * isolation on the original adapter node and are composed into a single `Visitor` that the
7
+ * `@kubb/ast` `transform` primitive applies. `applyTo` is a per-plugin lookup, not a cross-plugin
8
+ * chain, so plugin A's macros never see plugin B's output. When a plugin has no macros, `applyTo`
9
+ * returns the original node reference, and `transform` does the same when the composed visitor
10
+ * leaves the tree untouched, so callers can detect a no-op by identity.
11
+ *
12
+ * Registration order matches the order setup hooks fire, which the driver has already sorted by
13
+ * `enforce` and dependency edges. The registry preserves that order; macro `enforce` only reorders
14
+ * within a single plugin's list.
15
+ */
16
+ export class Transform {
17
+ readonly #macros = new Map<string, Array<Macro>>()
18
+ // Composed visitor per plugin, rebuilt lazily after the macro list changes.
19
+ readonly #composed = new Map<string, Visitor>()
20
+ // Memoized results per plugin. Repeated `applyTo` calls return the same node identity, so
21
+ // downstream WeakMap caches keyed by node (the resolver's resolveOptions memo) hit when the
22
+ // driver resolves a node a second time, and a stateful macro runs once per node.
23
+ readonly #memo = new Map<string, WeakMap<SchemaNode | OperationNode, SchemaNode | OperationNode>>()
24
+
25
+ /**
26
+ * Number of plugins with at least one registered macro.
27
+ */
28
+ get size(): number {
29
+ return this.#macros.size
30
+ }
31
+
32
+ /**
33
+ * Appends `macro` to the plugin's list, after any macros already registered.
34
+ */
35
+ add(pluginName: string, macro: Macro): void {
36
+ const list = this.#macros.get(pluginName)
37
+ if (list) list.push(macro)
38
+ else this.#macros.set(pluginName, [macro])
39
+ this.#invalidate(pluginName)
40
+ }
41
+
42
+ /**
43
+ * Replaces the plugin's macro list with `macros`.
44
+ */
45
+ set(pluginName: string, macros: ReadonlyArray<Macro>): void {
46
+ this.#macros.set(pluginName, [...macros])
47
+ this.#invalidate(pluginName)
48
+ }
49
+
50
+ /**
51
+ * Looks up the composed visitor for `pluginName`, or `undefined` when the plugin has no macros.
52
+ */
53
+ get(pluginName: string): Visitor | undefined {
54
+ return this.#visitorFor(pluginName)
55
+ }
56
+
57
+ /**
58
+ * Runs the plugin's macros on `node`. Returns the original node reference when the plugin has no
59
+ * macros, so callers can compare by identity to detect a no-op.
60
+ */
61
+ applyTo<TNode extends SchemaNode | OperationNode>(pluginName: string, node: TNode): TNode {
62
+ const visitor = this.#visitorFor(pluginName)
63
+ if (!visitor) return node
64
+
65
+ let memo = this.#memo.get(pluginName)
66
+ if (!memo) {
67
+ memo = new WeakMap()
68
+ this.#memo.set(pluginName, memo)
69
+ }
70
+
71
+ const cached = memo.get(node)
72
+ if (cached) return cached as TNode
73
+
74
+ const result = transform(node, visitor) as TNode
75
+ memo.set(node, result)
76
+ return result
77
+ }
78
+
79
+ /**
80
+ * Clears every registration. Called from the driver's `dispose()` so macros do not leak across
81
+ * builds.
82
+ */
83
+ dispose(): void {
84
+ this.#macros.clear()
85
+ this.#composed.clear()
86
+ this.#memo.clear()
87
+ }
88
+
89
+ #invalidate(pluginName: string): void {
90
+ this.#composed.delete(pluginName)
91
+ this.#memo.delete(pluginName)
92
+ }
93
+
94
+ #visitorFor(pluginName: string): Visitor | undefined {
95
+ const macros = this.#macros.get(pluginName)
96
+ if (!macros || macros.length === 0) return undefined
97
+
98
+ let composed = this.#composed.get(pluginName)
99
+ if (!composed) {
100
+ composed = composeMacros(macros)
101
+ this.#composed.set(pluginName, composed)
102
+ }
103
+ return composed
104
+ }
105
+ }
package/src/constants.ts CHANGED
@@ -1,35 +1,126 @@
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
+ * Maximum number of █ characters in a plugin timing bar.
8
+ */
9
+ export const SUMMARY_MAX_BAR_LENGTH = 10 as const
2
10
 
3
11
  /**
4
- * Base URL for the Kubb Studio web app.
12
+ * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).
5
13
  */
6
- export const DEFAULT_STUDIO_URL = 'https://kubb.studio' as const
14
+ export const SUMMARY_TIME_SCALE_DIVISOR = 100 as const
7
15
 
8
16
  /**
9
- * Maximum number of files processed in parallel by FileProcessor.
17
+ * Number of schema/operation nodes to dispatch concurrently during generation.
10
18
  */
11
- export const PARALLEL_CONCURRENCY_LIMIT = 100
19
+ export const SCHEMA_PARALLEL = 8
12
20
 
13
21
  /**
14
- * Default banner style written at the top of every generated file.
22
+ * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,
23
+ * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's
24
+ * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.
15
25
  */
16
- export const DEFAULT_BANNER = 'simple' as const
26
+ export const HOOK_LISTENERS_PER_PLUGIN = 4
17
27
 
18
28
  /**
19
- * Default file-extension mapping used when no explicit mapping is configured.
29
+ * Plugin `include` filter types that select operations directly. When one of these is set
30
+ * without a `schemaName` include, the generate phase pre-scans operations to compute the set
31
+ * of schemas they reach, so unreachable schemas can be pruned for that plugin.
20
32
  */
21
- export const DEFAULT_EXTENSION: Record<FileNode['extname'], FileNode['extname'] | ''> = { '.ts': '.ts' }
33
+ export const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])
22
34
 
23
35
  /**
24
- * Numeric log-level thresholds used internally to compare verbosity.
25
- *
26
- * Higher numbers are more verbose.
27
- */
28
- export const logLevel = {
29
- silent: Number.NEGATIVE_INFINITY,
30
- error: 0,
31
- warn: 1,
32
- info: 3,
33
- verbose: 4,
34
- debug: 5,
36
+ * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
37
+ * and stays stable so it can be referenced in tooling and (later) docs. Reference
38
+ * these instead of inlining the string at a throw site.
39
+ */
40
+ export const diagnosticCode = {
41
+ /**
42
+ * Fallback for an unstructured error with no specific code.
43
+ */
44
+ unknown: 'KUBB_UNKNOWN',
45
+ /**
46
+ * The `input.path` file or URL could not be read.
47
+ */
48
+ inputNotFound: 'KUBB_INPUT_NOT_FOUND',
49
+ /**
50
+ * An adapter was configured without an `input`.
51
+ */
52
+ inputRequired: 'KUBB_INPUT_REQUIRED',
53
+ /**
54
+ * A `$ref` (or equivalent reference) could not be resolved in the source document.
55
+ */
56
+ refNotFound: 'KUBB_REF_NOT_FOUND',
57
+ /**
58
+ * A server variable value is not allowed by its `enum`.
59
+ */
60
+ invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',
61
+ /**
62
+ * A required plugin is missing from the config.
63
+ */
64
+ pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',
65
+ /**
66
+ * A plugin threw while generating.
67
+ */
68
+ pluginFailed: 'KUBB_PLUGIN_FAILED',
69
+ /**
70
+ * A plugin reported a non-fatal warning through `ctx.warn`.
71
+ */
72
+ pluginWarning: 'KUBB_PLUGIN_WARNING',
73
+ /**
74
+ * A plugin reported an informational message through `ctx.info`.
75
+ */
76
+ pluginInfo: 'KUBB_PLUGIN_INFO',
77
+ /**
78
+ * A schema uses a `format` Kubb does not map to a specific type. Reserved for
79
+ * adapters to emit as a `warning`.
80
+ */
81
+ unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',
82
+ /**
83
+ * A referenced schema or operation is marked `deprecated`. Reserved for adapters
84
+ * to emit as an `info`.
85
+ */
86
+ deprecated: 'KUBB_DEPRECATED',
87
+ /**
88
+ * An adapter is required but the config has none. The build cannot read the input
89
+ * without one.
90
+ */
91
+ adapterRequired: 'KUBB_ADAPTER_REQUIRED',
92
+ /**
93
+ * A resolved output path escapes the output directory, which can stem from a path
94
+ * traversal in the spec or a misconfigured `group.name`.
95
+ */
96
+ pathTraversal: 'KUBB_PATH_TRAVERSAL',
97
+ /**
98
+ * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
99
+ */
100
+ invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',
101
+ /**
102
+ * A post-generate shell hook (`hooks.done`) exited with a failure.
103
+ */
104
+ hookFailed: 'KUBB_HOOK_FAILED',
105
+ /**
106
+ * The formatter pass over the generated files failed.
107
+ */
108
+ formatFailed: 'KUBB_FORMAT_FAILED',
109
+ /**
110
+ * The linter pass over the generated files failed.
111
+ */
112
+ lintFailed: 'KUBB_LINT_FAILED',
113
+ /**
114
+ * Not a failure. Carries a plugin's elapsed time, summed into the run total.
115
+ */
116
+ performance: 'KUBB_PERFORMANCE',
117
+ /**
118
+ * Not a failure. A newer Kubb version is available on npm.
119
+ */
120
+ updateAvailable: 'KUBB_UPDATE_AVAILABLE',
35
121
  } as const
122
+
123
+ /**
124
+ * Union of the stable {@link diagnosticCode} values.
125
+ */
126
+ export type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]
@@ -1,30 +1,125 @@
1
- import type { Adapter, AdapterFactoryOptions } from './types.ts'
1
+ import type { PossiblePromise } from '@internals/utils'
2
+ import type { ImportNode, InputNode, 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. A custom adapter can
39
+ * support GraphQL, gRPC, or another 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 `InputNode<true>` 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<InputNode<true>>
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, for example GraphQL, gRPC, or AsyncAPI. The built-in `@kubb/adapter-oas`
99
+ * handles OpenAPI/Swagger documents.
100
+ *
101
+ * Adapters must return an `InputNode` from `parse`. That node is what every
102
+ * plugin in the build consumes.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * import { createAdapter, ast, type AdapterFactoryOptions } from '@kubb/core'
107
+ *
108
+ * type MyAdapter = AdapterFactoryOptions<'my-adapter', { validate?: boolean }>
25
109
  *
26
- * // Instantiate:
27
- * const adapter = myAdapter({ validate: true })
110
+ * export const myAdapter = createAdapter<MyAdapter>((options) => ({
111
+ * name: 'my-adapter',
112
+ * options,
113
+ * document: null,
114
+ * async parse(_source) {
115
+ * // Convert `source` (path or inline data) into an InputNode.
116
+ * return ast.factory.createInput()
117
+ * },
118
+ * getImports: () => [],
119
+ * async validate() {
120
+ * // Throw or call ctx.error here when the spec is invalid.
121
+ * },
122
+ * }))
28
123
  * ```
29
124
  */
30
125
  export function createAdapter<T extends AdapterFactoryOptions = AdapterFactoryOptions>(build: AdapterBuilder<T>): (options?: T['options']) => Adapter<T> {