@kubb/core
Advanced tools
Sorry, the diff of this file is too big to display
| //#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_factory = require("@kubb/ast/factory"); | ||
| _kubb_ast_factory = __toESM(_kubb_ast_factory, 1); | ||
| let _kubb_ast_utils = require("@kubb/ast/utils"); | ||
| //#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; | ||
| return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `camelCase('hello-world') // 'helloWorld'` | ||
| * | ||
| * @example With a prefix | ||
| * `camelCase('tag', { prefix: 'create' }) // 'createTag'` | ||
| */ | ||
| function camelCase(text, { prefix = "", suffix = "" } = {}) { | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `pascalCase('hello-world') // 'HelloWorld'` | ||
| * | ||
| * @example With a suffix | ||
| * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'` | ||
| */ | ||
| function pascalCase(text, { prefix = "", suffix = "" } = {}) { | ||
| 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. A custom backend writes generated files elsewhere, such as cloud | ||
| * storage or a database. | ||
| * | ||
| * @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 ? _kubb_ast_factory.createFile(mergeFile(existing, file)) : _kubb_ast_factory.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_utils.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-CUj1hrxa.cjs.map |
| {"version":3,"file":"memoryStorage-CUj1hrxa.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","factory","#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 Array<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]: Array<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<Array<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<Array<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 * 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 return 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 .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\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 somewhere else, such as S3 or a database.\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 in-memory\n * storages. A custom backend writes generated files elsewhere, such as cloud\n * storage or a database.\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 * as factory from '@kubb/ast/factory'\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 ? factory.createFile(mergeFile(existing, file)) : factory.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/utils'\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 "./chunk-C0LytTxp.js"; | ||
| import { EventEmitter } from "node:events"; | ||
| import * as factory from "@kubb/ast/factory"; | ||
| import { extractStringsFromNodes } from "@kubb/ast/utils"; | ||
| //#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; | ||
| return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `camelCase('hello-world') // 'helloWorld'` | ||
| * | ||
| * @example With a prefix | ||
| * `camelCase('tag', { prefix: 'create' }) // 'createTag'` | ||
| */ | ||
| function camelCase(text, { prefix = "", suffix = "" } = {}) { | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `pascalCase('hello-world') // 'HelloWorld'` | ||
| * | ||
| * @example With a suffix | ||
| * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'` | ||
| */ | ||
| function pascalCase(text, { prefix = "", suffix = "" } = {}) { | ||
| 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. A custom backend writes generated files elsewhere, such as cloud | ||
| * storage or a database. | ||
| * | ||
| * @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 ? factory.createFile(mergeFile(existing, file)) : factory.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-CWFzAz4o.js.map |
| {"version":3,"file":"memoryStorage-CWFzAz4o.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 Array<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]: Array<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<Array<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<Array<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 * 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 return 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 .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\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 somewhere else, such as S3 or a database.\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 in-memory\n * storages. A custom backend writes generated files elsewhere, such as cloud\n * storage or a database.\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 * as factory from '@kubb/ast/factory'\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 ? factory.createFile(mergeFile(existing, file)) : factory.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/utils'\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
-2
| import { t as __name } from "./chunk-C0LytTxp.js"; | ||
| import { $ as KubbPluginSetupContext, A as KubbHookStartContext, At as Adapter, B as ParsedFile, C as KubbFilesProcessingEndContext, Ct as GenerationResult, D as KubbGenerationStartContext, Dt as UserReporter, E as KubbGenerationEndContext, Et as ReporterName, F as KubbSuccessContext, G as Generator, H as createKubb, I as KubbWarnContext, J as KubbDriver, K as GeneratorContext, L as PossibleConfig, M as KubbInfoContext, Mt as AdapterSource, N as KubbLifecycleStartContext, Nt as createAdapter, O as KubbHookEndContext, Ot as createReporter, P as KubbPluginsEndContext, Pt as AsyncEventEmitter, Q as KubbPluginEndContext, R as UserConfig, S as KubbFileProcessingUpdate, St as createStorage, T as KubbFilesProcessingUpdateContext, Tt as ReporterContext, U as Parser, V as Kubb, W as defineParser, X as Group, Y as Exclude, Z as Include, _ as InputPath, _t as defineResolver, a as DiagnosticLocation, at as Override, b as KubbDiagnosticContext, bt as createRenderer, c as PerformanceDiagnostic, ct as definePlugin, d as SerializedDiagnostic, dt as ResolveBannerFile, et as KubbPluginStartContext, f as UpdateDiagnostic, ft as ResolveOptionsContext, g as InputData, gt as ResolverPathParams, h as Config, ht as ResolverFileParams, i as DiagnosticKind, it as OutputOptions, j as KubbHooks, jt as AdapterFactoryOptions, k as KubbHookLineContext, kt as logLevel, l as ProblemCode, lt as BannerMeta, m as CLIOptions, mt as ResolverContext, n as DiagnosticByCode, nt as Output, o as DiagnosticSeverity, ot as Plugin, p as BuildOutput, pt as Resolver, q as defineGenerator, r as DiagnosticDoc, rt as OutputMode, s as Diagnostics, st as PluginFactoryOptions, t as Diagnostic, tt as NormalizedPlugin, u as ProblemDiagnostic, ut as ResolveBannerContext, v as KubbBuildEndContext, vt as Renderer, w as KubbFilesProcessingStartContext, wt as Reporter, x as KubbErrorContext, xt as Storage, y as KubbBuildStartContext, yt as RendererFactory, z as FileProcessorHooks } from "./diagnostics-BNcDERWL.js"; | ||
| import { $ as KubbPluginSetupContext, A as KubbHookStartContext, At as Adapter, B as ParsedFile, C as KubbFilesProcessingEndContext, Ct as GenerationResult, D as KubbGenerationStartContext, Dt as UserReporter, E as KubbGenerationEndContext, Et as ReporterName, F as KubbSuccessContext, G as Generator, H as createKubb, I as KubbWarnContext, J as KubbDriver, K as GeneratorContext, L as PossibleConfig, M as KubbInfoContext, Mt as AdapterSource, N as KubbLifecycleStartContext, Nt as createAdapter, O as KubbHookEndContext, Ot as createReporter, P as KubbPluginsEndContext, Pt as AsyncEventEmitter, Q as KubbPluginEndContext, R as UserConfig, S as KubbFileProcessingUpdate, St as createStorage, T as KubbFilesProcessingUpdateContext, Tt as ReporterContext, U as Parser, V as Kubb, W as defineParser, X as Group, Y as Exclude, Z as Include, _ as InputPath, _t as defineResolver, a as DiagnosticLocation, at as Override, b as KubbDiagnosticContext, bt as createRenderer, c as PerformanceDiagnostic, ct as definePlugin, d as SerializedDiagnostic, dt as ResolveBannerFile, et as KubbPluginStartContext, f as UpdateDiagnostic, ft as ResolveOptionsContext, g as InputData, gt as ResolverPathParams, h as Config, ht as ResolverFileParams, i as DiagnosticKind, it as OutputOptions, j as KubbHooks, jt as AdapterFactoryOptions, k as KubbHookLineContext, kt as logLevel, l as ProblemCode, lt as BannerMeta, m as CLIOptions, mt as ResolverContext, n as DiagnosticByCode, nt as Output, o as DiagnosticSeverity, ot as Plugin, p as BuildOutput, pt as Resolver, q as defineGenerator, r as DiagnosticDoc, rt as OutputMode, s as Diagnostics, st as PluginFactoryOptions, t as Diagnostic, tt as NormalizedPlugin, u as ProblemDiagnostic, ut as ResolveBannerContext, v as KubbBuildEndContext, vt as Renderer, w as KubbFilesProcessingStartContext, wt as Reporter, x as KubbErrorContext, xt as Storage, y as KubbBuildStartContext, yt as RendererFactory, z as FileProcessorHooks } from "./diagnostics-B-UZnFqP.js"; | ||
| import * as ast from "@kubb/ast"; | ||
@@ -107,3 +107,3 @@ | ||
| * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent | ||
| * of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings. | ||
| * of the live logger view. Suppressed at `silent`. The `verbose` level adds the per-plugin timings. | ||
| */ | ||
@@ -110,0 +110,0 @@ declare const cliReporter: Reporter; |
+1
-2
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const require_memoryStorage = require("./memoryStorage-Bz4ZDIZz.cjs"); | ||
| const require_memoryStorage = require("./memoryStorage-CUj1hrxa.cjs"); | ||
| let node_path = require("node:path"); | ||
@@ -9,3 +9,2 @@ node_path = require_memoryStorage.__toESM(node_path, 1); | ||
| /** | ||
| * Creates a minimal `PluginDriver` mock for unit tests. | ||
@@ -12,0 +11,0 @@ */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mocks.cjs","names":["FileManager","FileProcessor","memoryStorage","path","camelCase"],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"mocks.cjs","names":["FileManager","FileProcessor","memoryStorage","path","camelCase"],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"} |
+1
-2
| import { t as __name } from "./chunk-C0LytTxp.js"; | ||
| import { At as Adapter, G as Generator, J as KubbDriver, U as Parser, h as Config, jt as AdapterFactoryOptions, st as PluginFactoryOptions, tt as NormalizedPlugin } from "./diagnostics-BNcDERWL.js"; | ||
| import { At as Adapter, G as Generator, J as KubbDriver, U as Parser, h as Config, jt as AdapterFactoryOptions, st as PluginFactoryOptions, tt as NormalizedPlugin } from "./diagnostics-B-UZnFqP.js"; | ||
| import { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from "@kubb/ast"; | ||
@@ -7,3 +7,2 @@ | ||
| /** | ||
| * Creates a minimal `PluginDriver` mock for unit tests. | ||
@@ -10,0 +9,0 @@ */ |
+1
-2
| import "./chunk-C0LytTxp.js"; | ||
| import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-DZYKdzI6.js"; | ||
| import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-CWFzAz4o.js"; | ||
| import path, { resolve } from "node:path"; | ||
@@ -8,3 +8,2 @@ import { transform } from "@kubb/ast"; | ||
| /** | ||
| * Creates a minimal `PluginDriver` mock for unit tests. | ||
@@ -11,0 +10,0 @@ */ |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"} | ||
| {"version":3,"file":"mocks.js","names":[],"sources":["../src/mocks.ts"],"sourcesContent":["import path, { resolve } from 'node:path'\nimport { camelCase } from '@internals/utils'\nimport type { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from '@kubb/ast'\nimport { transform } from '@kubb/ast'\nimport { expect } from 'vitest'\nimport type { Parser } from './defineParser.ts'\nimport { FileManager } from './FileManager.ts'\nimport { FileProcessor } from './FileProcessor.ts'\nimport type { KubbDriver } from './KubbDriver.ts'\nimport { memoryStorage } from './storages/memoryStorage.ts'\nimport type { Adapter, AdapterFactoryOptions, Config, Generator, GeneratorContext, NormalizedPlugin, PluginFactoryOptions, RendererFactory } from './types.ts'\n\n/**\n * Creates a minimal `PluginDriver` mock for unit tests.\n */\nexport function createMockedPluginDriver(options: { name?: string; plugin?: NormalizedPlugin; config?: Config } = {}): KubbDriver {\n const fileManager = new FileManager()\n\n return {\n config: options?.config ?? {\n root: '.',\n output: {\n path: './path',\n },\n },\n getPlugin(_pluginName: string): NormalizedPlugin | undefined {\n return options?.plugin\n },\n getResolver: (_pluginName: string) => options?.plugin?.resolver,\n fileManager,\n async dispatch({ result, renderer }: { result: unknown; renderer?: RendererFactory | null }): Promise<void> {\n if (!result) return\n\n if (Array.isArray(result)) {\n fileManager.upsert(...(result as Array<FileNode>))\n return\n }\n\n if (!renderer) return\n\n using instance = renderer()\n if (instance.stream) {\n for (const file of instance.stream(result)) fileManager.upsert(file)\n return\n }\n\n await instance.render(result)\n fileManager.upsert(...instance.files)\n },\n } as unknown as KubbDriver\n}\n\n/**\n * Creates a minimal `Adapter` mock for unit tests.\n * `parse` returns an empty `InputNode` by default. Override via `options.parse`.\n * `getImports` returns `[]` by default.\n */\nexport function createMockedAdapter<TOptions extends AdapterFactoryOptions = AdapterFactoryOptions>(\n options: {\n name?: TOptions['name']\n resolvedOptions?: TOptions['resolvedOptions']\n parse?: Adapter<TOptions>['parse']\n getImports?: Adapter<TOptions>['getImports']\n } = {},\n): Adapter<TOptions> {\n return {\n name: (options.name ?? 'oas') as TOptions['name'],\n options: (options.resolvedOptions ?? {}) as TOptions['resolvedOptions'],\n parse: options.parse ?? (async () => ({ kind: 'Input' as const, schemas: [], operations: [] })),\n getImports: options.getImports ?? ((_node: SchemaNode, _resolve: (schemaName: string) => { name: string; path: string }) => []),\n } as Adapter<TOptions>\n}\n\n/**\n * Creates a minimal plugin mock for unit tests.\n *\n * @example\n * `const plugin = createMockedPlugin<PluginTs>({ name: '@kubb/plugin-ts', options })`\n */\nexport function createMockedPlugin<TOptions extends PluginFactoryOptions = PluginFactoryOptions>(params: {\n name: TOptions['name']\n options: TOptions['resolvedOptions']\n resolver?: TOptions['resolver']\n transformer?: Visitor\n dependencies?: Array<string>\n}): NormalizedPlugin<TOptions> {\n return {\n name: params.name,\n options: params.options,\n resolver: params.resolver,\n transformer: params.transformer,\n dependencies: params.dependencies,\n hooks: {},\n } as unknown as NormalizedPlugin<TOptions>\n}\n\ntype RenderGeneratorOptions<TOptions extends PluginFactoryOptions> = {\n config: Config\n adapter: Adapter\n meta?: InputMeta\n driver: KubbDriver\n plugin: NormalizedPlugin<TOptions>\n options: TOptions['resolvedOptions']\n resolver: TOptions['resolver']\n}\n\nfunction createMockedPluginContext<TOptions extends PluginFactoryOptions>(opts: RenderGeneratorOptions<TOptions>): Omit<GeneratorContext<TOptions>, 'options'> {\n const root = resolve(opts.config.root, opts.config.output.path)\n\n return {\n config: opts.config,\n root,\n adapter: opts.adapter,\n resolver: opts.resolver,\n plugin: opts.plugin,\n driver: opts.driver,\n getResolver: (name: string) => opts.driver.getResolver(name),\n meta: opts.meta ?? { circularNames: [], enumNames: [] },\n addFile: async (...files: Array<FileNode>) => opts.driver.fileManager.add(...files),\n upsertFile: async (...files: Array<FileNode>) => opts.driver.fileManager.upsert(...files),\n hooks: opts.driver.hooks ?? ({} as never),\n warn: (msg: string) => console.warn(msg),\n error: (msg: string) => console.error(msg),\n info: (msg: string) => console.info(msg),\n } as unknown as Omit<GeneratorContext<TOptions>, 'options'>\n}\n\n/**\n * Renders a generator's `schema` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorSchema<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: SchemaNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.schema) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.schema(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operation` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperation(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperation<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n node: OperationNode,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operation) return\n const context = createMockedPluginContext(opts)\n const transformedNode = opts.plugin.transformer ? transform(node, opts.plugin.transformer) : node\n const result = await generator.operation(transformedNode, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\n/**\n * Renders a generator's `operations` method in a test context.\n *\n * @example\n * ```ts\n * await renderGeneratorOperations(classClientGenerator, nodes, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files)\n * ```\n */\nexport async function renderGeneratorOperations<TOptions extends PluginFactoryOptions>(\n generator: Generator<TOptions>,\n nodes: Array<OperationNode>,\n opts: RenderGeneratorOptions<TOptions>,\n): Promise<void> {\n if (!generator.operations) return\n const context = createMockedPluginContext(opts)\n const transformedNodes = opts.plugin.transformer ? nodes.map((n) => transform(n, opts.plugin.transformer!)) : nodes\n const result = await generator.operations(transformedNodes, {\n ...context,\n options: opts.options,\n })\n await opts.driver.dispatch({ result, renderer: generator.renderer })\n}\n\ntype MatchFilesOptions = {\n /**\n * Parsers indexed by file extension, used to render each `FileNode` to source.\n * Without a matching parser the file's raw content is used.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Formatter applied to non-JSON output before snapshotting, e.g. prettier. When\n * omitted the parsed source is snapshotted as-is.\n */\n format?: (source?: string) => string | Promise<string>\n /**\n * Subfolder under `__snapshots__`, camelCased. Useful to keep variant snapshots apart.\n */\n pre?: string\n}\n\n/**\n * Renders the driver's collected `FileNode`s to source and asserts each against a file snapshot.\n * Pair it with the `renderGenerator*` helpers to snapshot a generator's output.\n *\n * @example\n * ```ts\n * await renderGeneratorSchema(typeGenerator, node, { config, adapter, driver, plugin, options, resolver })\n * await matchFiles(driver.fileManager.files, { parsers, format })\n * ```\n */\nexport async function matchFiles(files: Array<FileNode> | undefined, options: MatchFilesOptions = {}): Promise<Map<string, string> | undefined> {\n if (!files?.length) return\n\n const { parsers = new Map(), format, pre } = options\n const fileProcessor = new FileProcessor({ storage: memoryStorage(), parsers })\n const processed = new Map<string, string>()\n\n for (const file of files) {\n if (!file?.path || processed.has(file.path)) {\n continue\n }\n\n const parsed = fileProcessor.parse(file)\n const code = file.baseName.endsWith('.json') || !format ? parsed : await format(parsed)\n\n processed.set(file.path, code)\n\n const snapshotPath = path.join('__snapshots__', ...(pre ? [camelCase(pre)] : []), file.baseName)\n await expect(code).toMatchFileSnapshot(snapshotPath)\n }\n\n return processed\n}\n"],"mappings":"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"} |
+4
-4
| { | ||
| "name": "@kubb/core", | ||
| "version": "5.0.0-beta.58", | ||
| "version": "5.0.0-beta.59", | ||
| "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,10 +59,10 @@ "keywords": [ | ||
| "dependencies": { | ||
| "@kubb/ast": "5.0.0-beta.58" | ||
| "@kubb/ast": "5.0.0-beta.59" | ||
| }, | ||
| "devDependencies": { | ||
| "@internals/utils": "0.0.0", | ||
| "@kubb/renderer-jsx": "5.0.0-beta.58" | ||
| "@kubb/renderer-jsx": "5.0.0-beta.59" | ||
| }, | ||
| "peerDependencies": { | ||
| "@kubb/renderer-jsx": "5.0.0-beta.58" | ||
| "@kubb/renderer-jsx": "5.0.0-beta.59" | ||
| }, | ||
@@ -69,0 +69,0 @@ "engines": { |
@@ -38,4 +38,4 @@ import type { PossiblePromise } from '@internals/utils' | ||
| * Adapters live between the spec format and the plugins. The built-in | ||
| * `@kubb/adapter-oas` handles OpenAPI 2.0, 3.0, and 3.1; custom adapters can | ||
| * support GraphQL, gRPC, AsyncAPI, or any domain-specific schema language. | ||
| * `@kubb/adapter-oas` handles OpenAPI 2.0, 3.0, and 3.1. A custom adapter can | ||
| * support GraphQL, gRPC, or another schema language. | ||
| * | ||
@@ -99,5 +99,4 @@ * @example | ||
| * Defines a custom adapter that translates a spec format into Kubb's universal | ||
| * AST. Use this when you need to consume GraphQL, gRPC, AsyncAPI, or another | ||
| * domain-specific schema. Built-in adapters: `@kubb/adapter-oas` for | ||
| * OpenAPI/Swagger documents. | ||
| * AST, for example GraphQL, gRPC, or AsyncAPI. The built-in `@kubb/adapter-oas` | ||
| * handles OpenAPI/Swagger documents. | ||
| * | ||
@@ -104,0 +103,0 @@ * Adapters must return an `InputNode` from `parse`. That node is what every |
@@ -46,6 +46,5 @@ import type { FileNode } from '@kubb/ast' | ||
| * | ||
| * Use this to support output formats beyond JSX, for instance, a Handlebars | ||
| * renderer, a string-template renderer, or a renderer that writes binary | ||
| * files. Plugins and generators pick the renderer to use via the `renderer` | ||
| * field on `defineGenerator`. | ||
| * A renderer can target output formats beyond JSX, for instance a Handlebars | ||
| * renderer or one that writes binary files. Plugins and generators pick the | ||
| * renderer to use via the `renderer` field on `defineGenerator`. | ||
| * | ||
@@ -52,0 +51,0 @@ * @example A minimal renderer that wraps a custom runtime |
@@ -60,3 +60,3 @@ import type { Config } from './types.ts' | ||
| * Host-facing reporter, as installed onto a run. Unlike a Logger (the live TUI view), a reporter | ||
| * never sees the event emitter. `report` runs once per config; `drain`, when present, runs once | ||
| * never sees the event emitter. `report` runs once per config. `drain`, when present, runs once | ||
| * after the last config. | ||
@@ -63,0 +63,0 @@ */ |
| /** | ||
| * Backend that persists generated files. Kubb ships with `fsStorage` (writes | ||
| * to disk) and `memoryStorage` (keeps everything in RAM). Implement this | ||
| * interface to write to S3, a database, or any other target. | ||
| * interface to write somewhere else, such as S3 or a database. | ||
| */ | ||
@@ -45,5 +45,5 @@ export type Storage = { | ||
| * 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). | ||
| * returns a `Storage` implementation. Kubb ships with filesystem and in-memory | ||
| * storages. A custom backend writes generated files elsewhere, such as cloud | ||
| * storage or a database. | ||
| * | ||
@@ -50,0 +50,0 @@ * @example In-memory storage (the built-in implementation) |
@@ -120,4 +120,4 @@ import type { AsyncEventEmitter, PossiblePromise } from '@internals/utils' | ||
| * Each method (`schema`, `operation`, `operations`) is called for the matching node type. | ||
| * Each method returns `TElement | Array<FileNode> | undefined | null`. JSX-based generators require a `renderer` factory. | ||
| * Return `Array<FileNode>` directly or call `ctx.upsertFile()` manually and return `undefined` or `null` to bypass rendering. | ||
| * JSX-based generators require a `renderer` factory. Return `Array<FileNode>` directly, or call | ||
| * `ctx.upsertFile()` manually and return `null` to bypass rendering. | ||
| * | ||
@@ -124,0 +124,0 @@ * @note Generators are consumed by plugins and registered via `ctx.addGenerator()` in `kubb:plugin:setup`. |
+13
-18
@@ -9,5 +9,5 @@ import type { FileNode, HttpMethod, UserFileNode, Visitor } from '@kubb/ast' | ||
| /** | ||
| * 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. | ||
| * Reads a type from a registry, falling back to `{}` when the key is absent. Lets | ||
| * `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry` be augmented without | ||
| * touching core. | ||
| * | ||
@@ -96,3 +96,3 @@ * @internal | ||
| * Intersect into a plugin's `Options` type instead of declaring `output` and | ||
| * `group` directly — `mode` lives inside `output` while `group` is its sibling. | ||
| * `group` directly, since `mode` lives inside `output` while `group` is its sibling. | ||
| * The generic keeps a plugin's extended `Output` shape intact. | ||
@@ -206,5 +206,4 @@ * | ||
| /** | ||
| * Filter that skips matching operations or schemas during generation. Use it | ||
| * to drop deprecated endpoints, internal-only schemas, or anything you do | ||
| * not want code generated for. | ||
| * Filter that skips matching operations or schemas during generation, for example | ||
| * deprecated endpoints or internal-only schemas. | ||
| * | ||
@@ -289,4 +288,4 @@ * @example | ||
| /** | ||
| * Context for hook-style plugin `kubb:plugin:setup` handler. | ||
| * Provides methods to register generators, configure resolvers, transformers, and renderers. | ||
| * Context passed to a plugin's `kubb:plugin:setup` handler, where it registers generators and | ||
| * sets its resolver, transformer, and options. | ||
| */ | ||
@@ -332,7 +331,4 @@ export type KubbPluginSetupContext<TFactory extends PluginFactoryOptions = PluginFactoryOptions> = { | ||
| /** | ||
| * A plugin object produced by `definePlugin`. | ||
| * Instead of flat lifecycle methods, it groups all handlers under a `hooks:` property | ||
| * (matching Astro's integration naming convention). | ||
| * | ||
| * @template TFactory - The plugin's `PluginFactoryOptions` type. | ||
| * A plugin object produced by `definePlugin`. Its lifecycle handlers live under a single | ||
| * `hooks` property rather than flat methods. | ||
| */ | ||
@@ -375,4 +371,4 @@ export type Plugin<TFactory extends PluginFactoryOptions = PluginFactoryOptions> = { | ||
| /** | ||
| * Normalized plugin after setup, with runtime fields populated. | ||
| * For internal use only, plugins use the public `Plugin` type externally. | ||
| * Normalized plugin after setup, with runtime fields populated. Internal only. Plugins use the | ||
| * public `Plugin` type. | ||
| * | ||
@@ -418,4 +414,3 @@ * @internal | ||
| * Wraps a plugin factory and returns a function that accepts user options and | ||
| * yields a fully typed `Plugin`. Lifecycle handlers go inside a single | ||
| * `hooks` object (inspired by Astro integrations). | ||
| * yields a typed `Plugin`. Lifecycle handlers go inside a single `hooks` object. | ||
| * | ||
@@ -422,0 +417,0 @@ * Pass a `PluginFactoryOptions` type parameter to get a typed `ctx` inside |
@@ -380,4 +380,4 @@ import path from 'node:path' | ||
| * | ||
| * - `mode: 'file'` — resolves directly to `output.path` (the full file path, extension included). | ||
| * - `mode: 'directory'` (default) — resolves to `output.path/{baseName}`, or into a | ||
| * - `mode: 'file'` resolves directly to `output.path` (the full file path, extension included). | ||
| * - `mode: 'directory'` (default) resolves to `output.path/{baseName}`, or into a | ||
| * subdirectory when `group` and a `tag`/`path` value are provided. | ||
@@ -666,4 +666,4 @@ * | ||
| * | ||
| * Methods in the returned object can call sibling resolver methods via `this`, | ||
| * which keeps custom rules small (`this.default(name, 'type')` to delegate). | ||
| * Methods in the returned object can call sibling resolver methods via `this`. | ||
| * A custom rule can delegate to a default, for example `this.default(name, 'type')`. | ||
| * | ||
@@ -670,0 +670,0 @@ * @example Basic resolver with naming helpers |
+8
-11
@@ -10,3 +10,3 @@ import { AsyncLocalStorage } from 'node:async_hooks' | ||
| /** | ||
| * Docs major, derived from the package version so the link tracks the published major. | ||
| * Docs major version, derived from the package version so the link tracks the published major. | ||
| */ | ||
@@ -23,5 +23,4 @@ const docsMajor = version.split('.')[0] ?? '5' | ||
| * A human-readable explanation of a diagnostic code: a short title, what triggers it, and how | ||
| * to resolve it. This is the single source of truth the kubb.dev `/diagnostics/<slug>` pages | ||
| * mirror, so every code stays documented in one place. Typed as a total record over | ||
| * {@link DiagnosticCode}, so adding a code without documenting it fails the build. | ||
| * to resolve it. This is the source of truth the kubb.dev `/diagnostics/<slug>` pages mirror, so | ||
| * every code stays documented in one place. Adding a code without documenting it fails the build. | ||
| */ | ||
@@ -88,5 +87,3 @@ export type DiagnosticDoc = { | ||
| * A build problem collected during a run, gathered into the result instead of | ||
| * aborting on the first failure. It carries a stable {@link ProblemCode}, a | ||
| * `severity`, a `message`, and optionally a `location` into the source document, | ||
| * a `help`, the `plugin` that produced it, and the `cause` it wraps. | ||
| * aborting on the first failure. | ||
| */ | ||
@@ -120,5 +117,5 @@ export type ProblemDiagnostic = { | ||
| /** | ||
| * A per-plugin performance record, built with {@link Diagnostics.performance}. It carries the | ||
| * `plugin` and its elapsed `duration`, and the `performance` kind keeps it out of the problem | ||
| * list. It feeds the per-plugin timing bars, and reporters sum these into the run total. | ||
| * A per-plugin performance record, built with {@link Diagnostics.performance}. The `performance` | ||
| * kind keeps it out of the problem list. It feeds the per-plugin timing bars, and reporters sum | ||
| * these into the run total. | ||
| */ | ||
@@ -142,3 +139,3 @@ export type PerformanceDiagnostic = { | ||
| * A notice that a newer Kubb version is available on npm, built with {@link Diagnostics.update}. | ||
| * It carries the running and latest versions and renders like any info diagnostic. | ||
| * It renders like any info diagnostic. | ||
| */ | ||
@@ -145,0 +142,0 @@ export type UpdateDiagnostic = { |
@@ -57,3 +57,4 @@ import { resolve } from 'node:path' | ||
| /** | ||
| * The streaming `InputNode<true>` produced by the adapter. * Always set after adapter setup, parse-only adapters are wrapped automatically. | ||
| * The streaming `InputNode<true>` produced by the adapter. Set after adapter setup. | ||
| * Parse-only adapters are wrapped automatically. | ||
| */ | ||
@@ -476,3 +477,3 @@ inputNode: InputNode<true> | null = null | ||
| * of all at once after every plugin has marched through the parallel batches together. | ||
| * That ordering is what drives the CLI's `Plugins N/M` counter; without it the bar would | ||
| * That ordering is what drives the CLI's `Plugins N/M` counter. Without it the bar would | ||
| * sit at the initial value until the very end of the run. | ||
@@ -479,0 +480,0 @@ * |
+0
-1
@@ -14,3 +14,2 @@ import path, { resolve } from 'node:path' | ||
| /** | ||
| * Creates a minimal `PluginDriver` mock for unit tests. | ||
@@ -17,0 +16,0 @@ */ |
@@ -76,3 +76,3 @@ import { styleText } from 'node:util' | ||
| * The default `cli` reporter. Renders the {@link Report} for each config as it finishes, independent | ||
| * of the live logger view. Suppressed at `silent`; the `verbose` level adds the per-plugin timings. | ||
| * of the live logger view. Suppressed at `silent`. The `verbose` level adds the per-plugin timings. | ||
| */ | ||
@@ -79,0 +79,0 @@ export const cliReporter = createReporter({ |
+9
-15
@@ -13,5 +13,5 @@ import type { PossiblePromise } from '@internals/utils' | ||
| /** | ||
| * 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. | ||
| * Extracts a type from a registry, falling back to `{}` when the key doesn't exist. | ||
| * Lets plugins augment `Kubb.ConfigOptionsRegistry` and `Kubb.PluginOptionsRegistry` | ||
| * without changing core. | ||
| * | ||
@@ -141,3 +141,2 @@ * @internal | ||
| * | ||
| * @default false | ||
| * @example | ||
@@ -153,3 +152,2 @@ * ```ts | ||
| * | ||
| * @default false | ||
| * @example | ||
@@ -167,3 +165,2 @@ * ```ts | ||
| * | ||
| * @default false | ||
| * @example | ||
@@ -206,3 +203,2 @@ * ```ts | ||
| * | ||
| * @default false | ||
| * @example | ||
@@ -253,6 +249,5 @@ * ```ts | ||
| /** | ||
| * Lifecycle hooks that execute during or after the build process. | ||
| * Lifecycle hooks that run external tools (prettier, eslint, a custom script) on build events. | ||
| * | ||
| * 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. | ||
| * Currently supports the `done` hook, which fires after all plugins complete. | ||
| * | ||
@@ -270,7 +265,6 @@ * @example | ||
| /** | ||
| * Command(s) to run after all plugins complete generation. | ||
| * Command(s) to run after all plugins finish generating, for post-processing the output. | ||
| * | ||
| * 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. | ||
| * Pass a single command string, or an array to run them in sequence. | ||
| * Commands run relative to the `root` directory. | ||
| * | ||
@@ -772,3 +766,3 @@ * @example | ||
| * All accepted forms of a Kubb configuration. | ||
| * Accepts `Config`/`Config[]`/promise or a factory (optionally receiving `TCliOptions`. | ||
| * Accepts `Config`/`Config[]`/promise or a factory (optionally receiving `TCliOptions`). | ||
| */ | ||
@@ -775,0 +769,0 @@ export type PossibleConfig<TCliOptions = undefined> = |
Sorry, the diff of this file is too big to display
| //#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_factory = require("@kubb/ast/factory"); | ||
| _kubb_ast_factory = __toESM(_kubb_ast_factory, 1); | ||
| let _kubb_ast_utils = require("@kubb/ast/utils"); | ||
| //#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; | ||
| return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `camelCase('hello-world') // 'helloWorld'` | ||
| * | ||
| * @example With a prefix | ||
| * `camelCase('tag', { prefix: 'create' }) // 'createTag'` | ||
| */ | ||
| function camelCase(text, { prefix = "", suffix = "" } = {}) { | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `pascalCase('hello-world') // 'HelloWorld'` | ||
| * | ||
| * @example With a suffix | ||
| * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'` | ||
| */ | ||
| function pascalCase(text, { prefix = "", suffix = "" } = {}) { | ||
| 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 ? _kubb_ast_factory.createFile(mergeFile(existing, file)) : _kubb_ast_factory.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_utils.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-Bz4ZDIZz.cjs.map |
| {"version":3,"file":"memoryStorage-Bz4ZDIZz.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","factory","#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 Array<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]: Array<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<Array<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<Array<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 * 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 return 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 .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\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 * as factory from '@kubb/ast/factory'\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 ? factory.createFile(mergeFile(existing, file)) : factory.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/utils'\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 "./chunk-C0LytTxp.js"; | ||
| import { EventEmitter } from "node:events"; | ||
| import * as factory from "@kubb/ast/factory"; | ||
| import { extractStringsFromNodes } from "@kubb/ast/utils"; | ||
| //#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; | ||
| return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `camelCase('hello-world') // 'helloWorld'` | ||
| * | ||
| * @example With a prefix | ||
| * `camelCase('tag', { prefix: 'create' }) // 'createTag'` | ||
| */ | ||
| function camelCase(text, { prefix = "", suffix = "" } = {}) { | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * | ||
| * @example Word boundaries | ||
| * `pascalCase('hello-world') // 'HelloWorld'` | ||
| * | ||
| * @example With a suffix | ||
| * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'` | ||
| */ | ||
| function pascalCase(text, { prefix = "", suffix = "" } = {}) { | ||
| 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 ? factory.createFile(mergeFile(existing, file)) : factory.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-DZYKdzI6.js.map |
| {"version":3,"file":"memoryStorage-DZYKdzI6.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 Array<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]: Array<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<Array<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<Array<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 * 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 return 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 .split(/[\\s\\-_./\\\\:]+/)\n .filter(Boolean)\n .map((word, i) => {\n if (word.length > 1 && word === word.toUpperCase()) return word\n const head = i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()\n return head + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Converts `text` to camelCase.\n *\n * @example Word boundaries\n * `camelCase('hello-world') // 'helloWorld'`\n *\n * @example With a prefix\n * `camelCase('tag', { prefix: 'create' }) // 'createTag'`\n */\nexport function camelCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n *\n * @example Word boundaries\n * `pascalCase('hello-world') // 'HelloWorld'`\n *\n * @example With a suffix\n * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`\n */\nexport function pascalCase(text: string, { prefix = '', suffix = '' }: Options = {}): string {\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 * as factory from '@kubb/ast/factory'\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 ? factory.createFile(mergeFile(existing, file)) : factory.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/utils'\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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"} |
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
1101417
-0.39%16203
-0.21%+ Added
+ Added
- Removed
- Removed
Updated