Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@kubb/core

Package Overview
Dependencies
Maintainers
1
Versions
1315
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@kubb/core - npm Package Compare versions

Comparing version
5.0.0-beta.50
to
5.0.0-beta.51
dist/diagnostics-BCcJbIpZ.d.ts

Sorry, the diff of this file is too big to display

+738
import "./chunk-C0LytTxp.js";
import { EventEmitter } from "node:events";
import { createFile, extractStringsFromNodes } from "@kubb/ast";
//#region ../../internals/utils/src/errors.ts
/**
* Thrown when one or more errors occur during a Kubb build.
* Carries the full list of underlying errors on `errors`.
*
* @example
* ```ts
* throw new BuildError('Build failed', { errors: [err1, err2] })
* ```
*/
var BuildError = class extends Error {
errors;
constructor(message, options) {
super(message, { cause: options.cause });
this.name = "BuildError";
this.errors = options.errors;
}
};
/**
* Coerces an unknown thrown value to an `Error` instance.
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
*
* @example
* ```ts
* try { ... } catch(err) {
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
* }
* ```
*/
function toError(value) {
return value instanceof Error ? value : new Error(String(value));
}
/**
* Extracts a human-readable message from any thrown value.
*
* @example
* ```ts
* getErrorMessage(new Error('oops')) // 'oops'
* getErrorMessage('plain string') // 'plain string'
* ```
*/
function getErrorMessage(value) {
return value instanceof Error ? value.message : String(value);
}
//#endregion
//#region ../../internals/utils/src/asyncEventEmitter.ts
/**
* Typed `EventEmitter` that awaits all async listeners before resolving.
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
*
* @example
* ```ts
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
* emitter.on('build', async (name) => { console.log(name) })
* await emitter.emit('build', 'petstore') // all listeners awaited
* ```
*/
var AsyncEventEmitter = class {
/**
* Maximum number of listeners per event before Node emits a memory-leak warning.
* @default 10
*/
constructor(maxListener = 10) {
this.#emitter.setMaxListeners(maxListener);
}
#emitter = new EventEmitter();
/**
* Emits `eventName` and awaits all registered listeners sequentially.
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
*
* @example
* ```ts
* await emitter.emit('build', 'petstore')
* ```
*/
emit(eventName, ...eventArgs) {
const listeners = this.#emitter.listeners(eventName);
if (listeners.length === 0) return;
return this.#emitAll(eventName, listeners, eventArgs);
}
async #emitAll(eventName, listeners, eventArgs) {
for (const listener of listeners) try {
await listener(...eventArgs);
} catch (err) {
let serializedArgs;
try {
serializedArgs = JSON.stringify(eventArgs);
} catch {
serializedArgs = String(eventArgs);
}
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
}
}
/**
* Registers a persistent listener for `eventName`.
*
* @example
* ```ts
* emitter.on('build', async (name) => { console.log(name) })
* ```
*/
on(eventName, handler) {
this.#emitter.on(eventName, handler);
}
/**
* Registers a one-shot listener that removes itself after the first invocation.
*
* @example
* ```ts
* emitter.onOnce('build', async (name) => { console.log(name) })
* ```
*/
onOnce(eventName, handler) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
return handler(...args);
};
this.on(eventName, wrapper);
}
/**
* Removes a previously registered listener.
*
* @example
* ```ts
* emitter.off('build', handler)
* ```
*/
off(eventName, handler) {
this.#emitter.off(eventName, handler);
}
/**
* Returns the number of listeners registered for `eventName`.
*
* @example
* ```ts
* emitter.on('build', handler)
* emitter.listenerCount('build') // 1
* ```
*/
listenerCount(eventName) {
return this.#emitter.listenerCount(eventName);
}
/**
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
* Set this above the expected listener count when many listeners attach by design.
*
* @example
* ```ts
* emitter.setMaxListeners(40)
* ```
*/
setMaxListeners(max) {
this.#emitter.setMaxListeners(max);
}
/**
* Returns the current per-event listener ceiling.
*/
getMaxListeners() {
return this.#emitter.getMaxListeners();
}
/**
* Removes all listeners from every event channel.
*
* @example
* ```ts
* emitter.removeAll()
* ```
*/
removeAll() {
this.#emitter.removeAllListeners();
}
};
//#endregion
//#region ../../internals/utils/src/casing.ts
/**
* Shared implementation for camelCase and PascalCase conversion.
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
* and capitalizes each word according to `pascal`.
*
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
*/
function toCamelOrPascal(text, pascal) {
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
if (word.length > 1 && word === word.toUpperCase()) return word;
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
return word.charAt(0).toUpperCase() + word.slice(1);
}).join("").replace(/[^a-zA-Z0-9]/g, "");
}
/**
* Splits `text` on `.` and applies `transformPart` to each segment.
* The last segment receives `isLast = true`, all earlier segments receive `false`.
* Segments are joined with `/` to form a file path.
*
* Only splits on dots followed by a letter so that version numbers
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
*
* Empty segments are filtered before joining. They arise when the text starts with
* a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
* and `'..'` transforms to an empty string). Without this filter the join would produce
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
* generated files to escape the configured output directory.
*/
function applyToFileParts(text, transformPart) {
const parts = text.split(/\.(?=[a-zA-Z])/);
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
}
/**
* Converts `text` to camelCase.
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
*
* @example
* camelCase('hello-world') // 'helloWorld'
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
*/
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
prefix,
suffix
} : {}));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
}
/**
* Converts `text` to PascalCase.
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
*
* @example
* pascalCase('hello-world') // 'HelloWorld'
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
*/
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
prefix,
suffix
}) : camelCase(part));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
}
//#endregion
//#region src/constants.ts
/**
* Plugin `include` filter types that select operations directly. When one of these is set
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
*/
const OPERATION_FILTER_TYPES = new Set([
"tag",
"operationId",
"path",
"method",
"contentType"
]);
/**
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
* and stays stable so it can be referenced in tooling and (later) docs. Reference
* these instead of inlining the string at a throw site.
*/
const diagnosticCode = {
/**
* Fallback for an unstructured error with no specific code.
*/
unknown: "KUBB_UNKNOWN",
/**
* The `input.path` file or URL could not be read.
*/
inputNotFound: "KUBB_INPUT_NOT_FOUND",
/**
* An adapter was configured without an `input`.
*/
inputRequired: "KUBB_INPUT_REQUIRED",
/**
* A `$ref` (or equivalent reference) could not be resolved in the source document.
*/
refNotFound: "KUBB_REF_NOT_FOUND",
/**
* A server variable value is not allowed by its `enum`.
*/
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
/**
* A required plugin is missing from the config.
*/
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
/**
* A plugin threw while generating.
*/
pluginFailed: "KUBB_PLUGIN_FAILED",
/**
* A plugin reported a non-fatal warning through `ctx.warn`.
*/
pluginWarning: "KUBB_PLUGIN_WARNING",
/**
* A plugin reported an informational message through `ctx.info`.
*/
pluginInfo: "KUBB_PLUGIN_INFO",
/**
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
* adapters to emit as a `warning`.
*/
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
/**
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
* to emit as an `info`.
*/
deprecated: "KUBB_DEPRECATED",
/**
* An adapter is required but the config has none. The build cannot read the input
* without one.
*/
adapterRequired: "KUBB_ADAPTER_REQUIRED",
/**
* A resolved output path escapes the output directory, which can stem from a path
* traversal in the spec or a misconfigured `group.name`.
*/
pathTraversal: "KUBB_PATH_TRAVERSAL",
/**
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
*/
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
/**
* A post-generate shell hook (`hooks.done`) exited with a failure.
*/
hookFailed: "KUBB_HOOK_FAILED",
/**
* The formatter pass over the generated files failed.
*/
formatFailed: "KUBB_FORMAT_FAILED",
/**
* The linter pass over the generated files failed.
*/
lintFailed: "KUBB_LINT_FAILED",
/**
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
*/
performance: "KUBB_PERFORMANCE",
/**
* Not a failure. A newer Kubb version is available on npm.
*/
updateAvailable: "KUBB_UPDATE_AVAILABLE"
};
//#endregion
//#region src/createStorage.ts
/**
* Defines a custom storage backend. The builder receives user options and
* returns a `Storage` implementation. Kubb ships with filesystem and
* in-memory storages, reach for this when you need to write generated files
* elsewhere (cloud storage, a database, a remote API).
*
* @example In-memory storage (the built-in implementation)
* ```ts
* import { createStorage } from '@kubb/core'
*
* export const memoryStorage = createStorage(() => {
* const store = new Map<string, string>()
*
* return {
* name: 'memory',
* async hasItem(key) {
* return store.has(key)
* },
* async getItem(key) {
* return store.get(key) ?? null
* },
* async setItem(key, value) {
* store.set(key, value)
* },
* async removeItem(key) {
* store.delete(key)
* },
* async getKeys(base) {
* const keys = [...store.keys()]
* return base ? keys.filter((k) => k.startsWith(base)) : keys
* },
* async clear(base) {
* if (!base) store.clear()
* },
* }
* })
* ```
*/
function createStorage(build) {
return (options) => build(options ?? {});
}
//#endregion
//#region src/FileManager.ts
function mergeFile(a, b) {
return {
...a,
banner: b.banner,
footer: b.footer,
sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
};
}
function isIndexPath(path) {
return path.endsWith("/index.ts") || path === "index.ts";
}
function compareFiles(a, b) {
const lenDiff = a.path.length - b.path.length;
if (lenDiff !== 0) return lenDiff;
const aIsIndex = isIndexPath(a.path);
const bIsIndex = isIndexPath(b.path);
if (aIsIndex && !bIsIndex) return 1;
if (!aIsIndex && bIsIndex) return -1;
return 0;
}
/**
* In-memory file store for generated files. Files sharing a `path` are merged
* (sources/imports/exports concatenated). The `files` getter is sorted by
* path length (barrel `index.ts` last within a bucket).
*
* @example
* ```ts
* const manager = new FileManager()
* manager.upsert(myFile)
* manager.files // sorted view
* ```
*/
var FileManager = class {
/**
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
* through `add` or `upsert`.
*/
hooks = new AsyncEventEmitter();
#cache = /* @__PURE__ */ new Map();
#sorted = null;
add(...files) {
return this.#store(files, false);
}
upsert(...files) {
return this.#store(files, true);
}
#store(files, mergeExisting) {
const batch = files.length > 1 ? this.#dedupe(files) : files;
const resolved = [];
for (const file of batch) {
const existing = this.#cache.get(file.path);
const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file);
this.#cache.set(merged.path, merged);
resolved.push(merged);
this.hooks.emit("upsert", merged);
}
if (resolved.length > 0) this.#sorted = null;
return resolved;
}
#dedupe(files) {
const seen = /* @__PURE__ */ new Map();
for (const file of files) {
const prev = seen.get(file.path);
seen.set(file.path, prev ? mergeFile(prev, file) : file);
}
return [...seen.values()];
}
getByPath(path) {
return this.#cache.get(path) ?? null;
}
deleteByPath(path) {
if (!this.#cache.delete(path)) return;
this.#sorted = null;
}
clear() {
this.#cache.clear();
this.#sorted = null;
}
/**
* Releases all stored files and clears every `hooks` listener. Called by the core after
* `kubb:build:end`.
*/
dispose() {
this.clear();
this.hooks.removeAll();
}
[Symbol.dispose]() {
this.dispose();
}
/**
* All stored files in stable sort order (shortest path first, barrel files
* last within a length bucket). Returns a cached view, do not mutate.
*/
get files() {
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
}
};
//#endregion
//#region src/FileProcessor.ts
function joinSources(file) {
const sources = file.sources;
if (sources.length === 0) return "";
const parts = [];
for (const source of sources) {
const text = extractStringsFromNodes(source.nodes);
if (text) parts.push(text);
}
return parts.join("\n\n");
}
/**
* Turns `FileNode`s into source strings and writes them to storage.
*
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
*
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
* written and is meant for the end of a build.
*
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
* `hooks` and re-emit on the kubb bus.
*/
var FileProcessor = class {
hooks = new AsyncEventEmitter();
#parsers;
#storage;
#extension;
#pending = /* @__PURE__ */ new Map();
#runningFlush = null;
constructor(options) {
this.#parsers = options.parsers ?? null;
this.#storage = options.storage;
this.#extension = options.extension ?? null;
}
/**
* Files waiting in the queue.
*/
get size() {
return this.#pending.size;
}
parse(file) {
const parsers = this.#parsers;
const parseExtName = this.#extension?.[file.extname] || void 0;
if (!parsers || !file.extname) return joinSources(file);
const parser = parsers.get(file.extname);
if (!parser) return joinSources(file);
return parser.parse(file, { extname: parseExtName });
}
*stream(files) {
const total = files.length;
if (total === 0) return;
let processed = 0;
for (const file of files) {
const source = this.parse(file);
processed++;
yield {
file,
source,
processed,
total,
percentage: processed / total * 100
};
}
}
async run(files) {
await this.hooks.emit("start", files);
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
file,
source,
processed,
percentage,
total
});
await this.hooks.emit("end", files);
return files;
}
/**
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
*/
enqueue(file) {
this.#pending.set(file.path, file);
this.hooks.emit("enqueue", file);
}
/**
* Starts processing the queued files. Waits for any previous flush to finish (so two
* batches never run together) and then returns without waiting for the new one. The next
* `flush` or `drain` picks up the in-flight task.
*/
async flush() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size === 0) return;
const batch = [...this.#pending.values()];
this.#pending.clear();
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
this.#runningFlush = null;
});
}
/**
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
* when both are done.
*/
async drain() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size > 0) {
const batch = [...this.#pending.values()];
this.#pending.clear();
await this.#processAndWrite(batch);
}
await this.hooks.emit("drain");
}
async #processAndWrite(files) {
const storage = this.#storage;
await this.hooks.emit("start", files);
const queue = [];
for (const item of this.stream(files)) {
await this.hooks.emit("update", item);
if (item.source) {
queue.push(storage.setItem(item.file.path, item.source));
if (queue.length >= 50) await Promise.all(queue.splice(0));
}
}
await Promise.all(queue);
await this.hooks.emit("end", files);
}
/**
* Clears every listener and the pending queue.
*/
dispose() {
this.hooks.removeAll();
this.#pending.clear();
}
[Symbol.dispose]() {
this.dispose();
}
};
//#endregion
//#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js
function _usingCtx() {
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
var n = Error();
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
};
var e = {};
var n = [];
function using(r, e) {
if (null != e) {
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
t && (o = function o() {
try {
t.call(e);
} catch (r) {
return Promise.reject(r);
}
}), n.push({
v: e,
d: o,
a: r
});
} else r && n.push({
d: e,
a: r
});
return e;
}
return {
e,
u: using.bind(null, !1),
a: using.bind(null, !0),
d: function d() {
var o;
var t = this.e;
var s = 0;
function next() {
for (; o = n.pop();) try {
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
if (o.d) {
var r = o.d.call(o.v);
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
} else s |= 1;
} catch (r) {
return err(r);
}
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
if (t !== e) throw t;
}
function err(n) {
return t = t !== e ? new r(n, t) : n, next();
}
return next();
}
};
}
//#endregion
//#region src/storages/memoryStorage.ts
/**
* In-memory storage driver. Useful for testing and dry-run scenarios where
* generated output should be captured without touching the filesystem.
*
* All data lives in a `Map` scoped to the storage instance and is discarded
* when the instance is garbage-collected.
*
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
* import { defineConfig } from 'kubb'
*
* export default defineConfig({
* input: { path: './petStore.yaml' },
* output: { path: './src/gen' },
* storage: memoryStorage(),
* })
* ```
*/
const memoryStorage = createStorage(() => {
const store = /* @__PURE__ */ new Map();
return {
name: "memory",
async hasItem(key) {
return store.has(key);
},
async getItem(key) {
return store.get(key) ?? null;
},
async setItem(key, value) {
store.set(key, value);
},
async removeItem(key) {
store.delete(key);
},
async getKeys(base) {
const keys = [...store.keys()];
return base ? keys.filter((k) => k.startsWith(base)) : keys;
},
async clear(base) {
if (!base) {
store.clear();
return;
}
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
}
};
});
//#endregion
export { createStorage as a, camelCase as c, BuildError as d, getErrorMessage as f, FileManager as i, pascalCase as l, _usingCtx as n, OPERATION_FILTER_TYPES as o, FileProcessor as r, diagnosticCode as s, memoryStorage as t, AsyncEventEmitter as u };
//# sourceMappingURL=memoryStorage-Bdv42rxp.js.map
{"version":3,"file":"memoryStorage-Bdv42rxp.js","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"}
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", {
value,
configurable: true
});
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let node_events = require("node:events");
let _kubb_ast = require("@kubb/ast");
//#region ../../internals/utils/src/errors.ts
/**
* Thrown when one or more errors occur during a Kubb build.
* Carries the full list of underlying errors on `errors`.
*
* @example
* ```ts
* throw new BuildError('Build failed', { errors: [err1, err2] })
* ```
*/
var BuildError = class extends Error {
errors;
constructor(message, options) {
super(message, { cause: options.cause });
this.name = "BuildError";
this.errors = options.errors;
}
};
/**
* Coerces an unknown thrown value to an `Error` instance.
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
*
* @example
* ```ts
* try { ... } catch(err) {
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
* }
* ```
*/
function toError(value) {
return value instanceof Error ? value : new Error(String(value));
}
/**
* Extracts a human-readable message from any thrown value.
*
* @example
* ```ts
* getErrorMessage(new Error('oops')) // 'oops'
* getErrorMessage('plain string') // 'plain string'
* ```
*/
function getErrorMessage(value) {
return value instanceof Error ? value.message : String(value);
}
//#endregion
//#region ../../internals/utils/src/asyncEventEmitter.ts
/**
* Typed `EventEmitter` that awaits all async listeners before resolving.
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
*
* @example
* ```ts
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
* emitter.on('build', async (name) => { console.log(name) })
* await emitter.emit('build', 'petstore') // all listeners awaited
* ```
*/
var AsyncEventEmitter = class {
/**
* Maximum number of listeners per event before Node emits a memory-leak warning.
* @default 10
*/
constructor(maxListener = 10) {
this.#emitter.setMaxListeners(maxListener);
}
#emitter = new node_events.EventEmitter();
/**
* Emits `eventName` and awaits all registered listeners sequentially.
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
*
* @example
* ```ts
* await emitter.emit('build', 'petstore')
* ```
*/
emit(eventName, ...eventArgs) {
const listeners = this.#emitter.listeners(eventName);
if (listeners.length === 0) return;
return this.#emitAll(eventName, listeners, eventArgs);
}
async #emitAll(eventName, listeners, eventArgs) {
for (const listener of listeners) try {
await listener(...eventArgs);
} catch (err) {
let serializedArgs;
try {
serializedArgs = JSON.stringify(eventArgs);
} catch {
serializedArgs = String(eventArgs);
}
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
}
}
/**
* Registers a persistent listener for `eventName`.
*
* @example
* ```ts
* emitter.on('build', async (name) => { console.log(name) })
* ```
*/
on(eventName, handler) {
this.#emitter.on(eventName, handler);
}
/**
* Registers a one-shot listener that removes itself after the first invocation.
*
* @example
* ```ts
* emitter.onOnce('build', async (name) => { console.log(name) })
* ```
*/
onOnce(eventName, handler) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
return handler(...args);
};
this.on(eventName, wrapper);
}
/**
* Removes a previously registered listener.
*
* @example
* ```ts
* emitter.off('build', handler)
* ```
*/
off(eventName, handler) {
this.#emitter.off(eventName, handler);
}
/**
* Returns the number of listeners registered for `eventName`.
*
* @example
* ```ts
* emitter.on('build', handler)
* emitter.listenerCount('build') // 1
* ```
*/
listenerCount(eventName) {
return this.#emitter.listenerCount(eventName);
}
/**
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
* Set this above the expected listener count when many listeners attach by design.
*
* @example
* ```ts
* emitter.setMaxListeners(40)
* ```
*/
setMaxListeners(max) {
this.#emitter.setMaxListeners(max);
}
/**
* Returns the current per-event listener ceiling.
*/
getMaxListeners() {
return this.#emitter.getMaxListeners();
}
/**
* Removes all listeners from every event channel.
*
* @example
* ```ts
* emitter.removeAll()
* ```
*/
removeAll() {
this.#emitter.removeAllListeners();
}
};
//#endregion
//#region ../../internals/utils/src/casing.ts
/**
* Shared implementation for camelCase and PascalCase conversion.
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
* and capitalizes each word according to `pascal`.
*
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
*/
function toCamelOrPascal(text, pascal) {
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
if (word.length > 1 && word === word.toUpperCase()) return word;
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
return word.charAt(0).toUpperCase() + word.slice(1);
}).join("").replace(/[^a-zA-Z0-9]/g, "");
}
/**
* Splits `text` on `.` and applies `transformPart` to each segment.
* The last segment receives `isLast = true`, all earlier segments receive `false`.
* Segments are joined with `/` to form a file path.
*
* Only splits on dots followed by a letter so that version numbers
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
*
* Empty segments are filtered before joining. They arise when the text starts with
* a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
* and `'..'` transforms to an empty string). Without this filter the join would produce
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
* generated files to escape the configured output directory.
*/
function applyToFileParts(text, transformPart) {
const parts = text.split(/\.(?=[a-zA-Z])/);
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
}
/**
* Converts `text` to camelCase.
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
*
* @example
* camelCase('hello-world') // 'helloWorld'
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
*/
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
prefix,
suffix
} : {}));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
}
/**
* Converts `text` to PascalCase.
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
*
* @example
* pascalCase('hello-world') // 'HelloWorld'
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
*/
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
prefix,
suffix
}) : camelCase(part));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
}
//#endregion
//#region src/constants.ts
/**
* Plugin `include` filter types that select operations directly. When one of these is set
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
*/
const OPERATION_FILTER_TYPES = new Set([
"tag",
"operationId",
"path",
"method",
"contentType"
]);
/**
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
* and stays stable so it can be referenced in tooling and (later) docs. Reference
* these instead of inlining the string at a throw site.
*/
const diagnosticCode = {
/**
* Fallback for an unstructured error with no specific code.
*/
unknown: "KUBB_UNKNOWN",
/**
* The `input.path` file or URL could not be read.
*/
inputNotFound: "KUBB_INPUT_NOT_FOUND",
/**
* An adapter was configured without an `input`.
*/
inputRequired: "KUBB_INPUT_REQUIRED",
/**
* A `$ref` (or equivalent reference) could not be resolved in the source document.
*/
refNotFound: "KUBB_REF_NOT_FOUND",
/**
* A server variable value is not allowed by its `enum`.
*/
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
/**
* A required plugin is missing from the config.
*/
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
/**
* A plugin threw while generating.
*/
pluginFailed: "KUBB_PLUGIN_FAILED",
/**
* A plugin reported a non-fatal warning through `ctx.warn`.
*/
pluginWarning: "KUBB_PLUGIN_WARNING",
/**
* A plugin reported an informational message through `ctx.info`.
*/
pluginInfo: "KUBB_PLUGIN_INFO",
/**
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
* adapters to emit as a `warning`.
*/
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
/**
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
* to emit as an `info`.
*/
deprecated: "KUBB_DEPRECATED",
/**
* An adapter is required but the config has none. The build cannot read the input
* without one.
*/
adapterRequired: "KUBB_ADAPTER_REQUIRED",
/**
* A resolved output path escapes the output directory, which can stem from a path
* traversal in the spec or a misconfigured `group.name`.
*/
pathTraversal: "KUBB_PATH_TRAVERSAL",
/**
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
*/
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
/**
* A post-generate shell hook (`hooks.done`) exited with a failure.
*/
hookFailed: "KUBB_HOOK_FAILED",
/**
* The formatter pass over the generated files failed.
*/
formatFailed: "KUBB_FORMAT_FAILED",
/**
* The linter pass over the generated files failed.
*/
lintFailed: "KUBB_LINT_FAILED",
/**
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
*/
performance: "KUBB_PERFORMANCE",
/**
* Not a failure. A newer Kubb version is available on npm.
*/
updateAvailable: "KUBB_UPDATE_AVAILABLE"
};
//#endregion
//#region src/createStorage.ts
/**
* Defines a custom storage backend. The builder receives user options and
* returns a `Storage` implementation. Kubb ships with filesystem and
* in-memory storages, reach for this when you need to write generated files
* elsewhere (cloud storage, a database, a remote API).
*
* @example In-memory storage (the built-in implementation)
* ```ts
* import { createStorage } from '@kubb/core'
*
* export const memoryStorage = createStorage(() => {
* const store = new Map<string, string>()
*
* return {
* name: 'memory',
* async hasItem(key) {
* return store.has(key)
* },
* async getItem(key) {
* return store.get(key) ?? null
* },
* async setItem(key, value) {
* store.set(key, value)
* },
* async removeItem(key) {
* store.delete(key)
* },
* async getKeys(base) {
* const keys = [...store.keys()]
* return base ? keys.filter((k) => k.startsWith(base)) : keys
* },
* async clear(base) {
* if (!base) store.clear()
* },
* }
* })
* ```
*/
function createStorage(build) {
return (options) => build(options ?? {});
}
//#endregion
//#region src/FileManager.ts
function mergeFile(a, b) {
return {
...a,
banner: b.banner,
footer: b.footer,
sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
};
}
function isIndexPath(path) {
return path.endsWith("/index.ts") || path === "index.ts";
}
function compareFiles(a, b) {
const lenDiff = a.path.length - b.path.length;
if (lenDiff !== 0) return lenDiff;
const aIsIndex = isIndexPath(a.path);
const bIsIndex = isIndexPath(b.path);
if (aIsIndex && !bIsIndex) return 1;
if (!aIsIndex && bIsIndex) return -1;
return 0;
}
/**
* In-memory file store for generated files. Files sharing a `path` are merged
* (sources/imports/exports concatenated). The `files` getter is sorted by
* path length (barrel `index.ts` last within a bucket).
*
* @example
* ```ts
* const manager = new FileManager()
* manager.upsert(myFile)
* manager.files // sorted view
* ```
*/
var FileManager = class {
/**
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
* through `add` or `upsert`.
*/
hooks = new AsyncEventEmitter();
#cache = /* @__PURE__ */ new Map();
#sorted = null;
add(...files) {
return this.#store(files, false);
}
upsert(...files) {
return this.#store(files, true);
}
#store(files, mergeExisting) {
const batch = files.length > 1 ? this.#dedupe(files) : files;
const resolved = [];
for (const file of batch) {
const existing = this.#cache.get(file.path);
const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file);
this.#cache.set(merged.path, merged);
resolved.push(merged);
this.hooks.emit("upsert", merged);
}
if (resolved.length > 0) this.#sorted = null;
return resolved;
}
#dedupe(files) {
const seen = /* @__PURE__ */ new Map();
for (const file of files) {
const prev = seen.get(file.path);
seen.set(file.path, prev ? mergeFile(prev, file) : file);
}
return [...seen.values()];
}
getByPath(path) {
return this.#cache.get(path) ?? null;
}
deleteByPath(path) {
if (!this.#cache.delete(path)) return;
this.#sorted = null;
}
clear() {
this.#cache.clear();
this.#sorted = null;
}
/**
* Releases all stored files and clears every `hooks` listener. Called by the core after
* `kubb:build:end`.
*/
dispose() {
this.clear();
this.hooks.removeAll();
}
[Symbol.dispose]() {
this.dispose();
}
/**
* All stored files in stable sort order (shortest path first, barrel files
* last within a length bucket). Returns a cached view, do not mutate.
*/
get files() {
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
}
};
//#endregion
//#region src/FileProcessor.ts
function joinSources(file) {
const sources = file.sources;
if (sources.length === 0) return "";
const parts = [];
for (const source of sources) {
const text = (0, _kubb_ast.extractStringsFromNodes)(source.nodes);
if (text) parts.push(text);
}
return parts.join("\n\n");
}
/**
* Turns `FileNode`s into source strings and writes them to storage.
*
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
*
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
* written and is meant for the end of a build.
*
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
* `hooks` and re-emit on the kubb bus.
*/
var FileProcessor = class {
hooks = new AsyncEventEmitter();
#parsers;
#storage;
#extension;
#pending = /* @__PURE__ */ new Map();
#runningFlush = null;
constructor(options) {
this.#parsers = options.parsers ?? null;
this.#storage = options.storage;
this.#extension = options.extension ?? null;
}
/**
* Files waiting in the queue.
*/
get size() {
return this.#pending.size;
}
parse(file) {
const parsers = this.#parsers;
const parseExtName = this.#extension?.[file.extname] || void 0;
if (!parsers || !file.extname) return joinSources(file);
const parser = parsers.get(file.extname);
if (!parser) return joinSources(file);
return parser.parse(file, { extname: parseExtName });
}
*stream(files) {
const total = files.length;
if (total === 0) return;
let processed = 0;
for (const file of files) {
const source = this.parse(file);
processed++;
yield {
file,
source,
processed,
total,
percentage: processed / total * 100
};
}
}
async run(files) {
await this.hooks.emit("start", files);
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
file,
source,
processed,
percentage,
total
});
await this.hooks.emit("end", files);
return files;
}
/**
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
*/
enqueue(file) {
this.#pending.set(file.path, file);
this.hooks.emit("enqueue", file);
}
/**
* Starts processing the queued files. Waits for any previous flush to finish (so two
* batches never run together) and then returns without waiting for the new one. The next
* `flush` or `drain` picks up the in-flight task.
*/
async flush() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size === 0) return;
const batch = [...this.#pending.values()];
this.#pending.clear();
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
this.#runningFlush = null;
});
}
/**
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
* when both are done.
*/
async drain() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size > 0) {
const batch = [...this.#pending.values()];
this.#pending.clear();
await this.#processAndWrite(batch);
}
await this.hooks.emit("drain");
}
async #processAndWrite(files) {
const storage = this.#storage;
await this.hooks.emit("start", files);
const queue = [];
for (const item of this.stream(files)) {
await this.hooks.emit("update", item);
if (item.source) {
queue.push(storage.setItem(item.file.path, item.source));
if (queue.length >= 50) await Promise.all(queue.splice(0));
}
}
await Promise.all(queue);
await this.hooks.emit("end", files);
}
/**
* Clears every listener and the pending queue.
*/
dispose() {
this.hooks.removeAll();
this.#pending.clear();
}
[Symbol.dispose]() {
this.dispose();
}
};
//#endregion
//#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js
function _usingCtx() {
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
var n = Error();
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
};
var e = {};
var n = [];
function using(r, e) {
if (null != e) {
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
t && (o = function o() {
try {
t.call(e);
} catch (r) {
return Promise.reject(r);
}
}), n.push({
v: e,
d: o,
a: r
});
} else r && n.push({
d: e,
a: r
});
return e;
}
return {
e,
u: using.bind(null, !1),
a: using.bind(null, !0),
d: function d() {
var o;
var t = this.e;
var s = 0;
function next() {
for (; o = n.pop();) try {
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
if (o.d) {
var r = o.d.call(o.v);
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
} else s |= 1;
} catch (r) {
return err(r);
}
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
if (t !== e) throw t;
}
function err(n) {
return t = t !== e ? new r(n, t) : n, next();
}
return next();
}
};
}
//#endregion
//#region src/storages/memoryStorage.ts
/**
* In-memory storage driver. Useful for testing and dry-run scenarios where
* generated output should be captured without touching the filesystem.
*
* All data lives in a `Map` scoped to the storage instance and is discarded
* when the instance is garbage-collected.
*
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
* import { defineConfig } from 'kubb'
*
* export default defineConfig({
* input: { path: './petStore.yaml' },
* output: { path: './src/gen' },
* storage: memoryStorage(),
* })
* ```
*/
const memoryStorage = createStorage(() => {
const store = /* @__PURE__ */ new Map();
return {
name: "memory",
async hasItem(key) {
return store.has(key);
},
async getItem(key) {
return store.get(key) ?? null;
},
async setItem(key, value) {
store.set(key, value);
},
async removeItem(key) {
store.delete(key);
},
async getKeys(base) {
const keys = [...store.keys()];
return base ? keys.filter((k) => k.startsWith(base)) : keys;
},
async clear(base) {
if (!base) {
store.clear();
return;
}
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
}
};
});
//#endregion
Object.defineProperty(exports, "AsyncEventEmitter", {
enumerable: true,
get: function() {
return AsyncEventEmitter;
}
});
Object.defineProperty(exports, "BuildError", {
enumerable: true,
get: function() {
return BuildError;
}
});
Object.defineProperty(exports, "FileManager", {
enumerable: true,
get: function() {
return FileManager;
}
});
Object.defineProperty(exports, "FileProcessor", {
enumerable: true,
get: function() {
return FileProcessor;
}
});
Object.defineProperty(exports, "OPERATION_FILTER_TYPES", {
enumerable: true,
get: function() {
return OPERATION_FILTER_TYPES;
}
});
Object.defineProperty(exports, "__name", {
enumerable: true,
get: function() {
return __name;
}
});
Object.defineProperty(exports, "__toESM", {
enumerable: true,
get: function() {
return __toESM;
}
});
Object.defineProperty(exports, "_usingCtx", {
enumerable: true,
get: function() {
return _usingCtx;
}
});
Object.defineProperty(exports, "camelCase", {
enumerable: true,
get: function() {
return camelCase;
}
});
Object.defineProperty(exports, "createStorage", {
enumerable: true,
get: function() {
return createStorage;
}
});
Object.defineProperty(exports, "diagnosticCode", {
enumerable: true,
get: function() {
return diagnosticCode;
}
});
Object.defineProperty(exports, "getErrorMessage", {
enumerable: true,
get: function() {
return getErrorMessage;
}
});
Object.defineProperty(exports, "memoryStorage", {
enumerable: true,
get: function() {
return memoryStorage;
}
});
Object.defineProperty(exports, "pascalCase", {
enumerable: true,
get: function() {
return pascalCase;
}
});
//# sourceMappingURL=memoryStorage-CIEzDI6b.cjs.map
{"version":3,"file":"memoryStorage-CIEzDI6b.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"}
+2
-166
import { t as __name } from "./chunk-C0LytTxp.js";
import { $ as Output, A as KubbHookEndContext, At as LoggerOptions, B as UserConfig, Bt as createStorage, C as KubbErrorContext, Ct as ReporterContext, D as KubbFilesProcessingUpdateContext, Dt as selectReporters, E as KubbFilesProcessingStartContext, Et as createReporter, F as KubbLifecycleStartContext, Ft as fsCache, G as KubbDriver, Gt as AdapterFactoryOptions, H as Generator, Ht as CachedSnapshot, I as KubbPluginsEndContext, It as Renderer, J as Include, Jt as AsyncEventEmitter, K as Exclude, Kt as AdapterSource, L as KubbSuccessContext, Lt as RendererFactory, M as KubbHookStartContext, Mt as defineLogger, N as KubbHooks, Nt as logLevel, O as KubbGenerationEndContext, Ot as Logger, P as KubbInfoContext, Pt as FsCacheOptions, Q as NormalizedPlugin, R as KubbWarnContext, Rt as createRenderer, S as KubbDiagnosticContext, St as Reporter, T as KubbFilesProcessingEndContext, Tt as UserReporter, U as GeneratorContext, Ut as createCache, V as createKubb, Vt as Cache, W as defineGenerator, Wt as Adapter, X as KubbPluginSetupContext, Y as KubbPluginEndContext, Z as KubbPluginStartContext, _ as InputPath, _t as FileProcessorHooks, a as DiagnosticLocation, at as definePlugin, b as KubbBuildStartContext, bt as defineParser, c as PerformanceDiagnostic, ct as ResolveBannerFile, d as SerializedDiagnostic, dt as ResolverContext, et as OutputMode, f as UpdateDiagnostic, ft as ResolverFileParams, g as InputData, gt as defineMiddleware, h as Config, ht as Middleware, i as DiagnosticKind, it as PluginFactoryOptions, j as KubbHookLineContext, jt as UserLogger, k as KubbGenerationStartContext, kt as LoggerContext, l as ProblemCode, lt as ResolveOptionsContext, m as CLIOptions, mt as defineResolver, n as DiagnosticByCode, nt as Override, o as DiagnosticSeverity, ot as BannerMeta, p as BuildOutput, pt as ResolverPathParams, q as Group, qt as createAdapter, r as DiagnosticDoc, rt as Plugin, s as Diagnostics, st as ResolveBannerContext, t as Diagnostic, tt as OutputOptions, u as ProblemDiagnostic, ut as Resolver, v as Kubb, vt as ParsedFile, w as KubbFileProcessingUpdate, wt as ReporterName, x as KubbConfigEndContext, xt as GenerationResult, y as KubbBuildEndContext, yt as Parser, z as PossibleConfig, zt as Storage } from "./diagnostics-CtTSRHX7.js";
import { $ as Include, A as KubbHookStartContext, At as createReporter, B as ParsedFile, C as KubbFilesProcessingEndContext, Ct as Storage, D as KubbGenerationStartContext, Dt as ReporterContext, E as KubbGenerationEndContext, Et as Reporter, F as KubbSuccessContext, Ft as Adapter, G as Parser, H as createKubb, I as KubbWarnContext, It as AdapterFactoryOptions, J as GeneratorContext, K as defineParser, L as PossibleConfig, Lt as AdapterSource, M as KubbInfoContext, Mt as Cache, N as KubbLifecycleStartContext, Nt as CachedSnapshot, O as KubbHookEndContext, Ot as ReporterName, P as KubbPluginsEndContext, Pt as createCache, Q as Group, R as UserConfig, Rt as createAdapter, S as KubbFileProcessingUpdate, St as createRenderer, T as KubbFilesProcessingUpdateContext, Tt as GenerationResult, U as FsCacheOptions, V as Kubb, W as fsCache, X as KubbDriver, Y as defineGenerator, Z as Exclude, _ as InputPath, _t as ResolverFileParams, a as DiagnosticLocation, at as OutputMode, b as KubbDiagnosticContext, bt as Renderer, c as PerformanceDiagnostic, ct as Plugin, d as SerializedDiagnostic, dt as BannerMeta, et as KubbPluginEndContext, f as UpdateDiagnostic, ft as ResolveBannerContext, g as InputData, gt as ResolverContext, h as Config, ht as Resolver, i as DiagnosticKind, it as Output, j as KubbHooks, jt as logLevel, k as KubbHookLineContext, kt as UserReporter, l as ProblemCode, lt as PluginFactoryOptions, m as CLIOptions, mt as ResolveOptionsContext, n as DiagnosticByCode, nt as KubbPluginStartContext, o as DiagnosticSeverity, ot as OutputOptions, p as BuildOutput, pt as ResolveBannerFile, q as Generator, r as DiagnosticDoc, rt as NormalizedPlugin, s as Diagnostics, st as Override, t as Diagnostic, tt as KubbPluginSetupContext, u as ProblemDiagnostic, ut as definePlugin, v as KubbBuildEndContext, vt as ResolverPathParams, w as KubbFilesProcessingStartContext, wt as createStorage, x as KubbErrorContext, xt as RendererFactory, y as KubbBuildStartContext, yt as defineResolver, z as FileProcessorHooks, zt as AsyncEventEmitter } from "./diagnostics-BCcJbIpZ.js";
import * as ast from "@kubb/ast";
//#region ../../internals/utils/src/runtime.d.ts
/**
* Name of the JavaScript runtime executing the current process.
*/
type RuntimeName = 'bun' | 'deno' | 'node';
//#endregion
//#region ../../internals/utils/src/urlPath.d.ts

@@ -178,160 +172,2 @@ type URLObject = {

//#endregion
//#region src/Telemetry.d.ts
type OtlpStringValue = {
stringValue: string;
};
type OtlpBoolValue = {
boolValue: boolean;
};
type OtlpIntValue = {
intValue: number;
};
type OtlpDoubleValue = {
doubleValue: number;
};
type OtlpBytesValue = {
bytesValue: string;
};
type OtlpArrayValue = {
arrayValue: {
values: Array<OtlpAnyValue>;
};
};
type OtlpKvListValue = {
kvlistValue: {
values: Array<OtlpKeyValue>;
};
};
type OtlpAnyValue = OtlpStringValue | OtlpBoolValue | OtlpIntValue | OtlpDoubleValue | OtlpBytesValue | OtlpArrayValue | OtlpKvListValue;
type OtlpKeyValue = {
key: string;
value: OtlpAnyValue;
};
type OtlpResource = {
attributes: Array<OtlpKeyValue>;
droppedAttributesCount?: number;
};
type OtlpInstrumentationScope = {
name: string;
version?: string;
attributes?: Array<OtlpKeyValue>;
droppedAttributesCount?: number;
};
/** https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto#L103 */
type OtlpSpanKind = 0 | 1 | 2 | 3 | 4 | 5;
/** 0 = STATUS_CODE_UNSET, 1 = STATUS_CODE_OK, 2 = STATUS_CODE_ERROR */
type OtlpStatusCode = 0 | 1 | 2;
type OtlpStatus = {
code: OtlpStatusCode;
message?: string;
};
type OtlpSpan = {
traceId: string;
spanId: string;
traceState?: string;
parentSpanId?: string;
name: string;
kind: OtlpSpanKind;
startTimeUnixNano: string;
endTimeUnixNano: string;
attributes?: Array<OtlpKeyValue>;
droppedAttributesCount?: number;
events?: Array<OtlpSpanEvent>;
droppedEventsCount?: number;
links?: Array<OtlpSpanLink>;
droppedLinksCount?: number;
status?: OtlpStatus;
};
type OtlpSpanEvent = {
timeUnixNano: string;
name: string;
attributes?: Array<OtlpKeyValue>;
droppedAttributesCount?: number;
};
type OtlpSpanLink = {
traceId: string;
spanId: string;
traceState?: string;
attributes?: Array<OtlpKeyValue>;
droppedAttributesCount?: number;
};
type OtlpScopeSpans = {
scope: OtlpInstrumentationScope;
spans: Array<OtlpSpan>;
schemaUrl?: string;
};
type OtlpResourceSpans = {
resource: OtlpResource;
scopeSpans: Array<OtlpScopeSpans>;
schemaUrl?: string;
};
/** Root payload sent to POST /v1/traces */
type OtlpExportTraceServiceRequest = {
resourceSpans: Array<OtlpResourceSpans>;
};
/**
* Anonymous plugin name and options snapshot sent with each telemetry event.
*/
type TelemetryPlugin = {
/**
* Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.
*/
name: string;
/**
* anonymized plugin options snapshot, values are included but cannot be traced back to the user.
*/
options: Record<string, unknown>;
};
type TelemetryEvent = {
command: string;
kubbVersion: string;
nodeVersion: string;
/**
* Name of the JavaScript runtime that executed the run, `'bun'`, `'deno'`, or `'node'`.
*/
runtime: RuntimeName;
/**
* Major version of the active runtime, e.g. `'1'` under Bun or `'22'` under Node.
*/
runtimeVersion: string;
platform: string;
ci: boolean;
plugins: Array<TelemetryPlugin>;
duration: number;
filesCreated: number;
status: 'success' | 'failed';
};
/**
* Anonymous OTLP usage telemetry for the Kubb run. All methods are static, so call them as
* `Telemetry.build(...)`, `Telemetry.send(...)`, and `Telemetry.isDisabled()`. No file paths,
* OpenAPI specs, or secrets are ever included, and sending fails silently to never break a run.
*/
declare class Telemetry {
/**
* Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.
*/
static isDisabled(): boolean;
/**
* Build an anonymous telemetry payload from a completed generation run.
*/
static build(options: {
command: 'generate' | 'mcp' | 'validate' | 'agent';
kubbVersion: string;
plugins?: Array<TelemetryPlugin>;
hrStart: [number, number];
filesCreated?: number;
status: 'success' | 'failed';
}): TelemetryEvent;
/**
* Convert a {@link TelemetryEvent} into an OTLP-compatible JSON trace payload.
* See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
*/
static buildOtlpPayload(event: TelemetryEvent): OtlpExportTraceServiceRequest;
/**
* Send an anonymous telemetry event to the Kubb OTLP endpoint. Respects `DO_NOT_TRACK` and
* `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run.
*/
static send(event: TelemetryEvent): Promise<void>;
}
//#endregion
//#region src/storages/fsStorage.d.ts

@@ -387,3 +223,3 @@ /**

//#endregion
export { type Adapter, type AdapterFactoryOptions, type AdapterSource, AsyncEventEmitter, type BannerMeta, type BuildOutput, type CLIOptions, type Cache, type CachedSnapshot, type Config, type Diagnostic, type DiagnosticByCode, type DiagnosticDoc, type DiagnosticKind, type DiagnosticLocation, type DiagnosticSeverity, Diagnostics, type Exclude, type FileProcessorHooks, type FsCacheOptions, type GenerationResult, type Generator, type GeneratorContext, type Group, type Include, type InputData, type InputPath, type Kubb, type KubbBuildEndContext, type KubbBuildStartContext, type KubbConfigEndContext, type KubbDiagnosticContext, KubbDriver, type KubbErrorContext, type KubbFileProcessingUpdate, type KubbFilesProcessingEndContext, type KubbFilesProcessingStartContext, type KubbFilesProcessingUpdateContext, type KubbGenerationEndContext, type KubbGenerationStartContext, type KubbHookEndContext, type KubbHookLineContext, type KubbHookStartContext, type KubbHooks, type KubbInfoContext, type KubbLifecycleStartContext, type KubbPluginEndContext, type KubbPluginSetupContext, type KubbPluginStartContext, type KubbPluginsEndContext, type KubbSuccessContext, type KubbWarnContext, type Logger, type LoggerContext, type LoggerOptions, type Middleware, type NormalizedPlugin, type Output, type OutputMode, type OutputOptions, type Override, type ParsedFile, type Parser, type PerformanceDiagnostic, type Plugin, type PluginFactoryOptions, type PossibleConfig, type ProblemCode, type ProblemDiagnostic, type Renderer, type RendererFactory, type Reporter, type ReporterContext, type ReporterName, type ResolveBannerContext, type ResolveBannerFile, type ResolveOptionsContext, type Resolver, type ResolverContext, type ResolverFileParams, type ResolverPathParams, type SerializedDiagnostic, type Storage, Telemetry, URLPath, type UpdateDiagnostic, type UserConfig, type UserLogger, type UserReporter, ast, cliReporter, createAdapter, createCache, createKubb, createRenderer, createReporter, createStorage, defineGenerator, defineLogger, defineMiddleware, defineParser, definePlugin, defineResolver, fileReporter, fsCache, fsStorage, jsonReporter, logLevel, memoryStorage, selectReporters };
export { type Adapter, type AdapterFactoryOptions, type AdapterSource, AsyncEventEmitter, type BannerMeta, BuildOutput, CLIOptions, type Cache, type CachedSnapshot, Config, type Diagnostic, type DiagnosticByCode, type DiagnosticDoc, type DiagnosticKind, type DiagnosticLocation, type DiagnosticSeverity, Diagnostics, type Exclude, type FileProcessorHooks, type FsCacheOptions, type GenerationResult, type Generator, type GeneratorContext, type Group, type Include, InputData, InputPath, type Kubb, KubbBuildEndContext, KubbBuildStartContext, KubbDiagnosticContext, KubbDriver, KubbErrorContext, KubbFileProcessingUpdate, KubbFilesProcessingEndContext, KubbFilesProcessingStartContext, KubbFilesProcessingUpdateContext, KubbGenerationEndContext, KubbGenerationStartContext, KubbHookEndContext, KubbHookLineContext, KubbHookStartContext, KubbHooks, KubbInfoContext, KubbLifecycleStartContext, type KubbPluginEndContext, type KubbPluginSetupContext, type KubbPluginStartContext, KubbPluginsEndContext, KubbSuccessContext, KubbWarnContext, type NormalizedPlugin, type Output, type OutputMode, type OutputOptions, type Override, type ParsedFile, type Parser, type PerformanceDiagnostic, type Plugin, type PluginFactoryOptions, PossibleConfig, type ProblemCode, type ProblemDiagnostic, type Renderer, type RendererFactory, type Reporter, type ReporterContext, type ReporterName, type ResolveBannerContext, type ResolveBannerFile, type ResolveOptionsContext, type Resolver, type ResolverContext, type ResolverFileParams, type ResolverPathParams, type SerializedDiagnostic, type Storage, URLPath, type UpdateDiagnostic, UserConfig, type UserReporter, ast, cliReporter, createAdapter, createCache, createKubb, createRenderer, createReporter, createStorage, defineGenerator, defineParser, definePlugin, defineResolver, fileReporter, fsCache, fsStorage, jsonReporter, logLevel, memoryStorage };
//# sourceMappingURL=index.d.ts.map
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const require_memoryStorage = require("./memoryStorage-DlQ_0_bo.cjs");
const require_memoryStorage = require("./memoryStorage-CIEzDI6b.cjs");
let node_path = require("node:path");

@@ -4,0 +4,0 @@ node_path = require_memoryStorage.__toESM(node_path, 1);

import { t as __name } from "./chunk-C0LytTxp.js";
import { G as KubbDriver, Gt as AdapterFactoryOptions, H as Generator, Q as NormalizedPlugin, Wt as Adapter, h as Config, it as PluginFactoryOptions, yt as Parser } from "./diagnostics-CtTSRHX7.js";
import { Ft as Adapter, G as Parser, It as AdapterFactoryOptions, X as KubbDriver, h as Config, lt as PluginFactoryOptions, q as Generator, rt as NormalizedPlugin } from "./diagnostics-BCcJbIpZ.js";
import { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from "@kubb/ast";

@@ -4,0 +4,0 @@

import "./chunk-C0LytTxp.js";
import { i as FileManager, l as camelCase, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-CC6FfUEu.js";
import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-Bdv42rxp.js";
import path, { resolve } from "node:path";

@@ -4,0 +4,0 @@ import { transform } from "@kubb/ast";

{
"name": "@kubb/core",
"version": "5.0.0-beta.50",
"version": "5.0.0-beta.51",
"description": "Core engine for Kubb's plugin-based code generation system. Provides the plugin driver, file manager, defineConfig, and build orchestration used by every Kubb plugin.",

@@ -59,11 +59,10 @@ "keywords": [

"dependencies": {
"tinyexec": "~1.1.2",
"@kubb/ast": "5.0.0-beta.50"
"@kubb/ast": "5.0.0-beta.51"
},
"devDependencies": {
"@internals/utils": "0.0.0",
"@kubb/renderer-jsx": "5.0.0-beta.50"
"@kubb/renderer-jsx": "5.0.0-beta.51"
},
"peerDependencies": {
"@kubb/renderer-jsx": "5.0.0-beta.50"
"@kubb/renderer-jsx": "5.0.0-beta.51"
},

@@ -70,0 +69,0 @@ "engines": {

@@ -5,5 +5,87 @@ import { createHash } from 'node:crypto'

import { createCache } from '../createCache.ts'
import { Manifest } from '../Manifest.ts'
/**
* Bookkeeping for one cached build: the relative paths it covers and timestamps used by the pruner.
*/
type ManifestEntry = {
files: Array<string>
createdAt: number
lastAccess: number
}
/**
* The on-disk manifest: a version marker plus an entry per cached build, keyed by fingerprint.
*/
type ManifestData = {
version: number
entries: Record<string, ManifestEntry>
}
/**
* Reads and prunes the local cache manifest. All methods are static, so call them as
* `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the
* cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`.
*/
class Manifest {
/**
* On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a
* mismatch makes the whole local cache read as empty.
*/
static version = 1
/**
* Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads
* as an empty manifest.
*/
static async read(dir: string): Promise<ManifestData> {
try {
const parsed = JSON.parse(await read(join(dir, 'manifest.json'))) as ManifestData
if (parsed.version !== Manifest.version || typeof parsed.entries !== 'object') {
return Manifest.#empty()
}
return parsed
} catch {
return Manifest.#empty()
}
}
/**
* Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the
* surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO.
*/
static prune(
manifest: ManifestData,
{ maxEntries, ttlDays, now }: { maxEntries: number; ttlDays: number; now: number },
): {
manifest: ManifestData
removed: Array<string>
} {
const ttlMs = ttlDays * 24 * 60 * 60 * 1000
const removed: Array<string> = []
const kept: Array<[string, ManifestEntry]> = []
for (const [key, entry] of Object.entries(manifest.entries)) {
if (now - entry.lastAccess > ttlMs) {
removed.push(key)
} else {
kept.push([key, entry])
}
}
if (kept.length > maxEntries) {
kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess)
for (const [key] of kept.splice(maxEntries)) {
removed.push(key)
}
}
return { manifest: { version: Manifest.version, entries: Object.fromEntries(kept) }, removed }
}
static #empty(): ManifestData {
return { version: Manifest.version, entries: {} }
}
}
/**
* Options for {@link fsCache}.

@@ -10,0 +92,0 @@ */

@@ -7,7 +7,2 @@ /**

/**
* OpenTelemetry ingestion endpoint for anonymous usage telemetry.
*/
export const OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const
/**
* Maximum number of █ characters in a plugin timing bar.

@@ -14,0 +9,0 @@ */

import { resolve } from 'node:path'
import type { PossiblePromise } from '@internals/utils'
import { AsyncEventEmitter, BuildError } from '@internals/utils'
import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
import { HOOK_LISTENERS_PER_PLUGIN } from './constants.ts'
import type { Adapter } from './createAdapter.ts'
import { type Diagnostic, Diagnostics, type ProblemDiagnostic, type UpdateDiagnostic } from './diagnostics.ts'
import type { Cache } from './createCache.ts'
import { Diagnostics } from './diagnostics.ts'
import { createStorage, type Storage } from './createStorage.ts'
import type { GeneratorContext } from './defineGenerator.ts'
import type { Middleware } from './defineMiddleware.ts'
import type { Parser } from './defineParser.ts'
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, Plugin } from './definePlugin.ts'
import type { Reporter, ReporterName } from './createReporter.ts'
import { KubbDriver } from './KubbDriver.ts'
import { fsStorage } from './storages/fsStorage.ts'
import type { BuildOutput, Config, KubbHooks, UserConfig } from './types.ts'
/**
* Safely extracts a type from a registry, returning `{}` if the key doesn't exist.
* Enables optional interface augmentation for `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry`
* without requiring changes to core.
*
* @internal
*/
type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
/**
* Path to an input file to generate from, absolute or relative to the config file. The adapter
* parses it (e.g. an OpenAPI YAML or JSON spec) into the universal AST.
*/
export type InputPath = {
/**
* Path to your Swagger/OpenAPI file, absolute or relative to the config file location.
*
* @example
* ```ts
* { path: './petstore.yaml' }
* { path: '/absolute/path/to/openapi.json' }
* ```
*/
path: string
}
/**
* Inline spec to generate from, passed directly instead of read from a file. A string
* (YAML/JSON) or a parsed object.
*/
export type InputData = {
/**
* Swagger/OpenAPI data as a string (YAML/JSON) or a parsed object.
*
* @example
* ```ts
* { data: fs.readFileSync('./openapi.yaml', 'utf8') }
* { data: { openapi: '3.1.0', info: { ... } } }
* ```
*/
data: string | unknown
}
type Input = InputPath | InputData
/**
* Resolved build configuration for a Kubb run: what to generate from (adapter, input), where to
* write it (output), how (plugins, middleware), and the runtime pieces (parsers, storage). See
* `UserConfig` for the relaxed form with defaults applied.
*
* @private
*/
export type Config<TInput = Input> = {
/**
* Display name for this configuration in CLI output and logs.
* Useful when running multiple builds with `defineConfig` arrays.
*
* @example
* ```ts
* name: 'api-client'
* ```
*/
name?: string
/**
* Project root directory, absolute or relative to the config file. Already
* resolved on the `Config` instance (see `UserConfig` for the optional
* form that defaults to `process.cwd()`).
*/
root: string
/**
* Parsers that convert generated files into strings. Each parser handles a
* set of file extensions, and a fallback parser handles anything else.
*
* Already resolved on the `Config` instance (see `UserConfig` for the
* optional form that defaults to `[parserTs, parserTsx, parserMd]`).
*
* @example
* ```ts
* import { defineConfig } from 'kubb'
* import { parserTs, parserTsx } from '@kubb/parser-ts'
*
* export default defineConfig({
* parsers: [parserTs, parserTsx],
* })
* ```
*/
parsers: Array<Parser>
/**
* Adapter that parses input files into the universal AST representation.
* Use `@kubb/adapter-oas` for OpenAPI/Swagger or `@kubb/adapter-asyncapi` for other formats.
*
* When omitted, Kubb runs in plugin-only mode: `kubb:plugin:setup` fires and files
* injected via `injectFile` are written, but no AST walk occurs and generator hooks
* (`kubb:generate:schema`, `kubb:generate:operation`) are never emitted.
*
* @example
* ```ts
* import { adapterOas } from '@kubb/adapter-oas'
* export default defineConfig({
* adapter: adapterOas(),
* input: { path: './petstore.yaml' },
* })
* ```
*/
adapter?: Adapter
/**
* Source file or data to generate code from.
* Use `input.path` for a file path or `input.data` for inline data.
* Required when an adapter is configured. Omit it when running in plugin-only mode.
*/
input?: TInput
output: {
/**
* Output directory for generated files, absolute or relative to `root`. Plugins can nest
* subdirectories under it by grouping strategy (tag, path).
*
* @example
* ```ts
* output: {
* path: './src/gen', // generates ./src/gen/api.ts, ./src/gen/types.ts, etc.
* }
* ```
*/
path: string
/**
* Remove every file in the output directory before the build, so stale output isn't mixed
* with new files. Leave `false` to preserve manual edits in the output directory.
*
* @default false
* @example
* ```ts
* clean: true // wipes ./src/gen/* before generating
* ```
*/
clean?: boolean
/**
* Format the generated files after generation. `'auto'` runs the first formatter it finds
* (oxfmt, biome, or prettier), a named tool forces that one, and `false` skips formatting.
*
* @default false
* @example
* ```ts
* format: 'auto' // auto-detect prettier, biome, or oxfmt
* format: 'prettier' // force prettier
* format: false // skip formatting
* ```
*/
format?: 'auto' | 'prettier' | 'biome' | 'oxfmt' | false
/**
* Lint the generated files after generation. `'auto'` runs the first linter it finds
* (oxlint, biome, or eslint), a named tool forces that one, and `false` skips linting.
*
* @default false
* @example
* ```ts
* lint: 'auto' // auto-detect oxlint, biome, or eslint
* lint: 'eslint' // force eslint
* lint: false // skip linting
* ```
*/
lint?: 'auto' | 'eslint' | 'biome' | 'oxlint' | false
/**
* Rewrite import extensions in generated files, e.g. emit `.js` imports from `.ts` sources for
* ESM dual packages. Keys are the source extension, values the output, and `''` drops it.
*
* @default { '.ts': '.ts' }
* @example
* ```ts
* extension: { '.ts': '.js' } // generates import './api.js' instead of './api.ts'
* extension: { '.ts': '', '.tsx': '.jsx' }
* ```
*/
extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
/**
* Banner prepended to every generated file. `'simple'` is the basic Kubb notice, `'full'` adds
* source, title, description, and API version, and `false` omits it.
*
* @default 'simple'
* @example
* ```ts
* defaultBanner: 'simple' // "This file was autogenerated by Kubb"
* defaultBanner: 'full' // adds source, title, description, API version
* defaultBanner: false // no banner
* ```
*/
defaultBanner?: 'simple' | 'full' | false
/**
* Overwrite existing files when `true`, skip files that already exist when `false`. Individual
* plugins can override it. Keep `false` to avoid clobbering local edits in the output folder.
*
* @default false
* @example
* ```ts
* override: true // regenerate everything, even existing files
* override: false // skip files that already exist
* ```
*/
override?: boolean
} & ExtractRegistryKey<Kubb.ConfigOptionsRegistry, 'output'>
/**
* Where generated files are persisted. Defaults to `fsStorage()` (disk). Pass `memoryStorage()`
* to keep files in RAM, or implement `Storage` for a custom backend such as cloud or a database.
*
* @default fsStorage()
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
*
* // Keep generated files in memory (useful for testing, CI pipelines)
* storage: memoryStorage()
*
* // Use custom S3 storage
* storage: myS3Storage()
* ```
*
* @see {@link Storage} interface for implementing custom backends.
*/
storage: Storage
/**
* Incremental build cache. Kubb fingerprints the inputs (spec content, config, plugin options,
* versions) and, on an unchanged "hot" run, restores the previously generated output instead of
* regenerating it. Same idea as Nx's computation cache.
*
* `defineConfig` enables `fsCache()` (local disk under `node_modules/.cache/kubb`) by default.
* Pass another backend to change where snapshots live, or `false` to turn caching off. A bare
* `createKubb` leaves it off unless a cache is provided.
*
* @example
* ```ts
* import { fsCache } from '@kubb/core'
*
* cache: fsCache({ dir: '.kubb-cache' })
* cache: false
* ```
*
* @see {@link Cache} interface for implementing custom backends.
*/
cache?: Cache
/**
* Plugins that run during the build to generate code and transform the AST. Each one processes
* the adapter's AST and can emit files for a different target (TypeScript, Zod, Faker). A plugin
* that depends on another throws when that plugin isn't registered.
*
* @example
* ```ts
* import { pluginTs } from '@kubb/plugin-ts'
* import { pluginZod } from '@kubb/plugin-zod'
*
* plugins: [
* pluginTs({ output: { path: './src/gen' } }),
* pluginZod({ output: { path: './src/gen' } }),
* ]
* ```
*/
plugins: Array<Plugin>
/**
* Middleware instances that observe build events and post-process generated code.
*
* Middleware fires AFTER all plugins for each event. Perfect for tasks like:
* - Auditing what was generated
* - Adding barrel/index files
* - Validating output
* - Running custom transformations
*
* @example
* ```ts
* import { middlewareBarrel } from '@kubb/middleware-barrel'
*
* middleware: [middlewareBarrel()]
* ```
*
* @see {@link defineMiddleware} to create custom middleware.
*/
middleware?: Array<Middleware>
/**
* Lifecycle hooks that execute during or after the build process.
*
* Hooks allow you to run external tools (prettier, eslint, custom scripts) based on build events.
* Currently supports the `done` hook which fires after all plugins and middleware complete.
*
* @example
* ```ts
* hooks: {
* done: 'prettier --write "./src/gen"', // auto-format generated files
* // or multiple commands:
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
* }
* ```
*/
hooks?: {
/**
* Command(s) to run after all plugins and middleware complete generation.
*
* Useful for post-processing: formatting, linting, copying files, or custom validation.
* Pass a single command string or array of command strings to run sequentially.
* Commands are executed relative to the `root` directory.
*
* @example
* ```ts
* done: 'prettier --write "./src/gen"'
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
* ```
*/
done?: string | Array<string>
}
/**
* The reporters available to the run, registered as instances. The host
* (the CLI via `--reporter`) selects which ones to trigger by `name` with {@link selectReporters}.
* `defineConfig` from the `kubb` package registers the built-in `cli`, `json`, and `file`
* reporters by default.
*
* - `cli` writes the end-of-run summary to the terminal.
* - `json` writes a machine-readable report to stdout, for CI.
* - `file` writes a debug log to `.kubb/<name>-<timestamp>.log`.
*
* @example
* ```ts
* import { cliReporter, jsonReporter } from '@kubb/core'
*
* reporters: [cliReporter, jsonReporter, myReporter]
* ```
*/
reporters: Array<Reporter>
}
/**
* Partial `Config` for user-facing entry points with sensible defaults.
*
* `UserConfig` is what you pass to `defineConfig()`. It has optional `root`, `plugins`, `parsers`, and `adapter`
* fields (which fall back to sensible defaults). All other Config options are available, including `output`, `input`,
* `storage`, `middleware`, and `hooks`.
*
* @example
* ```ts
* export default defineConfig({
* input: { path: './petstore.yaml' },
* output: { path: './src/gen' },
* plugins: [pluginTs(), pluginZod()],
* })
* ```
*/
export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters' | 'cache'> & {
/**
* Incremental build cache. Defaults to `fsCache()` (local disk). Pass another {@link Cache}
* backend, or `false` to turn caching off.
*/
cache?: Cache | false
/**
* Project root directory, absolute or relative to the config file location.
* @default process.cwd()
*/
root?: string
/**
* Custom parsers that convert generated AST nodes to strings (TypeScript, JSON, markdown, etc.).
* @default [parserTs, parserTsx, parserMd] // applied by `defineConfig` from the `kubb` package
*/
parsers?: Array<Parser>
/**
* Adapter that parses your API specification into Kubb's universal AST.
* When omitted, Kubb runs in plugin-only mode.
*/
adapter?: Adapter
/**
* Plugins that execute during the build to generate code and transform the AST.
* @default []
*/
plugins?: Array<Plugin>
/**
* Storage backend that controls where and how generated files are persisted.
* @default fsStorage()
*/
storage?: Storage
/**
* Reporters available to the run. `defineConfig` registers the built-in `cli`, `json`, and
* `file` reporters when omitted.
* @default [cliReporter, jsonReporter, fileReporter] // applied by `defineConfig` from the `kubb` package
*/
reporters?: Array<Reporter>
}
declare global {
namespace Kubb {
/**
* Registry that maps plugin names to their `PluginFactoryOptions`.
* Augment this interface in each plugin's `types.ts` to enable automatic
* typing for `getPlugin` and `requirePlugin`.
*
* @example
* ```ts
* // packages/plugin-ts/src/types.ts
* declare global {
* namespace Kubb {
* interface PluginRegistry {
* 'plugin-ts': PluginTs
* }
* }
* }
* ```
*/
interface PluginRegistry {}
/**
* Extension point for root `Config['output']` options.
* Augment the `output` key in middleware or plugin packages to add extra fields
* to the global output configuration without touching core types.
*
* @example
* ```ts
* // packages/middleware-barrel/src/types.ts
* declare global {
* namespace Kubb {
* interface ConfigOptionsRegistry {
* output: {
* barrel?: import('./types.ts').BarrelConfig | false
* }
* }
* }
* }
* ```
*/
interface ConfigOptionsRegistry {}
/**
* Extension point for per-plugin `Output` options.
* Augment the `output` key in middleware or plugin packages to add extra fields
* to the per-plugin output configuration without touching core types.
*
* @example
* ```ts
* // packages/middleware-barrel/src/types.ts
* declare global {
* namespace Kubb {
* interface PluginOptionsRegistry {
* output: {
* barrel?: import('./types.ts').PluginBarrelConfig | false
* }
* }
* }
* }
* ```
*/
interface PluginOptionsRegistry {}
}
}
/**
* Lifecycle events emitted during Kubb code generation.
* Attach listeners before calling `setup()` or `build()` to observe and react to build progress.
*
* @example
* ```ts
* kubb.hooks.on('kubb:lifecycle:start', () => {
* console.log('Starting Kubb generation')
* })
*
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
* console.log(`${plugin.name} completed in ${duration}ms`)
* })
* ```
*/
export interface KubbHooks {
'kubb:lifecycle:start': [ctx: KubbLifecycleStartContext]
'kubb:lifecycle:end': []
'kubb:config:start': []
'kubb:config:end': [ctx: KubbConfigEndContext]
'kubb:generation:start': [ctx: KubbGenerationStartContext]
'kubb:generation:end': [ctx: KubbGenerationEndContext]
'kubb:format:start': []
'kubb:format:end': []
'kubb:lint:start': []
'kubb:lint:end': []
'kubb:hooks:start': []
'kubb:hooks:end': []
'kubb:hook:start': [ctx: KubbHookStartContext]
'kubb:hook:line': [ctx: KubbHookLineContext]
'kubb:hook:end': [ctx: KubbHookEndContext]
'kubb:info': [ctx: KubbInfoContext]
'kubb:error': [ctx: KubbErrorContext]
'kubb:success': [ctx: KubbSuccessContext]
'kubb:warn': [ctx: KubbWarnContext]
'kubb:diagnostic': [ctx: KubbDiagnosticContext]
'kubb:files:processing:start': [ctx: KubbFilesProcessingStartContext]
'kubb:files:processing:update': [ctx: KubbFilesProcessingUpdateContext]
'kubb:files:processing:end': [ctx: KubbFilesProcessingEndContext]
'kubb:plugin:start': [ctx: KubbPluginStartContext]
'kubb:plugin:end': [ctx: KubbPluginEndContext]
'kubb:plugin:setup': [ctx: KubbPluginSetupContext]
'kubb:build:start': [ctx: KubbBuildStartContext]
'kubb:plugins:end': [ctx: KubbPluginsEndContext]
'kubb:build:end': [ctx: KubbBuildEndContext]
'kubb:generate:schema': [node: SchemaNode, ctx: GeneratorContext]
'kubb:generate:operation': [node: OperationNode, ctx: GeneratorContext]
'kubb:generate:operations': [nodes: Array<OperationNode>, ctx: GeneratorContext]
}
export type KubbBuildStartContext = {
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Adapter that parsed the input into the universal AST.
*/
adapter: Adapter
/**
* Metadata about the parsed document (title, version, base URL, circular schema names, enum names).
* To observe individual schemas and operations use the `kubb:generate:schema` / `kubb:generate:operation` hooks.
*/
meta: InputMeta | undefined
/**
* Looks up a registered plugin by name, typed by the plugin registry.
*/
getPlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
getPlugin(name: string): Plugin | undefined
/**
* Snapshot of all files accumulated so far.
*/
readonly files: ReadonlyArray<FileNode>
/**
* Adds or merges one or more files into the file manager.
*/
upsertFile: (...files: Array<FileNode>) => void
}
export type KubbPluginsEndContext = {
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Snapshot of all files accumulated across all plugins.
*/
readonly files: ReadonlyArray<FileNode>
/**
* Adds or merges one or more files into the file manager.
*/
upsertFile: (...files: Array<FileNode>) => void
}
export type KubbBuildEndContext = {
/**
* All files generated during this build.
*/
files: Array<FileNode>
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Absolute path to the output directory.
*/
outputDir: string
}
export type KubbLifecycleStartContext = {
/**
* Current Kubb version string.
*/
version: string
}
export type KubbConfigEndContext = {
/**
* All resolved configs after defaults are applied.
*/
configs: Array<Config>
}
export type KubbGenerationStartContext = {
/**
* Resolved configuration for this generation run.
*/
config: Config
}
export type KubbGenerationEndContext = {
/**
* Resolved configuration for this generation run.
*/
config: Config
/**
* Read-only view of the files written during this build.
* Reads go directly to `config.storage`, nothing extra is held in memory.
*
* @example Read a generated file
* `const code = await storage.getItem('/src/gen/pet.ts')`
*
* @example Walk every generated file
* ```ts
* for (const path of await storage.getKeys()) {
* const code = await storage.getItem(path)
* }
* ```
*/
storage: Storage
/**
* Diagnostics collected during the build: error/warning/info problems plus a
* `performance` diagnostic per plugin. The end-of-run summary derives its failure counts
* and per-plugin timings from these. Set by the CLI runner, omitted by other callers.
*/
diagnostics?: Array<Diagnostic>
/**
* `'success'` when all plugins completed without errors, `'failed'` otherwise.
*/
status?: 'success' | 'failed'
/**
* High-resolution start time from `process.hrtime()`, used to compute the elapsed time.
*/
hrStart?: [number, number]
/**
* Total number of files created during this run.
*/
filesCreated?: number
}
export type KubbInfoContext = {
/**
* Human-readable info message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbErrorContext = {
/**
* The caught error.
*/
error: Error
/**
* Optional structured metadata for additional context.
*/
meta?: Record<string, unknown>
}
export type KubbSuccessContext = {
/**
* Human-readable success message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbWarnContext = {
/**
* Human-readable warning message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbDiagnosticContext = {
/**
* The structured diagnostic to render: a build problem or a version-update notice.
*/
diagnostic: ProblemDiagnostic | UpdateDiagnostic
}
export type KubbFilesProcessingStartContext = {
/**
* Files about to be serialized and written.
*/
files: Array<FileNode>
}
export type KubbFileProcessingUpdate = {
/**
* Number of files processed so far in this batch.
*/
processed: number
/**
* Total number of files in this batch.
*/
total: number
/**
* Completion percentage, `0` to `100`.
*/
percentage: number
/**
* Serialized file content, or `undefined` when the file produced no output.
*/
source?: string
/**
* The file that was just processed.
*/
file: FileNode
/**
* Resolved configuration for this build.
*/
config: Config
}
export type KubbFilesProcessingUpdateContext = {
/**
* All files processed in this flush chunk.
*/
files: Array<KubbFileProcessingUpdate>
}
export type KubbFilesProcessingEndContext = {
/**
* All files that were serialized in this batch.
*/
files: Array<FileNode>
}
export type KubbHookStartContext = {
/**
* Optional identifier for correlating start/end events.
*/
id?: string
/**
* The shell command that is about to run.
*/
command: string
/**
* Parsed argument list, when available.
*/
args?: ReadonlyArray<string>
}
/**
* Emitted for each line streamed from a hook's stdout while it runs.
* A logger correlates the line to its active UI element via `id`.
*/
export type KubbHookLineContext = {
/**
* Identifier matching the corresponding `kubb:hook:start` event.
*/
id: string
/**
* A single streamed stdout line, without its trailing newline.
*/
line: string
}
export type KubbHookEndContext = {
/**
* Optional identifier matching the corresponding `kubb:hook:start` event.
*/
id?: string
/**
* The shell command that ran.
*/
command: string
/**
* Parsed argument list, when available.
*/
args?: ReadonlyArray<string>
/**
* `true` when the command exited with code `0`.
*/
success: boolean
/**
* Error thrown by the command, or `null` on success.
*/
error: Error | null
/**
* Captured stdout from the process, populated when it exits non-zero.
*/
stdout?: string
/**
* Captured stderr from the process, populated when it exits non-zero.
*/
stderr?: string
}
/**
* CLI options derived from command-line flags.
*/
export type CLIOptions = {
/**
* Path to the Kubb config file.
*/
config?: string
/**
* OpenAPI input path passed as the positional argument to `kubb generate`.
* Overrides `config.input.path` when set.
*/
input?: string
/**
* Re-run generation whenever input files change.
*/
watch?: boolean
/**
* Controls how much output the CLI prints.
*
* @default 'info'
*/
logLevel?: 'silent' | 'info' | 'verbose'
/**
* Reporters selected on the CLI via `--reporter`, overriding `config.reporters`.
*/
reporters?: Array<ReporterName>
/**
* Turns off the incremental build cache for this run, forcing a full regeneration.
* Set by the `--no-cache` flag.
*/
noCache?: boolean
}
/**
* All accepted forms of a Kubb configuration.
* Accepts `Config`/`Config[]`/promise or a factory (optionally receiving `TCliOptions`.
*/
export type PossibleConfig<TCliOptions = undefined> =
| PossiblePromise<Config | Array<Config>>
| ((...args: [TCliOptions] extends [undefined] ? [] : [TCliOptions]) => PossiblePromise<Config | Array<Config>>)
/**
* Full output produced by a successful or failed build.
*/
export type BuildOutput = {
/**
* Structured diagnostics collected during the build: error/warning/info problems
* (each with a code, severity, and where known a JSON-pointer location) plus a
* `performance` diagnostic per plugin. Includes a top-level diagnostic when the build
* threw before completing. Use {@link Diagnostics.hasError} to test for failure.
*/
diagnostics: Array<Diagnostic>
/**
* All files generated during this build.
*/
files: Array<FileNode>
/**
* The plugin driver that orchestrated this build.
*/
driver: KubbDriver
/**
* Read-only view of every file written during this build.
* Reads go straight to `config.storage`, nothing extra is held in memory.
*
* @example Read a generated file
* `const code = await buildOutput.storage.getItem('/src/gen/pet.ts')`
*
* @example List all generated file paths
* `const paths = await buildOutput.storage.getKeys()`
*/
storage: Storage
}
/**
* Builds a `Storage` view scoped to the file paths produced by the current build.

@@ -935,5 +77,9 @@ * Reads delegate to the underlying `storage` so source bytes stay where they were

* Kubb code-generation instance bound to a single config entry. Resolves the user
* config during `setup()` and shares `hooks`, `storage`, `driver`, and `config` across
* the `setup → build` lifecycle.
* config in the constructor, so `config` is available right away, and shares `hooks`,
* `storage`, and `driver` across the `setup → build` lifecycle.
*
* `createKubb` takes a plain, serializable config object (the shape `defineConfig`
* produces), not a fluent builder. Config stays plain data so it can be cache
* fingerprinted and validated against the shipped JSON schema.
*
* Attach event listeners to `.hooks` before calling `setup()` or `build()`.

@@ -950,4 +96,3 @@ *

readonly hooks: AsyncEventEmitter<KubbHooks>
readonly #userConfig: UserConfig
#config: Config | null = null
readonly config: Config
#driver: KubbDriver | null = null

@@ -957,3 +102,3 @@ #storage: Storage | null = null

constructor(userConfig: UserConfig, options: CreateKubbOptions = {}) {
this.#userConfig = userConfig
this.config = resolveConfig(userConfig)
this.hooks = options.hooks ?? new AsyncEventEmitter<KubbHooks>()

@@ -972,12 +117,7 @@ }

get config(): Config {
if (!this.#config) throw new Error('[kubb] setup() must be called before accessing config')
return this.#config
}
/**
* Resolves config and initializes the driver. `build()` calls this automatically.
* Initializes the driver and storage. `build()` calls this automatically.
*/
async setup(): Promise<void> {
const config = resolveConfig(this.#userConfig)
const config = this.config
const driver = new KubbDriver(config, { hooks: this.hooks })

@@ -997,3 +137,2 @@ const storage = createSourcesView(config.storage)

this.#config = config
this.#driver = driver

@@ -1021,3 +160,4 @@ this.#storage = storage

* Runs the full pipeline and captures errors in `BuildOutput` instead of throwing.
* Automatically calls `setup()` if needed.
* Automatically calls `setup()` if needed. This is the canonical call: it never throws on
* plugin errors, so callers stay in control of how failures surface.
*/

@@ -1024,0 +164,0 @@ async safeBuild(): Promise<BuildOutput> {

@@ -1,6 +0,18 @@

import type { logLevel } from './defineLogger.ts'
import type { Config } from './createKubb.ts'
import type { Config } from './types.ts'
import type { Diagnostic } from './diagnostics.ts'
/**
* Numeric log-level thresholds used internally to compare verbosity.
*
* Higher numbers are more verbose.
*/
export const logLevel = {
silent: Number.NEGATIVE_INFINITY,
error: 0,
warn: 1,
info: 3,
verbose: 4,
} as const
/**
* A built-in reporter that renders a run's output, independent of the live logger view.

@@ -123,26 +135,1 @@ *

}
/**
* Picks the reporters whose `name` matches one of `names`, in the order the names are given.
* The config carries every available reporter, and the host selects which to activate by name
* (the CLI maps `--reporter` to this). Duplicate names and names without a matching reporter are
* skipped.
*/
export function selectReporters(reporters: ReadonlyArray<Reporter>, names: ReadonlyArray<string>): Array<Reporter> {
const seen = new Set<string>()
const selected: Array<Reporter> = []
for (const name of names) {
if (seen.has(name)) {
continue
}
seen.add(name)
const reporter = reporters.find((candidate) => candidate.name === name)
if (reporter) {
selected.push(reporter)
}
}
return selected
}

@@ -144,3 +144,3 @@ import path from 'node:path'

*
* Supplied by the generator (or the barrel middleware) at resolve-time and merged
* Supplied by the generator (or the barrel plugin) at resolve-time and merged
* into `BannerMeta` so a `banner`/`footer` function can branch on the file kind,

@@ -218,3 +218,3 @@ * e.g. omit a `'use server'` directive on re-export files.

* `banner`/`footer` function. Missing fields default to empty/`false` so the object shape
* is stable even when a caller (e.g. the barrel middleware) has no document metadata.
* is stable even when a caller (e.g. the barrel plugin) has no document metadata.
*/

@@ -221,0 +221,0 @@ function buildBannerMeta({ meta, file }: { meta: InputMeta | undefined; file: ResolveBannerFile | undefined }): BannerMeta {

@@ -7,3 +7,3 @@ import { AsyncLocalStorage } from 'node:async_hooks'

import { type DiagnosticCode, diagnosticCode } from './constants.ts'
import type { KubbHooks } from './createKubb.ts'
import type { KubbHooks } from './types.ts'

@@ -10,0 +10,0 @@ /**

@@ -18,3 +18,3 @@ import { AsyncEventEmitter } from '@internals/utils'

// Incoming file (b) takes precedence for banner/footer so a barrel file (whose
// banner/footer the barrel middleware resolves last) wins over a plugin-generated
// banner/footer the barrel plugin resolves last) wins over a plugin-generated
// file at the same path.

@@ -21,0 +21,0 @@ banner: b.banner,

@@ -6,3 +6,3 @@ import { createHash } from 'node:crypto'

import type { AdapterSource } from './createAdapter.ts'
import type { Config } from './createKubb.ts'
import type { Config } from './types.ts'

@@ -32,3 +32,3 @@ /**

* Computes a cache key from everything that affects the generated output: the spec content, the
* output-shaping config, each plugin's name and options, the middleware names, the running
* output-shaping config, each plugin's name and options, the running
* `@kubb/core` version, and the cache format version. Returns `null` when the input can't be

@@ -56,3 +56,2 @@ * fingerprinted (remote URL or no adapter source), which disables caching for that build.

plugins: config.plugins.map((plugin) => ({ name: plugin.name, options: plugin.options })),
middleware: (config.middleware ?? []).map((middleware) => middleware.name),
}

@@ -59,0 +58,0 @@

@@ -7,12 +7,9 @@ export { AsyncEventEmitter, URLPath } from '@internals/utils'

export { createKubb } from './createKubb.ts'
export { createReporter, selectReporters } from './createReporter.ts'
export { createReporter, logLevel } from './createReporter.ts'
export { cliReporter } from './reporters/cliReporter.ts'
export { fileReporter } from './reporters/fileReporter.ts'
export { jsonReporter } from './reporters/jsonReporter.ts'
export { Telemetry } from './Telemetry.ts'
export { createRenderer } from './createRenderer.ts'
export { createStorage } from './createStorage.ts'
export { defineGenerator } from './defineGenerator.ts'
export { defineLogger, logLevel } from './defineLogger.ts'
export { defineMiddleware } from './defineMiddleware.ts'
export { defineParser } from './defineParser.ts'

@@ -19,0 +16,0 @@ export { definePlugin } from './definePlugin.ts'

@@ -19,3 +19,2 @@ import { basename, join, relative, resolve } from 'node:path'

import { FileProcessor } from './FileProcessor.ts'
import { type HookListener, HookRegistry } from './HookRegistry.ts'
import { Transform } from './Transform.ts'

@@ -31,3 +30,2 @@

KubbPluginSetupContext,
Middleware,
NormalizedPlugin,

@@ -42,2 +40,6 @@ PluginFactoryOptions,

type HookListener<TArgs extends Array<unknown>, TResult = void> = (...args: TArgs) => TResult | Promise<TResult>
type ListenerEntry = [event: keyof KubbHooks & string, handler: HookListener<Array<unknown>, unknown>]
type RequirePluginContext = {

@@ -88,7 +90,6 @@ /**

/**
* Tracks every listener the driver added (plugin, middleware, generator) so `dispose()` can
* remove them in one pass. Middleware registers after plugins, so it fires last via `Set`
* insertion order. External `hooks.on(...)` listeners are not tracked.
* Tracks every listener the driver added (plugin, generator) so `dispose()` can remove them
* in one pass. External `hooks.on(...)` listeners are not tracked.
*/
readonly #registry: HookRegistry<KubbHooks>
readonly #listeners: Array<ListenerEntry> = []

@@ -105,5 +106,13 @@ /**

this.adapter = config.adapter ?? null
this.#registry = new HookRegistry({ emitter: options.hooks })
}
/**
* Attaches a listener to the shared emitter and tracks it so `dispose()` can remove it later.
* Listeners attached directly via `hooks.on(...)` are not tracked and survive disposal.
*/
#trackListener<K extends keyof KubbHooks & string>(event: K, handler: HookListener<KubbHooks[K], unknown>): void {
this.hooks.on(event, handler as HookListener<KubbHooks[K]>)
this.#listeners.push([event, handler as HookListener<Array<unknown>, unknown>])
}
async setup() {

@@ -130,9 +139,2 @@ const normalized: Array<NormalizedPlugin> = this.config.plugins.map((rawPlugin) => this.#normalizePlugin(rawPlugin as Plugin))

if (this.config.middleware) {
for (const middleware of this.config.middleware) {
for (const event of Object.keys(middleware.hooks) as Array<keyof KubbHooks & string>) {
this.#registerMiddleware(event, middleware.hooks)
}
}
}
if (this.config.adapter) {

@@ -188,12 +190,2 @@ this.#adapterSource = inputToAdapterSource(this.config)

#registerMiddleware<K extends keyof KubbHooks & string>(event: K, middlewareHooks: Middleware['hooks']) {
const handler = middlewareHooks[event]
if (!handler) {
return
}
this.#registry.register({ event, handler, source: 'middleware' })
}
/**

@@ -245,3 +237,3 @@ * Registers a hook-style plugin's lifecycle handlers on the shared `AsyncEventEmitter`.

this.#registry.register({ event: 'kubb:plugin:setup', handler: setupHandler, source: 'plugin' })
this.#trackListener('kubb:plugin:setup', setupHandler)
}

@@ -255,7 +247,3 @@

this.#registry.register({
event,
handler: handler as HookListener<KubbHooks[typeof event], unknown>,
source: 'plugin',
})
this.#trackListener(event, handler as HookListener<KubbHooks[typeof event], unknown>)
}

@@ -307,3 +295,3 @@ }

this.#registry.register({ event: 'kubb:generate:schema', handler: schemaHandler, source: 'driver' })
this.#trackListener('kubb:generate:schema', schemaHandler)
}

@@ -319,3 +307,3 @@

this.#registry.register({ event: 'kubb:generate:operation', handler: operationHandler, source: 'driver' })
this.#trackListener('kubb:generate:operation', operationHandler)
}

@@ -330,3 +318,3 @@

this.#registry.register({ event: 'kubb:generate:operations', handler: operationsHandler, source: 'driver' })
this.#trackListener('kubb:generate:operations', operationsHandler)
}

@@ -464,3 +452,3 @@

// Plugins-end listeners (barrel middleware etc.) may have queued more files.
// Plugins-end listeners (barrel plugin etc.) may have queued more files.
await processor.drain()

@@ -562,3 +550,3 @@

* When `entries` is empty or `this.inputNode` is `null`, every entry still gets a
* `kubb:plugin:end` so middleware listeners (the barrel writer and friends) complete.
* `kubb:plugin:end` so post-plugin listeners (the barrel writer and friends) complete.
*/

@@ -828,3 +816,6 @@ async #runGenerators(

dispose(): void {
this.#registry.dispose()
for (const [event, handler] of this.#listeners) {
this.hooks.off(event, handler as HookListener<KubbHooks[typeof event]>)
}
this.#listeners.length = 0
this.#eventGeneratorPlugins.clear()

@@ -831,0 +822,0 @@ this.#transforms.dispose()

import { styleText } from 'node:util'
import { formatMs, randomCliColor } from '@internals/utils'
import { SUMMARY_MAX_BAR_LENGTH, SUMMARY_TIME_SCALE_DIVISOR } from '../constants.ts'
import { logLevel as logLevelMap } from '../defineLogger.ts'
import { createReporter } from '../createReporter.ts'
import { createReporter, logLevel as logLevelMap } from '../createReporter.ts'
import { buildReport, type Report } from './report.ts'

@@ -7,0 +6,0 @@

@@ -0,1 +1,834 @@

import type { PossiblePromise } from '@internals/utils'
import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast'
import type { Adapter } from './createAdapter.ts'
import type { Cache } from './createCache.ts'
import type { Reporter, ReporterName } from './createReporter.ts'
import type { Storage } from './createStorage.ts'
import type { Diagnostic, ProblemDiagnostic, UpdateDiagnostic } from './diagnostics.ts'
import type { GeneratorContext } from './defineGenerator.ts'
import type { Parser } from './defineParser.ts'
import type { KubbPluginEndContext, KubbPluginSetupContext, KubbPluginStartContext, Plugin } from './definePlugin.ts'
import type { KubbDriver } from './KubbDriver.ts'
/**
* Safely extracts a type from a registry, returning `{}` if the key doesn't exist.
* Enables optional interface augmentation for `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry`
* without requiring changes to core.
*
* @internal
*/
type ExtractRegistryKey<T, K extends PropertyKey> = K extends keyof T ? T[K] : {}
/**
* Path to an input file to generate from, absolute or relative to the config file. The adapter
* parses it (e.g. an OpenAPI YAML or JSON spec) into the universal AST.
*/
export type InputPath = {
/**
* Path to your Swagger/OpenAPI file, absolute or relative to the config file location.
*
* @example
* ```ts
* { path: './petstore.yaml' }
* { path: '/absolute/path/to/openapi.json' }
* ```
*/
path: string
}
/**
* Inline spec to generate from, passed directly instead of read from a file. A string
* (YAML/JSON) or a parsed object.
*/
export type InputData = {
/**
* Swagger/OpenAPI data as a string (YAML/JSON) or a parsed object.
*
* @example
* ```ts
* { data: fs.readFileSync('./openapi.yaml', 'utf8') }
* { data: { openapi: '3.1.0', info: { ... } } }
* ```
*/
data: string | unknown
}
type Input = InputPath | InputData
/**
* Resolved build configuration for a Kubb run: what to generate from (adapter, input), where to
* write it (output), how (plugins), and the runtime pieces (parsers, storage). See
* `UserConfig` for the relaxed form with defaults applied.
*
* @private
*/
export type Config<TInput = Input> = {
/**
* Display name for this configuration in CLI output and logs.
* Useful when running multiple builds with `defineConfig` arrays.
*
* @example
* ```ts
* name: 'api-client'
* ```
*/
name?: string
/**
* Project root directory, absolute or relative to the config file. Already
* resolved on the `Config` instance (see `UserConfig` for the optional
* form that defaults to `process.cwd()`).
*/
root: string
/**
* Parsers that convert generated files into strings. Each parser handles a
* set of file extensions, and a fallback parser handles anything else.
*
* Already resolved on the `Config` instance (see `UserConfig` for the
* optional form that defaults to `[parserTs, parserTsx, parserMd]`).
*
* @example
* ```ts
* import { defineConfig } from 'kubb'
* import { parserTs, parserTsx } from '@kubb/parser-ts'
*
* export default defineConfig({
* parsers: [parserTs, parserTsx],
* })
* ```
*/
parsers: Array<Parser>
/**
* Adapter that parses input files into the universal AST representation.
* Use `@kubb/adapter-oas` for OpenAPI/Swagger or `@kubb/adapter-asyncapi` for other formats.
*
* When omitted, Kubb runs in plugin-only mode: `kubb:plugin:setup` fires and files
* injected via `injectFile` are written, but no AST walk occurs and generator hooks
* (`kubb:generate:schema`, `kubb:generate:operation`) are never emitted.
*
* @example
* ```ts
* import { adapterOas } from '@kubb/adapter-oas'
* export default defineConfig({
* adapter: adapterOas(),
* input: { path: './petstore.yaml' },
* })
* ```
*/
adapter?: Adapter
/**
* Source file or data to generate code from.
* Use `input.path` for a file path or `input.data` for inline data.
* Required when an adapter is configured. Omit it when running in plugin-only mode.
*/
input?: TInput
output: {
/**
* Output directory for generated files, absolute or relative to `root`. Plugins can nest
* subdirectories under it by grouping strategy (tag, path).
*
* @example
* ```ts
* output: {
* path: './src/gen', // generates ./src/gen/api.ts, ./src/gen/types.ts, etc.
* }
* ```
*/
path: string
/**
* Remove every file in the output directory before the build, so stale output isn't mixed
* with new files. Leave `false` to preserve manual edits in the output directory.
*
* @default false
* @example
* ```ts
* clean: true // wipes ./src/gen/* before generating
* ```
*/
clean?: boolean
/**
* Format the generated files after generation. `'auto'` runs the first formatter it finds
* (oxfmt, biome, or prettier), a named tool forces that one, and `false` skips formatting.
*
* @default false
* @example
* ```ts
* format: 'auto' // auto-detect prettier, biome, or oxfmt
* format: 'prettier' // force prettier
* format: false // skip formatting
* ```
*/
format?: 'auto' | 'prettier' | 'biome' | 'oxfmt' | false
/**
* Lint the generated files after generation. `'auto'` runs the first linter it finds
* (oxlint, biome, or eslint), a named tool forces that one, and `false` skips linting.
*
* @default false
* @example
* ```ts
* lint: 'auto' // auto-detect oxlint, biome, or eslint
* lint: 'eslint' // force eslint
* lint: false // skip linting
* ```
*/
lint?: 'auto' | 'eslint' | 'biome' | 'oxlint' | false
/**
* Rewrite import extensions in generated files, e.g. emit `.js` imports from `.ts` sources for
* ESM dual packages. Keys are the source extension, values the output, and `''` drops it.
*
* @default { '.ts': '.ts' }
* @example
* ```ts
* extension: { '.ts': '.js' } // generates import './api.js' instead of './api.ts'
* extension: { '.ts': '', '.tsx': '.jsx' }
* ```
*/
extension?: Record<FileNode['extname'], FileNode['extname'] | ''>
/**
* Banner prepended to every generated file. `'simple'` is the basic Kubb notice, `'full'` adds
* source, title, description, and API version, and `false` omits it.
*
* @default 'simple'
* @example
* ```ts
* defaultBanner: 'simple' // "This file was autogenerated by Kubb"
* defaultBanner: 'full' // adds source, title, description, API version
* defaultBanner: false // no banner
* ```
*/
defaultBanner?: 'simple' | 'full' | false
/**
* Overwrite existing files when `true`, skip files that already exist when `false`. Individual
* plugins can override it. Keep `false` to avoid clobbering local edits in the output folder.
*
* @default false
* @example
* ```ts
* override: true // regenerate everything, even existing files
* override: false // skip files that already exist
* ```
*/
override?: boolean
} & ExtractRegistryKey<Kubb.ConfigOptionsRegistry, 'output'>
/**
* Where generated files are persisted. Defaults to `fsStorage()` (disk). Pass `memoryStorage()`
* to keep files in RAM, or implement `Storage` for a custom backend such as cloud or a database.
*
* @default fsStorage()
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
*
* // Keep generated files in memory (useful for testing, CI pipelines)
* storage: memoryStorage()
*
* // Use custom S3 storage
* storage: myS3Storage()
* ```
*
* @see {@link Storage} interface for implementing custom backends.
*/
storage: Storage
/**
* Incremental build cache. Kubb fingerprints the inputs (spec content, config, plugin options,
* versions) and, on an unchanged "hot" run, restores the previously generated output instead of
* regenerating it. Same idea as Nx's computation cache.
*
* `defineConfig` enables `fsCache()` (local disk under `node_modules/.cache/kubb`) by default.
* Pass another backend to change where snapshots live, or `false` to turn caching off. A bare
* `createKubb` leaves it off unless a cache is provided.
*
* @example
* ```ts
* import { fsCache } from '@kubb/core'
*
* cache: fsCache({ dir: '.kubb-cache' })
* cache: false
* ```
*
* @see {@link Cache} interface for implementing custom backends.
*/
cache?: Cache
/**
* Plugins that run during the build to generate code and transform the AST. Each one processes
* the adapter's AST and can emit files for a different target (TypeScript, Zod, Faker). A plugin
* that depends on another throws when that plugin isn't registered.
*
* @example
* ```ts
* import { pluginTs } from '@kubb/plugin-ts'
* import { pluginZod } from '@kubb/plugin-zod'
*
* plugins: [
* pluginTs({ output: { path: './src/gen' } }),
* pluginZod({ output: { path: './src/gen' } }),
* ]
* ```
*/
plugins: Array<Plugin>
/**
* Lifecycle hooks that execute during or after the build process.
*
* Hooks allow you to run external tools (prettier, eslint, custom scripts) based on build events.
* Currently supports the `done` hook which fires after all plugins complete.
*
* @example
* ```ts
* hooks: {
* done: 'prettier --write "./src/gen"', // auto-format generated files
* // or multiple commands:
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
* }
* ```
*/
hooks?: {
/**
* Command(s) to run after all plugins complete generation.
*
* Useful for post-processing: formatting, linting, copying files, or custom validation.
* Pass a single command string or array of command strings to run sequentially.
* Commands are executed relative to the `root` directory.
*
* @example
* ```ts
* done: 'prettier --write "./src/gen"'
* done: ['prettier --write "./src/gen"', 'eslint --fix "./src/gen"']
* ```
*/
done?: string | Array<string>
}
/**
* The reporters available to the run, registered as instances. The host
* (the CLI via `--reporter`) selects which ones to trigger by `name` with {@link selectReporters}.
* `defineConfig` from the `kubb` package registers the built-in `cli`, `json`, and `file`
* reporters by default.
*
* - `cli` writes the end-of-run summary to the terminal.
* - `json` writes a machine-readable report to stdout, for CI.
* - `file` writes a debug log to `.kubb/<name>-<timestamp>.log`.
*
* @example
* ```ts
* import { cliReporter, jsonReporter } from '@kubb/core'
*
* reporters: [cliReporter, jsonReporter, myReporter]
* ```
*/
reporters: Array<Reporter>
}
/**
* Partial `Config` for user-facing entry points with sensible defaults.
*
* `UserConfig` is what you pass to `defineConfig()`. It has optional `root`, `plugins`, `parsers`, and `adapter`
* fields (which fall back to sensible defaults). All other Config options are available, including `output`, `input`,
* `storage`, and `hooks`.
*
* @example
* ```ts
* export default defineConfig({
* input: { path: './petstore.yaml' },
* output: { path: './src/gen' },
* plugins: [pluginTs(), pluginZod()],
* })
* ```
*/
export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters' | 'cache'> & {
/**
* Incremental build cache. Defaults to `fsCache()` (local disk). Pass another {@link Cache}
* backend, or `false` to turn caching off.
*/
cache?: Cache | false
/**
* Project root directory, absolute or relative to the config file location.
* @default process.cwd()
*/
root?: string
/**
* Custom parsers that convert generated AST nodes to strings (TypeScript, JSON, markdown, etc.).
* @default [parserTs, parserTsx, parserMd] // applied by `defineConfig` from the `kubb` package
*/
parsers?: Array<Parser>
/**
* Adapter that parses your API specification into Kubb's universal AST.
* When omitted, Kubb runs in plugin-only mode.
*/
adapter?: Adapter
/**
* Plugins that execute during the build to generate code and transform the AST.
* @default []
*/
plugins?: Array<Plugin>
/**
* Storage backend that controls where and how generated files are persisted.
* @default fsStorage()
*/
storage?: Storage
/**
* Reporters available to the run. `defineConfig` registers the built-in `cli`, `json`, and
* `file` reporters when omitted.
* @default [cliReporter, jsonReporter, fileReporter] // applied by `defineConfig` from the `kubb` package
*/
reporters?: Array<Reporter>
}
declare global {
namespace Kubb {
/**
* Registry that maps plugin names to their `PluginFactoryOptions`.
* Augment this interface in each plugin's `types.ts` to enable automatic
* typing for `getPlugin` and `requirePlugin`.
*
* @example
* ```ts
* // packages/plugin-ts/src/types.ts
* declare global {
* namespace Kubb {
* interface PluginRegistry {
* 'plugin-ts': PluginTs
* }
* }
* }
* ```
*/
interface PluginRegistry {}
/**
* Extension point for root `Config['output']` options.
* Augment the `output` key in plugin packages to add extra fields
* to the global output configuration without touching core types.
*
* @example
* ```ts
* // packages/plugin-barrel/src/plugin.ts
* declare global {
* namespace Kubb {
* interface ConfigOptionsRegistry {
* output: {
* barrel?: import('./types.ts').BarrelConfig | false
* }
* }
* }
* }
* ```
*/
interface ConfigOptionsRegistry {}
/**
* Extension point for per-plugin `Output` options.
* Augment the `output` key in plugin packages to add extra fields
* to the per-plugin output configuration without touching core types.
*
* @example
* ```ts
* // packages/plugin-barrel/src/plugin.ts
* declare global {
* namespace Kubb {
* interface PluginOptionsRegistry {
* output: {
* barrel?: import('./types.ts').PluginBarrelConfig | false
* }
* }
* }
* }
* ```
*/
interface PluginOptionsRegistry {}
}
}
/**
* Lifecycle events emitted during Kubb code generation.
* Attach listeners before calling `setup()` or `build()` to observe and react to build progress.
*
* @example
* ```ts
* kubb.hooks.on('kubb:lifecycle:start', () => {
* console.log('Starting Kubb generation')
* })
*
* kubb.hooks.on('kubb:plugin:end', ({ plugin, duration }) => {
* console.log(`${plugin.name} completed in ${duration}ms`)
* })
* ```
*/
export interface KubbHooks {
'kubb:lifecycle:start': [ctx: KubbLifecycleStartContext]
'kubb:lifecycle:end': []
'kubb:generation:start': [ctx: KubbGenerationStartContext]
'kubb:generation:end': [ctx: KubbGenerationEndContext]
'kubb:format:start': []
'kubb:format:end': []
'kubb:lint:start': []
'kubb:lint:end': []
'kubb:hooks:start': []
'kubb:hooks:end': []
'kubb:hook:start': [ctx: KubbHookStartContext]
'kubb:hook:line': [ctx: KubbHookLineContext]
'kubb:hook:end': [ctx: KubbHookEndContext]
'kubb:info': [ctx: KubbInfoContext]
'kubb:error': [ctx: KubbErrorContext]
'kubb:success': [ctx: KubbSuccessContext]
'kubb:warn': [ctx: KubbWarnContext]
'kubb:diagnostic': [ctx: KubbDiagnosticContext]
'kubb:files:processing:start': [ctx: KubbFilesProcessingStartContext]
'kubb:files:processing:update': [ctx: KubbFilesProcessingUpdateContext]
'kubb:files:processing:end': [ctx: KubbFilesProcessingEndContext]
'kubb:plugin:start': [ctx: KubbPluginStartContext]
'kubb:plugin:end': [ctx: KubbPluginEndContext]
'kubb:plugin:setup': [ctx: KubbPluginSetupContext]
'kubb:build:start': [ctx: KubbBuildStartContext]
'kubb:plugins:end': [ctx: KubbPluginsEndContext]
'kubb:build:end': [ctx: KubbBuildEndContext]
'kubb:generate:schema': [node: SchemaNode, ctx: GeneratorContext]
'kubb:generate:operation': [node: OperationNode, ctx: GeneratorContext]
'kubb:generate:operations': [nodes: Array<OperationNode>, ctx: GeneratorContext]
}
export type KubbBuildStartContext = {
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Adapter that parsed the input into the universal AST.
*/
adapter: Adapter
/**
* Metadata about the parsed document (title, version, base URL, circular schema names, enum names).
* To observe individual schemas and operations use the `kubb:generate:schema` / `kubb:generate:operation` hooks.
*/
meta: InputMeta | undefined
/**
* Looks up a registered plugin by name, typed by the plugin registry.
*/
getPlugin<TName extends keyof Kubb.PluginRegistry>(name: TName): Plugin<Kubb.PluginRegistry[TName]> | undefined
getPlugin(name: string): Plugin | undefined
/**
* Snapshot of all files accumulated so far.
*/
readonly files: ReadonlyArray<FileNode>
/**
* Adds or merges one or more files into the file manager.
*/
upsertFile: (...files: Array<FileNode>) => void
}
export type KubbPluginsEndContext = {
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Snapshot of all files accumulated across all plugins.
*/
readonly files: ReadonlyArray<FileNode>
/**
* Adds or merges one or more files into the file manager.
*/
upsertFile: (...files: Array<FileNode>) => void
}
export type KubbBuildEndContext = {
/**
* All files generated during this build.
*/
files: Array<FileNode>
/**
* Resolved configuration for this build.
*/
config: Config
/**
* Absolute path to the output directory.
*/
outputDir: string
}
export type KubbLifecycleStartContext = {
/**
* Current Kubb version string.
*/
version: string
}
export type KubbGenerationStartContext = {
/**
* Resolved configuration for this generation run.
*/
config: Config
}
export type KubbGenerationEndContext = {
/**
* Resolved configuration for this generation run.
*/
config: Config
/**
* Read-only view of the files written during this build.
* Reads go directly to `config.storage`, nothing extra is held in memory.
*
* @example Read a generated file
* `const code = await storage.getItem('/src/gen/pet.ts')`
*
* @example Walk every generated file
* ```ts
* for (const path of await storage.getKeys()) {
* const code = await storage.getItem(path)
* }
* ```
*/
storage: Storage
/**
* Diagnostics collected during the build: error/warning/info problems plus a
* `performance` diagnostic per plugin. The end-of-run summary derives its failure counts
* and per-plugin timings from these. Set by the CLI runner, omitted by other callers.
*/
diagnostics?: Array<Diagnostic>
/**
* `'success'` when all plugins completed without errors, `'failed'` otherwise.
*/
status?: 'success' | 'failed'
/**
* High-resolution start time from `process.hrtime()`, used to compute the elapsed time.
*/
hrStart?: [number, number]
/**
* Total number of files created during this run.
*/
filesCreated?: number
}
export type KubbInfoContext = {
/**
* Human-readable info message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbErrorContext = {
/**
* The caught error.
*/
error: Error
/**
* Optional structured metadata for additional context.
*/
meta?: Record<string, unknown>
}
export type KubbSuccessContext = {
/**
* Human-readable success message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbWarnContext = {
/**
* Human-readable warning message.
*/
message: string
/**
* Optional supplementary detail.
*/
info?: string
}
export type KubbDiagnosticContext = {
/**
* The structured diagnostic to render: a build problem or a version-update notice.
*/
diagnostic: ProblemDiagnostic | UpdateDiagnostic
}
export type KubbFilesProcessingStartContext = {
/**
* Files about to be serialized and written.
*/
files: Array<FileNode>
}
export type KubbFileProcessingUpdate = {
/**
* Number of files processed so far in this batch.
*/
processed: number
/**
* Total number of files in this batch.
*/
total: number
/**
* Completion percentage, `0` to `100`.
*/
percentage: number
/**
* Serialized file content, or `undefined` when the file produced no output.
*/
source?: string
/**
* The file that was just processed.
*/
file: FileNode
/**
* Resolved configuration for this build.
*/
config: Config
}
export type KubbFilesProcessingUpdateContext = {
/**
* All files processed in this flush chunk.
*/
files: Array<KubbFileProcessingUpdate>
}
export type KubbFilesProcessingEndContext = {
/**
* All files that were serialized in this batch.
*/
files: Array<FileNode>
}
export type KubbHookStartContext = {
/**
* Optional identifier for correlating start/end events.
*/
id?: string
/**
* The shell command that is about to run.
*/
command: string
/**
* Parsed argument list, when available.
*/
args?: ReadonlyArray<string>
}
/**
* Emitted for each line streamed from a hook's stdout while it runs.
* A logger correlates the line to its active UI element via `id`.
*/
export type KubbHookLineContext = {
/**
* Identifier matching the corresponding `kubb:hook:start` event.
*/
id: string
/**
* A single streamed stdout line, without its trailing newline.
*/
line: string
}
export type KubbHookEndContext = {
/**
* Optional identifier matching the corresponding `kubb:hook:start` event.
*/
id?: string
/**
* The shell command that ran.
*/
command: string
/**
* Parsed argument list, when available.
*/
args?: ReadonlyArray<string>
/**
* `true` when the command exited with code `0`.
*/
success: boolean
/**
* Error thrown by the command, or `null` on success.
*/
error: Error | null
/**
* Captured stdout from the process, populated when it exits non-zero.
*/
stdout?: string
/**
* Captured stderr from the process, populated when it exits non-zero.
*/
stderr?: string
}
/**
* CLI options derived from command-line flags.
*/
export type CLIOptions = {
/**
* Path to the Kubb config file.
*/
config?: string
/**
* OpenAPI input path passed as the positional argument to `kubb generate`.
* Overrides `config.input.path` when set.
*/
input?: string
/**
* Re-run generation whenever input files change.
*/
watch?: boolean
/**
* Controls how much output the CLI prints.
*
* @default 'info'
*/
logLevel?: 'silent' | 'info' | 'verbose'
/**
* Reporters selected on the CLI via `--reporter`, overriding `config.reporters`.
*/
reporters?: Array<ReporterName>
/**
* Turns off the incremental build cache for this run, forcing a full regeneration.
* Set by the `--no-cache` flag.
*/
noCache?: boolean
}
/**
* All accepted forms of a Kubb configuration.
* Accepts `Config`/`Config[]`/promise or a factory (optionally receiving `TCliOptions`.
*/
export type PossibleConfig<TCliOptions = undefined> =
| PossiblePromise<Config | Array<Config>>
| ((...args: [TCliOptions] extends [undefined] ? [] : [TCliOptions]) => PossiblePromise<Config | Array<Config>>)
/**
* Full output produced by a successful or failed build.
*/
export type BuildOutput = {
/**
* Structured diagnostics collected during the build: error/warning/info problems
* (each with a code, severity, and where known a JSON-pointer location) plus a
* `performance` diagnostic per plugin. Includes a top-level diagnostic when the build
* threw before completing. Use {@link Diagnostics.hasError} to test for failure.
*/
diagnostics: Array<Diagnostic>
/**
* All files generated during this build.
*/
files: Array<FileNode>
/**
* The plugin driver that orchestrated this build.
*/
driver: KubbDriver
/**
* Read-only view of every file written during this build.
* Reads go straight to `config.storage`, nothing extra is held in memory.
*
* @example Read a generated file
* `const code = await buildOutput.storage.getItem('/src/gen/pet.ts')`
*
* @example List all generated file paths
* `const paths = await buildOutput.storage.getKeys()`
*/
storage: Storage
}
export type { Adapter, AdapterFactoryOptions, AdapterSource } from './createAdapter.ts'

@@ -17,32 +850,3 @@ export type { Cache, CachedSnapshot } from './createCache.ts'

} from './diagnostics.ts'
export type {
BuildOutput,
CLIOptions,
Config,
InputData,
InputPath,
Kubb,
KubbBuildEndContext,
KubbBuildStartContext,
KubbConfigEndContext,
KubbDiagnosticContext,
KubbErrorContext,
KubbFileProcessingUpdate,
KubbFilesProcessingEndContext,
KubbFilesProcessingStartContext,
KubbFilesProcessingUpdateContext,
KubbGenerationEndContext,
KubbGenerationStartContext,
KubbHookEndContext,
KubbHookLineContext,
KubbHookStartContext,
KubbHooks,
KubbInfoContext,
KubbLifecycleStartContext,
KubbPluginsEndContext,
KubbSuccessContext,
KubbWarnContext,
PossibleConfig,
UserConfig,
} from './createKubb.ts'
export type { Kubb } from './createKubb.ts'
export type { GenerationResult, Reporter, ReporterContext, ReporterName, UserReporter } from './createReporter.ts'

@@ -53,4 +857,2 @@ export type { Renderer, RendererFactory } from './createRenderer.ts'

export type { Generator, GeneratorContext } from './defineGenerator.ts'
export type { Logger, LoggerContext, LoggerOptions, UserLogger } from './defineLogger.ts'
export type { Middleware } from './defineMiddleware.ts'
export type { Parser } from './defineParser.ts'

@@ -57,0 +859,0 @@ export type { Exclude, Group, Include, Output, OutputMode, OutputOptions, Override } from './definePlugin.ts'

Sorry, the diff of this file is too big to display

import "./chunk-C0LytTxp.js";
import { EventEmitter } from "node:events";
import { createFile, extractStringsFromNodes } from "@kubb/ast";
//#region ../../internals/utils/src/errors.ts
/**
* Thrown when one or more errors occur during a Kubb build.
* Carries the full list of underlying errors on `errors`.
*
* @example
* ```ts
* throw new BuildError('Build failed', { errors: [err1, err2] })
* ```
*/
var BuildError = class extends Error {
errors;
constructor(message, options) {
super(message, { cause: options.cause });
this.name = "BuildError";
this.errors = options.errors;
}
};
/**
* Coerces an unknown thrown value to an `Error` instance.
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
*
* @example
* ```ts
* try { ... } catch(err) {
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
* }
* ```
*/
function toError(value) {
return value instanceof Error ? value : new Error(String(value));
}
/**
* Extracts a human-readable message from any thrown value.
*
* @example
* ```ts
* getErrorMessage(new Error('oops')) // 'oops'
* getErrorMessage('plain string') // 'plain string'
* ```
*/
function getErrorMessage(value) {
return value instanceof Error ? value.message : String(value);
}
//#endregion
//#region ../../internals/utils/src/asyncEventEmitter.ts
/**
* Typed `EventEmitter` that awaits all async listeners before resolving.
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
*
* @example
* ```ts
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
* emitter.on('build', async (name) => { console.log(name) })
* await emitter.emit('build', 'petstore') // all listeners awaited
* ```
*/
var AsyncEventEmitter = class {
/**
* Maximum number of listeners per event before Node emits a memory-leak warning.
* @default 10
*/
constructor(maxListener = 10) {
this.#emitter.setMaxListeners(maxListener);
}
#emitter = new EventEmitter();
/**
* Emits `eventName` and awaits all registered listeners sequentially.
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
*
* @example
* ```ts
* await emitter.emit('build', 'petstore')
* ```
*/
emit(eventName, ...eventArgs) {
const listeners = this.#emitter.listeners(eventName);
if (listeners.length === 0) return;
return this.#emitAll(eventName, listeners, eventArgs);
}
async #emitAll(eventName, listeners, eventArgs) {
for (const listener of listeners) try {
await listener(...eventArgs);
} catch (err) {
let serializedArgs;
try {
serializedArgs = JSON.stringify(eventArgs);
} catch {
serializedArgs = String(eventArgs);
}
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
}
}
/**
* Registers a persistent listener for `eventName`.
*
* @example
* ```ts
* emitter.on('build', async (name) => { console.log(name) })
* ```
*/
on(eventName, handler) {
this.#emitter.on(eventName, handler);
}
/**
* Registers a one-shot listener that removes itself after the first invocation.
*
* @example
* ```ts
* emitter.onOnce('build', async (name) => { console.log(name) })
* ```
*/
onOnce(eventName, handler) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
return handler(...args);
};
this.on(eventName, wrapper);
}
/**
* Removes a previously registered listener.
*
* @example
* ```ts
* emitter.off('build', handler)
* ```
*/
off(eventName, handler) {
this.#emitter.off(eventName, handler);
}
/**
* Returns the number of listeners registered for `eventName`.
*
* @example
* ```ts
* emitter.on('build', handler)
* emitter.listenerCount('build') // 1
* ```
*/
listenerCount(eventName) {
return this.#emitter.listenerCount(eventName);
}
/**
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
* Set this above the expected listener count when many listeners attach by design.
*
* @example
* ```ts
* emitter.setMaxListeners(40)
* ```
*/
setMaxListeners(max) {
this.#emitter.setMaxListeners(max);
}
/**
* Returns the current per-event listener ceiling.
*/
getMaxListeners() {
return this.#emitter.getMaxListeners();
}
/**
* Removes all listeners from every event channel.
*
* @example
* ```ts
* emitter.removeAll()
* ```
*/
removeAll() {
this.#emitter.removeAllListeners();
}
};
//#endregion
//#region ../../internals/utils/src/casing.ts
/**
* Shared implementation for camelCase and PascalCase conversion.
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
* and capitalizes each word according to `pascal`.
*
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
*/
function toCamelOrPascal(text, pascal) {
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
if (word.length > 1 && word === word.toUpperCase()) return word;
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
return word.charAt(0).toUpperCase() + word.slice(1);
}).join("").replace(/[^a-zA-Z0-9]/g, "");
}
/**
* Splits `text` on `.` and applies `transformPart` to each segment.
* The last segment receives `isLast = true`, all earlier segments receive `false`.
* Segments are joined with `/` to form a file path.
*
* Only splits on dots followed by a letter so that version numbers
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
*
* Empty segments are filtered before joining. They arise when the text starts with
* a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
* and `'..'` transforms to an empty string). Without this filter the join would produce
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
* generated files to escape the configured output directory.
*/
function applyToFileParts(text, transformPart) {
const parts = text.split(/\.(?=[a-zA-Z])/);
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
}
/**
* Converts `text` to camelCase.
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
*
* @example
* camelCase('hello-world') // 'helloWorld'
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
*/
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
prefix,
suffix
} : {}));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
}
/**
* Converts `text` to PascalCase.
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
*
* @example
* pascalCase('hello-world') // 'HelloWorld'
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
*/
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
prefix,
suffix
}) : camelCase(part));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
}
//#endregion
//#region src/constants.ts
/**
* OpenTelemetry ingestion endpoint for anonymous usage telemetry.
*/
const OTLP_ENDPOINT = "https://otlp.kubb.dev";
/**
* Plugin `include` filter types that select operations directly. When one of these is set
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
*/
const OPERATION_FILTER_TYPES = new Set([
"tag",
"operationId",
"path",
"method",
"contentType"
]);
/**
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
* and stays stable so it can be referenced in tooling and (later) docs. Reference
* these instead of inlining the string at a throw site.
*/
const diagnosticCode = {
/**
* Fallback for an unstructured error with no specific code.
*/
unknown: "KUBB_UNKNOWN",
/**
* The `input.path` file or URL could not be read.
*/
inputNotFound: "KUBB_INPUT_NOT_FOUND",
/**
* An adapter was configured without an `input`.
*/
inputRequired: "KUBB_INPUT_REQUIRED",
/**
* A `$ref` (or equivalent reference) could not be resolved in the source document.
*/
refNotFound: "KUBB_REF_NOT_FOUND",
/**
* A server variable value is not allowed by its `enum`.
*/
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
/**
* A required plugin is missing from the config.
*/
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
/**
* A plugin threw while generating.
*/
pluginFailed: "KUBB_PLUGIN_FAILED",
/**
* A plugin reported a non-fatal warning through `ctx.warn`.
*/
pluginWarning: "KUBB_PLUGIN_WARNING",
/**
* A plugin reported an informational message through `ctx.info`.
*/
pluginInfo: "KUBB_PLUGIN_INFO",
/**
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
* adapters to emit as a `warning`.
*/
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
/**
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
* to emit as an `info`.
*/
deprecated: "KUBB_DEPRECATED",
/**
* An adapter is required but the config has none. The build cannot read the input
* without one.
*/
adapterRequired: "KUBB_ADAPTER_REQUIRED",
/**
* A resolved output path escapes the output directory, which can stem from a path
* traversal in the spec or a misconfigured `group.name`.
*/
pathTraversal: "KUBB_PATH_TRAVERSAL",
/**
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
*/
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
/**
* A post-generate shell hook (`hooks.done`) exited with a failure.
*/
hookFailed: "KUBB_HOOK_FAILED",
/**
* The formatter pass over the generated files failed.
*/
formatFailed: "KUBB_FORMAT_FAILED",
/**
* The linter pass over the generated files failed.
*/
lintFailed: "KUBB_LINT_FAILED",
/**
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
*/
performance: "KUBB_PERFORMANCE",
/**
* Not a failure. A newer Kubb version is available on npm.
*/
updateAvailable: "KUBB_UPDATE_AVAILABLE"
};
//#endregion
//#region src/createStorage.ts
/**
* Defines a custom storage backend. The builder receives user options and
* returns a `Storage` implementation. Kubb ships with filesystem and
* in-memory storages, reach for this when you need to write generated files
* elsewhere (cloud storage, a database, a remote API).
*
* @example In-memory storage (the built-in implementation)
* ```ts
* import { createStorage } from '@kubb/core'
*
* export const memoryStorage = createStorage(() => {
* const store = new Map<string, string>()
*
* return {
* name: 'memory',
* async hasItem(key) {
* return store.has(key)
* },
* async getItem(key) {
* return store.get(key) ?? null
* },
* async setItem(key, value) {
* store.set(key, value)
* },
* async removeItem(key) {
* store.delete(key)
* },
* async getKeys(base) {
* const keys = [...store.keys()]
* return base ? keys.filter((k) => k.startsWith(base)) : keys
* },
* async clear(base) {
* if (!base) store.clear()
* },
* }
* })
* ```
*/
function createStorage(build) {
return (options) => build(options ?? {});
}
//#endregion
//#region src/FileManager.ts
function mergeFile(a, b) {
return {
...a,
banner: b.banner,
footer: b.footer,
sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
};
}
function isIndexPath(path) {
return path.endsWith("/index.ts") || path === "index.ts";
}
function compareFiles(a, b) {
const lenDiff = a.path.length - b.path.length;
if (lenDiff !== 0) return lenDiff;
const aIsIndex = isIndexPath(a.path);
const bIsIndex = isIndexPath(b.path);
if (aIsIndex && !bIsIndex) return 1;
if (!aIsIndex && bIsIndex) return -1;
return 0;
}
/**
* In-memory file store for generated files. Files sharing a `path` are merged
* (sources/imports/exports concatenated). The `files` getter is sorted by
* path length (barrel `index.ts` last within a bucket).
*
* @example
* ```ts
* const manager = new FileManager()
* manager.upsert(myFile)
* manager.files // sorted view
* ```
*/
var FileManager = class {
/**
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
* through `add` or `upsert`.
*/
hooks = new AsyncEventEmitter();
#cache = /* @__PURE__ */ new Map();
#sorted = null;
add(...files) {
return this.#store(files, false);
}
upsert(...files) {
return this.#store(files, true);
}
#store(files, mergeExisting) {
const batch = files.length > 1 ? this.#dedupe(files) : files;
const resolved = [];
for (const file of batch) {
const existing = this.#cache.get(file.path);
const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file);
this.#cache.set(merged.path, merged);
resolved.push(merged);
this.hooks.emit("upsert", merged);
}
if (resolved.length > 0) this.#sorted = null;
return resolved;
}
#dedupe(files) {
const seen = /* @__PURE__ */ new Map();
for (const file of files) {
const prev = seen.get(file.path);
seen.set(file.path, prev ? mergeFile(prev, file) : file);
}
return [...seen.values()];
}
getByPath(path) {
return this.#cache.get(path) ?? null;
}
deleteByPath(path) {
if (!this.#cache.delete(path)) return;
this.#sorted = null;
}
clear() {
this.#cache.clear();
this.#sorted = null;
}
/**
* Releases all stored files and clears every `hooks` listener. Called by the core after
* `kubb:build:end`.
*/
dispose() {
this.clear();
this.hooks.removeAll();
}
[Symbol.dispose]() {
this.dispose();
}
/**
* All stored files in stable sort order (shortest path first, barrel files
* last within a length bucket). Returns a cached view, do not mutate.
*/
get files() {
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
}
};
//#endregion
//#region src/FileProcessor.ts
function joinSources(file) {
const sources = file.sources;
if (sources.length === 0) return "";
const parts = [];
for (const source of sources) {
const text = extractStringsFromNodes(source.nodes);
if (text) parts.push(text);
}
return parts.join("\n\n");
}
/**
* Turns `FileNode`s into source strings and writes them to storage.
*
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
*
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
* written and is meant for the end of a build.
*
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
* `hooks` and re-emit on the kubb bus.
*/
var FileProcessor = class {
hooks = new AsyncEventEmitter();
#parsers;
#storage;
#extension;
#pending = /* @__PURE__ */ new Map();
#runningFlush = null;
constructor(options) {
this.#parsers = options.parsers ?? null;
this.#storage = options.storage;
this.#extension = options.extension ?? null;
}
/**
* Files waiting in the queue.
*/
get size() {
return this.#pending.size;
}
parse(file) {
const parsers = this.#parsers;
const parseExtName = this.#extension?.[file.extname] || void 0;
if (!parsers || !file.extname) return joinSources(file);
const parser = parsers.get(file.extname);
if (!parser) return joinSources(file);
return parser.parse(file, { extname: parseExtName });
}
*stream(files) {
const total = files.length;
if (total === 0) return;
let processed = 0;
for (const file of files) {
const source = this.parse(file);
processed++;
yield {
file,
source,
processed,
total,
percentage: processed / total * 100
};
}
}
async run(files) {
await this.hooks.emit("start", files);
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
file,
source,
processed,
percentage,
total
});
await this.hooks.emit("end", files);
return files;
}
/**
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
*/
enqueue(file) {
this.#pending.set(file.path, file);
this.hooks.emit("enqueue", file);
}
/**
* Starts processing the queued files. Waits for any previous flush to finish (so two
* batches never run together) and then returns without waiting for the new one. The next
* `flush` or `drain` picks up the in-flight task.
*/
async flush() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size === 0) return;
const batch = [...this.#pending.values()];
this.#pending.clear();
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
this.#runningFlush = null;
});
}
/**
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
* when both are done.
*/
async drain() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size > 0) {
const batch = [...this.#pending.values()];
this.#pending.clear();
await this.#processAndWrite(batch);
}
await this.hooks.emit("drain");
}
async #processAndWrite(files) {
const storage = this.#storage;
await this.hooks.emit("start", files);
const queue = [];
for (const item of this.stream(files)) {
await this.hooks.emit("update", item);
if (item.source) {
queue.push(storage.setItem(item.file.path, item.source));
if (queue.length >= 50) await Promise.all(queue.splice(0));
}
}
await Promise.all(queue);
await this.hooks.emit("end", files);
}
/**
* Clears every listener and the pending queue.
*/
dispose() {
this.hooks.removeAll();
this.#pending.clear();
}
[Symbol.dispose]() {
this.dispose();
}
};
//#endregion
//#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js
function _usingCtx() {
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
var n = Error();
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
};
var e = {};
var n = [];
function using(r, e) {
if (null != e) {
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
t && (o = function o() {
try {
t.call(e);
} catch (r) {
return Promise.reject(r);
}
}), n.push({
v: e,
d: o,
a: r
});
} else r && n.push({
d: e,
a: r
});
return e;
}
return {
e,
u: using.bind(null, !1),
a: using.bind(null, !0),
d: function d() {
var o;
var t = this.e;
var s = 0;
function next() {
for (; o = n.pop();) try {
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
if (o.d) {
var r = o.d.call(o.v);
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
} else s |= 1;
} catch (r) {
return err(r);
}
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
if (t !== e) throw t;
}
function err(n) {
return t = t !== e ? new r(n, t) : n, next();
}
return next();
}
};
}
//#endregion
//#region src/storages/memoryStorage.ts
/**
* In-memory storage driver. Useful for testing and dry-run scenarios where
* generated output should be captured without touching the filesystem.
*
* All data lives in a `Map` scoped to the storage instance and is discarded
* when the instance is garbage-collected.
*
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
* import { defineConfig } from 'kubb'
*
* export default defineConfig({
* input: { path: './petStore.yaml' },
* output: { path: './src/gen' },
* storage: memoryStorage(),
* })
* ```
*/
const memoryStorage = createStorage(() => {
const store = /* @__PURE__ */ new Map();
return {
name: "memory",
async hasItem(key) {
return store.has(key);
},
async getItem(key) {
return store.get(key) ?? null;
},
async setItem(key, value) {
store.set(key, value);
},
async removeItem(key) {
store.delete(key);
},
async getKeys(base) {
const keys = [...store.keys()];
return base ? keys.filter((k) => k.startsWith(base)) : keys;
},
async clear(base) {
if (!base) {
store.clear();
return;
}
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
}
};
});
//#endregion
export { createStorage as a, diagnosticCode as c, AsyncEventEmitter as d, BuildError as f, FileManager as i, camelCase as l, _usingCtx as n, OPERATION_FILTER_TYPES as o, getErrorMessage as p, FileProcessor as r, OTLP_ENDPOINT as s, memoryStorage as t, pascalCase as u };
//# sourceMappingURL=memoryStorage-CC6FfUEu.js.map
{"version":3,"file":"memoryStorage-CC6FfUEu.js","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * OpenTelemetry ingestion endpoint for anonymous usage telemetry.\n */\nexport const OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel middleware resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"}
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __name = (target, value) => __defProp(target, "name", {
value,
configurable: true
});
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let node_events = require("node:events");
let _kubb_ast = require("@kubb/ast");
//#region ../../internals/utils/src/errors.ts
/**
* Thrown when one or more errors occur during a Kubb build.
* Carries the full list of underlying errors on `errors`.
*
* @example
* ```ts
* throw new BuildError('Build failed', { errors: [err1, err2] })
* ```
*/
var BuildError = class extends Error {
errors;
constructor(message, options) {
super(message, { cause: options.cause });
this.name = "BuildError";
this.errors = options.errors;
}
};
/**
* Coerces an unknown thrown value to an `Error` instance.
* Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.
*
* @example
* ```ts
* try { ... } catch(err) {
* throw new BuildError('Build failed', { cause: toError(err), errors: [] })
* }
* ```
*/
function toError(value) {
return value instanceof Error ? value : new Error(String(value));
}
/**
* Extracts a human-readable message from any thrown value.
*
* @example
* ```ts
* getErrorMessage(new Error('oops')) // 'oops'
* getErrorMessage('plain string') // 'plain string'
* ```
*/
function getErrorMessage(value) {
return value instanceof Error ? value.message : String(value);
}
//#endregion
//#region ../../internals/utils/src/asyncEventEmitter.ts
/**
* Typed `EventEmitter` that awaits all async listeners before resolving.
* Wraps Node's `EventEmitter` with full TypeScript event-map inference.
*
* @example
* ```ts
* const emitter = new AsyncEventEmitter<{ build: [name: string] }>()
* emitter.on('build', async (name) => { console.log(name) })
* await emitter.emit('build', 'petstore') // all listeners awaited
* ```
*/
var AsyncEventEmitter = class {
/**
* Maximum number of listeners per event before Node emits a memory-leak warning.
* @default 10
*/
constructor(maxListener = 10) {
this.#emitter.setMaxListeners(maxListener);
}
#emitter = new node_events.EventEmitter();
/**
* Emits `eventName` and awaits all registered listeners sequentially.
* Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.
*
* @example
* ```ts
* await emitter.emit('build', 'petstore')
* ```
*/
emit(eventName, ...eventArgs) {
const listeners = this.#emitter.listeners(eventName);
if (listeners.length === 0) return;
return this.#emitAll(eventName, listeners, eventArgs);
}
async #emitAll(eventName, listeners, eventArgs) {
for (const listener of listeners) try {
await listener(...eventArgs);
} catch (err) {
let serializedArgs;
try {
serializedArgs = JSON.stringify(eventArgs);
} catch {
serializedArgs = String(eventArgs);
}
throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) });
}
}
/**
* Registers a persistent listener for `eventName`.
*
* @example
* ```ts
* emitter.on('build', async (name) => { console.log(name) })
* ```
*/
on(eventName, handler) {
this.#emitter.on(eventName, handler);
}
/**
* Registers a one-shot listener that removes itself after the first invocation.
*
* @example
* ```ts
* emitter.onOnce('build', async (name) => { console.log(name) })
* ```
*/
onOnce(eventName, handler) {
const wrapper = (...args) => {
this.off(eventName, wrapper);
return handler(...args);
};
this.on(eventName, wrapper);
}
/**
* Removes a previously registered listener.
*
* @example
* ```ts
* emitter.off('build', handler)
* ```
*/
off(eventName, handler) {
this.#emitter.off(eventName, handler);
}
/**
* Returns the number of listeners registered for `eventName`.
*
* @example
* ```ts
* emitter.on('build', handler)
* emitter.listenerCount('build') // 1
* ```
*/
listenerCount(eventName) {
return this.#emitter.listenerCount(eventName);
}
/**
* Raises or lowers the per-event listener ceiling before Node warns about a memory leak.
* Set this above the expected listener count when many listeners attach by design.
*
* @example
* ```ts
* emitter.setMaxListeners(40)
* ```
*/
setMaxListeners(max) {
this.#emitter.setMaxListeners(max);
}
/**
* Returns the current per-event listener ceiling.
*/
getMaxListeners() {
return this.#emitter.getMaxListeners();
}
/**
* Removes all listeners from every event channel.
*
* @example
* ```ts
* emitter.removeAll()
* ```
*/
removeAll() {
this.#emitter.removeAllListeners();
}
};
//#endregion
//#region ../../internals/utils/src/casing.ts
/**
* Shared implementation for camelCase and PascalCase conversion.
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
* and capitalizes each word according to `pascal`.
*
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
*/
function toCamelOrPascal(text, pascal) {
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
if (word.length > 1 && word === word.toUpperCase()) return word;
if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
return word.charAt(0).toUpperCase() + word.slice(1);
}).join("").replace(/[^a-zA-Z0-9]/g, "");
}
/**
* Splits `text` on `.` and applies `transformPart` to each segment.
* The last segment receives `isLast = true`, all earlier segments receive `false`.
* Segments are joined with `/` to form a file path.
*
* Only splits on dots followed by a letter so that version numbers
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
*
* Empty segments are filtered before joining. They arise when the text starts with
* a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
* and `'..'` transforms to an empty string). Without this filter the join would produce
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
* generated files to escape the configured output directory.
*/
function applyToFileParts(text, transformPart) {
const parts = text.split(/\.(?=[a-zA-Z])/);
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
}
/**
* Converts `text` to camelCase.
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
*
* @example
* camelCase('hello-world') // 'helloWorld'
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
*/
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
prefix,
suffix
} : {}));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
}
/**
* Converts `text` to PascalCase.
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
*
* @example
* pascalCase('hello-world') // 'HelloWorld'
* pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
*/
function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
prefix,
suffix
}) : camelCase(part));
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
}
//#endregion
//#region src/constants.ts
/**
* OpenTelemetry ingestion endpoint for anonymous usage telemetry.
*/
const OTLP_ENDPOINT = "https://otlp.kubb.dev";
/**
* Plugin `include` filter types that select operations directly. When one of these is set
* without a `schemaName` include, the generate phase pre-scans operations to compute the set
* of schemas they reach, so unreachable schemas can be pruned for that plugin.
*/
const OPERATION_FILTER_TYPES = new Set([
"tag",
"operationId",
"path",
"method",
"contentType"
]);
/**
* Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode
* and stays stable so it can be referenced in tooling and (later) docs. Reference
* these instead of inlining the string at a throw site.
*/
const diagnosticCode = {
/**
* Fallback for an unstructured error with no specific code.
*/
unknown: "KUBB_UNKNOWN",
/**
* The `input.path` file or URL could not be read.
*/
inputNotFound: "KUBB_INPUT_NOT_FOUND",
/**
* An adapter was configured without an `input`.
*/
inputRequired: "KUBB_INPUT_REQUIRED",
/**
* A `$ref` (or equivalent reference) could not be resolved in the source document.
*/
refNotFound: "KUBB_REF_NOT_FOUND",
/**
* A server variable value is not allowed by its `enum`.
*/
invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE",
/**
* A required plugin is missing from the config.
*/
pluginNotFound: "KUBB_PLUGIN_NOT_FOUND",
/**
* A plugin threw while generating.
*/
pluginFailed: "KUBB_PLUGIN_FAILED",
/**
* A plugin reported a non-fatal warning through `ctx.warn`.
*/
pluginWarning: "KUBB_PLUGIN_WARNING",
/**
* A plugin reported an informational message through `ctx.info`.
*/
pluginInfo: "KUBB_PLUGIN_INFO",
/**
* A schema uses a `format` Kubb does not map to a specific type. Reserved for
* adapters to emit as a `warning`.
*/
unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT",
/**
* A referenced schema or operation is marked `deprecated`. Reserved for adapters
* to emit as an `info`.
*/
deprecated: "KUBB_DEPRECATED",
/**
* An adapter is required but the config has none. The build cannot read the input
* without one.
*/
adapterRequired: "KUBB_ADAPTER_REQUIRED",
/**
* A resolved output path escapes the output directory, which can stem from a path
* traversal in the spec or a misconfigured `group.name`.
*/
pathTraversal: "KUBB_PATH_TRAVERSAL",
/**
* A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.
*/
invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS",
/**
* A post-generate shell hook (`hooks.done`) exited with a failure.
*/
hookFailed: "KUBB_HOOK_FAILED",
/**
* The formatter pass over the generated files failed.
*/
formatFailed: "KUBB_FORMAT_FAILED",
/**
* The linter pass over the generated files failed.
*/
lintFailed: "KUBB_LINT_FAILED",
/**
* Not a failure. Carries a plugin's elapsed time, summed into the run total.
*/
performance: "KUBB_PERFORMANCE",
/**
* Not a failure. A newer Kubb version is available on npm.
*/
updateAvailable: "KUBB_UPDATE_AVAILABLE"
};
//#endregion
//#region src/createStorage.ts
/**
* Defines a custom storage backend. The builder receives user options and
* returns a `Storage` implementation. Kubb ships with filesystem and
* in-memory storages, reach for this when you need to write generated files
* elsewhere (cloud storage, a database, a remote API).
*
* @example In-memory storage (the built-in implementation)
* ```ts
* import { createStorage } from '@kubb/core'
*
* export const memoryStorage = createStorage(() => {
* const store = new Map<string, string>()
*
* return {
* name: 'memory',
* async hasItem(key) {
* return store.has(key)
* },
* async getItem(key) {
* return store.get(key) ?? null
* },
* async setItem(key, value) {
* store.set(key, value)
* },
* async removeItem(key) {
* store.delete(key)
* },
* async getKeys(base) {
* const keys = [...store.keys()]
* return base ? keys.filter((k) => k.startsWith(base)) : keys
* },
* async clear(base) {
* if (!base) store.clear()
* },
* }
* })
* ```
*/
function createStorage(build) {
return (options) => build(options ?? {});
}
//#endregion
//#region src/FileManager.ts
function mergeFile(a, b) {
return {
...a,
banner: b.banner,
footer: b.footer,
sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources,
imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports,
exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports
};
}
function isIndexPath(path) {
return path.endsWith("/index.ts") || path === "index.ts";
}
function compareFiles(a, b) {
const lenDiff = a.path.length - b.path.length;
if (lenDiff !== 0) return lenDiff;
const aIsIndex = isIndexPath(a.path);
const bIsIndex = isIndexPath(b.path);
if (aIsIndex && !bIsIndex) return 1;
if (!aIsIndex && bIsIndex) return -1;
return 0;
}
/**
* In-memory file store for generated files. Files sharing a `path` are merged
* (sources/imports/exports concatenated). The `files` getter is sorted by
* path length (barrel `index.ts` last within a bucket).
*
* @example
* ```ts
* const manager = new FileManager()
* manager.upsert(myFile)
* manager.files // sorted view
* ```
*/
var FileManager = class {
/**
* Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands
* through `add` or `upsert`.
*/
hooks = new AsyncEventEmitter();
#cache = /* @__PURE__ */ new Map();
#sorted = null;
add(...files) {
return this.#store(files, false);
}
upsert(...files) {
return this.#store(files, true);
}
#store(files, mergeExisting) {
const batch = files.length > 1 ? this.#dedupe(files) : files;
const resolved = [];
for (const file of batch) {
const existing = this.#cache.get(file.path);
const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file);
this.#cache.set(merged.path, merged);
resolved.push(merged);
this.hooks.emit("upsert", merged);
}
if (resolved.length > 0) this.#sorted = null;
return resolved;
}
#dedupe(files) {
const seen = /* @__PURE__ */ new Map();
for (const file of files) {
const prev = seen.get(file.path);
seen.set(file.path, prev ? mergeFile(prev, file) : file);
}
return [...seen.values()];
}
getByPath(path) {
return this.#cache.get(path) ?? null;
}
deleteByPath(path) {
if (!this.#cache.delete(path)) return;
this.#sorted = null;
}
clear() {
this.#cache.clear();
this.#sorted = null;
}
/**
* Releases all stored files and clears every `hooks` listener. Called by the core after
* `kubb:build:end`.
*/
dispose() {
this.clear();
this.hooks.removeAll();
}
[Symbol.dispose]() {
this.dispose();
}
/**
* All stored files in stable sort order (shortest path first, barrel files
* last within a length bucket). Returns a cached view, do not mutate.
*/
get files() {
return this.#sorted ??= [...this.#cache.values()].sort(compareFiles);
}
};
//#endregion
//#region src/FileProcessor.ts
function joinSources(file) {
const sources = file.sources;
if (sources.length === 0) return "";
const parts = [];
for (const source of sources) {
const text = (0, _kubb_ast.extractStringsFromNodes)(source.nodes);
if (text) parts.push(text);
}
return parts.join("\n\n");
}
/**
* Turns `FileNode`s into source strings and writes them to storage.
*
* Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the
* conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and
* writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.
*
* `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next
* `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been
* written and is meant for the end of a build.
*
* To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to
* `hooks` and re-emit on the kubb bus.
*/
var FileProcessor = class {
hooks = new AsyncEventEmitter();
#parsers;
#storage;
#extension;
#pending = /* @__PURE__ */ new Map();
#runningFlush = null;
constructor(options) {
this.#parsers = options.parsers ?? null;
this.#storage = options.storage;
this.#extension = options.extension ?? null;
}
/**
* Files waiting in the queue.
*/
get size() {
return this.#pending.size;
}
parse(file) {
const parsers = this.#parsers;
const parseExtName = this.#extension?.[file.extname] || void 0;
if (!parsers || !file.extname) return joinSources(file);
const parser = parsers.get(file.extname);
if (!parser) return joinSources(file);
return parser.parse(file, { extname: parseExtName });
}
*stream(files) {
const total = files.length;
if (total === 0) return;
let processed = 0;
for (const file of files) {
const source = this.parse(file);
processed++;
yield {
file,
source,
processed,
total,
percentage: processed / total * 100
};
}
}
async run(files) {
await this.hooks.emit("start", files);
for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", {
file,
source,
processed,
percentage,
total
});
await this.hooks.emit("end", files);
return files;
}
/**
* Adds a file to the next flush. A later `enqueue` for the same path replaces the previous
* entry, matching `FileManager.upsert`. Fires the `enqueue` event.
*/
enqueue(file) {
this.#pending.set(file.path, file);
this.hooks.emit("enqueue", file);
}
/**
* Starts processing the queued files. Waits for any previous flush to finish (so two
* batches never run together) and then returns without waiting for the new one. The next
* `flush` or `drain` picks up the in-flight task.
*/
async flush() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size === 0) return;
const batch = [...this.#pending.values()];
this.#pending.clear();
this.#runningFlush = this.#processAndWrite(batch).finally(() => {
this.#runningFlush = null;
});
}
/**
* Waits for the in-flight flush and writes any files still queued. Fires the `drain` event
* when both are done.
*/
async drain() {
if (this.#runningFlush) await this.#runningFlush;
if (this.#pending.size > 0) {
const batch = [...this.#pending.values()];
this.#pending.clear();
await this.#processAndWrite(batch);
}
await this.hooks.emit("drain");
}
async #processAndWrite(files) {
const storage = this.#storage;
await this.hooks.emit("start", files);
const queue = [];
for (const item of this.stream(files)) {
await this.hooks.emit("update", item);
if (item.source) {
queue.push(storage.setItem(item.file.path, item.source));
if (queue.length >= 50) await Promise.all(queue.splice(0));
}
}
await Promise.all(queue);
await this.hooks.emit("end", files);
}
/**
* Clears every listener and the pending queue.
*/
dispose() {
this.hooks.removeAll();
this.#pending.clear();
}
[Symbol.dispose]() {
this.dispose();
}
};
//#endregion
//#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js
function _usingCtx() {
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
var n = Error();
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
};
var e = {};
var n = [];
function using(r, e) {
if (null != e) {
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
t && (o = function o() {
try {
t.call(e);
} catch (r) {
return Promise.reject(r);
}
}), n.push({
v: e,
d: o,
a: r
});
} else r && n.push({
d: e,
a: r
});
return e;
}
return {
e,
u: using.bind(null, !1),
a: using.bind(null, !0),
d: function d() {
var o;
var t = this.e;
var s = 0;
function next() {
for (; o = n.pop();) try {
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
if (o.d) {
var r = o.d.call(o.v);
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
} else s |= 1;
} catch (r) {
return err(r);
}
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
if (t !== e) throw t;
}
function err(n) {
return t = t !== e ? new r(n, t) : n, next();
}
return next();
}
};
}
//#endregion
//#region src/storages/memoryStorage.ts
/**
* In-memory storage driver. Useful for testing and dry-run scenarios where
* generated output should be captured without touching the filesystem.
*
* All data lives in a `Map` scoped to the storage instance and is discarded
* when the instance is garbage-collected.
*
* @example
* ```ts
* import { memoryStorage } from '@kubb/core'
* import { defineConfig } from 'kubb'
*
* export default defineConfig({
* input: { path: './petStore.yaml' },
* output: { path: './src/gen' },
* storage: memoryStorage(),
* })
* ```
*/
const memoryStorage = createStorage(() => {
const store = /* @__PURE__ */ new Map();
return {
name: "memory",
async hasItem(key) {
return store.has(key);
},
async getItem(key) {
return store.get(key) ?? null;
},
async setItem(key, value) {
store.set(key, value);
},
async removeItem(key) {
store.delete(key);
},
async getKeys(base) {
const keys = [...store.keys()];
return base ? keys.filter((k) => k.startsWith(base)) : keys;
},
async clear(base) {
if (!base) {
store.clear();
return;
}
for (const key of store.keys()) if (key.startsWith(base)) store.delete(key);
}
};
});
//#endregion
Object.defineProperty(exports, "AsyncEventEmitter", {
enumerable: true,
get: function() {
return AsyncEventEmitter;
}
});
Object.defineProperty(exports, "BuildError", {
enumerable: true,
get: function() {
return BuildError;
}
});
Object.defineProperty(exports, "FileManager", {
enumerable: true,
get: function() {
return FileManager;
}
});
Object.defineProperty(exports, "FileProcessor", {
enumerable: true,
get: function() {
return FileProcessor;
}
});
Object.defineProperty(exports, "OPERATION_FILTER_TYPES", {
enumerable: true,
get: function() {
return OPERATION_FILTER_TYPES;
}
});
Object.defineProperty(exports, "OTLP_ENDPOINT", {
enumerable: true,
get: function() {
return OTLP_ENDPOINT;
}
});
Object.defineProperty(exports, "__name", {
enumerable: true,
get: function() {
return __name;
}
});
Object.defineProperty(exports, "__toESM", {
enumerable: true,
get: function() {
return __toESM;
}
});
Object.defineProperty(exports, "_usingCtx", {
enumerable: true,
get: function() {
return _usingCtx;
}
});
Object.defineProperty(exports, "camelCase", {
enumerable: true,
get: function() {
return camelCase;
}
});
Object.defineProperty(exports, "createStorage", {
enumerable: true,
get: function() {
return createStorage;
}
});
Object.defineProperty(exports, "diagnosticCode", {
enumerable: true,
get: function() {
return diagnosticCode;
}
});
Object.defineProperty(exports, "getErrorMessage", {
enumerable: true,
get: function() {
return getErrorMessage;
}
});
Object.defineProperty(exports, "memoryStorage", {
enumerable: true,
get: function() {
return memoryStorage;
}
});
Object.defineProperty(exports, "pascalCase", {
enumerable: true,
get: function() {
return pascalCase;
}
});
//# sourceMappingURL=memoryStorage-DlQ_0_bo.cjs.map
{"version":3,"file":"memoryStorage-DlQ_0_bo.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * OpenTelemetry ingestion endpoint for anonymous usage telemetry.\n */\nexport const OTLP_ENDPOINT = 'https://otlp.kubb.dev' as const\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel middleware resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"}
import type { AsyncEventEmitter } from '@internals/utils'
import type { KubbHooks } from './types.ts'
/**
* Numeric log-level thresholds used internally to compare verbosity.
*
* Higher numbers are more verbose.
*/
export const logLevel = {
silent: Number.NEGATIVE_INFINITY,
error: 0,
warn: 1,
info: 3,
verbose: 4,
} as const
/**
* Options accepted by a logger's `install` callback.
*/
export type LoggerOptions = {
/**
* Output verbosity. Use the `logLevel` constants exported from `@kubb/core`
* (`silent`, `error`, `warn`, `info`, `verbose`, `debug`).
*/
logLevel: (typeof logLevel)[keyof typeof logLevel]
}
/**
* Event emitter handed to `Logger.install`. Use `.on('kubb:info', ...)` and
* friends to subscribe to build events.
*/
export type LoggerContext = AsyncEventEmitter<KubbHooks>
/**
* Logger contract. A logger receives the build's event emitter and subscribes
* to whichever lifecycle events it wants to forward to its destination
* (console, file, remote service).
*/
export type Logger<TOptions extends LoggerOptions = LoggerOptions> = {
/**
* Display name used in diagnostics.
*/
name: string
/**
* Called once per build with the shared event emitter. Subscribe to the
* lifecycle events the logger wants to forward to its destination.
*/
install: (context: LoggerContext, options?: TOptions) => void | Promise<void>
}
export type UserLogger<TOptions extends LoggerOptions = LoggerOptions> = Logger<TOptions>
/**
* Defines a typed logger. The `install` method subscribes to lifecycle events
* on the shared emitter and forwards them to the logger's destination.
*
* @example
* ```ts
* import { defineLogger } from '@kubb/core'
*
* export const myLogger = defineLogger({
* name: 'my-logger',
* install(context) {
* context.on('kubb:info', ({ message }) => console.log('ℹ', message))
* context.on('kubb:error', ({ error }) => console.error('✗', error.message))
* },
* })
* ```
*/
export function defineLogger<Options extends LoggerOptions = LoggerOptions>(logger: UserLogger<Options>): Logger<Options> {
return logger
}
import type { KubbHooks } from './types.ts'
/**
* A middleware instance. Subscribes to lifecycle events via `hooks`. Middleware
* handlers always fire after every plugin handler for the same event, so they
* see the full set of generated files.
*/
export type Middleware = {
/**
* Unique name. Use a `middleware-<feature>` convention (e.g.
* `middleware-barrel`).
*/
name: string
/**
* Lifecycle event handlers. Subscribe to any event from the global `KubbHooks`
* map. Handlers run after all plugin handlers for that event.
*
* The driver finishes every `kubb:generate:schema` event for a plugin before
* it fires that plugin's first `kubb:generate:operation` or
* `kubb:generate:operations` event, so an operation handler can count on the
* plugin's schemas being done.
*/
hooks: {
[K in keyof KubbHooks]?: (...args: KubbHooks[K]) => void | Promise<void>
}
}
/**
* Creates a middleware factory. Middleware fires after every plugin handler
* for the same event, which makes it the natural place for post-processing
* (barrel files, lint runs, audit logs).
*
* Per-build state belongs inside the factory closure so each `createKubb`
* invocation gets its own isolated instance.
*
* @example Stateless middleware
* ```ts
* import { defineMiddleware } from '@kubb/core'
*
* export const logMiddleware = defineMiddleware(() => ({
* name: 'log-middleware',
* hooks: {
* 'kubb:build:end'({ files }) {
* console.log(`Build complete with ${files.length} files`)
* },
* },
* }))
* ```
*
* @example Middleware with options and per-build state
* ```ts
* import { defineMiddleware } from '@kubb/core'
*
* export const prefixMiddleware = defineMiddleware((options: { prefix: string } = { prefix: '' }) => {
* const seen = new Set<string>()
* return {
* name: 'prefix-middleware',
* hooks: {
* 'kubb:plugin:end'({ plugin }) {
* seen.add(`${options.prefix}${plugin.name}`)
* },
* },
* }
* })
* ```
*/
export function defineMiddleware<TOptions extends object = object>(factory: (options: TOptions) => Middleware): (options?: TOptions) => Middleware {
return (options) => factory(options ?? ({} as TOptions))
}
import type { AsyncEventEmitter } from '@internals/utils'
export type HookSource = 'plugin' | 'middleware' | 'driver'
export type HookListener<TArgs extends Array<unknown>, TResult = void> = (...args: TArgs) => TResult | Promise<TResult>
type AnyEntry = {
event: string
handler: HookListener<Array<unknown>, unknown>
source: HookSource
}
/**
* Listener bookkeeping around an `AsyncEventEmitter`. Listeners attached through `register`
* stay on the emitter but are tracked so `dispose()` removes only them, listeners attached
* directly via `emitter.on(...)` survive.
*/
export class HookRegistry<TEvents extends { [K in keyof TEvents]: Array<unknown> }> {
readonly #emitter: AsyncEventEmitter<TEvents>
readonly #entries = new Set<AnyEntry>()
constructor(options: { emitter: AsyncEventEmitter<TEvents> }) {
this.#emitter = options.emitter
}
get emitter(): AsyncEventEmitter<TEvents> {
return this.#emitter
}
get size(): number {
return this.#entries.size
}
register<K extends keyof TEvents & string>(options: { event: K; handler: HookListener<TEvents[K], unknown>; source: HookSource }): void {
this.#emitter.on(options.event, options.handler as HookListener<TEvents[K]>)
this.#entries.add(options as unknown as AnyEntry)
}
dispose(): void {
for (const entry of this.#entries) {
this.#emitter.off(entry.event as keyof TEvents & string, entry.handler as HookListener<Array<unknown>>)
}
this.#entries.clear()
}
}
import { join } from 'node:path'
import { read } from '@internals/utils'
/**
* Bookkeeping for one cached build: the relative paths it covers and timestamps used by the pruner.
*/
export type ManifestEntry = {
files: Array<string>
createdAt: number
lastAccess: number
}
/**
* The on-disk manifest: a version marker plus an entry per cached build, keyed by fingerprint.
*/
export type ManifestData = {
version: number
entries: Record<string, ManifestEntry>
}
/**
* Reads and prunes the local cache manifest. All methods are static, so call them as
* `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the
* cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`.
*/
export class Manifest {
/**
* On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a
* mismatch makes the whole local cache read as empty.
*/
static version = 1
/**
* Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads
* as an empty manifest.
*/
static async read(dir: string): Promise<ManifestData> {
try {
const parsed = JSON.parse(await read(join(dir, 'manifest.json'))) as ManifestData
if (parsed.version !== Manifest.version || typeof parsed.entries !== 'object') {
return Manifest.#empty()
}
return parsed
} catch {
return Manifest.#empty()
}
}
/**
* Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the
* surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO.
*/
static prune(
manifest: ManifestData,
{ maxEntries, ttlDays, now }: { maxEntries: number; ttlDays: number; now: number },
): {
manifest: ManifestData
removed: Array<string>
} {
const ttlMs = ttlDays * 24 * 60 * 60 * 1000
const removed: Array<string> = []
const kept: Array<[string, ManifestEntry]> = []
for (const [key, entry] of Object.entries(manifest.entries)) {
if (now - entry.lastAccess > ttlMs) {
removed.push(key)
} else {
kept.push([key, entry])
}
}
if (kept.length > maxEntries) {
kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess)
for (const [key] of kept.splice(maxEntries)) {
removed.push(key)
}
}
return { manifest: { version: Manifest.version, entries: Object.fromEntries(kept) }, removed }
}
static #empty(): ManifestData {
return { version: Manifest.version, entries: {} }
}
}
import { randomBytes } from 'node:crypto'
import os from 'node:os'
import process from 'node:process'
import { executeIfOnline, getRuntimeName, getRuntimeVersion, isCIEnvironment, type RuntimeName } from '@internals/utils'
import { OTLP_ENDPOINT } from './constants.ts'
// OpenTelemetry OTLP JSON types
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/common/v1/common.proto
type OtlpStringValue = { stringValue: string }
type OtlpBoolValue = { boolValue: boolean }
type OtlpIntValue = { intValue: number }
type OtlpDoubleValue = { doubleValue: number }
type OtlpBytesValue = { bytesValue: string }
type OtlpArrayValue = { arrayValue: { values: Array<OtlpAnyValue> } }
type OtlpKvListValue = { kvlistValue: { values: Array<OtlpKeyValue> } }
type OtlpAnyValue = OtlpStringValue | OtlpBoolValue | OtlpIntValue | OtlpDoubleValue | OtlpBytesValue | OtlpArrayValue | OtlpKvListValue
type OtlpKeyValue = {
key: string
value: OtlpAnyValue
}
type OtlpResource = {
attributes: Array<OtlpKeyValue>
droppedAttributesCount?: number
}
type OtlpInstrumentationScope = {
name: string
version?: string
attributes?: Array<OtlpKeyValue>
droppedAttributesCount?: number
}
/** https://github.com/open-telemetry/opentelemetry-proto/blob/main/opentelemetry/proto/trace/v1/trace.proto#L103 */
type OtlpSpanKind = 0 | 1 | 2 | 3 | 4 | 5
/** 0 = STATUS_CODE_UNSET, 1 = STATUS_CODE_OK, 2 = STATUS_CODE_ERROR */
type OtlpStatusCode = 0 | 1 | 2
type OtlpStatus = {
code: OtlpStatusCode
message?: string
}
type OtlpSpan = {
traceId: string
spanId: string
traceState?: string
parentSpanId?: string
name: string
kind: OtlpSpanKind
startTimeUnixNano: string
endTimeUnixNano: string
attributes?: Array<OtlpKeyValue>
droppedAttributesCount?: number
events?: Array<OtlpSpanEvent>
droppedEventsCount?: number
links?: Array<OtlpSpanLink>
droppedLinksCount?: number
status?: OtlpStatus
}
type OtlpSpanEvent = {
timeUnixNano: string
name: string
attributes?: Array<OtlpKeyValue>
droppedAttributesCount?: number
}
type OtlpSpanLink = {
traceId: string
spanId: string
traceState?: string
attributes?: Array<OtlpKeyValue>
droppedAttributesCount?: number
}
type OtlpScopeSpans = {
scope: OtlpInstrumentationScope
spans: Array<OtlpSpan>
schemaUrl?: string
}
type OtlpResourceSpans = {
resource: OtlpResource
scopeSpans: Array<OtlpScopeSpans>
schemaUrl?: string
}
/** Root payload sent to POST /v1/traces */
type OtlpExportTraceServiceRequest = {
resourceSpans: Array<OtlpResourceSpans>
}
/**
* Anonymous plugin name and options snapshot sent with each telemetry event.
*/
export type TelemetryPlugin = {
/**
* Plugin name as registered in the Kubb config, e.g. `'@kubb/plugin-ts'`.
*/
name: string
/**
* anonymized plugin options snapshot, values are included but cannot be traced back to the user.
*/
options: Record<string, unknown>
}
export type TelemetryEvent = {
command: string
kubbVersion: string
nodeVersion: string
/**
* Name of the JavaScript runtime that executed the run, `'bun'`, `'deno'`, or `'node'`.
*/
runtime: RuntimeName
/**
* Major version of the active runtime, e.g. `'1'` under Bun or `'22'` under Node.
*/
runtimeVersion: string
platform: string
ci: boolean
plugins: Array<TelemetryPlugin>
duration: number
filesCreated: number
status: 'success' | 'failed'
}
/**
* Anonymous OTLP usage telemetry for the Kubb run. All methods are static, so call them as
* `Telemetry.build(...)`, `Telemetry.send(...)`, and `Telemetry.isDisabled()`. No file paths,
* OpenAPI specs, or secrets are ever included, and sending fails silently to never break a run.
*/
export class Telemetry {
/**
* Returns `true` when telemetry is disabled via `DO_NOT_TRACK` or `KUBB_DISABLE_TELEMETRY`.
*/
static isDisabled(): boolean {
return (
process.env['DO_NOT_TRACK'] === '1' ||
process.env['DO_NOT_TRACK'] === 'true' ||
process.env['KUBB_DISABLE_TELEMETRY'] === '1' ||
process.env['KUBB_DISABLE_TELEMETRY'] === 'true'
)
}
/**
* Build an anonymous telemetry payload from a completed generation run.
*/
static build(options: {
command: 'generate' | 'mcp' | 'validate' | 'agent'
kubbVersion: string
plugins?: Array<TelemetryPlugin>
hrStart: [number, number]
filesCreated?: number
status: 'success' | 'failed'
}): TelemetryEvent {
const [seconds, nanoseconds] = process.hrtime(options.hrStart)
const duration = Math.round(seconds * 1000 + nanoseconds / 1e6)
return {
command: options.command,
kubbVersion: options.kubbVersion,
nodeVersion: process.versions.node.split('.')[0] as string,
runtime: getRuntimeName(),
runtimeVersion: getRuntimeVersion().split('.')[0] as string,
platform: os.platform(),
ci: isCIEnvironment(),
plugins: options.plugins ?? [],
duration,
filesCreated: options.filesCreated ?? 0,
status: options.status,
}
}
/**
* Convert a {@link TelemetryEvent} into an OTLP-compatible JSON trace payload.
* See https://opentelemetry.io/docs/languages/sdk-configuration/otlp-exporter/
*/
static buildOtlpPayload(event: TelemetryEvent): OtlpExportTraceServiceRequest {
const traceId = randomBytes(16).toString('hex')
const spanId = randomBytes(8).toString('hex')
const endTimeNs = BigInt(Date.now()) * 1_000_000n
const startTimeNs = endTimeNs - BigInt(event.duration) * 1_000_000n
const attributes: Array<OtlpKeyValue> = [
{ key: 'kubb.command', value: { stringValue: event.command } },
{ key: 'kubb.version', value: { stringValue: event.kubbVersion } },
{ key: 'kubb.node_version', value: { stringValue: event.nodeVersion } },
{ key: 'kubb.runtime', value: { stringValue: event.runtime } },
{ key: 'kubb.runtime_version', value: { stringValue: event.runtimeVersion } },
{ key: 'kubb.platform', value: { stringValue: event.platform } },
{ key: 'kubb.ci', value: { boolValue: event.ci } },
{ key: 'kubb.files_created', value: { intValue: event.filesCreated } },
{ key: 'kubb.status', value: { stringValue: event.status } },
{
key: 'kubb.plugins',
value: {
arrayValue: {
values: event.plugins.map(
(p): OtlpKvListValue => ({
kvlistValue: {
values: [
{ key: 'name', value: { stringValue: p.name } },
{
key: 'options',
value: {
stringValue: JSON.stringify({
...p.options,
usedEnumNames: undefined,
}),
},
},
],
},
}),
),
},
},
},
]
return {
resourceSpans: [
{
resource: {
attributes: [
{ key: 'service.name', value: { stringValue: 'kubb-core' } },
{
key: 'service.version',
value: { stringValue: event.kubbVersion },
},
{ key: 'telemetry.sdk.language', value: { stringValue: 'nodejs' } },
],
},
scopeSpans: [
{
scope: { name: 'kubb-core', version: event.kubbVersion },
spans: [
{
traceId,
spanId,
name: event.command,
kind: 1 satisfies OtlpSpanKind,
startTimeUnixNano: String(startTimeNs),
endTimeUnixNano: String(endTimeNs),
attributes,
status: {
code: (event.status === 'success' ? 1 : 2) satisfies OtlpStatusCode,
},
},
],
},
],
},
],
}
}
/**
* Send an anonymous telemetry event to the Kubb OTLP endpoint. Respects `DO_NOT_TRACK` and
* `KUBB_DISABLE_TELEMETRY`, and fails silently so telemetry never interrupts a run.
*/
static async send(event: TelemetryEvent): Promise<void> {
if (Telemetry.isDisabled()) {
return
}
await executeIfOnline(async () => {
try {
await fetch(`${OTLP_ENDPOINT}/v1/traces`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Kubb-Telemetry-Version': '1',
'Kubb-Telemetry-Source': 'kubb-core',
},
body: JSON.stringify(Telemetry.buildOtlpPayload(event)),
signal: AbortSignal.timeout(5_000),
})
} catch (_e) {
// Fail silently, telemetry must never break the run
}
})
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display