@kubb/core 5.0.0-beta.61 → 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/src/KubbDriver.ts CHANGED
@@ -2,7 +2,7 @@ import { resolve } from 'node:path'
2
2
  import { arrayToAsyncIterable, type AsyncEventEmitter, forBatches, getElapsedMs, isPromise, memoize, Url } from '@internals/utils'
3
3
  import * as factory from '@kubb/ast/factory'
4
4
  import { collectUsedSchemaNames } from '@kubb/ast/utils'
5
- import type { FileNode, InputMeta, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
5
+ import type { Enforce, FileNode, InputMeta, InputNode, OperationNode, SchemaNode } from '@kubb/ast'
6
6
  import { OPERATION_FILTER_TYPES, SCHEMA_PARALLEL } from './constants.ts'
7
7
  import { type Diagnostic, Diagnostics, type ProblemDiagnostic } from './diagnostics.ts'
8
8
  import type { RendererFactory } from './createRenderer.ts'
@@ -45,7 +45,7 @@ type RequirePluginContext = {
45
45
  requiredBy?: string
46
46
  }
47
47
 
48
- function enforceOrder(enforce: 'pre' | 'post' | undefined): number {
48
+ function enforceOrder(enforce: Enforce | undefined): number {
49
49
  return enforce === 'pre' ? -1 : enforce === 'post' ? 1 : 0
50
50
  }
51
51
 
@@ -108,6 +108,12 @@ export class KubbDriver {
108
108
  this.#listeners.push([event, handler as HookListener<Array<unknown>, unknown>])
109
109
  }
110
110
 
111
+ /**
112
+ * Normalizes every configured plugin, orders them, and registers their lifecycle handlers.
113
+ * A plugin that another lists as a dependency runs first, then `enforce: 'pre'` before
114
+ * `'post'`. When the config has an adapter, the adapter source is resolved from the input
115
+ * so `run` can parse it later.
116
+ */
111
117
  async setup() {
112
118
  const normalized: Array<NormalizedPlugin> = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin as Plugin))
113
119
 
@@ -139,8 +145,9 @@ export class KubbDriver {
139
145
  }
140
146
 
141
147
  /**
142
- * Creates an `NormalizedPlugin` from a hook-style plugin and registers
143
- * its lifecycle handlers on the `AsyncEventEmitter`.
148
+ * Builds a `NormalizedPlugin` from a hook-style plugin, filling in default
149
+ * options and copying `apply` when present. Registering its lifecycle handlers
150
+ * on the `AsyncEventEmitter` is done separately by `#registerPlugin`.
144
151
  */
145
152
  #normalizePlugin(plugin: Plugin): NormalizedPlugin {
146
153
  const normalized: NormalizedPlugin = {
@@ -250,7 +257,7 @@ export class KubbDriver {
250
257
  * Emits the `kubb:plugin:setup` event so that all registered hook-style plugin listeners
251
258
  * can configure generators, resolvers, macros and renderers before `buildStart` runs.
252
259
  *
253
- * Call this once from `safeBuild` before the plugin execution loop begins.
260
+ * Called once from `run` before the plugin execution loop begins.
254
261
  */
255
262
  async emitSetupHooks(): Promise<void> {
256
263
  const noop = () => {}
@@ -282,37 +289,21 @@ export class KubbDriver {
282
289
  * Call this method inside `addGenerator()` (in `kubb:plugin:setup`) to wire up a generator.
283
290
  */
284
291
  registerGenerator(pluginName: string, generator: Generator): void {
285
- if (generator.schema) {
286
- const schemaHandler = async (node: SchemaNode, ctx: GeneratorContext) => {
287
- if (ctx.plugin.name !== pluginName) return
288
- const result = await generator.schema!(node, ctx)
289
-
290
- await this.dispatch({ result, renderer: generator.renderer })
291
- }
292
-
293
- this.#trackListener('kubb:generate:schema', schemaHandler)
294
- }
292
+ const register = <TNode>(event: keyof KubbHooks & string, method: ((node: TNode, ctx: GeneratorContext) => unknown) | undefined): void => {
293
+ if (!method) return
295
294
 
296
- if (generator.operation) {
297
- const operationHandler = async (node: OperationNode, ctx: GeneratorContext) => {
295
+ const handler = async (node: TNode, ctx: GeneratorContext) => {
298
296
  if (ctx.plugin.name !== pluginName) return
299
-
300
- const result = await generator.operation!(node, ctx)
297
+ const result = await method(node, ctx)
301
298
  await this.dispatch({ result, renderer: generator.renderer })
302
299
  }
303
300
 
304
- this.#trackListener('kubb:generate:operation', operationHandler)
301
+ this.#trackListener(event, handler as HookListener<KubbHooks[typeof event], unknown>)
305
302
  }
306
303
 
307
- if (generator.operations) {
308
- const operationsHandler = async (nodes: Array<OperationNode>, ctx: GeneratorContext) => {
309
- if (ctx.plugin.name !== pluginName) return
310
- const result = await generator.operations!(nodes, ctx)
311
- await this.dispatch({ result, renderer: generator.renderer })
312
- }
313
-
314
- this.#trackListener('kubb:generate:operations', operationsHandler)
315
- }
304
+ register('kubb:generate:schema', generator.schema)
305
+ register('kubb:generate:operation', generator.operation)
306
+ register('kubb:generate:operations', generator.operations)
316
307
 
317
308
  this.#eventGeneratorPlugins.add(pluginName)
318
309
  }
@@ -422,8 +413,8 @@ export class KubbDriver {
422
413
  }
423
414
 
424
415
  // Stream every node through the transform registry and into each plugin's generators.
425
- // Handles the empty-entries and missing-`inputNode` cases by closing out each entry's
426
- // `kubb:plugin:end` directly.
416
+ // When there are no entries it returns early. When `inputNode` is missing it still
417
+ // closes out each entry's `kubb:plugin:end` directly.
427
418
  diagnostics.push(...(await this.#runGenerators(generatorPlugins, () => processor.flush())))
428
419
  // Wait for the last in-flight batch and write anything still pending.
429
420
  await processor.drain()
@@ -480,8 +471,8 @@ export class KubbDriver {
480
471
  * That ordering is what drives the CLI's `Plugins N/M` counter. Without it the bar would
481
472
  * sit at the initial value until the very end of the run.
482
473
  *
483
- * When `entries` is empty or `this.inputNode` is `null`, every entry still gets a
484
- * `kubb:plugin:end` so post-plugin listeners (the barrel writer and friends) complete.
474
+ * When `this.inputNode` is `null`, every entry still gets a `kubb:plugin:end` so
475
+ * post-plugin listeners (the barrel writer and friends) complete.
485
476
  */
486
477
  async #runGenerators(
487
478
  entries: Array<{ plugin: NormalizedPlugin; context: Omit<GeneratorContext, 'options'>; hrStart: ReturnType<typeof process.hrtime> }>,
package/src/Transform.ts CHANGED
@@ -10,7 +10,7 @@ import { composeMacros, transform } from '@kubb/ast'
10
10
  * leaves the tree untouched, so callers can detect a no-op by identity.
11
11
  *
12
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
13
+ * `enforce` and dependency edges. The registry preserves that order. Macro `enforce` only reorders
14
14
  * within a single plugin's list.
15
15
  */
16
16
  export class Transform {
@@ -112,12 +112,12 @@ type AdapterBuilder<T extends AdapterFactoryOptions> = (options: T['options']) =
112
112
  * options,
113
113
  * document: null,
114
114
  * async parse(_source) {
115
- * // Convert `source` (path or inline data) into an InputNode.
115
+ * // Convert the source (path or inline data) into an InputNode.
116
116
  * return ast.factory.createInput()
117
117
  * },
118
118
  * getImports: () => [],
119
119
  * async validate() {
120
- * // Throw or call ctx.error here when the spec is invalid.
120
+ * // Throw here when the spec is invalid.
121
121
  * },
122
122
  * }))
123
123
  * ```
package/src/createKubb.ts CHANGED
@@ -74,9 +74,8 @@ type CreateKubbOptions = {
74
74
  * config in the constructor, so `config` is available right away, and shares `hooks`,
75
75
  * `storage`, and `driver` across the `setup → build` lifecycle.
76
76
  *
77
- * `createKubb` takes a plain, serializable config object (the shape `defineConfig`
78
- * produces), not a fluent builder. Config stays plain data so it can be cache
79
- * fingerprinted and validated against the shipped JSON schema.
77
+ * `createKubb` takes a plain config object (the shape `defineConfig` produces),
78
+ * not a fluent builder.
80
79
  *
81
80
  * Attach event listeners to `.hooks` before calling `setup()` or `build()`.
82
81
  *
@@ -44,13 +44,13 @@ export type GenerationResult = {
44
44
  }
45
45
 
46
46
  /**
47
- * Render context passed alongside the {@link GenerationResult}, carrying knobs a reporter needs
48
- * but that are not part of the run data (e.g. verbosity).
47
+ * Render settings passed alongside the {@link GenerationResult}. These are not part of the run
48
+ * data, such as the output verbosity.
49
49
  */
50
50
  export type ReporterContext = {
51
51
  /**
52
52
  * Output verbosity. Use the `logLevel` constants exported from `@kubb/core`
53
- * (`silent`, `error`, `warn`, `info`, `verbose`, `debug`).
53
+ * (`silent`, `error`, `warn`, `info`, `verbose`).
54
54
  */
55
55
  logLevel: (typeof logLevel)[keyof typeof logLevel]
56
56
  }
@@ -71,7 +71,7 @@ export type Reporter = {
71
71
  report: (result: GenerationResult, context: ReporterContext) => void | Promise<void>
72
72
  /**
73
73
  * Optional finalizer called once after the run's last config. The host wires it to
74
- * `kubb:lifecycle:end`. {@link createReporter} closes it over the reports `report` returned.
74
+ * `kubb:lifecycle:end`. {@link createReporter} closes it over the values that `report` returned.
75
75
  */
76
76
  drain?: (context: ReporterContext) => void | Promise<void>
77
77
  }
@@ -34,8 +34,8 @@ export type Storage = {
34
34
  */
35
35
  clear(base?: string): Promise<void>
36
36
  /**
37
- * Optional teardown hook called after the build completes. Use to flush
38
- * buffers, close connections, or release file locks.
37
+ * Optional teardown hook for a backend to flush buffers, close connections,
38
+ * or release file locks.
39
39
  */
40
40
  dispose?(): Promise<void>
41
41
  }
@@ -9,11 +9,11 @@ import type { Resolver } from './defineResolver.ts'
9
9
  import type { Config } from './types.ts'
10
10
 
11
11
  /**
12
- * Context object passed to generator `schema`, `operation`, and `operations` methods.
12
+ * Context passed to a generator's `schema`, `operation`, and `operations` methods.
13
13
  *
14
- * The adapter is always defined (guaranteed by `runPluginAstHooks`) so no runtime checks
15
- * are needed. `ctx.options` carries resolved per-node options after exclude/include/override
16
- * filtering for individual schema/operation calls, or plugin-level options for operations.
14
+ * The driver sets `adapter` on the context before it runs a generator, so methods can read it
15
+ * without a null check. `ctx.options` carries the per-node options after exclude/include/override
16
+ * filtering for `schema` and `operation`, or the plugin-level options for `operations`.
17
17
  */
18
18
  export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFactoryOptions> = {
19
19
  /**
@@ -109,9 +109,10 @@ export type GeneratorContext<TOptions extends PluginFactoryOptions = PluginFacto
109
109
  /**
110
110
  * Declares a named generator unit that walks the AST and emits files.
111
111
  *
112
- * Each method (`schema`, `operation`, `operations`) is called for the matching node type.
113
- * JSX-based generators require a `renderer` factory. Return `Array<FileNode>` directly, or call
114
- * `ctx.upsertFile()` manually and return `null` to bypass rendering.
112
+ * `schema` runs for each schema node and `operation` for each operation node. `operations` runs
113
+ * once after every operation node is walked. JSX-based generators require a `renderer` factory.
114
+ * Return `Array<FileNode>` directly, or call `ctx.upsertFile()` manually and return `null` to
115
+ * bypass rendering.
115
116
  *
116
117
  * @note Generators are consumed by plugins and registered via `ctx.addGenerator()` in `kubb:plugin:setup`.
117
118
  *
@@ -15,8 +15,9 @@ export type Parser<TMeta extends object = object, TNode = unknown> = {
15
15
  */
16
16
  name: string
17
17
  /**
18
- * File extensions this parser handles. Set to `undefined` to define a
19
- * catch-all fallback used when no other parser claims the extension.
18
+ * File extensions this parser handles. The driver registers the parser for each
19
+ * extension in this list. A parser with `undefined` here is not registered, so
20
+ * files of an unclaimed extension fall back to joining their sources verbatim.
20
21
  *
21
22
  * @example
22
23
  * `['.ts', '.js']`
@@ -1,4 +1,4 @@
1
- import type { FileNode, HttpMethod, Macro, UserFileNode } from '@kubb/ast'
1
+ import type { Enforce, FileNode, HttpMethod, Macro, UserFileNode } from '@kubb/ast'
2
2
  import { diagnosticCode } from './constants.ts'
3
3
  import type { Generator } from './defineGenerator.ts'
4
4
  import type { BannerMeta, Resolver } from './defineResolver.ts'
@@ -79,8 +79,8 @@ export type Group = {
79
79
  */
80
80
  type: 'tag' | 'path'
81
81
  /**
82
- * Returns the subdirectory name from the group key. Defaults to the
83
- * camelCased tag for `tag` groups, or the first path segment for `path` groups.
82
+ * Returns the subdirectory name from the group key. Defaults to the camelCased tag for
83
+ * `tag` groups, or the camelCased first path segment for `path` groups.
84
84
  */
85
85
  name?: (context: { group: string }) => string
86
86
  }
@@ -169,11 +169,12 @@ type ByPath = {
169
169
 
170
170
  type ByMethod = {
171
171
  /**
172
- * Filter by HTTP method: `'get'`, `'post'`, `'put'`, `'delete'`, `'patch'`, `'head'`, `'options'`.
172
+ * Filter by HTTP method: `'GET'`, `'POST'`, `'PUT'`, `'PATCH'`, `'DELETE'`, `'HEAD'`, `'OPTIONS'`, `'TRACE'`.
173
173
  */
174
174
  type: 'method'
175
175
  /**
176
- * HTTP method to match (case-insensitive when using string, or regex for dynamic matching).
176
+ * HTTP method to match, as one of the `HttpMethod` values (`'GET'`, `'POST'`, `'PUT'`,
177
+ * `'PATCH'`, `'DELETE'`, `'HEAD'`, `'OPTIONS'`, `'TRACE'`) or a regex.
177
178
  */
178
179
  pattern: HttpMethod | RegExp
179
180
  }
@@ -347,11 +348,11 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions>
347
348
  *
348
349
  * - `'pre'` runs before all normal plugins.
349
350
  * - `'post'` runs after all normal plugins.
350
- * - `undefined` (default), runs in declaration order among normal plugins.
351
+ * - `undefined` (default) runs in declaration order among normal plugins.
351
352
  *
352
353
  * Dependency constraints always take precedence over `enforce`.
353
354
  */
354
- enforce?: 'pre' | 'post'
355
+ enforce?: Enforce
355
356
  /**
356
357
  * The options passed by the user when calling the plugin factory.
357
358
  */
@@ -235,7 +235,7 @@ const severityStyle: Record<DiagnosticSeverity, { glyph: string; color: 'red' |
235
235
  /**
236
236
  * A {@link Diagnostic} reduced to its JSON-safe fields plus a `docsUrl`, for
237
237
  * machine-readable output (the `--reporter json` report, the MCP tools). Drops the
238
- * non-serializable `cause` and the `timing`/`duration` bookkeeping.
238
+ * non-serializable `cause` and the `kind`/`duration` bookkeeping.
239
239
  */
240
240
  export type SerializedDiagnostic = {
241
241
  code: DiagnosticCode
@@ -541,8 +541,8 @@ export class Diagnostics {
541
541
  }
542
542
 
543
543
  /**
544
- * Counts `problem` diagnostics by severity for the run summary. `timing`
545
- * diagnostics are ignored.
544
+ * Counts `problem` diagnostics by severity for the run summary. `performance` and
545
+ * `update` diagnostics are ignored.
546
546
  */
547
547
  static count(diagnostics: ReadonlyArray<Diagnostic>): { errors: number; warnings: number; infos: number } {
548
548
  let errors = 0
package/src/mocks.ts CHANGED
@@ -11,7 +11,7 @@ import { memoryStorage } from './storages/memoryStorage.ts'
11
11
  import type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'
12
12
 
13
13
  /**
14
- * Creates a minimal `PluginDriver` mock for unit tests.
14
+ * Creates a minimal `KubbDriver` mock for unit tests.
15
15
  */
16
16
  export function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {
17
17
  const fileManager = new FileManager()
@@ -74,12 +74,11 @@ function buildTimingSection(report: Report): Array<string> {
74
74
  * The `file` reporter. Writes a config's {@link Report} to `.kubb/kubb-<name>-<timestamp>.log` as a
75
75
  * plain-text document: a `# <name> — <timestamp>` header, a `## Summary` with the same counts the
76
76
  * cli and json reporters expose, a `## Problems` section in the miette block format, and a
77
- * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`), replacing the
78
- * old `--debug` flag.
77
+ * `## Timings` section. Selected with `--reporter file` (or `reporters: ['file']`).
79
78
  *
80
- * @note Unlike the streaming logger it replaced, it captures the collected diagnostics once a
81
- * config finishes, not the live `kubb:info`/`kubb:plugin` event stream. Color is stripped so the
82
- * file stays plain text even when the run is attached to a TTY.
79
+ * @note It captures the collected diagnostics once a config finishes, not the live
80
+ * `kubb:info`/`kubb:plugin` event stream. Color is stripped so the file stays plain text even when
81
+ * the run is attached to a TTY.
83
82
  */
84
83
  export const fileReporter = createReporter({
85
84
  name: 'file',
package/src/types.ts CHANGED
@@ -149,7 +149,7 @@ export type Config<TInput = Input> = {
149
149
  *
150
150
  * @example
151
151
  * ```ts
152
- * format: 'auto' // auto-detect prettier, biome, or oxfmt
152
+ * format: 'auto' // auto-detect oxfmt, biome, or prettier
153
153
  * format: 'prettier' // force prettier
154
154
  * format: false // skip formatting
155
155
  * ```