@kubb/core
Advanced tools
Sorry, the diff of this file is too big to display
| import "./chunk-C0LytTxp.js"; | ||
| import { EventEmitter } from "node:events"; | ||
| import { createFile, extractStringsFromNodes } from "@kubb/ast"; | ||
| //#region ../../internals/utils/src/errors.ts | ||
| /** | ||
| * Thrown when one or more errors occur during a Kubb build. | ||
| * Carries the full list of underlying errors on `errors`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * throw new BuildError('Build failed', { errors: [err1, err2] }) | ||
| * ``` | ||
| */ | ||
| var BuildError = class extends Error { | ||
| errors; | ||
| constructor(message, options) { | ||
| super(message, { cause: options.cause }); | ||
| this.name = "BuildError"; | ||
| this.errors = options.errors; | ||
| } | ||
| }; | ||
| /** | ||
| * Coerces an unknown thrown value to an `Error` instance. | ||
| * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * try { ... } catch(err) { | ||
| * throw new BuildError('Build failed', { cause: toError(err), errors: [] }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| function toError(value) { | ||
| return value instanceof Error ? value : new Error(String(value)); | ||
| } | ||
| /** | ||
| * Extracts a human-readable message from any thrown value. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * getErrorMessage(new Error('oops')) // 'oops' | ||
| * getErrorMessage('plain string') // 'plain string' | ||
| * ``` | ||
| */ | ||
| function getErrorMessage(value) { | ||
| return value instanceof Error ? value.message : String(value); | ||
| } | ||
| //#endregion | ||
| //#region ../../internals/utils/src/asyncEventEmitter.ts | ||
| /** | ||
| * Typed `EventEmitter` that awaits all async listeners before resolving. | ||
| * Wraps Node's `EventEmitter` with full TypeScript event-map inference. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const emitter = new AsyncEventEmitter<{ build: [name: string] }>() | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * await emitter.emit('build', 'petstore') // all listeners awaited | ||
| * ``` | ||
| */ | ||
| var AsyncEventEmitter = class { | ||
| /** | ||
| * Maximum number of listeners per event before Node emits a memory-leak warning. | ||
| * @default 10 | ||
| */ | ||
| constructor(maxListener = 10) { | ||
| this.#emitter.setMaxListeners(maxListener); | ||
| } | ||
| #emitter = new EventEmitter(); | ||
| /** | ||
| * Emits `eventName` and awaits all registered listeners sequentially. | ||
| * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * await emitter.emit('build', 'petstore') | ||
| * ``` | ||
| */ | ||
| emit(eventName, ...eventArgs) { | ||
| const listeners = this.#emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| return this.#emitAll(eventName, listeners, eventArgs); | ||
| } | ||
| async #emitAll(eventName, listeners, eventArgs) { | ||
| for (const listener of listeners) try { | ||
| await listener(...eventArgs); | ||
| } catch (err) { | ||
| let serializedArgs; | ||
| try { | ||
| serializedArgs = JSON.stringify(eventArgs); | ||
| } catch { | ||
| serializedArgs = String(eventArgs); | ||
| } | ||
| throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) }); | ||
| } | ||
| } | ||
| /** | ||
| * Registers a persistent listener for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| on(eventName, handler) { | ||
| this.#emitter.on(eventName, handler); | ||
| } | ||
| /** | ||
| * Registers a one-shot listener that removes itself after the first invocation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.onOnce('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| onOnce(eventName, handler) { | ||
| const wrapper = (...args) => { | ||
| this.off(eventName, wrapper); | ||
| return handler(...args); | ||
| }; | ||
| this.on(eventName, wrapper); | ||
| } | ||
| /** | ||
| * Removes a previously registered listener. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.off('build', handler) | ||
| * ``` | ||
| */ | ||
| off(eventName, handler) { | ||
| this.#emitter.off(eventName, handler); | ||
| } | ||
| /** | ||
| * Returns the number of listeners registered for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', handler) | ||
| * emitter.listenerCount('build') // 1 | ||
| * ``` | ||
| */ | ||
| listenerCount(eventName) { | ||
| return this.#emitter.listenerCount(eventName); | ||
| } | ||
| /** | ||
| * Raises or lowers the per-event listener ceiling before Node warns about a memory leak. | ||
| * Set this above the expected listener count when many listeners attach by design. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.setMaxListeners(40) | ||
| * ``` | ||
| */ | ||
| setMaxListeners(max) { | ||
| this.#emitter.setMaxListeners(max); | ||
| } | ||
| /** | ||
| * Returns the current per-event listener ceiling. | ||
| */ | ||
| getMaxListeners() { | ||
| return this.#emitter.getMaxListeners(); | ||
| } | ||
| /** | ||
| * Removes all listeners from every event channel. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.removeAll() | ||
| * ``` | ||
| */ | ||
| removeAll() { | ||
| this.#emitter.removeAllListeners(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region ../../internals/utils/src/casing.ts | ||
| /** | ||
| * Shared implementation for camelCase and PascalCase conversion. | ||
| * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons) | ||
| * and capitalizes each word according to `pascal`. | ||
| * | ||
| * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are. | ||
| */ | ||
| function toCamelOrPascal(text, pascal) { | ||
| return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => { | ||
| if (word.length > 1 && word === word.toUpperCase()) return word; | ||
| 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 ? createFile(mergeFile(existing, file)) : createFile(file); | ||
| this.#cache.set(merged.path, merged); | ||
| resolved.push(merged); | ||
| this.hooks.emit("upsert", merged); | ||
| } | ||
| if (resolved.length > 0) this.#sorted = null; | ||
| return resolved; | ||
| } | ||
| #dedupe(files) { | ||
| const seen = /* @__PURE__ */ new Map(); | ||
| for (const file of files) { | ||
| const prev = seen.get(file.path); | ||
| seen.set(file.path, prev ? mergeFile(prev, file) : file); | ||
| } | ||
| return [...seen.values()]; | ||
| } | ||
| getByPath(path) { | ||
| return this.#cache.get(path) ?? null; | ||
| } | ||
| deleteByPath(path) { | ||
| if (!this.#cache.delete(path)) return; | ||
| this.#sorted = null; | ||
| } | ||
| clear() { | ||
| this.#cache.clear(); | ||
| this.#sorted = null; | ||
| } | ||
| /** | ||
| * Releases all stored files and clears every `hooks` listener. Called by the core after | ||
| * `kubb:build:end`. | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.hooks.removeAll(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| /** | ||
| * All stored files in stable sort order (shortest path first, barrel files | ||
| * last within a length bucket). Returns a cached view, do not mutate. | ||
| */ | ||
| get files() { | ||
| return this.#sorted ??= [...this.#cache.values()].sort(compareFiles); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/FileProcessor.ts | ||
| function joinSources(file) { | ||
| const sources = file.sources; | ||
| if (sources.length === 0) return ""; | ||
| const parts = []; | ||
| for (const source of sources) { | ||
| const text = extractStringsFromNodes(source.nodes); | ||
| if (text) parts.push(text); | ||
| } | ||
| return parts.join("\n\n"); | ||
| } | ||
| /** | ||
| * Turns `FileNode`s into source strings and writes them to storage. | ||
| * | ||
| * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the | ||
| * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and | ||
| * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight. | ||
| * | ||
| * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next | ||
| * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been | ||
| * written and is meant for the end of a build. | ||
| * | ||
| * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to | ||
| * `hooks` and re-emit on the kubb bus. | ||
| */ | ||
| var FileProcessor = class { | ||
| hooks = new AsyncEventEmitter(); | ||
| #parsers; | ||
| #storage; | ||
| #extension; | ||
| #pending = /* @__PURE__ */ new Map(); | ||
| #runningFlush = null; | ||
| constructor(options) { | ||
| this.#parsers = options.parsers ?? null; | ||
| this.#storage = options.storage; | ||
| this.#extension = options.extension ?? null; | ||
| } | ||
| /** | ||
| * Files waiting in the queue. | ||
| */ | ||
| get size() { | ||
| return this.#pending.size; | ||
| } | ||
| parse(file) { | ||
| const parsers = this.#parsers; | ||
| const parseExtName = this.#extension?.[file.extname] || void 0; | ||
| if (!parsers || !file.extname) return joinSources(file); | ||
| const parser = parsers.get(file.extname); | ||
| if (!parser) return joinSources(file); | ||
| return parser.parse(file, { extname: parseExtName }); | ||
| } | ||
| *stream(files) { | ||
| const total = files.length; | ||
| if (total === 0) return; | ||
| let processed = 0; | ||
| for (const file of files) { | ||
| const source = this.parse(file); | ||
| processed++; | ||
| yield { | ||
| file, | ||
| source, | ||
| processed, | ||
| total, | ||
| percentage: processed / total * 100 | ||
| }; | ||
| } | ||
| } | ||
| async run(files) { | ||
| await this.hooks.emit("start", files); | ||
| for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", { | ||
| file, | ||
| source, | ||
| processed, | ||
| percentage, | ||
| total | ||
| }); | ||
| await this.hooks.emit("end", files); | ||
| return files; | ||
| } | ||
| /** | ||
| * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous | ||
| * entry, matching `FileManager.upsert`. Fires the `enqueue` event. | ||
| */ | ||
| enqueue(file) { | ||
| this.#pending.set(file.path, file); | ||
| this.hooks.emit("enqueue", file); | ||
| } | ||
| /** | ||
| * Starts processing the queued files. Waits for any previous flush to finish (so two | ||
| * batches never run together) and then returns without waiting for the new one. The next | ||
| * `flush` or `drain` picks up the in-flight task. | ||
| */ | ||
| async flush() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size === 0) return; | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| this.#runningFlush = this.#processAndWrite(batch).finally(() => { | ||
| this.#runningFlush = null; | ||
| }); | ||
| } | ||
| /** | ||
| * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event | ||
| * when both are done. | ||
| */ | ||
| async drain() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size > 0) { | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| await this.#processAndWrite(batch); | ||
| } | ||
| await this.hooks.emit("drain"); | ||
| } | ||
| async #processAndWrite(files) { | ||
| const storage = this.#storage; | ||
| await this.hooks.emit("start", files); | ||
| const queue = []; | ||
| for (const item of this.stream(files)) { | ||
| await this.hooks.emit("update", item); | ||
| if (item.source) { | ||
| queue.push(storage.setItem(item.file.path, item.source)); | ||
| if (queue.length >= 50) await Promise.all(queue.splice(0)); | ||
| } | ||
| } | ||
| await Promise.all(queue); | ||
| await this.hooks.emit("end", files); | ||
| } | ||
| /** | ||
| * Clears every listener and the pending queue. | ||
| */ | ||
| dispose() { | ||
| this.hooks.removeAll(); | ||
| this.#pending.clear(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js | ||
| function _usingCtx() { | ||
| var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) { | ||
| var n = Error(); | ||
| return n.name = "SuppressedError", n.error = r, n.suppressed = e, n; | ||
| }; | ||
| var e = {}; | ||
| var n = []; | ||
| function using(r, e) { | ||
| if (null != e) { | ||
| if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined."); | ||
| if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")]; | ||
| if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o; | ||
| if ("function" != typeof o) throw new TypeError("Object is not disposable."); | ||
| t && (o = function o() { | ||
| try { | ||
| t.call(e); | ||
| } catch (r) { | ||
| return Promise.reject(r); | ||
| } | ||
| }), n.push({ | ||
| v: e, | ||
| d: o, | ||
| a: r | ||
| }); | ||
| } else r && n.push({ | ||
| d: e, | ||
| a: r | ||
| }); | ||
| return e; | ||
| } | ||
| return { | ||
| e, | ||
| u: using.bind(null, !1), | ||
| a: using.bind(null, !0), | ||
| d: function d() { | ||
| var o; | ||
| var t = this.e; | ||
| var s = 0; | ||
| function next() { | ||
| for (; o = n.pop();) try { | ||
| if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next); | ||
| if (o.d) { | ||
| var r = o.d.call(o.v); | ||
| if (o.a) return s |= 2, Promise.resolve(r).then(next, err); | ||
| } else s |= 1; | ||
| } catch (r) { | ||
| return err(r); | ||
| } | ||
| if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve(); | ||
| if (t !== e) throw t; | ||
| } | ||
| function err(n) { | ||
| return t = t !== e ? new r(n, t) : n, next(); | ||
| } | ||
| return next(); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/storages/memoryStorage.ts | ||
| /** | ||
| * In-memory storage driver. Useful for testing and dry-run scenarios where | ||
| * generated output should be captured without touching the filesystem. | ||
| * | ||
| * All data lives in a `Map` scoped to the storage instance and is discarded | ||
| * when the instance is garbage-collected. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { memoryStorage } from '@kubb/core' | ||
| * import { defineConfig } from 'kubb' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * storage: memoryStorage(), | ||
| * }) | ||
| * ``` | ||
| */ | ||
| const memoryStorage = createStorage(() => { | ||
| const store = /* @__PURE__ */ new Map(); | ||
| return { | ||
| name: "memory", | ||
| async hasItem(key) { | ||
| return store.has(key); | ||
| }, | ||
| async getItem(key) { | ||
| return store.get(key) ?? null; | ||
| }, | ||
| async setItem(key, value) { | ||
| store.set(key, value); | ||
| }, | ||
| async removeItem(key) { | ||
| store.delete(key); | ||
| }, | ||
| async getKeys(base) { | ||
| const keys = [...store.keys()]; | ||
| return base ? keys.filter((k) => k.startsWith(base)) : keys; | ||
| }, | ||
| async clear(base) { | ||
| if (!base) { | ||
| store.clear(); | ||
| return; | ||
| } | ||
| for (const key of store.keys()) if (key.startsWith(base)) store.delete(key); | ||
| } | ||
| }; | ||
| }); | ||
| //#endregion | ||
| export { createStorage as a, camelCase as c, BuildError as d, getErrorMessage as f, FileManager as i, pascalCase as l, _usingCtx as n, OPERATION_FILTER_TYPES as o, FileProcessor as r, diagnosticCode as s, memoryStorage as t, AsyncEventEmitter as u }; | ||
| //# sourceMappingURL=memoryStorage-B0W-w994.js.map |
| {"version":3,"file":"memoryStorage-B0W-w994.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 { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"} |
| //#region \0rolldown/runtime.js | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { | ||
| value, | ||
| configurable: true | ||
| }); | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| let node_events = require("node:events"); | ||
| let _kubb_ast = require("@kubb/ast"); | ||
| //#region ../../internals/utils/src/errors.ts | ||
| /** | ||
| * Thrown when one or more errors occur during a Kubb build. | ||
| * Carries the full list of underlying errors on `errors`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * throw new BuildError('Build failed', { errors: [err1, err2] }) | ||
| * ``` | ||
| */ | ||
| var BuildError = class extends Error { | ||
| errors; | ||
| constructor(message, options) { | ||
| super(message, { cause: options.cause }); | ||
| this.name = "BuildError"; | ||
| this.errors = options.errors; | ||
| } | ||
| }; | ||
| /** | ||
| * Coerces an unknown thrown value to an `Error` instance. | ||
| * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * try { ... } catch(err) { | ||
| * throw new BuildError('Build failed', { cause: toError(err), errors: [] }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| function toError(value) { | ||
| return value instanceof Error ? value : new Error(String(value)); | ||
| } | ||
| /** | ||
| * Extracts a human-readable message from any thrown value. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * getErrorMessage(new Error('oops')) // 'oops' | ||
| * getErrorMessage('plain string') // 'plain string' | ||
| * ``` | ||
| */ | ||
| function getErrorMessage(value) { | ||
| return value instanceof Error ? value.message : String(value); | ||
| } | ||
| //#endregion | ||
| //#region ../../internals/utils/src/asyncEventEmitter.ts | ||
| /** | ||
| * Typed `EventEmitter` that awaits all async listeners before resolving. | ||
| * Wraps Node's `EventEmitter` with full TypeScript event-map inference. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const emitter = new AsyncEventEmitter<{ build: [name: string] }>() | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * await emitter.emit('build', 'petstore') // all listeners awaited | ||
| * ``` | ||
| */ | ||
| var AsyncEventEmitter = class { | ||
| /** | ||
| * Maximum number of listeners per event before Node emits a memory-leak warning. | ||
| * @default 10 | ||
| */ | ||
| constructor(maxListener = 10) { | ||
| this.#emitter.setMaxListeners(maxListener); | ||
| } | ||
| #emitter = new node_events.EventEmitter(); | ||
| /** | ||
| * Emits `eventName` and awaits all registered listeners sequentially. | ||
| * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * await emitter.emit('build', 'petstore') | ||
| * ``` | ||
| */ | ||
| emit(eventName, ...eventArgs) { | ||
| const listeners = this.#emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| return this.#emitAll(eventName, listeners, eventArgs); | ||
| } | ||
| async #emitAll(eventName, listeners, eventArgs) { | ||
| for (const listener of listeners) try { | ||
| await listener(...eventArgs); | ||
| } catch (err) { | ||
| let serializedArgs; | ||
| try { | ||
| serializedArgs = JSON.stringify(eventArgs); | ||
| } catch { | ||
| serializedArgs = String(eventArgs); | ||
| } | ||
| throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) }); | ||
| } | ||
| } | ||
| /** | ||
| * Registers a persistent listener for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| on(eventName, handler) { | ||
| this.#emitter.on(eventName, handler); | ||
| } | ||
| /** | ||
| * Registers a one-shot listener that removes itself after the first invocation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.onOnce('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| onOnce(eventName, handler) { | ||
| const wrapper = (...args) => { | ||
| this.off(eventName, wrapper); | ||
| return handler(...args); | ||
| }; | ||
| this.on(eventName, wrapper); | ||
| } | ||
| /** | ||
| * Removes a previously registered listener. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.off('build', handler) | ||
| * ``` | ||
| */ | ||
| off(eventName, handler) { | ||
| this.#emitter.off(eventName, handler); | ||
| } | ||
| /** | ||
| * Returns the number of listeners registered for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', handler) | ||
| * emitter.listenerCount('build') // 1 | ||
| * ``` | ||
| */ | ||
| listenerCount(eventName) { | ||
| return this.#emitter.listenerCount(eventName); | ||
| } | ||
| /** | ||
| * Raises or lowers the per-event listener ceiling before Node warns about a memory leak. | ||
| * Set this above the expected listener count when many listeners attach by design. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.setMaxListeners(40) | ||
| * ``` | ||
| */ | ||
| setMaxListeners(max) { | ||
| this.#emitter.setMaxListeners(max); | ||
| } | ||
| /** | ||
| * Returns the current per-event listener ceiling. | ||
| */ | ||
| getMaxListeners() { | ||
| return this.#emitter.getMaxListeners(); | ||
| } | ||
| /** | ||
| * Removes all listeners from every event channel. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.removeAll() | ||
| * ``` | ||
| */ | ||
| removeAll() { | ||
| this.#emitter.removeAllListeners(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region ../../internals/utils/src/casing.ts | ||
| /** | ||
| * Shared implementation for camelCase and PascalCase conversion. | ||
| * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons) | ||
| * and capitalizes each word according to `pascal`. | ||
| * | ||
| * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are. | ||
| */ | ||
| function toCamelOrPascal(text, pascal) { | ||
| return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => { | ||
| if (word.length > 1 && word === word.toUpperCase()) return word; | ||
| 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 ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file); | ||
| this.#cache.set(merged.path, merged); | ||
| resolved.push(merged); | ||
| this.hooks.emit("upsert", merged); | ||
| } | ||
| if (resolved.length > 0) this.#sorted = null; | ||
| return resolved; | ||
| } | ||
| #dedupe(files) { | ||
| const seen = /* @__PURE__ */ new Map(); | ||
| for (const file of files) { | ||
| const prev = seen.get(file.path); | ||
| seen.set(file.path, prev ? mergeFile(prev, file) : file); | ||
| } | ||
| return [...seen.values()]; | ||
| } | ||
| getByPath(path) { | ||
| return this.#cache.get(path) ?? null; | ||
| } | ||
| deleteByPath(path) { | ||
| if (!this.#cache.delete(path)) return; | ||
| this.#sorted = null; | ||
| } | ||
| clear() { | ||
| this.#cache.clear(); | ||
| this.#sorted = null; | ||
| } | ||
| /** | ||
| * Releases all stored files and clears every `hooks` listener. Called by the core after | ||
| * `kubb:build:end`. | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.hooks.removeAll(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| /** | ||
| * All stored files in stable sort order (shortest path first, barrel files | ||
| * last within a length bucket). Returns a cached view, do not mutate. | ||
| */ | ||
| get files() { | ||
| return this.#sorted ??= [...this.#cache.values()].sort(compareFiles); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/FileProcessor.ts | ||
| function joinSources(file) { | ||
| const sources = file.sources; | ||
| if (sources.length === 0) return ""; | ||
| const parts = []; | ||
| for (const source of sources) { | ||
| const text = (0, _kubb_ast.extractStringsFromNodes)(source.nodes); | ||
| if (text) parts.push(text); | ||
| } | ||
| return parts.join("\n\n"); | ||
| } | ||
| /** | ||
| * Turns `FileNode`s into source strings and writes them to storage. | ||
| * | ||
| * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the | ||
| * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and | ||
| * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight. | ||
| * | ||
| * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next | ||
| * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been | ||
| * written and is meant for the end of a build. | ||
| * | ||
| * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to | ||
| * `hooks` and re-emit on the kubb bus. | ||
| */ | ||
| var FileProcessor = class { | ||
| hooks = new AsyncEventEmitter(); | ||
| #parsers; | ||
| #storage; | ||
| #extension; | ||
| #pending = /* @__PURE__ */ new Map(); | ||
| #runningFlush = null; | ||
| constructor(options) { | ||
| this.#parsers = options.parsers ?? null; | ||
| this.#storage = options.storage; | ||
| this.#extension = options.extension ?? null; | ||
| } | ||
| /** | ||
| * Files waiting in the queue. | ||
| */ | ||
| get size() { | ||
| return this.#pending.size; | ||
| } | ||
| parse(file) { | ||
| const parsers = this.#parsers; | ||
| const parseExtName = this.#extension?.[file.extname] || void 0; | ||
| if (!parsers || !file.extname) return joinSources(file); | ||
| const parser = parsers.get(file.extname); | ||
| if (!parser) return joinSources(file); | ||
| return parser.parse(file, { extname: parseExtName }); | ||
| } | ||
| *stream(files) { | ||
| const total = files.length; | ||
| if (total === 0) return; | ||
| let processed = 0; | ||
| for (const file of files) { | ||
| const source = this.parse(file); | ||
| processed++; | ||
| yield { | ||
| file, | ||
| source, | ||
| processed, | ||
| total, | ||
| percentage: processed / total * 100 | ||
| }; | ||
| } | ||
| } | ||
| async run(files) { | ||
| await this.hooks.emit("start", files); | ||
| for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", { | ||
| file, | ||
| source, | ||
| processed, | ||
| percentage, | ||
| total | ||
| }); | ||
| await this.hooks.emit("end", files); | ||
| return files; | ||
| } | ||
| /** | ||
| * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous | ||
| * entry, matching `FileManager.upsert`. Fires the `enqueue` event. | ||
| */ | ||
| enqueue(file) { | ||
| this.#pending.set(file.path, file); | ||
| this.hooks.emit("enqueue", file); | ||
| } | ||
| /** | ||
| * Starts processing the queued files. Waits for any previous flush to finish (so two | ||
| * batches never run together) and then returns without waiting for the new one. The next | ||
| * `flush` or `drain` picks up the in-flight task. | ||
| */ | ||
| async flush() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size === 0) return; | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| this.#runningFlush = this.#processAndWrite(batch).finally(() => { | ||
| this.#runningFlush = null; | ||
| }); | ||
| } | ||
| /** | ||
| * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event | ||
| * when both are done. | ||
| */ | ||
| async drain() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size > 0) { | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| await this.#processAndWrite(batch); | ||
| } | ||
| await this.hooks.emit("drain"); | ||
| } | ||
| async #processAndWrite(files) { | ||
| const storage = this.#storage; | ||
| await this.hooks.emit("start", files); | ||
| const queue = []; | ||
| for (const item of this.stream(files)) { | ||
| await this.hooks.emit("update", item); | ||
| if (item.source) { | ||
| queue.push(storage.setItem(item.file.path, item.source)); | ||
| if (queue.length >= 50) await Promise.all(queue.splice(0)); | ||
| } | ||
| } | ||
| await Promise.all(queue); | ||
| await this.hooks.emit("end", files); | ||
| } | ||
| /** | ||
| * Clears every listener and the pending queue. | ||
| */ | ||
| dispose() { | ||
| this.hooks.removeAll(); | ||
| this.#pending.clear(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js | ||
| function _usingCtx() { | ||
| var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) { | ||
| var n = Error(); | ||
| return n.name = "SuppressedError", n.error = r, n.suppressed = e, n; | ||
| }; | ||
| var e = {}; | ||
| var n = []; | ||
| function using(r, e) { | ||
| if (null != e) { | ||
| if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined."); | ||
| if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")]; | ||
| if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o; | ||
| if ("function" != typeof o) throw new TypeError("Object is not disposable."); | ||
| t && (o = function o() { | ||
| try { | ||
| t.call(e); | ||
| } catch (r) { | ||
| return Promise.reject(r); | ||
| } | ||
| }), n.push({ | ||
| v: e, | ||
| d: o, | ||
| a: r | ||
| }); | ||
| } else r && n.push({ | ||
| d: e, | ||
| a: r | ||
| }); | ||
| return e; | ||
| } | ||
| return { | ||
| e, | ||
| u: using.bind(null, !1), | ||
| a: using.bind(null, !0), | ||
| d: function d() { | ||
| var o; | ||
| var t = this.e; | ||
| var s = 0; | ||
| function next() { | ||
| for (; o = n.pop();) try { | ||
| if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next); | ||
| if (o.d) { | ||
| var r = o.d.call(o.v); | ||
| if (o.a) return s |= 2, Promise.resolve(r).then(next, err); | ||
| } else s |= 1; | ||
| } catch (r) { | ||
| return err(r); | ||
| } | ||
| if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve(); | ||
| if (t !== e) throw t; | ||
| } | ||
| function err(n) { | ||
| return t = t !== e ? new r(n, t) : n, next(); | ||
| } | ||
| return next(); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/storages/memoryStorage.ts | ||
| /** | ||
| * In-memory storage driver. Useful for testing and dry-run scenarios where | ||
| * generated output should be captured without touching the filesystem. | ||
| * | ||
| * All data lives in a `Map` scoped to the storage instance and is discarded | ||
| * when the instance is garbage-collected. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { memoryStorage } from '@kubb/core' | ||
| * import { defineConfig } from 'kubb' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * storage: memoryStorage(), | ||
| * }) | ||
| * ``` | ||
| */ | ||
| const memoryStorage = createStorage(() => { | ||
| const store = /* @__PURE__ */ new Map(); | ||
| return { | ||
| name: "memory", | ||
| async hasItem(key) { | ||
| return store.has(key); | ||
| }, | ||
| async getItem(key) { | ||
| return store.get(key) ?? null; | ||
| }, | ||
| async setItem(key, value) { | ||
| store.set(key, value); | ||
| }, | ||
| async removeItem(key) { | ||
| store.delete(key); | ||
| }, | ||
| async getKeys(base) { | ||
| const keys = [...store.keys()]; | ||
| return base ? keys.filter((k) => k.startsWith(base)) : keys; | ||
| }, | ||
| async clear(base) { | ||
| if (!base) { | ||
| store.clear(); | ||
| return; | ||
| } | ||
| for (const key of store.keys()) if (key.startsWith(base)) store.delete(key); | ||
| } | ||
| }; | ||
| }); | ||
| //#endregion | ||
| Object.defineProperty(exports, "AsyncEventEmitter", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return AsyncEventEmitter; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "BuildError", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return BuildError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "FileManager", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return FileManager; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "FileProcessor", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return FileProcessor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "OPERATION_FILTER_TYPES", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return OPERATION_FILTER_TYPES; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "__name", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return __name; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "__toESM", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return __toESM; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "_usingCtx", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return _usingCtx; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "camelCase", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return camelCase; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "createStorage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return createStorage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "diagnosticCode", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return diagnosticCode; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "getErrorMessage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return getErrorMessage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "memoryStorage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return memoryStorage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "pascalCase", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return pascalCase; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=memoryStorage-skOz0dXZ.cjs.map |
| {"version":3,"file":"memoryStorage-skOz0dXZ.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends 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 { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"} |
+7
-143
| import { t as __name } from "./chunk-C0LytTxp.js"; | ||
| import { $ as Include, A as KubbHookStartContext, At as createReporter, B as ParsedFile, C as KubbFilesProcessingEndContext, Ct as Storage, D as KubbGenerationStartContext, Dt as ReporterContext, E as KubbGenerationEndContext, Et as Reporter, F as KubbSuccessContext, Ft as Adapter, G as Parser, H as createKubb, I as KubbWarnContext, It as AdapterFactoryOptions, J as GeneratorContext, K as defineParser, L as PossibleConfig, Lt as AdapterSource, M as KubbInfoContext, Mt as Cache, N as KubbLifecycleStartContext, Nt as CachedSnapshot, O as KubbHookEndContext, Ot as ReporterName, P as KubbPluginsEndContext, Pt as createCache, Q as Group, R as UserConfig, Rt as createAdapter, S as KubbFileProcessingUpdate, St as createRenderer, T as KubbFilesProcessingUpdateContext, Tt as GenerationResult, U as FsCacheOptions, V as Kubb, W as fsCache, X as KubbDriver, Y as defineGenerator, Z as Exclude, _ as InputPath, _t as ResolverFileParams, a as DiagnosticLocation, at as OutputMode, b as KubbDiagnosticContext, bt as Renderer, c as PerformanceDiagnostic, ct as Plugin, d as SerializedDiagnostic, dt as BannerMeta, et as KubbPluginEndContext, f as UpdateDiagnostic, ft as ResolveBannerContext, g as InputData, gt as ResolverContext, h as Config, ht as Resolver, i as DiagnosticKind, it as Output, j as KubbHooks, jt as logLevel, k as KubbHookLineContext, kt as UserReporter, l as ProblemCode, lt as PluginFactoryOptions, m as CLIOptions, mt as ResolveOptionsContext, n as DiagnosticByCode, nt as KubbPluginStartContext, o as DiagnosticSeverity, ot as OutputOptions, p as BuildOutput, pt as ResolveBannerFile, q as Generator, r as DiagnosticDoc, rt as NormalizedPlugin, s as Diagnostics, st as Override, t as Diagnostic, tt as KubbPluginSetupContext, u as ProblemDiagnostic, ut as definePlugin, v as KubbBuildEndContext, vt as ResolverPathParams, w as KubbFilesProcessingStartContext, wt as createStorage, x as KubbErrorContext, xt as RendererFactory, y as KubbBuildStartContext, yt as defineResolver, z as FileProcessorHooks, zt as AsyncEventEmitter } from "./diagnostics-D_LOtOCv.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-Bf2bC8lV.js"; | ||
| import * as ast from "@kubb/ast"; | ||
| //#region ../../internals/utils/src/urlPath.d.ts | ||
| type URLObject = { | ||
| /** | ||
| * The resolved URL string (Express-style or template literal, depending on context). | ||
| */ | ||
| url: string; | ||
| /** | ||
| * Extracted path parameters as a key-value map, or `undefined` when the path has none. | ||
| */ | ||
| params?: Record<string, string>; | ||
| }; | ||
| type ObjectOptions = { | ||
| /** | ||
| * Controls whether the `url` is rendered as an Express path or a template literal. | ||
| * @default 'path' | ||
| */ | ||
| type?: 'path' | 'template'; | ||
| /** | ||
| * Optional transform applied to each extracted parameter name. | ||
| */ | ||
| replacer?: (pathParam: string) => string; | ||
| /** | ||
| * When `true`, the result is serialized to a string expression instead of a plain object. | ||
| */ | ||
| stringify?: boolean; | ||
| }; | ||
| /** | ||
| * Supported identifier casing strategies for path parameters. | ||
| */ | ||
| type PathCasing = 'camelcase'; | ||
| type Options = { | ||
| /** | ||
| * Casing strategy applied to path parameter names. | ||
| * @default undefined (original identifier preserved) | ||
| */ | ||
| casing?: PathCasing; | ||
| }; | ||
| /** | ||
| * Parses and transforms an OpenAPI/Swagger path string into various URL formats. | ||
| * | ||
| * @example | ||
| * const p = new URLPath('/pet/{petId}') | ||
| * p.URL // '/pet/:petId' | ||
| * p.template // '`/pet/${petId}`' | ||
| */ | ||
| declare class URLPath { | ||
| #private; | ||
| /** | ||
| * The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`. | ||
| */ | ||
| path: string; | ||
| constructor(path: string, options?: Options); | ||
| /** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('/pet/{petId}').URL // '/pet/:petId' | ||
| * ``` | ||
| */ | ||
| get URL(): string; | ||
| /** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`). | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('https://petstore.swagger.io/v2/pet').isURL // true | ||
| * new URLPath('/pet/{petId}').isURL // false | ||
| * ``` | ||
| */ | ||
| get isURL(): boolean; | ||
| /** | ||
| * Converts the OpenAPI path to a TypeScript template literal string. | ||
| * | ||
| * @example | ||
| * new URLPath('/pet/{petId}').template // '`/pet/${petId}`' | ||
| * new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`' | ||
| */ | ||
| get template(): string; | ||
| /** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('/pet/{petId}').object | ||
| * // { url: '/pet/:petId', params: { petId: 'petId' } } | ||
| * ``` | ||
| */ | ||
| get object(): URLObject | string; | ||
| /** Returns a map of path parameter names, or `undefined` when the path has no parameters. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('/pet/{petId}').params // { petId: 'petId' } | ||
| * new URLPath('/pet').params // undefined | ||
| * ``` | ||
| */ | ||
| get params(): Record<string, string> | undefined; | ||
| toObject({ | ||
| type, | ||
| replacer, | ||
| stringify | ||
| }?: ObjectOptions): URLObject | string; | ||
| /** | ||
| * Converts the OpenAPI path to a TypeScript template literal string. | ||
| * An optional `replacer` can transform each extracted parameter name before interpolation. | ||
| * | ||
| * @example | ||
| * new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`' | ||
| */ | ||
| toTemplateString({ | ||
| prefix, | ||
| replacer | ||
| }?: { | ||
| prefix?: string | null; | ||
| replacer?: (pathParam: string) => string; | ||
| }): string; | ||
| /** | ||
| * Extracts all `{param}` segments from the path and returns them as a key-value map. | ||
| * An optional `replacer` transforms each parameter name in both key and value positions. | ||
| * Returns `undefined` when no path parameters are found. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('/pet/{petId}/tag/{tagId}').getParams() | ||
| * // { petId: 'petId', tagId: 'tagId' } | ||
| * ``` | ||
| */ | ||
| getParams(replacer?: (pathParam: string) => string): Record<string, string> | undefined; | ||
| /** Converts the OpenAPI path to Express-style colon syntax. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId' | ||
| * ``` | ||
| */ | ||
| toURLPath(): string; | ||
| } | ||
| //#endregion | ||
| //#region src/reporters/cliReporter.d.ts | ||
@@ -180,7 +44,7 @@ /** | ||
| * | ||
| * Internally uses the `write` utility from `@internals/utils`, which: | ||
| * - trims leading/trailing whitespace before writing | ||
| * - skips the write when file content is already identical (deduplication) | ||
| * - creates missing parent directories automatically | ||
| * - supports Bun's native file API when running under Bun | ||
| * Writes are deduplicated and directory-safe: | ||
| * - leading and trailing whitespace is trimmed before writing | ||
| * - the write is skipped when the file content is already identical | ||
| * - missing parent directories are created automatically | ||
| * - Bun's native file API is used when running under Bun | ||
| * | ||
@@ -223,3 +87,3 @@ * @example | ||
| //#endregion | ||
| export { type Adapter, type AdapterFactoryOptions, type AdapterSource, AsyncEventEmitter, type BannerMeta, BuildOutput, CLIOptions, type Cache, type CachedSnapshot, Config, type Diagnostic, type DiagnosticByCode, type DiagnosticDoc, type DiagnosticKind, type DiagnosticLocation, type DiagnosticSeverity, Diagnostics, type Exclude, type FileProcessorHooks, type FsCacheOptions, type GenerationResult, type Generator, type GeneratorContext, type Group, type Include, InputData, InputPath, type Kubb, KubbBuildEndContext, KubbBuildStartContext, KubbDiagnosticContext, KubbDriver, KubbErrorContext, KubbFileProcessingUpdate, KubbFilesProcessingEndContext, KubbFilesProcessingStartContext, KubbFilesProcessingUpdateContext, KubbGenerationEndContext, KubbGenerationStartContext, KubbHookEndContext, KubbHookLineContext, KubbHookStartContext, KubbHooks, KubbInfoContext, KubbLifecycleStartContext, type KubbPluginEndContext, type KubbPluginSetupContext, type KubbPluginStartContext, KubbPluginsEndContext, KubbSuccessContext, KubbWarnContext, type NormalizedPlugin, type Output, type OutputMode, type OutputOptions, type Override, type ParsedFile, type Parser, type PerformanceDiagnostic, type Plugin, type PluginFactoryOptions, PossibleConfig, type ProblemCode, type ProblemDiagnostic, type Renderer, type RendererFactory, type Reporter, type ReporterContext, type ReporterName, type ResolveBannerContext, type ResolveBannerFile, type ResolveOptionsContext, type Resolver, type ResolverContext, type ResolverFileParams, type ResolverPathParams, type SerializedDiagnostic, type Storage, URLPath, type UpdateDiagnostic, UserConfig, type UserReporter, ast, cliReporter, createAdapter, createCache, createKubb, createRenderer, createReporter, createStorage, defineGenerator, defineParser, definePlugin, defineResolver, fileReporter, fsCache, fsStorage, jsonReporter, logLevel, memoryStorage }; | ||
| export { type Adapter, type AdapterFactoryOptions, type AdapterSource, AsyncEventEmitter, type BannerMeta, BuildOutput, CLIOptions, Config, type Diagnostic, type DiagnosticByCode, type DiagnosticDoc, type DiagnosticKind, type DiagnosticLocation, type DiagnosticSeverity, Diagnostics, type Exclude, type FileProcessorHooks, type GenerationResult, type Generator, type GeneratorContext, type Group, type Include, InputData, InputPath, type Kubb, KubbBuildEndContext, KubbBuildStartContext, KubbDiagnosticContext, KubbDriver, KubbErrorContext, KubbFileProcessingUpdate, KubbFilesProcessingEndContext, KubbFilesProcessingStartContext, KubbFilesProcessingUpdateContext, KubbGenerationEndContext, KubbGenerationStartContext, KubbHookEndContext, KubbHookLineContext, KubbHookStartContext, KubbHooks, KubbInfoContext, KubbLifecycleStartContext, type KubbPluginEndContext, type KubbPluginSetupContext, type KubbPluginStartContext, KubbPluginsEndContext, KubbSuccessContext, KubbWarnContext, type NormalizedPlugin, type Output, type OutputMode, type OutputOptions, type Override, type ParsedFile, type Parser, type PerformanceDiagnostic, type Plugin, type PluginFactoryOptions, PossibleConfig, type ProblemCode, type ProblemDiagnostic, type Renderer, type RendererFactory, type Reporter, type ReporterContext, type ReporterName, type ResolveBannerContext, type ResolveBannerFile, type ResolveOptionsContext, type Resolver, type ResolverContext, type ResolverFileParams, type ResolverPathParams, type SerializedDiagnostic, type Storage, type UpdateDiagnostic, UserConfig, type UserReporter, ast, cliReporter, createAdapter, createKubb, createRenderer, createReporter, createStorage, defineGenerator, defineParser, definePlugin, defineResolver, fileReporter, fsStorage, jsonReporter, logLevel, memoryStorage }; | ||
| //# sourceMappingURL=index.d.ts.map |
+1
-1
| Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); | ||
| const require_memoryStorage = require("./memoryStorage-CIEzDI6b.cjs"); | ||
| const require_memoryStorage = require("./memoryStorage-skOz0dXZ.cjs"); | ||
| let node_path = require("node:path"); | ||
@@ -4,0 +4,0 @@ node_path = require_memoryStorage.__toESM(node_path, 1); |
+1
-1
| import { t as __name } from "./chunk-C0LytTxp.js"; | ||
| import { Ft as Adapter, G as Parser, It as AdapterFactoryOptions, X as KubbDriver, h as Config, lt as PluginFactoryOptions, q as Generator, rt as NormalizedPlugin } from "./diagnostics-D_LOtOCv.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-Bf2bC8lV.js"; | ||
| import { FileNode, InputMeta, OperationNode, SchemaNode, Visitor } from "@kubb/ast"; | ||
@@ -4,0 +4,0 @@ |
+1
-1
| import "./chunk-C0LytTxp.js"; | ||
| import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-Bdv42rxp.js"; | ||
| import { c as camelCase, i as FileManager, n as _usingCtx, r as FileProcessor, t as memoryStorage } from "./memoryStorage-B0W-w994.js"; | ||
| import path, { resolve } from "node:path"; | ||
@@ -4,0 +4,0 @@ import { transform } from "@kubb/ast"; |
+4
-4
| { | ||
| "name": "@kubb/core", | ||
| "version": "5.0.0-beta.54", | ||
| "version": "5.0.0-beta.55", | ||
| "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.54" | ||
| "@kubb/ast": "5.0.0-beta.55" | ||
| }, | ||
| "devDependencies": { | ||
| "@internals/utils": "0.0.0", | ||
| "@kubb/renderer-jsx": "5.0.0-beta.54" | ||
| "@kubb/renderer-jsx": "5.0.0-beta.55" | ||
| }, | ||
| "peerDependencies": { | ||
| "@kubb/renderer-jsx": "5.0.0-beta.54" | ||
| "@kubb/renderer-jsx": "5.0.0-beta.55" | ||
| }, | ||
@@ -69,0 +69,0 @@ "engines": { |
| import type { PossiblePromise } from '@internals/utils' | ||
| import type { ImportNode, InputNode, InputStreamNode, SchemaNode } from '@kubb/ast' | ||
| import type { ImportNode, InputNode, SchemaNode } from '@kubb/ast' | ||
@@ -87,7 +87,7 @@ /** | ||
| * | ||
| * Returns an `InputStreamNode` whose `schemas` and `operations` are `AsyncIterable`. | ||
| * Returns an `InputNode<true>` whose `schemas` and `operations` are `AsyncIterable`. | ||
| * Each `for await` loop creates a fresh parse pass over the cached in-memory document. | ||
| * No pre-built arrays are held in memory. | ||
| */ | ||
| stream?: (source: AdapterSource) => Promise<InputStreamNode> | ||
| stream?: (source: AdapterSource) => Promise<InputNode<true>> | ||
| } | ||
@@ -94,0 +94,0 @@ |
@@ -63,5 +63,2 @@ import { resolve } from 'node:path' | ||
| storage: userConfig.storage ?? fsStorage(), | ||
| // Resolve `false` to "no cache". The default `fsCache()` is applied by `defineConfig`, not here, | ||
| // so a raw `createKubb` stays deterministic (no surprise on-disk cache) unless a cache is passed. | ||
| cache: userConfig.cache === false ? undefined : userConfig.cache, | ||
| reporters: userConfig.reporters ?? [], | ||
@@ -68,0 +65,0 @@ plugins: userConfig.plugins ?? [], |
| import path from 'node:path' | ||
| import { camelCase, pascalCase } from '@internals/utils' | ||
| import { camelCase, pascalCase, toFilePath } from '@internals/utils' | ||
| import type { FileNode, InputMeta, Node, OperationNode, SchemaNode } from '@kubb/ast' | ||
@@ -292,8 +292,8 @@ import { createFile, isOperationNode, isSchemaNode } from '@kubb/ast' | ||
| * | ||
| * - `camelCase` for `function` and `file` types. | ||
| * - `camelCase` for `file`, with dotted names split into `/`-joined nested paths. | ||
| * - `PascalCase` for `type`. | ||
| * - `camelCase` for everything else. | ||
| * - `camelCase` for `function` and everything else. | ||
| */ | ||
| function defaultResolver(name: string, type?: 'file' | 'function' | 'type' | 'const'): string { | ||
| if (type === 'file' || type === 'function') return camelCase(name, { isFile: type === 'file' }) | ||
| if (type === 'file') return toFilePath(name) | ||
| if (type === 'type') return pascalCase(name) | ||
@@ -300,0 +300,0 @@ return camelCase(name) |
+1
-3
@@ -1,5 +0,4 @@ | ||
| export { AsyncEventEmitter, URLPath } from '@internals/utils' | ||
| export { AsyncEventEmitter } from '@internals/utils' | ||
| export * as ast from '@kubb/ast' | ||
| export { createAdapter } from './createAdapter.ts' | ||
| export { createCache } from './createCache.ts' | ||
| export { Diagnostics } from './diagnostics.ts' | ||
@@ -18,5 +17,4 @@ export { createKubb } from './createKubb.ts' | ||
| export { KubbDriver } from './KubbDriver.ts' | ||
| export { fsCache } from './caches/fsCache.ts' | ||
| export { fsStorage } from './storages/fsStorage.ts' | ||
| export { memoryStorage } from './storages/memoryStorage.ts' | ||
| export * from './types.ts' |
+7
-71
@@ -1,10 +0,7 @@ | ||
| import { basename, join, relative, resolve } from 'node:path' | ||
| import { arrayToAsyncIterable, type AsyncEventEmitter, forBatches, getElapsedMs, isPromise, memoize, URLPath } from '@internals/utils' | ||
| import { resolve } from 'node:path' | ||
| import { arrayToAsyncIterable, type AsyncEventEmitter, forBatches, getElapsedMs, isPromise, memoize, Url } from '@internals/utils' | ||
| import { collectUsedSchemaNames, createFile, createStreamInput } from '@kubb/ast' | ||
| import type { FileNode, InputMeta, InputStreamNode, OperationNode, SchemaNode } from '@kubb/ast' | ||
| import { version as coreVersion } from '../package.json' | ||
| import { OPERATION_FILTER_TYPES, SCHEMA_PARALLEL, STREAM_FLUSH_EVERY } from './constants.ts' | ||
| import { Fingerprint } from './Fingerprint.ts' | ||
| import type { FileNode, InputMeta, InputNode, OperationNode, SchemaNode } from '@kubb/ast' | ||
| import { OPERATION_FILTER_TYPES, SCHEMA_PARALLEL } from './constants.ts' | ||
| import { type Diagnostic, Diagnostics, type ProblemDiagnostic } from './diagnostics.ts' | ||
| import type { Cache } from './createCache.ts' | ||
| import type { RendererFactory } from './createRenderer.ts' | ||
@@ -59,6 +56,5 @@ import type { Storage } from './createStorage.ts' | ||
| /** | ||
| * The streaming `InputStreamNode` produced by the adapter. | ||
| * Always set after adapter setup, parse-only adapters are wrapped automatically. | ||
| * The streaming `InputNode<true>` produced by the adapter. * Always set after adapter setup, parse-only adapters are wrapped automatically. | ||
| */ | ||
| inputNode: InputStreamNode | null = null | ||
| inputNode: InputNode<true> | null = null | ||
| adapter: Adapter | null = null | ||
@@ -349,10 +345,4 @@ /** | ||
| const updateBuffer: Array<{ file: FileNode; source?: string; processed: number; total: number; percentage: number }> = [] | ||
| // Final rendered source per output path, captured for the cache snapshot on a miss. Barrel | ||
| // files flow through here too, after the second `drain()`. Only collected when caching is | ||
| // enabled, otherwise the Map would retain every rendered source for the whole build. | ||
| const cacheEnabled = Boolean(config.cache) | ||
| const snapshotSources = new Map<string, string>() | ||
| processor.hooks.on('update', (item) => { | ||
| updateBuffer.push(item) | ||
| if (cacheEnabled && item.source !== undefined) snapshotSources.set(item.file.path, item.source) | ||
| }) | ||
@@ -377,12 +367,4 @@ processor.hooks.on('end', async (files) => { | ||
| try { | ||
| const cache = config.cache | ||
| const outputRoot = resolve(config.root, config.output.path) | ||
| const cacheKey = cache ? await Fingerprint.compute({ config, adapterSource: this.#adapterSource, version: coreVersion }) : null | ||
| // On a cache hit, restore the snapshot and skip everything below. Skipping the work is the | ||
| // whole point, so the only event emitted is `kubb:build:end`, which reporters key off. | ||
| if (cache && cacheKey && (await this.#restoreSnapshot({ cache, cacheKey, outputRoot, storage }))) { | ||
| return { diagnostics: Diagnostics.dedupe(diagnostics) } | ||
| } | ||
| // Parse the adapter source into the streaming `InputNode`. | ||
@@ -448,6 +430,2 @@ await this.#parseInput() | ||
| if (cache && cacheKey && !Diagnostics.hasError(diagnostics)) { | ||
| await this.#persistSnapshot({ cache, cacheKey, outputRoot, sources: snapshotSources }) | ||
| } | ||
| return { diagnostics: Diagnostics.dedupe(diagnostics) } | ||
@@ -464,44 +442,2 @@ } catch (caughtError) { | ||
| /** | ||
| * Writes a restored snapshot straight to storage and emits `kubb:build:end`. Returns `true` on a | ||
| * hit (the build is done), `false` on a miss so the caller falls through to a full build. | ||
| */ | ||
| async #restoreSnapshot({ cache, cacheKey, outputRoot, storage }: { cache: Cache; cacheKey: string; outputRoot: string; storage: Storage }): Promise<boolean> { | ||
| const snapshot = await cache.restore({ key: cacheKey }) | ||
| if (!snapshot) return false | ||
| const queue: Array<Promise<void>> = [] | ||
| for (const [relativePath, source] of Object.entries(snapshot.files)) { | ||
| const absolutePath = join(outputRoot, relativePath) | ||
| this.fileManager.upsert(createFile({ path: absolutePath, baseName: basename(relativePath) as `${string}.${string}` })) | ||
| queue.push(storage.setItem(absolutePath, source)) | ||
| if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0)) | ||
| } | ||
| await Promise.all(queue) | ||
| await this.hooks.emit('kubb:build:end', { files: this.fileManager.files, config: this.config, outputDir: outputRoot }) | ||
| return true | ||
| } | ||
| /** | ||
| * Stores this run's rendered output, keyed by the input fingerprint, so the next unchanged build | ||
| * restores it instead of regenerating. `sources` is keyed by absolute path and relativized here. | ||
| */ | ||
| async #persistSnapshot({ | ||
| cache, | ||
| cacheKey, | ||
| outputRoot, | ||
| sources, | ||
| }: { | ||
| cache: Cache | ||
| cacheKey: string | ||
| outputRoot: string | ||
| sources: ReadonlyMap<string, string> | ||
| }): Promise<void> { | ||
| const files: Record<string, string> = {} | ||
| for (const [absolutePath, source] of sources) { | ||
| files[relative(outputRoot, absolutePath)] = source | ||
| } | ||
| await cache.persist({ key: cacheKey, snapshot: { files } }) | ||
| } | ||
| // Returns a fresh object with a lazy `files` getter and a bound `upsertFile`. | ||
@@ -963,3 +899,3 @@ // Caller must use `Object.assign(extra, this.#filesPayload())`, not object spread. | ||
| if (new URLPath(input.path).isURL) { | ||
| if (Url.canParse(input.path)) { | ||
| return { type: 'path', path: input.path } | ||
@@ -966,0 +902,0 @@ } |
| import { access, glob, readFile, rm } from 'node:fs/promises' | ||
| import { join, relative, resolve } from 'node:path' | ||
| import { clean, isBun, toPosixPath, write } from '@internals/utils' | ||
| import { clean, runtime, toPosixPath, write } from '@internals/utils' | ||
| import { createStorage } from '../createStorage.ts' | ||
@@ -13,7 +13,7 @@ | ||
| * | ||
| * Internally uses the `write` utility from `@internals/utils`, which: | ||
| * - trims leading/trailing whitespace before writing | ||
| * - skips the write when file content is already identical (deduplication) | ||
| * - creates missing parent directories automatically | ||
| * - supports Bun's native file API when running under Bun | ||
| * Writes are deduplicated and directory-safe: | ||
| * - leading and trailing whitespace is trimmed before writing | ||
| * - the write is skipped when the file content is already identical | ||
| * - missing parent directories are created automatically | ||
| * - Bun's native file API is used when running under Bun | ||
| * | ||
@@ -58,3 +58,3 @@ * @example | ||
| if (isBun()) { | ||
| if (runtime.isBun) { | ||
| const bunGlob = new Bun.Glob('**/*') | ||
@@ -61,0 +61,0 @@ return Array.fromAsync(bunGlob.scan({ cwd: resolvedBase, onlyFiles: true, dot: true })) |
+1
-34
| import type { PossiblePromise } from '@internals/utils' | ||
| import type { FileNode, InputMeta, OperationNode, SchemaNode } from '@kubb/ast' | ||
| import type { Adapter } from './createAdapter.ts' | ||
| import type { Cache } from './createCache.ts' | ||
| import type { Reporter, ReporterName } from './createReporter.ts' | ||
@@ -232,22 +231,2 @@ import type { Storage } from './createStorage.ts' | ||
| /** | ||
| * Incremental build cache. Kubb fingerprints the inputs (spec content, config, plugin options, | ||
| * versions) and, on an unchanged "hot" run, restores the previously generated output instead of | ||
| * regenerating it. Same idea as Nx's computation cache. | ||
| * | ||
| * `defineConfig` enables `fsCache()` (local disk under `node_modules/.cache/kubb`) by default. | ||
| * Pass another backend to change where snapshots live, or `false` to turn caching off. A bare | ||
| * `createKubb` leaves it off unless a cache is provided. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { fsCache } from '@kubb/core' | ||
| * | ||
| * cache: fsCache({ dir: '.kubb-cache' }) | ||
| * cache: false | ||
| * ``` | ||
| * | ||
| * @see {@link Cache} interface for implementing custom backends. | ||
| */ | ||
| cache?: Cache | ||
| /** | ||
| * Plugins that run during the build to generate code and transform the AST. Each one processes | ||
@@ -336,9 +315,4 @@ * the adapter's AST and can emit files for a different target (TypeScript, Zod, Faker). A plugin | ||
| */ | ||
| export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters' | 'cache'> & { | ||
| export type UserConfig<TInput = Input> = Omit<Config<TInput>, 'root' | 'plugins' | 'parsers' | 'adapter' | 'storage' | 'reporters'> & { | ||
| /** | ||
| * Incremental build cache. Defaults to `fsCache()` (local disk). Pass another {@link Cache} | ||
| * backend, or `false` to turn caching off. | ||
| */ | ||
| cache?: Cache | false | ||
| /** | ||
| * Project root directory, absolute or relative to the config file location. | ||
@@ -789,7 +763,2 @@ * @default process.cwd() | ||
| reporters?: Array<ReporterName> | ||
| /** | ||
| * Turns off the incremental build cache for this run, forcing a full regeneration. | ||
| * Set by the `--no-cache` flag. | ||
| */ | ||
| noCache?: boolean | ||
| } | ||
@@ -838,4 +807,2 @@ | ||
| export type { Adapter, AdapterFactoryOptions, AdapterSource } from './createAdapter.ts' | ||
| export type { Cache, CachedSnapshot } from './createCache.ts' | ||
| export type { FsCacheOptions } from './caches/fsCache.ts' | ||
| export type { | ||
@@ -842,0 +809,0 @@ Diagnostic, |
Sorry, the diff of this file is too big to display
| import "./chunk-C0LytTxp.js"; | ||
| import { EventEmitter } from "node:events"; | ||
| import { createFile, extractStringsFromNodes } from "@kubb/ast"; | ||
| //#region ../../internals/utils/src/errors.ts | ||
| /** | ||
| * Thrown when one or more errors occur during a Kubb build. | ||
| * Carries the full list of underlying errors on `errors`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * throw new BuildError('Build failed', { errors: [err1, err2] }) | ||
| * ``` | ||
| */ | ||
| var BuildError = class extends Error { | ||
| errors; | ||
| constructor(message, options) { | ||
| super(message, { cause: options.cause }); | ||
| this.name = "BuildError"; | ||
| this.errors = options.errors; | ||
| } | ||
| }; | ||
| /** | ||
| * Coerces an unknown thrown value to an `Error` instance. | ||
| * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * try { ... } catch(err) { | ||
| * throw new BuildError('Build failed', { cause: toError(err), errors: [] }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| function toError(value) { | ||
| return value instanceof Error ? value : new Error(String(value)); | ||
| } | ||
| /** | ||
| * Extracts a human-readable message from any thrown value. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * getErrorMessage(new Error('oops')) // 'oops' | ||
| * getErrorMessage('plain string') // 'plain string' | ||
| * ``` | ||
| */ | ||
| function getErrorMessage(value) { | ||
| return value instanceof Error ? value.message : String(value); | ||
| } | ||
| //#endregion | ||
| //#region ../../internals/utils/src/asyncEventEmitter.ts | ||
| /** | ||
| * Typed `EventEmitter` that awaits all async listeners before resolving. | ||
| * Wraps Node's `EventEmitter` with full TypeScript event-map inference. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const emitter = new AsyncEventEmitter<{ build: [name: string] }>() | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * await emitter.emit('build', 'petstore') // all listeners awaited | ||
| * ``` | ||
| */ | ||
| var AsyncEventEmitter = class { | ||
| /** | ||
| * Maximum number of listeners per event before Node emits a memory-leak warning. | ||
| * @default 10 | ||
| */ | ||
| constructor(maxListener = 10) { | ||
| this.#emitter.setMaxListeners(maxListener); | ||
| } | ||
| #emitter = new EventEmitter(); | ||
| /** | ||
| * Emits `eventName` and awaits all registered listeners sequentially. | ||
| * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * await emitter.emit('build', 'petstore') | ||
| * ``` | ||
| */ | ||
| emit(eventName, ...eventArgs) { | ||
| const listeners = this.#emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| return this.#emitAll(eventName, listeners, eventArgs); | ||
| } | ||
| async #emitAll(eventName, listeners, eventArgs) { | ||
| for (const listener of listeners) try { | ||
| await listener(...eventArgs); | ||
| } catch (err) { | ||
| let serializedArgs; | ||
| try { | ||
| serializedArgs = JSON.stringify(eventArgs); | ||
| } catch { | ||
| serializedArgs = String(eventArgs); | ||
| } | ||
| throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) }); | ||
| } | ||
| } | ||
| /** | ||
| * Registers a persistent listener for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| on(eventName, handler) { | ||
| this.#emitter.on(eventName, handler); | ||
| } | ||
| /** | ||
| * Registers a one-shot listener that removes itself after the first invocation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.onOnce('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| onOnce(eventName, handler) { | ||
| const wrapper = (...args) => { | ||
| this.off(eventName, wrapper); | ||
| return handler(...args); | ||
| }; | ||
| this.on(eventName, wrapper); | ||
| } | ||
| /** | ||
| * Removes a previously registered listener. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.off('build', handler) | ||
| * ``` | ||
| */ | ||
| off(eventName, handler) { | ||
| this.#emitter.off(eventName, handler); | ||
| } | ||
| /** | ||
| * Returns the number of listeners registered for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', handler) | ||
| * emitter.listenerCount('build') // 1 | ||
| * ``` | ||
| */ | ||
| listenerCount(eventName) { | ||
| return this.#emitter.listenerCount(eventName); | ||
| } | ||
| /** | ||
| * Raises or lowers the per-event listener ceiling before Node warns about a memory leak. | ||
| * Set this above the expected listener count when many listeners attach by design. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.setMaxListeners(40) | ||
| * ``` | ||
| */ | ||
| setMaxListeners(max) { | ||
| this.#emitter.setMaxListeners(max); | ||
| } | ||
| /** | ||
| * Returns the current per-event listener ceiling. | ||
| */ | ||
| getMaxListeners() { | ||
| return this.#emitter.getMaxListeners(); | ||
| } | ||
| /** | ||
| * Removes all listeners from every event channel. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.removeAll() | ||
| * ``` | ||
| */ | ||
| removeAll() { | ||
| this.#emitter.removeAllListeners(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region ../../internals/utils/src/casing.ts | ||
| /** | ||
| * Shared implementation for camelCase and PascalCase conversion. | ||
| * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons) | ||
| * and capitalizes each word according to `pascal`. | ||
| * | ||
| * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are. | ||
| */ | ||
| function toCamelOrPascal(text, pascal) { | ||
| return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => { | ||
| if (word.length > 1 && word === word.toUpperCase()) return word; | ||
| if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1); | ||
| return word.charAt(0).toUpperCase() + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Splits `text` on `.` and applies `transformPart` to each segment. | ||
| * The last segment receives `isLast = true`, all earlier segments receive `false`. | ||
| * Segments are joined with `/` to form a file path. | ||
| * | ||
| * Only splits on dots followed by a letter so that version numbers | ||
| * embedded in operationIds (e.g. `v2025.0`) are kept intact. | ||
| * | ||
| * Empty segments are filtered before joining. They arise when the text starts with | ||
| * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']` | ||
| * and `'..'` transforms to an empty string). Without this filter the join would produce | ||
| * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing | ||
| * generated files to escape the configured output directory. | ||
| */ | ||
| function applyToFileParts(text, transformPart) { | ||
| const parts = text.split(/\.(?=[a-zA-Z])/); | ||
| return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/"); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`. | ||
| * | ||
| * @example | ||
| * camelCase('hello-world') // 'helloWorld' | ||
| * camelCase('pet.petId', { isFile: true }) // 'pet/petId' | ||
| */ | ||
| function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) { | ||
| if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { | ||
| prefix, | ||
| suffix | ||
| } : {})); | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased. | ||
| * | ||
| * @example | ||
| * pascalCase('hello-world') // 'HelloWorld' | ||
| * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId' | ||
| */ | ||
| function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) { | ||
| if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, { | ||
| prefix, | ||
| suffix | ||
| }) : camelCase(part)); | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true); | ||
| } | ||
| //#endregion | ||
| //#region src/constants.ts | ||
| /** | ||
| * Plugin `include` filter types that select operations directly. When one of these is set | ||
| * without a `schemaName` include, the generate phase pre-scans operations to compute the set | ||
| * of schemas they reach, so unreachable schemas can be pruned for that plugin. | ||
| */ | ||
| const OPERATION_FILTER_TYPES = new Set([ | ||
| "tag", | ||
| "operationId", | ||
| "path", | ||
| "method", | ||
| "contentType" | ||
| ]); | ||
| /** | ||
| * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode | ||
| * and stays stable so it can be referenced in tooling and (later) docs. Reference | ||
| * these instead of inlining the string at a throw site. | ||
| */ | ||
| const diagnosticCode = { | ||
| /** | ||
| * Fallback for an unstructured error with no specific code. | ||
| */ | ||
| unknown: "KUBB_UNKNOWN", | ||
| /** | ||
| * The `input.path` file or URL could not be read. | ||
| */ | ||
| inputNotFound: "KUBB_INPUT_NOT_FOUND", | ||
| /** | ||
| * An adapter was configured without an `input`. | ||
| */ | ||
| inputRequired: "KUBB_INPUT_REQUIRED", | ||
| /** | ||
| * A `$ref` (or equivalent reference) could not be resolved in the source document. | ||
| */ | ||
| refNotFound: "KUBB_REF_NOT_FOUND", | ||
| /** | ||
| * A server variable value is not allowed by its `enum`. | ||
| */ | ||
| invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE", | ||
| /** | ||
| * A required plugin is missing from the config. | ||
| */ | ||
| pluginNotFound: "KUBB_PLUGIN_NOT_FOUND", | ||
| /** | ||
| * A plugin threw while generating. | ||
| */ | ||
| pluginFailed: "KUBB_PLUGIN_FAILED", | ||
| /** | ||
| * A plugin reported a non-fatal warning through `ctx.warn`. | ||
| */ | ||
| pluginWarning: "KUBB_PLUGIN_WARNING", | ||
| /** | ||
| * A plugin reported an informational message through `ctx.info`. | ||
| */ | ||
| pluginInfo: "KUBB_PLUGIN_INFO", | ||
| /** | ||
| * A schema uses a `format` Kubb does not map to a specific type. Reserved for | ||
| * adapters to emit as a `warning`. | ||
| */ | ||
| unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT", | ||
| /** | ||
| * A referenced schema or operation is marked `deprecated`. Reserved for adapters | ||
| * to emit as an `info`. | ||
| */ | ||
| deprecated: "KUBB_DEPRECATED", | ||
| /** | ||
| * An adapter is required but the config has none. The build cannot read the input | ||
| * without one. | ||
| */ | ||
| adapterRequired: "KUBB_ADAPTER_REQUIRED", | ||
| /** | ||
| * A resolved output path escapes the output directory, which can stem from a path | ||
| * traversal in the spec or a misconfigured `group.name`. | ||
| */ | ||
| pathTraversal: "KUBB_PATH_TRAVERSAL", | ||
| /** | ||
| * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option. | ||
| */ | ||
| invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS", | ||
| /** | ||
| * A post-generate shell hook (`hooks.done`) exited with a failure. | ||
| */ | ||
| hookFailed: "KUBB_HOOK_FAILED", | ||
| /** | ||
| * The formatter pass over the generated files failed. | ||
| */ | ||
| formatFailed: "KUBB_FORMAT_FAILED", | ||
| /** | ||
| * The linter pass over the generated files failed. | ||
| */ | ||
| lintFailed: "KUBB_LINT_FAILED", | ||
| /** | ||
| * Not a failure. Carries a plugin's elapsed time, summed into the run total. | ||
| */ | ||
| performance: "KUBB_PERFORMANCE", | ||
| /** | ||
| * Not a failure. A newer Kubb version is available on npm. | ||
| */ | ||
| updateAvailable: "KUBB_UPDATE_AVAILABLE" | ||
| }; | ||
| //#endregion | ||
| //#region src/createStorage.ts | ||
| /** | ||
| * Defines a custom storage backend. The builder receives user options and | ||
| * returns a `Storage` implementation. Kubb ships with filesystem and | ||
| * in-memory storages, reach for this when you need to write generated files | ||
| * elsewhere (cloud storage, a database, a remote API). | ||
| * | ||
| * @example In-memory storage (the built-in implementation) | ||
| * ```ts | ||
| * import { createStorage } from '@kubb/core' | ||
| * | ||
| * export const memoryStorage = createStorage(() => { | ||
| * const store = new Map<string, string>() | ||
| * | ||
| * return { | ||
| * name: 'memory', | ||
| * async hasItem(key) { | ||
| * return store.has(key) | ||
| * }, | ||
| * async getItem(key) { | ||
| * return store.get(key) ?? null | ||
| * }, | ||
| * async setItem(key, value) { | ||
| * store.set(key, value) | ||
| * }, | ||
| * async removeItem(key) { | ||
| * store.delete(key) | ||
| * }, | ||
| * async getKeys(base) { | ||
| * const keys = [...store.keys()] | ||
| * return base ? keys.filter((k) => k.startsWith(base)) : keys | ||
| * }, | ||
| * async clear(base) { | ||
| * if (!base) store.clear() | ||
| * }, | ||
| * } | ||
| * }) | ||
| * ``` | ||
| */ | ||
| function createStorage(build) { | ||
| return (options) => build(options ?? {}); | ||
| } | ||
| //#endregion | ||
| //#region src/FileManager.ts | ||
| function mergeFile(a, b) { | ||
| return { | ||
| ...a, | ||
| banner: b.banner, | ||
| footer: b.footer, | ||
| sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources, | ||
| imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports, | ||
| exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports | ||
| }; | ||
| } | ||
| function isIndexPath(path) { | ||
| return path.endsWith("/index.ts") || path === "index.ts"; | ||
| } | ||
| function compareFiles(a, b) { | ||
| const lenDiff = a.path.length - b.path.length; | ||
| if (lenDiff !== 0) return lenDiff; | ||
| const aIsIndex = isIndexPath(a.path); | ||
| const bIsIndex = isIndexPath(b.path); | ||
| if (aIsIndex && !bIsIndex) return 1; | ||
| if (!aIsIndex && bIsIndex) return -1; | ||
| return 0; | ||
| } | ||
| /** | ||
| * In-memory file store for generated files. Files sharing a `path` are merged | ||
| * (sources/imports/exports concatenated). The `files` getter is sorted by | ||
| * path length (barrel `index.ts` last within a bucket). | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const manager = new FileManager() | ||
| * manager.upsert(myFile) | ||
| * manager.files // sorted view | ||
| * ``` | ||
| */ | ||
| var FileManager = class { | ||
| /** | ||
| * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands | ||
| * through `add` or `upsert`. | ||
| */ | ||
| hooks = new AsyncEventEmitter(); | ||
| #cache = /* @__PURE__ */ new Map(); | ||
| #sorted = null; | ||
| add(...files) { | ||
| return this.#store(files, false); | ||
| } | ||
| upsert(...files) { | ||
| return this.#store(files, true); | ||
| } | ||
| #store(files, mergeExisting) { | ||
| const batch = files.length > 1 ? this.#dedupe(files) : files; | ||
| const resolved = []; | ||
| for (const file of batch) { | ||
| const existing = this.#cache.get(file.path); | ||
| const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file); | ||
| this.#cache.set(merged.path, merged); | ||
| resolved.push(merged); | ||
| this.hooks.emit("upsert", merged); | ||
| } | ||
| if (resolved.length > 0) this.#sorted = null; | ||
| return resolved; | ||
| } | ||
| #dedupe(files) { | ||
| const seen = /* @__PURE__ */ new Map(); | ||
| for (const file of files) { | ||
| const prev = seen.get(file.path); | ||
| seen.set(file.path, prev ? mergeFile(prev, file) : file); | ||
| } | ||
| return [...seen.values()]; | ||
| } | ||
| getByPath(path) { | ||
| return this.#cache.get(path) ?? null; | ||
| } | ||
| deleteByPath(path) { | ||
| if (!this.#cache.delete(path)) return; | ||
| this.#sorted = null; | ||
| } | ||
| clear() { | ||
| this.#cache.clear(); | ||
| this.#sorted = null; | ||
| } | ||
| /** | ||
| * Releases all stored files and clears every `hooks` listener. Called by the core after | ||
| * `kubb:build:end`. | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.hooks.removeAll(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| /** | ||
| * All stored files in stable sort order (shortest path first, barrel files | ||
| * last within a length bucket). Returns a cached view, do not mutate. | ||
| */ | ||
| get files() { | ||
| return this.#sorted ??= [...this.#cache.values()].sort(compareFiles); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/FileProcessor.ts | ||
| function joinSources(file) { | ||
| const sources = file.sources; | ||
| if (sources.length === 0) return ""; | ||
| const parts = []; | ||
| for (const source of sources) { | ||
| const text = extractStringsFromNodes(source.nodes); | ||
| if (text) parts.push(text); | ||
| } | ||
| return parts.join("\n\n"); | ||
| } | ||
| /** | ||
| * Turns `FileNode`s into source strings and writes them to storage. | ||
| * | ||
| * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the | ||
| * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and | ||
| * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight. | ||
| * | ||
| * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next | ||
| * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been | ||
| * written and is meant for the end of a build. | ||
| * | ||
| * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to | ||
| * `hooks` and re-emit on the kubb bus. | ||
| */ | ||
| var FileProcessor = class { | ||
| hooks = new AsyncEventEmitter(); | ||
| #parsers; | ||
| #storage; | ||
| #extension; | ||
| #pending = /* @__PURE__ */ new Map(); | ||
| #runningFlush = null; | ||
| constructor(options) { | ||
| this.#parsers = options.parsers ?? null; | ||
| this.#storage = options.storage; | ||
| this.#extension = options.extension ?? null; | ||
| } | ||
| /** | ||
| * Files waiting in the queue. | ||
| */ | ||
| get size() { | ||
| return this.#pending.size; | ||
| } | ||
| parse(file) { | ||
| const parsers = this.#parsers; | ||
| const parseExtName = this.#extension?.[file.extname] || void 0; | ||
| if (!parsers || !file.extname) return joinSources(file); | ||
| const parser = parsers.get(file.extname); | ||
| if (!parser) return joinSources(file); | ||
| return parser.parse(file, { extname: parseExtName }); | ||
| } | ||
| *stream(files) { | ||
| const total = files.length; | ||
| if (total === 0) return; | ||
| let processed = 0; | ||
| for (const file of files) { | ||
| const source = this.parse(file); | ||
| processed++; | ||
| yield { | ||
| file, | ||
| source, | ||
| processed, | ||
| total, | ||
| percentage: processed / total * 100 | ||
| }; | ||
| } | ||
| } | ||
| async run(files) { | ||
| await this.hooks.emit("start", files); | ||
| for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", { | ||
| file, | ||
| source, | ||
| processed, | ||
| percentage, | ||
| total | ||
| }); | ||
| await this.hooks.emit("end", files); | ||
| return files; | ||
| } | ||
| /** | ||
| * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous | ||
| * entry, matching `FileManager.upsert`. Fires the `enqueue` event. | ||
| */ | ||
| enqueue(file) { | ||
| this.#pending.set(file.path, file); | ||
| this.hooks.emit("enqueue", file); | ||
| } | ||
| /** | ||
| * Starts processing the queued files. Waits for any previous flush to finish (so two | ||
| * batches never run together) and then returns without waiting for the new one. The next | ||
| * `flush` or `drain` picks up the in-flight task. | ||
| */ | ||
| async flush() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size === 0) return; | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| this.#runningFlush = this.#processAndWrite(batch).finally(() => { | ||
| this.#runningFlush = null; | ||
| }); | ||
| } | ||
| /** | ||
| * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event | ||
| * when both are done. | ||
| */ | ||
| async drain() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size > 0) { | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| await this.#processAndWrite(batch); | ||
| } | ||
| await this.hooks.emit("drain"); | ||
| } | ||
| async #processAndWrite(files) { | ||
| const storage = this.#storage; | ||
| await this.hooks.emit("start", files); | ||
| const queue = []; | ||
| for (const item of this.stream(files)) { | ||
| await this.hooks.emit("update", item); | ||
| if (item.source) { | ||
| queue.push(storage.setItem(item.file.path, item.source)); | ||
| if (queue.length >= 50) await Promise.all(queue.splice(0)); | ||
| } | ||
| } | ||
| await Promise.all(queue); | ||
| await this.hooks.emit("end", files); | ||
| } | ||
| /** | ||
| * Clears every listener and the pending queue. | ||
| */ | ||
| dispose() { | ||
| this.hooks.removeAll(); | ||
| this.#pending.clear(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js | ||
| function _usingCtx() { | ||
| var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) { | ||
| var n = Error(); | ||
| return n.name = "SuppressedError", n.error = r, n.suppressed = e, n; | ||
| }; | ||
| var e = {}; | ||
| var n = []; | ||
| function using(r, e) { | ||
| if (null != e) { | ||
| if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined."); | ||
| if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")]; | ||
| if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o; | ||
| if ("function" != typeof o) throw new TypeError("Object is not disposable."); | ||
| t && (o = function o() { | ||
| try { | ||
| t.call(e); | ||
| } catch (r) { | ||
| return Promise.reject(r); | ||
| } | ||
| }), n.push({ | ||
| v: e, | ||
| d: o, | ||
| a: r | ||
| }); | ||
| } else r && n.push({ | ||
| d: e, | ||
| a: r | ||
| }); | ||
| return e; | ||
| } | ||
| return { | ||
| e, | ||
| u: using.bind(null, !1), | ||
| a: using.bind(null, !0), | ||
| d: function d() { | ||
| var o; | ||
| var t = this.e; | ||
| var s = 0; | ||
| function next() { | ||
| for (; o = n.pop();) try { | ||
| if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next); | ||
| if (o.d) { | ||
| var r = o.d.call(o.v); | ||
| if (o.a) return s |= 2, Promise.resolve(r).then(next, err); | ||
| } else s |= 1; | ||
| } catch (r) { | ||
| return err(r); | ||
| } | ||
| if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve(); | ||
| if (t !== e) throw t; | ||
| } | ||
| function err(n) { | ||
| return t = t !== e ? new r(n, t) : n, next(); | ||
| } | ||
| return next(); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/storages/memoryStorage.ts | ||
| /** | ||
| * In-memory storage driver. Useful for testing and dry-run scenarios where | ||
| * generated output should be captured without touching the filesystem. | ||
| * | ||
| * All data lives in a `Map` scoped to the storage instance and is discarded | ||
| * when the instance is garbage-collected. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { memoryStorage } from '@kubb/core' | ||
| * import { defineConfig } from 'kubb' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * storage: memoryStorage(), | ||
| * }) | ||
| * ``` | ||
| */ | ||
| const memoryStorage = createStorage(() => { | ||
| const store = /* @__PURE__ */ new Map(); | ||
| return { | ||
| name: "memory", | ||
| async hasItem(key) { | ||
| return store.has(key); | ||
| }, | ||
| async getItem(key) { | ||
| return store.get(key) ?? null; | ||
| }, | ||
| async setItem(key, value) { | ||
| store.set(key, value); | ||
| }, | ||
| async removeItem(key) { | ||
| store.delete(key); | ||
| }, | ||
| async getKeys(base) { | ||
| const keys = [...store.keys()]; | ||
| return base ? keys.filter((k) => k.startsWith(base)) : keys; | ||
| }, | ||
| async clear(base) { | ||
| if (!base) { | ||
| store.clear(); | ||
| return; | ||
| } | ||
| for (const key of store.keys()) if (key.startsWith(base)) store.delete(key); | ||
| } | ||
| }; | ||
| }); | ||
| //#endregion | ||
| export { createStorage as a, camelCase as c, BuildError as d, getErrorMessage as f, FileManager as i, pascalCase as l, _usingCtx as n, OPERATION_FILTER_TYPES as o, FileProcessor as r, diagnosticCode as s, memoryStorage as t, AsyncEventEmitter as u }; | ||
| //# sourceMappingURL=memoryStorage-Bdv42rxp.js.map |
| {"version":3,"file":"memoryStorage-Bdv42rxp.js","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"} |
| //#region \0rolldown/runtime.js | ||
| var __create = Object.create; | ||
| var __defProp = Object.defineProperty; | ||
| var __name = (target, value) => __defProp(target, "name", { | ||
| value, | ||
| configurable: true | ||
| }); | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __getProtoOf = Object.getPrototypeOf; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { | ||
| key = keys[i]; | ||
| if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { | ||
| get: ((k) => from[k]).bind(null, key), | ||
| enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable | ||
| }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { | ||
| value: mod, | ||
| enumerable: true | ||
| }) : target, mod)); | ||
| //#endregion | ||
| let node_events = require("node:events"); | ||
| let _kubb_ast = require("@kubb/ast"); | ||
| //#region ../../internals/utils/src/errors.ts | ||
| /** | ||
| * Thrown when one or more errors occur during a Kubb build. | ||
| * Carries the full list of underlying errors on `errors`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * throw new BuildError('Build failed', { errors: [err1, err2] }) | ||
| * ``` | ||
| */ | ||
| var BuildError = class extends Error { | ||
| errors; | ||
| constructor(message, options) { | ||
| super(message, { cause: options.cause }); | ||
| this.name = "BuildError"; | ||
| this.errors = options.errors; | ||
| } | ||
| }; | ||
| /** | ||
| * Coerces an unknown thrown value to an `Error` instance. | ||
| * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * try { ... } catch(err) { | ||
| * throw new BuildError('Build failed', { cause: toError(err), errors: [] }) | ||
| * } | ||
| * ``` | ||
| */ | ||
| function toError(value) { | ||
| return value instanceof Error ? value : new Error(String(value)); | ||
| } | ||
| /** | ||
| * Extracts a human-readable message from any thrown value. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * getErrorMessage(new Error('oops')) // 'oops' | ||
| * getErrorMessage('plain string') // 'plain string' | ||
| * ``` | ||
| */ | ||
| function getErrorMessage(value) { | ||
| return value instanceof Error ? value.message : String(value); | ||
| } | ||
| //#endregion | ||
| //#region ../../internals/utils/src/asyncEventEmitter.ts | ||
| /** | ||
| * Typed `EventEmitter` that awaits all async listeners before resolving. | ||
| * Wraps Node's `EventEmitter` with full TypeScript event-map inference. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const emitter = new AsyncEventEmitter<{ build: [name: string] }>() | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * await emitter.emit('build', 'petstore') // all listeners awaited | ||
| * ``` | ||
| */ | ||
| var AsyncEventEmitter = class { | ||
| /** | ||
| * Maximum number of listeners per event before Node emits a memory-leak warning. | ||
| * @default 10 | ||
| */ | ||
| constructor(maxListener = 10) { | ||
| this.#emitter.setMaxListeners(maxListener); | ||
| } | ||
| #emitter = new node_events.EventEmitter(); | ||
| /** | ||
| * Emits `eventName` and awaits all registered listeners sequentially. | ||
| * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * await emitter.emit('build', 'petstore') | ||
| * ``` | ||
| */ | ||
| emit(eventName, ...eventArgs) { | ||
| const listeners = this.#emitter.listeners(eventName); | ||
| if (listeners.length === 0) return; | ||
| return this.#emitAll(eventName, listeners, eventArgs); | ||
| } | ||
| async #emitAll(eventName, listeners, eventArgs) { | ||
| for (const listener of listeners) try { | ||
| await listener(...eventArgs); | ||
| } catch (err) { | ||
| let serializedArgs; | ||
| try { | ||
| serializedArgs = JSON.stringify(eventArgs); | ||
| } catch { | ||
| serializedArgs = String(eventArgs); | ||
| } | ||
| throw new Error(`Error in async listener for "${eventName}" with eventArgs ${serializedArgs}`, { cause: toError(err) }); | ||
| } | ||
| } | ||
| /** | ||
| * Registers a persistent listener for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| on(eventName, handler) { | ||
| this.#emitter.on(eventName, handler); | ||
| } | ||
| /** | ||
| * Registers a one-shot listener that removes itself after the first invocation. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.onOnce('build', async (name) => { console.log(name) }) | ||
| * ``` | ||
| */ | ||
| onOnce(eventName, handler) { | ||
| const wrapper = (...args) => { | ||
| this.off(eventName, wrapper); | ||
| return handler(...args); | ||
| }; | ||
| this.on(eventName, wrapper); | ||
| } | ||
| /** | ||
| * Removes a previously registered listener. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.off('build', handler) | ||
| * ``` | ||
| */ | ||
| off(eventName, handler) { | ||
| this.#emitter.off(eventName, handler); | ||
| } | ||
| /** | ||
| * Returns the number of listeners registered for `eventName`. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.on('build', handler) | ||
| * emitter.listenerCount('build') // 1 | ||
| * ``` | ||
| */ | ||
| listenerCount(eventName) { | ||
| return this.#emitter.listenerCount(eventName); | ||
| } | ||
| /** | ||
| * Raises or lowers the per-event listener ceiling before Node warns about a memory leak. | ||
| * Set this above the expected listener count when many listeners attach by design. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.setMaxListeners(40) | ||
| * ``` | ||
| */ | ||
| setMaxListeners(max) { | ||
| this.#emitter.setMaxListeners(max); | ||
| } | ||
| /** | ||
| * Returns the current per-event listener ceiling. | ||
| */ | ||
| getMaxListeners() { | ||
| return this.#emitter.getMaxListeners(); | ||
| } | ||
| /** | ||
| * Removes all listeners from every event channel. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * emitter.removeAll() | ||
| * ``` | ||
| */ | ||
| removeAll() { | ||
| this.#emitter.removeAllListeners(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region ../../internals/utils/src/casing.ts | ||
| /** | ||
| * Shared implementation for camelCase and PascalCase conversion. | ||
| * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons) | ||
| * and capitalizes each word according to `pascal`. | ||
| * | ||
| * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are. | ||
| */ | ||
| function toCamelOrPascal(text, pascal) { | ||
| return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => { | ||
| if (word.length > 1 && word === word.toUpperCase()) return word; | ||
| if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1); | ||
| return word.charAt(0).toUpperCase() + word.slice(1); | ||
| }).join("").replace(/[^a-zA-Z0-9]/g, ""); | ||
| } | ||
| /** | ||
| * Splits `text` on `.` and applies `transformPart` to each segment. | ||
| * The last segment receives `isLast = true`, all earlier segments receive `false`. | ||
| * Segments are joined with `/` to form a file path. | ||
| * | ||
| * Only splits on dots followed by a letter so that version numbers | ||
| * embedded in operationIds (e.g. `v2025.0`) are kept intact. | ||
| * | ||
| * Empty segments are filtered before joining. They arise when the text starts with | ||
| * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']` | ||
| * and `'..'` transforms to an empty string). Without this filter the join would produce | ||
| * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing | ||
| * generated files to escape the configured output directory. | ||
| */ | ||
| function applyToFileParts(text, transformPart) { | ||
| const parts = text.split(/\.(?=[a-zA-Z])/); | ||
| return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/"); | ||
| } | ||
| /** | ||
| * Converts `text` to camelCase. | ||
| * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`. | ||
| * | ||
| * @example | ||
| * camelCase('hello-world') // 'helloWorld' | ||
| * camelCase('pet.petId', { isFile: true }) // 'pet/petId' | ||
| */ | ||
| function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) { | ||
| if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { | ||
| prefix, | ||
| suffix | ||
| } : {})); | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false); | ||
| } | ||
| /** | ||
| * Converts `text` to PascalCase. | ||
| * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased. | ||
| * | ||
| * @example | ||
| * pascalCase('hello-world') // 'HelloWorld' | ||
| * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId' | ||
| */ | ||
| function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) { | ||
| if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, { | ||
| prefix, | ||
| suffix | ||
| }) : camelCase(part)); | ||
| return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true); | ||
| } | ||
| //#endregion | ||
| //#region src/constants.ts | ||
| /** | ||
| * Plugin `include` filter types that select operations directly. When one of these is set | ||
| * without a `schemaName` include, the generate phase pre-scans operations to compute the set | ||
| * of schemas they reach, so unreachable schemas can be pruned for that plugin. | ||
| */ | ||
| const OPERATION_FILTER_TYPES = new Set([ | ||
| "tag", | ||
| "operationId", | ||
| "path", | ||
| "method", | ||
| "contentType" | ||
| ]); | ||
| /** | ||
| * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode | ||
| * and stays stable so it can be referenced in tooling and (later) docs. Reference | ||
| * these instead of inlining the string at a throw site. | ||
| */ | ||
| const diagnosticCode = { | ||
| /** | ||
| * Fallback for an unstructured error with no specific code. | ||
| */ | ||
| unknown: "KUBB_UNKNOWN", | ||
| /** | ||
| * The `input.path` file or URL could not be read. | ||
| */ | ||
| inputNotFound: "KUBB_INPUT_NOT_FOUND", | ||
| /** | ||
| * An adapter was configured without an `input`. | ||
| */ | ||
| inputRequired: "KUBB_INPUT_REQUIRED", | ||
| /** | ||
| * A `$ref` (or equivalent reference) could not be resolved in the source document. | ||
| */ | ||
| refNotFound: "KUBB_REF_NOT_FOUND", | ||
| /** | ||
| * A server variable value is not allowed by its `enum`. | ||
| */ | ||
| invalidServerVariable: "KUBB_INVALID_SERVER_VARIABLE", | ||
| /** | ||
| * A required plugin is missing from the config. | ||
| */ | ||
| pluginNotFound: "KUBB_PLUGIN_NOT_FOUND", | ||
| /** | ||
| * A plugin threw while generating. | ||
| */ | ||
| pluginFailed: "KUBB_PLUGIN_FAILED", | ||
| /** | ||
| * A plugin reported a non-fatal warning through `ctx.warn`. | ||
| */ | ||
| pluginWarning: "KUBB_PLUGIN_WARNING", | ||
| /** | ||
| * A plugin reported an informational message through `ctx.info`. | ||
| */ | ||
| pluginInfo: "KUBB_PLUGIN_INFO", | ||
| /** | ||
| * A schema uses a `format` Kubb does not map to a specific type. Reserved for | ||
| * adapters to emit as a `warning`. | ||
| */ | ||
| unsupportedFormat: "KUBB_UNSUPPORTED_FORMAT", | ||
| /** | ||
| * A referenced schema or operation is marked `deprecated`. Reserved for adapters | ||
| * to emit as an `info`. | ||
| */ | ||
| deprecated: "KUBB_DEPRECATED", | ||
| /** | ||
| * An adapter is required but the config has none. The build cannot read the input | ||
| * without one. | ||
| */ | ||
| adapterRequired: "KUBB_ADAPTER_REQUIRED", | ||
| /** | ||
| * A resolved output path escapes the output directory, which can stem from a path | ||
| * traversal in the spec or a misconfigured `group.name`. | ||
| */ | ||
| pathTraversal: "KUBB_PATH_TRAVERSAL", | ||
| /** | ||
| * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option. | ||
| */ | ||
| invalidPluginOptions: "KUBB_INVALID_PLUGIN_OPTIONS", | ||
| /** | ||
| * A post-generate shell hook (`hooks.done`) exited with a failure. | ||
| */ | ||
| hookFailed: "KUBB_HOOK_FAILED", | ||
| /** | ||
| * The formatter pass over the generated files failed. | ||
| */ | ||
| formatFailed: "KUBB_FORMAT_FAILED", | ||
| /** | ||
| * The linter pass over the generated files failed. | ||
| */ | ||
| lintFailed: "KUBB_LINT_FAILED", | ||
| /** | ||
| * Not a failure. Carries a plugin's elapsed time, summed into the run total. | ||
| */ | ||
| performance: "KUBB_PERFORMANCE", | ||
| /** | ||
| * Not a failure. A newer Kubb version is available on npm. | ||
| */ | ||
| updateAvailable: "KUBB_UPDATE_AVAILABLE" | ||
| }; | ||
| //#endregion | ||
| //#region src/createStorage.ts | ||
| /** | ||
| * Defines a custom storage backend. The builder receives user options and | ||
| * returns a `Storage` implementation. Kubb ships with filesystem and | ||
| * in-memory storages, reach for this when you need to write generated files | ||
| * elsewhere (cloud storage, a database, a remote API). | ||
| * | ||
| * @example In-memory storage (the built-in implementation) | ||
| * ```ts | ||
| * import { createStorage } from '@kubb/core' | ||
| * | ||
| * export const memoryStorage = createStorage(() => { | ||
| * const store = new Map<string, string>() | ||
| * | ||
| * return { | ||
| * name: 'memory', | ||
| * async hasItem(key) { | ||
| * return store.has(key) | ||
| * }, | ||
| * async getItem(key) { | ||
| * return store.get(key) ?? null | ||
| * }, | ||
| * async setItem(key, value) { | ||
| * store.set(key, value) | ||
| * }, | ||
| * async removeItem(key) { | ||
| * store.delete(key) | ||
| * }, | ||
| * async getKeys(base) { | ||
| * const keys = [...store.keys()] | ||
| * return base ? keys.filter((k) => k.startsWith(base)) : keys | ||
| * }, | ||
| * async clear(base) { | ||
| * if (!base) store.clear() | ||
| * }, | ||
| * } | ||
| * }) | ||
| * ``` | ||
| */ | ||
| function createStorage(build) { | ||
| return (options) => build(options ?? {}); | ||
| } | ||
| //#endregion | ||
| //#region src/FileManager.ts | ||
| function mergeFile(a, b) { | ||
| return { | ||
| ...a, | ||
| banner: b.banner, | ||
| footer: b.footer, | ||
| sources: a.sources.length ? b.sources.length ? [...a.sources, ...b.sources] : a.sources : b.sources, | ||
| imports: a.imports.length ? b.imports.length ? [...a.imports, ...b.imports] : a.imports : b.imports, | ||
| exports: a.exports.length ? b.exports.length ? [...a.exports, ...b.exports] : a.exports : b.exports | ||
| }; | ||
| } | ||
| function isIndexPath(path) { | ||
| return path.endsWith("/index.ts") || path === "index.ts"; | ||
| } | ||
| function compareFiles(a, b) { | ||
| const lenDiff = a.path.length - b.path.length; | ||
| if (lenDiff !== 0) return lenDiff; | ||
| const aIsIndex = isIndexPath(a.path); | ||
| const bIsIndex = isIndexPath(b.path); | ||
| if (aIsIndex && !bIsIndex) return 1; | ||
| if (!aIsIndex && bIsIndex) return -1; | ||
| return 0; | ||
| } | ||
| /** | ||
| * In-memory file store for generated files. Files sharing a `path` are merged | ||
| * (sources/imports/exports concatenated). The `files` getter is sorted by | ||
| * path length (barrel `index.ts` last within a bucket). | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * const manager = new FileManager() | ||
| * manager.upsert(myFile) | ||
| * manager.files // sorted view | ||
| * ``` | ||
| */ | ||
| var FileManager = class { | ||
| /** | ||
| * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands | ||
| * through `add` or `upsert`. | ||
| */ | ||
| hooks = new AsyncEventEmitter(); | ||
| #cache = /* @__PURE__ */ new Map(); | ||
| #sorted = null; | ||
| add(...files) { | ||
| return this.#store(files, false); | ||
| } | ||
| upsert(...files) { | ||
| return this.#store(files, true); | ||
| } | ||
| #store(files, mergeExisting) { | ||
| const batch = files.length > 1 ? this.#dedupe(files) : files; | ||
| const resolved = []; | ||
| for (const file of batch) { | ||
| const existing = this.#cache.get(file.path); | ||
| const merged = existing && mergeExisting ? (0, _kubb_ast.createFile)(mergeFile(existing, file)) : (0, _kubb_ast.createFile)(file); | ||
| this.#cache.set(merged.path, merged); | ||
| resolved.push(merged); | ||
| this.hooks.emit("upsert", merged); | ||
| } | ||
| if (resolved.length > 0) this.#sorted = null; | ||
| return resolved; | ||
| } | ||
| #dedupe(files) { | ||
| const seen = /* @__PURE__ */ new Map(); | ||
| for (const file of files) { | ||
| const prev = seen.get(file.path); | ||
| seen.set(file.path, prev ? mergeFile(prev, file) : file); | ||
| } | ||
| return [...seen.values()]; | ||
| } | ||
| getByPath(path) { | ||
| return this.#cache.get(path) ?? null; | ||
| } | ||
| deleteByPath(path) { | ||
| if (!this.#cache.delete(path)) return; | ||
| this.#sorted = null; | ||
| } | ||
| clear() { | ||
| this.#cache.clear(); | ||
| this.#sorted = null; | ||
| } | ||
| /** | ||
| * Releases all stored files and clears every `hooks` listener. Called by the core after | ||
| * `kubb:build:end`. | ||
| */ | ||
| dispose() { | ||
| this.clear(); | ||
| this.hooks.removeAll(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| /** | ||
| * All stored files in stable sort order (shortest path first, barrel files | ||
| * last within a length bucket). Returns a cached view, do not mutate. | ||
| */ | ||
| get files() { | ||
| return this.#sorted ??= [...this.#cache.values()].sort(compareFiles); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/FileProcessor.ts | ||
| function joinSources(file) { | ||
| const sources = file.sources; | ||
| if (sources.length === 0) return ""; | ||
| const parts = []; | ||
| for (const source of sources) { | ||
| const text = (0, _kubb_ast.extractStringsFromNodes)(source.nodes); | ||
| if (text) parts.push(text); | ||
| } | ||
| return parts.join("\n\n"); | ||
| } | ||
| /** | ||
| * Turns `FileNode`s into source strings and writes them to storage. | ||
| * | ||
| * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the | ||
| * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and | ||
| * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight. | ||
| * | ||
| * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next | ||
| * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been | ||
| * written and is meant for the end of a build. | ||
| * | ||
| * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to | ||
| * `hooks` and re-emit on the kubb bus. | ||
| */ | ||
| var FileProcessor = class { | ||
| hooks = new AsyncEventEmitter(); | ||
| #parsers; | ||
| #storage; | ||
| #extension; | ||
| #pending = /* @__PURE__ */ new Map(); | ||
| #runningFlush = null; | ||
| constructor(options) { | ||
| this.#parsers = options.parsers ?? null; | ||
| this.#storage = options.storage; | ||
| this.#extension = options.extension ?? null; | ||
| } | ||
| /** | ||
| * Files waiting in the queue. | ||
| */ | ||
| get size() { | ||
| return this.#pending.size; | ||
| } | ||
| parse(file) { | ||
| const parsers = this.#parsers; | ||
| const parseExtName = this.#extension?.[file.extname] || void 0; | ||
| if (!parsers || !file.extname) return joinSources(file); | ||
| const parser = parsers.get(file.extname); | ||
| if (!parser) return joinSources(file); | ||
| return parser.parse(file, { extname: parseExtName }); | ||
| } | ||
| *stream(files) { | ||
| const total = files.length; | ||
| if (total === 0) return; | ||
| let processed = 0; | ||
| for (const file of files) { | ||
| const source = this.parse(file); | ||
| processed++; | ||
| yield { | ||
| file, | ||
| source, | ||
| processed, | ||
| total, | ||
| percentage: processed / total * 100 | ||
| }; | ||
| } | ||
| } | ||
| async run(files) { | ||
| await this.hooks.emit("start", files); | ||
| for (const { file, source, processed, total, percentage } of this.stream(files)) await this.hooks.emit("update", { | ||
| file, | ||
| source, | ||
| processed, | ||
| percentage, | ||
| total | ||
| }); | ||
| await this.hooks.emit("end", files); | ||
| return files; | ||
| } | ||
| /** | ||
| * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous | ||
| * entry, matching `FileManager.upsert`. Fires the `enqueue` event. | ||
| */ | ||
| enqueue(file) { | ||
| this.#pending.set(file.path, file); | ||
| this.hooks.emit("enqueue", file); | ||
| } | ||
| /** | ||
| * Starts processing the queued files. Waits for any previous flush to finish (so two | ||
| * batches never run together) and then returns without waiting for the new one. The next | ||
| * `flush` or `drain` picks up the in-flight task. | ||
| */ | ||
| async flush() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size === 0) return; | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| this.#runningFlush = this.#processAndWrite(batch).finally(() => { | ||
| this.#runningFlush = null; | ||
| }); | ||
| } | ||
| /** | ||
| * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event | ||
| * when both are done. | ||
| */ | ||
| async drain() { | ||
| if (this.#runningFlush) await this.#runningFlush; | ||
| if (this.#pending.size > 0) { | ||
| const batch = [...this.#pending.values()]; | ||
| this.#pending.clear(); | ||
| await this.#processAndWrite(batch); | ||
| } | ||
| await this.hooks.emit("drain"); | ||
| } | ||
| async #processAndWrite(files) { | ||
| const storage = this.#storage; | ||
| await this.hooks.emit("start", files); | ||
| const queue = []; | ||
| for (const item of this.stream(files)) { | ||
| await this.hooks.emit("update", item); | ||
| if (item.source) { | ||
| queue.push(storage.setItem(item.file.path, item.source)); | ||
| if (queue.length >= 50) await Promise.all(queue.splice(0)); | ||
| } | ||
| } | ||
| await Promise.all(queue); | ||
| await this.hooks.emit("end", files); | ||
| } | ||
| /** | ||
| * Clears every listener and the pending queue. | ||
| */ | ||
| dispose() { | ||
| this.hooks.removeAll(); | ||
| this.#pending.clear(); | ||
| } | ||
| [Symbol.dispose]() { | ||
| this.dispose(); | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region \0@oxc-project+runtime@0.134.0/helpers/esm/usingCtx.js | ||
| function _usingCtx() { | ||
| var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) { | ||
| var n = Error(); | ||
| return n.name = "SuppressedError", n.error = r, n.suppressed = e, n; | ||
| }; | ||
| var e = {}; | ||
| var n = []; | ||
| function using(r, e) { | ||
| if (null != e) { | ||
| if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined."); | ||
| if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")]; | ||
| if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o; | ||
| if ("function" != typeof o) throw new TypeError("Object is not disposable."); | ||
| t && (o = function o() { | ||
| try { | ||
| t.call(e); | ||
| } catch (r) { | ||
| return Promise.reject(r); | ||
| } | ||
| }), n.push({ | ||
| v: e, | ||
| d: o, | ||
| a: r | ||
| }); | ||
| } else r && n.push({ | ||
| d: e, | ||
| a: r | ||
| }); | ||
| return e; | ||
| } | ||
| return { | ||
| e, | ||
| u: using.bind(null, !1), | ||
| a: using.bind(null, !0), | ||
| d: function d() { | ||
| var o; | ||
| var t = this.e; | ||
| var s = 0; | ||
| function next() { | ||
| for (; o = n.pop();) try { | ||
| if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next); | ||
| if (o.d) { | ||
| var r = o.d.call(o.v); | ||
| if (o.a) return s |= 2, Promise.resolve(r).then(next, err); | ||
| } else s |= 1; | ||
| } catch (r) { | ||
| return err(r); | ||
| } | ||
| if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve(); | ||
| if (t !== e) throw t; | ||
| } | ||
| function err(n) { | ||
| return t = t !== e ? new r(n, t) : n, next(); | ||
| } | ||
| return next(); | ||
| } | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/storages/memoryStorage.ts | ||
| /** | ||
| * In-memory storage driver. Useful for testing and dry-run scenarios where | ||
| * generated output should be captured without touching the filesystem. | ||
| * | ||
| * All data lives in a `Map` scoped to the storage instance and is discarded | ||
| * when the instance is garbage-collected. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { memoryStorage } from '@kubb/core' | ||
| * import { defineConfig } from 'kubb' | ||
| * | ||
| * export default defineConfig({ | ||
| * input: { path: './petStore.yaml' }, | ||
| * output: { path: './src/gen' }, | ||
| * storage: memoryStorage(), | ||
| * }) | ||
| * ``` | ||
| */ | ||
| const memoryStorage = createStorage(() => { | ||
| const store = /* @__PURE__ */ new Map(); | ||
| return { | ||
| name: "memory", | ||
| async hasItem(key) { | ||
| return store.has(key); | ||
| }, | ||
| async getItem(key) { | ||
| return store.get(key) ?? null; | ||
| }, | ||
| async setItem(key, value) { | ||
| store.set(key, value); | ||
| }, | ||
| async removeItem(key) { | ||
| store.delete(key); | ||
| }, | ||
| async getKeys(base) { | ||
| const keys = [...store.keys()]; | ||
| return base ? keys.filter((k) => k.startsWith(base)) : keys; | ||
| }, | ||
| async clear(base) { | ||
| if (!base) { | ||
| store.clear(); | ||
| return; | ||
| } | ||
| for (const key of store.keys()) if (key.startsWith(base)) store.delete(key); | ||
| } | ||
| }; | ||
| }); | ||
| //#endregion | ||
| Object.defineProperty(exports, "AsyncEventEmitter", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return AsyncEventEmitter; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "BuildError", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return BuildError; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "FileManager", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return FileManager; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "FileProcessor", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return FileProcessor; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "OPERATION_FILTER_TYPES", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return OPERATION_FILTER_TYPES; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "__name", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return __name; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "__toESM", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return __toESM; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "_usingCtx", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return _usingCtx; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "camelCase", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return camelCase; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "createStorage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return createStorage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "diagnosticCode", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return diagnosticCode; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "getErrorMessage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return getErrorMessage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "memoryStorage", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return memoryStorage; | ||
| } | ||
| }); | ||
| Object.defineProperty(exports, "pascalCase", { | ||
| enumerable: true, | ||
| get: function() { | ||
| return pascalCase; | ||
| } | ||
| }); | ||
| //# sourceMappingURL=memoryStorage-CIEzDI6b.cjs.map |
| {"version":3,"file":"memoryStorage-CIEzDI6b.cjs","names":["#emitter","NodeEventEmitter","#emitAll","#cache","#store","#dedupe","#sorted","#parsers","#storage","#extension","#pending","#runningFlush","#processAndWrite"],"sources":["../../../internals/utils/src/errors.ts","../../../internals/utils/src/asyncEventEmitter.ts","../../../internals/utils/src/casing.ts","../src/constants.ts","../src/createStorage.ts","../src/FileManager.ts","../src/FileProcessor.ts","../src/storages/memoryStorage.ts"],"sourcesContent":["/**\n * Thrown when one or more errors occur during a Kubb build.\n * Carries the full list of underlying errors on `errors`.\n *\n * @example\n * ```ts\n * throw new BuildError('Build failed', { errors: [err1, err2] })\n * ```\n */\nexport class BuildError extends Error {\n errors: Array<Error>\n\n constructor(message: string, options: { cause?: Error; errors: Array<Error> }) {\n super(message, { cause: options.cause })\n this.name = 'BuildError'\n this.errors = options.errors\n }\n}\n\n/**\n * Coerces an unknown thrown value to an `Error` instance.\n * Returns the value as-is when it is already an `Error`; otherwise wraps it with `String(value)`.\n *\n * @example\n * ```ts\n * try { ... } catch(err) {\n * throw new BuildError('Build failed', { cause: toError(err), errors: [] })\n * }\n * ```\n */\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\n/**\n * Extracts a human-readable message from any thrown value.\n *\n * @example\n * ```ts\n * getErrorMessage(new Error('oops')) // 'oops'\n * getErrorMessage('plain string') // 'plain string'\n * ```\n */\nexport function getErrorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n\n/**\n * Extracts the `.cause` of an `Error` as an `Error`, or `undefined` when absent or not an `Error`.\n *\n * @example\n * ```ts\n * const cause = toCause(buildError) // Error | undefined\n * ```\n */\nexport function toCause(error: Error): Error | undefined {\n return error.cause instanceof Error ? error.cause : undefined\n}\n","import { EventEmitter as NodeEventEmitter } from 'node:events'\nimport { toError } from './errors.ts'\n\n/**\n * A function that can be registered as an event listener, synchronous or async.\n */\ntype AsyncListener<TArgs extends unknown[]> = (...args: TArgs) => void | Promise<void>\n\n/**\n * Typed `EventEmitter` that awaits all async listeners before resolving.\n * Wraps Node's `EventEmitter` with full TypeScript event-map inference.\n *\n * @example\n * ```ts\n * const emitter = new AsyncEventEmitter<{ build: [name: string] }>()\n * emitter.on('build', async (name) => { console.log(name) })\n * await emitter.emit('build', 'petstore') // all listeners awaited\n * ```\n */\nexport class AsyncEventEmitter<TEvents extends { [K in keyof TEvents]: unknown[] }> {\n /**\n * Maximum number of listeners per event before Node emits a memory-leak warning.\n * @default 10\n */\n constructor(maxListener = 10) {\n this.#emitter.setMaxListeners(maxListener)\n }\n\n #emitter = new NodeEventEmitter()\n\n /**\n * Emits `eventName` and awaits all registered listeners sequentially.\n * Throws if any listener rejects, wrapping the cause with the event name and serialized arguments.\n *\n * @example\n * ```ts\n * await emitter.emit('build', 'petstore')\n * ```\n */\n emit<TEventName extends keyof TEvents & string>(eventName: TEventName, ...eventArgs: TEvents[TEventName]): Promise<void> | void {\n const listeners = this.#emitter.listeners(eventName) as Array<AsyncListener<TEvents[TEventName]>>\n\n if (listeners.length === 0) {\n return\n }\n\n return this.#emitAll(eventName, listeners, eventArgs)\n }\n\n async #emitAll<TEventName extends keyof TEvents & string>(\n eventName: TEventName,\n listeners: Array<AsyncListener<TEvents[TEventName]>>,\n eventArgs: TEvents[TEventName],\n ): Promise<void> {\n for (const listener of listeners) {\n try {\n await listener(...eventArgs)\n } catch (err) {\n let serializedArgs: string\n try {\n serializedArgs = JSON.stringify(eventArgs)\n } catch {\n serializedArgs = String(eventArgs)\n }\n throw new Error(`Error in async listener for \"${eventName}\" with eventArgs ${serializedArgs}`, { cause: toError(err) })\n }\n }\n }\n\n /**\n * Registers a persistent listener for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', async (name) => { console.log(name) })\n * ```\n */\n on<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.on(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Registers a one-shot listener that removes itself after the first invocation.\n *\n * @example\n * ```ts\n * emitter.onOnce('build', async (name) => { console.log(name) })\n * ```\n */\n onOnce<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n const wrapper: AsyncListener<TEvents[TEventName]> = (...args) => {\n this.off(eventName, wrapper)\n return handler(...args)\n }\n this.on(eventName, wrapper)\n }\n\n /**\n * Removes a previously registered listener.\n *\n * @example\n * ```ts\n * emitter.off('build', handler)\n * ```\n */\n off<TEventName extends keyof TEvents & string>(eventName: TEventName, handler: AsyncListener<TEvents[TEventName]>): void {\n this.#emitter.off(eventName, handler as AsyncListener<unknown[]>)\n }\n\n /**\n * Returns the number of listeners registered for `eventName`.\n *\n * @example\n * ```ts\n * emitter.on('build', handler)\n * emitter.listenerCount('build') // 1\n * ```\n */\n listenerCount<TEventName extends keyof TEvents & string>(eventName: TEventName): number {\n return this.#emitter.listenerCount(eventName)\n }\n\n /**\n * Raises or lowers the per-event listener ceiling before Node warns about a memory leak.\n * Set this above the expected listener count when many listeners attach by design.\n *\n * @example\n * ```ts\n * emitter.setMaxListeners(40)\n * ```\n */\n setMaxListeners(max: number): void {\n this.#emitter.setMaxListeners(max)\n }\n\n /**\n * Returns the current per-event listener ceiling.\n */\n getMaxListeners(): number {\n return this.#emitter.getMaxListeners()\n }\n\n /**\n * Removes all listeners from every event channel.\n *\n * @example\n * ```ts\n * emitter.removeAll()\n * ```\n */\n removeAll(): void {\n this.#emitter.removeAllListeners()\n }\n}\n","type Options = {\n /**\n * When `true`, dot-separated segments are split on `.` and joined with `/` after casing.\n */\n isFile?: boolean\n /**\n * Text prepended before casing is applied.\n */\n prefix?: string\n /**\n * Text appended before casing is applied.\n */\n suffix?: string\n}\n\n/**\n * Shared implementation for camelCase and PascalCase conversion.\n * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)\n * and capitalizes each word according to `pascal`.\n *\n * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.\n */\nfunction toCamelOrPascal(text: string, pascal: boolean): string {\n const normalized = text\n .trim()\n .replace(/([a-z\\d])([A-Z])/g, '$1 $2')\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')\n .replace(/(\\d)([a-z])/g, '$1 $2')\n\n const words = normalized.split(/[\\s\\-_./\\\\:]+/).filter(Boolean)\n\n return words\n .map((word, i) => {\n const allUpper = word.length > 1 && word === word.toUpperCase()\n if (allUpper) return word\n if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1)\n return word.charAt(0).toUpperCase() + word.slice(1)\n })\n .join('')\n .replace(/[^a-zA-Z0-9]/g, '')\n}\n\n/**\n * Splits `text` on `.` and applies `transformPart` to each segment.\n * The last segment receives `isLast = true`, all earlier segments receive `false`.\n * Segments are joined with `/` to form a file path.\n *\n * Only splits on dots followed by a letter so that version numbers\n * embedded in operationIds (e.g. `v2025.0`) are kept intact.\n *\n * Empty segments are filtered before joining. They arise when the text starts with\n * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`\n * and `'..'` transforms to an empty string). Without this filter the join would produce\n * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing\n * generated files to escape the configured output directory.\n */\nfunction applyToFileParts(text: string, transformPart: (part: string, isLast: boolean) => string): string {\n const parts = text.split(/\\.(?=[a-zA-Z])/)\n return parts\n .map((part, i) => transformPart(part, i === parts.length - 1))\n .filter(Boolean)\n .join('/')\n}\n\n/**\n * Converts `text` to camelCase.\n * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.\n *\n * @example\n * camelCase('hello-world') // 'helloWorld'\n * camelCase('pet.petId', { isFile: true }) // 'pet/petId'\n */\nexport function camelCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? { prefix, suffix } : {}))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false)\n}\n\n/**\n * Converts `text` to PascalCase.\n * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.\n *\n * @example\n * pascalCase('hello-world') // 'HelloWorld'\n * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'\n */\nexport function pascalCase(text: string, { isFile, prefix = '', suffix = '' }: Options = {}): string {\n if (isFile) {\n return applyToFileParts(text, (part, isLast) => (isLast ? pascalCase(part, { prefix, suffix }) : camelCase(part)))\n }\n\n return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true)\n}\n","/**\n * Number of file writes to batch in parallel during `flushPendingFiles`.\n */\nexport const STREAM_FLUSH_EVERY = 50\n\n/**\n * Maximum number of █ characters in a plugin timing bar.\n */\nexport const SUMMARY_MAX_BAR_LENGTH = 10 as const\n\n/**\n * Divides elapsed milliseconds into bar-length units (1 block per 100 ms).\n */\nexport const SUMMARY_TIME_SCALE_DIVISOR = 100 as const\n\n/**\n * Number of schema/operation nodes to dispatch concurrently during generation.\n */\nexport const SCHEMA_PARALLEL = 8\n\n/**\n * Upper bound of hook listeners a single plugin can add to one event (its schema, operation,\n * and operations generators, plus lifecycle hooks). Used to size the hooks emitter's\n * max-listener ceiling so a multi-generator plugin set does not trip Node's leak warning.\n */\nexport const HOOK_LISTENERS_PER_PLUGIN = 4\n\n/**\n * Plugin `include` filter types that select operations directly. When one of these is set\n * without a `schemaName` include, the generate phase pre-scans operations to compute the set\n * of schemas they reach, so unreachable schemas can be pruned for that plugin.\n */\nexport const OPERATION_FILTER_TYPES: ReadonlySet<string> = new Set(['tag', 'operationId', 'path', 'method', 'contentType'])\n\n/**\n * Stable codes Kubb attaches to a `Diagnostic`. Each maps to a known failure mode\n * and stays stable so it can be referenced in tooling and (later) docs. Reference\n * these instead of inlining the string at a throw site.\n */\nexport const diagnosticCode = {\n /**\n * Fallback for an unstructured error with no specific code.\n */\n unknown: 'KUBB_UNKNOWN',\n /**\n * The `input.path` file or URL could not be read.\n */\n inputNotFound: 'KUBB_INPUT_NOT_FOUND',\n /**\n * An adapter was configured without an `input`.\n */\n inputRequired: 'KUBB_INPUT_REQUIRED',\n /**\n * A `$ref` (or equivalent reference) could not be resolved in the source document.\n */\n refNotFound: 'KUBB_REF_NOT_FOUND',\n /**\n * A server variable value is not allowed by its `enum`.\n */\n invalidServerVariable: 'KUBB_INVALID_SERVER_VARIABLE',\n /**\n * A required plugin is missing from the config.\n */\n pluginNotFound: 'KUBB_PLUGIN_NOT_FOUND',\n /**\n * A plugin threw while generating.\n */\n pluginFailed: 'KUBB_PLUGIN_FAILED',\n /**\n * A plugin reported a non-fatal warning through `ctx.warn`.\n */\n pluginWarning: 'KUBB_PLUGIN_WARNING',\n /**\n * A plugin reported an informational message through `ctx.info`.\n */\n pluginInfo: 'KUBB_PLUGIN_INFO',\n /**\n * A schema uses a `format` Kubb does not map to a specific type. Reserved for\n * adapters to emit as a `warning`.\n */\n unsupportedFormat: 'KUBB_UNSUPPORTED_FORMAT',\n /**\n * A referenced schema or operation is marked `deprecated`. Reserved for adapters\n * to emit as an `info`.\n */\n deprecated: 'KUBB_DEPRECATED',\n /**\n * An adapter is required but the config has none. The build cannot read the input\n * without one.\n */\n adapterRequired: 'KUBB_ADAPTER_REQUIRED',\n /**\n * A resolved output path escapes the output directory, which can stem from a path\n * traversal in the spec or a misconfigured `group.name`.\n */\n pathTraversal: 'KUBB_PATH_TRAVERSAL',\n /**\n * A plugin's options are invalid, for example `output.mode: 'file'` paired with a `group` option.\n */\n invalidPluginOptions: 'KUBB_INVALID_PLUGIN_OPTIONS',\n /**\n * A post-generate shell hook (`hooks.done`) exited with a failure.\n */\n hookFailed: 'KUBB_HOOK_FAILED',\n /**\n * The formatter pass over the generated files failed.\n */\n formatFailed: 'KUBB_FORMAT_FAILED',\n /**\n * The linter pass over the generated files failed.\n */\n lintFailed: 'KUBB_LINT_FAILED',\n /**\n * Not a failure. Carries a plugin's elapsed time, summed into the run total.\n */\n performance: 'KUBB_PERFORMANCE',\n /**\n * Not a failure. A newer Kubb version is available on npm.\n */\n updateAvailable: 'KUBB_UPDATE_AVAILABLE',\n} as const\n\n/**\n * Union of the stable {@link diagnosticCode} values.\n */\nexport type DiagnosticCode = (typeof diagnosticCode)[keyof typeof diagnosticCode]\n","/**\n * Backend that persists generated files. Kubb ships with `fsStorage` (writes\n * to disk) and `memoryStorage` (keeps everything in RAM). Implement this\n * interface to write to S3, a database, or any other target.\n */\nexport type Storage = {\n /**\n * Identifier used in logs and diagnostics (`'fs'`, `'memory'`, `'s3'`).\n */\n readonly name: string\n /**\n * Returns `true` when an entry for `key` exists.\n */\n hasItem(key: string): Promise<boolean>\n /**\n * Reads the stored string. Returns `null` when the key is missing.\n */\n getItem(key: string): Promise<string | null>\n /**\n * Stores `value` under `key`, creating any required structure (directories,\n * buckets, ...).\n */\n setItem(key: string, value: string): Promise<void>\n /**\n * Deletes the entry for `key`. No-op when the key does not exist.\n */\n removeItem(key: string): Promise<void>\n /**\n * Returns every key. Pass `base` to filter to keys starting with that prefix.\n */\n getKeys(base?: string): Promise<Array<string>>\n /**\n * Removes every entry. Pass `base` to scope the wipe to a key prefix.\n */\n clear(base?: string): Promise<void>\n /**\n * Optional teardown hook called after the build completes. Use to flush\n * buffers, close connections, or release file locks.\n */\n dispose?(): Promise<void>\n}\n\n/**\n * Defines a custom storage backend. The builder receives user options and\n * returns a `Storage` implementation. Kubb ships with filesystem and\n * in-memory storages, reach for this when you need to write generated files\n * elsewhere (cloud storage, a database, a remote API).\n *\n * @example In-memory storage (the built-in implementation)\n * ```ts\n * import { createStorage } from '@kubb/core'\n *\n * export const memoryStorage = createStorage(() => {\n * const store = new Map<string, string>()\n *\n * return {\n * name: 'memory',\n * async hasItem(key) {\n * return store.has(key)\n * },\n * async getItem(key) {\n * return store.get(key) ?? null\n * },\n * async setItem(key, value) {\n * store.set(key, value)\n * },\n * async removeItem(key) {\n * store.delete(key)\n * },\n * async getKeys(base) {\n * const keys = [...store.keys()]\n * return base ? keys.filter((k) => k.startsWith(base)) : keys\n * },\n * async clear(base) {\n * if (!base) store.clear()\n * },\n * }\n * })\n * ```\n */\nexport function createStorage<TOptions = Record<string, never>>(build: (options: TOptions) => Storage): (options?: TOptions) => Storage {\n return (options) => build(options ?? ({} as TOptions))\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { FileNode } from '@kubb/ast'\nimport { createFile } from '@kubb/ast'\n\n/**\n * Hooks fired by a `FileManager`.\n *\n * - `upsert` fires once per resolved file added through `add` or `upsert`.\n */\nexport type FileManagerHooks = {\n upsert: [file: FileNode]\n}\n\nfunction mergeFile<TMeta extends object = object>(a: FileNode<TMeta>, b: FileNode<TMeta>): FileNode<TMeta> {\n return {\n ...a,\n // Incoming file (b) takes precedence for banner/footer so a barrel file (whose\n // banner/footer the barrel plugin resolves last) wins over a plugin-generated\n // file at the same path.\n banner: b.banner,\n footer: b.footer,\n sources: a.sources.length ? (b.sources.length ? [...a.sources, ...b.sources] : a.sources) : b.sources,\n imports: a.imports.length ? (b.imports.length ? [...a.imports, ...b.imports] : a.imports) : b.imports,\n exports: a.exports.length ? (b.exports.length ? [...a.exports, ...b.exports] : a.exports) : b.exports,\n }\n}\n\nfunction isIndexPath(path: string): boolean {\n return path.endsWith('/index.ts') || path === 'index.ts'\n}\n\n// Sort order: shortest path first. Within a length bucket, index.ts barrels last.\nfunction compareFiles(a: FileNode, b: FileNode): number {\n const lenDiff = a.path.length - b.path.length\n if (lenDiff !== 0) return lenDiff\n const aIsIndex = isIndexPath(a.path)\n const bIsIndex = isIndexPath(b.path)\n if (aIsIndex && !bIsIndex) return 1\n if (!aIsIndex && bIsIndex) return -1\n return 0\n}\n\n/**\n * In-memory file store for generated files. Files sharing a `path` are merged\n * (sources/imports/exports concatenated). The `files` getter is sorted by\n * path length (barrel `index.ts` last within a bucket).\n *\n * @example\n * ```ts\n * const manager = new FileManager()\n * manager.upsert(myFile)\n * manager.files // sorted view\n * ```\n */\nexport class FileManager {\n /**\n * Subscribe to file-store changes. Listeners on `upsert` see each resolved file as it lands\n * through `add` or `upsert`.\n */\n readonly hooks = new AsyncEventEmitter<FileManagerHooks>()\n readonly #cache = new Map<string, FileNode>()\n // Cached sorted view. Null means stale and rebuilt lazily on next `files` read.\n // Nulled (not mutated) on every write so callers holding a prior reference\n // keep their snapshot, `dispose()` must not silently empty an array the\n // consumer already holds.\n #sorted: Array<FileNode> | null = null\n\n add(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, false)\n }\n\n upsert(...files: Array<FileNode>): Array<FileNode> {\n return this.#store(files, true)\n }\n\n #store(files: ReadonlyArray<FileNode>, mergeExisting: boolean): Array<FileNode> {\n const batch = files.length > 1 ? this.#dedupe(files) : files\n const resolved: Array<FileNode> = []\n\n for (const file of batch) {\n const existing = this.#cache.get(file.path)\n const merged = existing && mergeExisting ? createFile(mergeFile(existing, file)) : createFile(file)\n this.#cache.set(merged.path, merged)\n resolved.push(merged)\n this.hooks.emit('upsert', merged)\n }\n\n if (resolved.length > 0) this.#sorted = null\n return resolved\n }\n\n // Merges same-path entries within a batch so the cache update loop stays\n // uniform. Only called for multi-file batches.\n #dedupe(files: ReadonlyArray<FileNode>): Array<FileNode> {\n const seen = new Map<string, FileNode>()\n for (const file of files) {\n const prev = seen.get(file.path)\n seen.set(file.path, prev ? mergeFile(prev, file) : file)\n }\n return [...seen.values()]\n }\n\n getByPath(path: string): FileNode | null {\n return this.#cache.get(path) ?? null\n }\n\n deleteByPath(path: string): void {\n if (!this.#cache.delete(path)) return\n this.#sorted = null\n }\n\n clear(): void {\n this.#cache.clear()\n this.#sorted = null\n }\n\n /**\n * Releases all stored files and clears every `hooks` listener. Called by the core after\n * `kubb:build:end`.\n */\n dispose(): void {\n this.clear()\n this.hooks.removeAll()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n\n /**\n * All stored files in stable sort order (shortest path first, barrel files\n * last within a length bucket). Returns a cached view, do not mutate.\n */\n get files(): Array<FileNode> {\n return (this.#sorted ??= [...this.#cache.values()].sort(compareFiles))\n }\n}\n","import { AsyncEventEmitter } from '@internals/utils'\nimport type { CodeNode, FileNode } from '@kubb/ast'\nimport { extractStringsFromNodes } from '@kubb/ast'\nimport { STREAM_FLUSH_EVERY } from './constants.ts'\nimport type { Storage } from './createStorage.ts'\nimport type { Parser } from './defineParser.ts'\n\n/**\n * Hooks fired by a `FileProcessor`.\n *\n * - `start` opens a batch, from `run` or a queue flush.\n * - `update` fires once per file as it is converted.\n * - `end` closes a batch.\n * - `enqueue` fires for every `enqueue` call.\n * - `drain` fires when `drain()` empties the queue with no in-flight batch left.\n */\nexport type FileProcessorHooks = {\n start: [files: Array<FileNode>]\n update: [params: { file: FileNode; source?: string; processed: number; total: number; percentage: number }]\n end: [files: Array<FileNode>]\n enqueue: [file: FileNode]\n drain: []\n}\n\n/**\n * Per-file progress record yielded by `stream` and surfaced through the `update` event.\n */\nexport type ParsedFile = {\n file: FileNode\n source: string\n processed: number\n total: number\n percentage: number\n}\n\ntype FileProcessorOptions = {\n /**\n * Storage destination for queued writes.\n */\n storage: Storage\n /**\n * Parsers indexed by file extension.\n */\n parsers?: Map<FileNode['extname'], Parser>\n /**\n * Output extname per source extname, applied during conversion.\n */\n extension?: Record<FileNode['extname'], FileNode['extname'] | ''>\n}\n\nfunction joinSources(file: FileNode): string {\n const sources = file.sources\n if (sources.length === 0) return ''\n const parts: Array<string> = []\n for (const source of sources) {\n const text = extractStringsFromNodes(source.nodes as Array<CodeNode>)\n if (text) parts.push(text)\n }\n return parts.join('\\n\\n')\n}\n\n/**\n * Turns `FileNode`s into source strings and writes them to storage.\n *\n * Two modes share the same instance. Stateless mode (`parse`, `stream`, `run`) just runs the\n * conversion. Queue mode (`enqueue`, `flush`, `drain`) buffers files deduped by path and\n * writes each batch through storage with up to `STREAM_FLUSH_EVERY` requests in flight.\n *\n * `flush` does not wait for its batch to finish, so dispatch can overlap with IO. The next\n * `flush` or `drain` picks the in-flight batch up. `drain` blocks until everything has been\n * written and is meant for the end of a build.\n *\n * To surface build-level hook signals (`kubb:files:processing:*` and friends) subscribe to\n * `hooks` and re-emit on the kubb bus.\n */\nexport class FileProcessor {\n readonly hooks = new AsyncEventEmitter<FileProcessorHooks>()\n readonly #parsers: Map<FileNode['extname'], Parser> | null\n readonly #storage: Storage\n readonly #extension: Record<FileNode['extname'], FileNode['extname'] | ''> | null\n readonly #pending = new Map<string, FileNode>()\n #runningFlush: Promise<void> | null = null\n\n constructor(options: FileProcessorOptions) {\n this.#parsers = options.parsers ?? null\n this.#storage = options.storage\n this.#extension = options.extension ?? null\n }\n\n /**\n * Files waiting in the queue.\n */\n get size(): number {\n return this.#pending.size\n }\n\n parse(file: FileNode): string {\n const parsers = this.#parsers\n const parseExtName = this.#extension?.[file.extname] || undefined\n\n if (!parsers || !file.extname) {\n return joinSources(file)\n }\n\n const parser = parsers.get(file.extname)\n\n if (!parser) {\n return joinSources(file)\n }\n\n return parser.parse(file, { extname: parseExtName })\n }\n\n *stream(files: ReadonlyArray<FileNode>): Generator<ParsedFile> {\n const total = files.length\n if (total === 0) return\n\n let processed = 0\n for (const file of files) {\n const source = this.parse(file)\n processed++\n\n yield { file, source, processed, total, percentage: (processed / total) * 100 }\n }\n }\n\n async run(files: Array<FileNode>): Promise<Array<FileNode>> {\n await this.hooks.emit('start', files)\n\n for (const { file, source, processed, total, percentage } of this.stream(files)) {\n await this.hooks.emit('update', { file, source, processed, percentage, total })\n }\n\n await this.hooks.emit('end', files)\n\n return files\n }\n\n /**\n * Adds a file to the next flush. A later `enqueue` for the same path replaces the previous\n * entry, matching `FileManager.upsert`. Fires the `enqueue` event.\n */\n enqueue(file: FileNode): void {\n this.#pending.set(file.path, file)\n this.hooks.emit('enqueue', file)\n }\n\n /**\n * Starts processing the queued files. Waits for any previous flush to finish (so two\n * batches never run together) and then returns without waiting for the new one. The next\n * `flush` or `drain` picks up the in-flight task.\n */\n async flush(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n if (this.#pending.size === 0) return\n\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n\n this.#runningFlush = this.#processAndWrite(batch).finally(() => {\n this.#runningFlush = null\n })\n }\n\n /**\n * Waits for the in-flight flush and writes any files still queued. Fires the `drain` event\n * when both are done.\n */\n async drain(): Promise<void> {\n if (this.#runningFlush) await this.#runningFlush\n\n if (this.#pending.size > 0) {\n const batch = [...this.#pending.values()]\n this.#pending.clear()\n await this.#processAndWrite(batch)\n }\n\n await this.hooks.emit('drain')\n }\n\n async #processAndWrite(files: Array<FileNode>): Promise<void> {\n const storage = this.#storage\n\n await this.hooks.emit('start', files)\n\n // Single pass: each file's write starts right after its `update` fires, so IO overlaps\n // parsing and the batch never holds every rendered source in memory at once.\n const queue: Array<Promise<void>> = []\n for (const item of this.stream(files)) {\n await this.hooks.emit('update', item)\n if (item.source) {\n queue.push(storage.setItem(item.file.path, item.source))\n if (queue.length >= STREAM_FLUSH_EVERY) await Promise.all(queue.splice(0))\n }\n }\n await Promise.all(queue)\n\n await this.hooks.emit('end', files)\n }\n\n /**\n * Clears every listener and the pending queue.\n */\n dispose(): void {\n this.hooks.removeAll()\n this.#pending.clear()\n }\n\n [Symbol.dispose](): void {\n this.dispose()\n }\n}\n","import { createStorage } from '../createStorage.ts'\n\n/**\n * In-memory storage driver. Useful for testing and dry-run scenarios where\n * generated output should be captured without touching the filesystem.\n *\n * All data lives in a `Map` scoped to the storage instance and is discarded\n * when the instance is garbage-collected.\n *\n * @example\n * ```ts\n * import { memoryStorage } from '@kubb/core'\n * import { defineConfig } from 'kubb'\n *\n * export default defineConfig({\n * input: { path: './petStore.yaml' },\n * output: { path: './src/gen' },\n * storage: memoryStorage(),\n * })\n * ```\n */\nexport const memoryStorage = createStorage(() => {\n const store = new Map<string, string>()\n\n return {\n name: 'memory',\n async hasItem(key: string) {\n return store.has(key)\n },\n async getItem(key: string) {\n return store.get(key) ?? null\n },\n async setItem(key: string, value: string) {\n store.set(key, value)\n },\n async removeItem(key: string) {\n store.delete(key)\n },\n async getKeys(base?: string) {\n const keys = [...store.keys()]\n return base ? keys.filter((k) => k.startsWith(base)) : keys\n },\n async clear(base?: string) {\n if (!base) {\n store.clear()\n return\n }\n for (const key of store.keys()) {\n if (key.startsWith(base)) {\n store.delete(key)\n }\n }\n },\n }\n})\n"],"mappings":"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"} |
| import { createHash } from 'node:crypto' | ||
| import { join, resolve } from 'node:path' | ||
| import { clean, read, write } from '@internals/utils' | ||
| import { createCache } from '../createCache.ts' | ||
| /** | ||
| * Bookkeeping for one cached build: the relative paths it covers and timestamps used by the pruner. | ||
| */ | ||
| type ManifestEntry = { | ||
| files: Array<string> | ||
| createdAt: number | ||
| lastAccess: number | ||
| } | ||
| /** | ||
| * The on-disk manifest: a version marker plus an entry per cached build, keyed by fingerprint. | ||
| */ | ||
| type ManifestData = { | ||
| version: number | ||
| entries: Record<string, ManifestEntry> | ||
| } | ||
| /** | ||
| * Reads and prunes the local cache manifest. All methods are static, so call them as | ||
| * `Manifest.read(dir)` and `Manifest.prune(data, ...)`. A damaged manifest reads as empty so the | ||
| * cache degrades to misses instead of throwing. Writing goes through `write` from `@internals/utils`. | ||
| */ | ||
| class Manifest { | ||
| /** | ||
| * On-disk layout version for the manifest itself. Bumped when the manifest shape changes; a | ||
| * mismatch makes the whole local cache read as empty. | ||
| */ | ||
| static version = 1 | ||
| /** | ||
| * Reads the manifest at `dir/manifest.json`. A missing, corrupt, or version-mismatched file reads | ||
| * as an empty manifest. | ||
| */ | ||
| static async read(dir: string): Promise<ManifestData> { | ||
| try { | ||
| const parsed = JSON.parse(await read(join(dir, 'manifest.json'))) as ManifestData | ||
| if (parsed.version !== Manifest.version || typeof parsed.entries !== 'object') { | ||
| return Manifest.#empty() | ||
| } | ||
| return parsed | ||
| } catch { | ||
| return Manifest.#empty() | ||
| } | ||
| } | ||
| /** | ||
| * Selects the keys to evict so the cache stays within `ttlDays` and `maxEntries`. Returns the | ||
| * surviving manifest plus the evicted keys (the caller deletes their blobs). Pure, does no IO. | ||
| */ | ||
| static prune( | ||
| manifest: ManifestData, | ||
| { maxEntries, ttlDays, now }: { maxEntries: number; ttlDays: number; now: number }, | ||
| ): { | ||
| manifest: ManifestData | ||
| removed: Array<string> | ||
| } { | ||
| const ttlMs = ttlDays * 24 * 60 * 60 * 1000 | ||
| const removed: Array<string> = [] | ||
| const kept: Array<[string, ManifestEntry]> = [] | ||
| for (const [key, entry] of Object.entries(manifest.entries)) { | ||
| if (now - entry.lastAccess > ttlMs) { | ||
| removed.push(key) | ||
| } else { | ||
| kept.push([key, entry]) | ||
| } | ||
| } | ||
| if (kept.length > maxEntries) { | ||
| kept.sort((a, b) => b[1].lastAccess - a[1].lastAccess) | ||
| for (const [key] of kept.splice(maxEntries)) { | ||
| removed.push(key) | ||
| } | ||
| } | ||
| return { manifest: { version: Manifest.version, entries: Object.fromEntries(kept) }, removed } | ||
| } | ||
| static #empty(): ManifestData { | ||
| return { version: Manifest.version, entries: {} } | ||
| } | ||
| } | ||
| /** | ||
| * Options for {@link fsCache}. | ||
| */ | ||
| export type FsCacheOptions = { | ||
| /** | ||
| * Directory that holds the cache. Resolved against `process.cwd()` when relative. | ||
| * | ||
| * @default 'node_modules/.cache/kubb' | ||
| */ | ||
| dir?: string | ||
| /** | ||
| * Maximum number of build snapshots to keep. The least-recently-used entries are | ||
| * evicted once the cache grows past it. | ||
| * | ||
| * @default 50 | ||
| */ | ||
| maxEntries?: number | ||
| /** | ||
| * Days a snapshot may go untouched before it is evicted. | ||
| * | ||
| * @default 7 | ||
| */ | ||
| ttlDays?: number | ||
| } | ||
| type IndexFile = Array<{ path: string; blob: string }> | ||
| function blobName(relativePath: string): string { | ||
| return `${createHash('sha256').update(relativePath).digest('hex')}.blob` | ||
| } | ||
| /** | ||
| * Local filesystem cache. Stores each build snapshot as content blobs plus an index, | ||
| * tracked by a manifest under `node_modules/.cache/kubb/` (the Nx and Vitest | ||
| * convention). Least-recently-used and expired entries are pruned on every persist. | ||
| * | ||
| * @example | ||
| * ```ts | ||
| * import { fsCache } from '@kubb/core' | ||
| * | ||
| * export default defineConfig({ | ||
| * cache: fsCache(), | ||
| * }) | ||
| * ``` | ||
| */ | ||
| export const fsCache = createCache((options: FsCacheOptions = {}) => { | ||
| const dir = resolve(options.dir ?? join('node_modules', '.cache', 'kubb')) | ||
| const maxEntries = options.maxEntries ?? 50 | ||
| const ttlDays = options.ttlDays ?? 7 | ||
| const blobsDir = join(dir, 'blobs') | ||
| const manifestPath = join(dir, 'manifest.json') | ||
| return { | ||
| name: 'fs', | ||
| async restore({ key }) { | ||
| const manifest = await Manifest.read(dir) | ||
| const entry = manifest.entries[key] | ||
| if (!entry) { | ||
| return null | ||
| } | ||
| try { | ||
| const index = JSON.parse(await read(join(blobsDir, key, 'index.json'))) as IndexFile | ||
| const files: Record<string, string> = {} | ||
| for (const { path, blob } of index) { | ||
| files[path] = await read(join(blobsDir, key, blob)) | ||
| } | ||
| entry.lastAccess = Date.now() | ||
| await write(manifestPath, JSON.stringify(manifest)).catch(() => {}) | ||
| return { files } | ||
| } catch { | ||
| return null | ||
| } | ||
| }, | ||
| async persist({ key, snapshot }) { | ||
| const entryDir = join(blobsDir, key) | ||
| const index: IndexFile = [] | ||
| for (const [path, source] of Object.entries(snapshot.files)) { | ||
| const blob = blobName(path) | ||
| await write(join(entryDir, blob), source) | ||
| index.push({ path, blob }) | ||
| } | ||
| await write(join(entryDir, 'index.json'), JSON.stringify(index)) | ||
| const manifest = await Manifest.read(dir) | ||
| const now = Date.now() | ||
| manifest.entries[key] = { files: index.map((item) => item.path), createdAt: now, lastAccess: now } | ||
| const pruned = Manifest.prune(manifest, { maxEntries, ttlDays, now }) | ||
| await Promise.all(pruned.removed.map((removedKey) => clean(join(blobsDir, removedKey)))) | ||
| await write(manifestPath, JSON.stringify(pruned.manifest)) | ||
| }, | ||
| } | ||
| }) |
| /** | ||
| * A snapshot of a completed build: the final rendered source of every generated | ||
| * file, keyed by its path relative to the output root. Restoring a snapshot writes | ||
| * those sources straight to storage, skipping generation entirely. | ||
| * | ||
| * Paths are relative, not absolute, so the snapshot never depends on where the | ||
| * project lives on disk. | ||
| */ | ||
| export type CachedSnapshot = { | ||
| /** | ||
| * Final source per output file, keyed by the path relative to | ||
| * `resolve(config.root, config.output.path)`. | ||
| */ | ||
| files: Record<string, string> | ||
| } | ||
| /** | ||
| * Backend that stores build snapshots for incremental ("hot") rebuilds. When the | ||
| * input fingerprint matches a stored key, Kubb restores the snapshot instead of | ||
| * regenerating. Kubb ships with `fsCache` (local disk). Implement this interface to | ||
| * back the cache with any other store. | ||
| * | ||
| * @see {@link createCache} to build a custom backend. | ||
| */ | ||
| export type Cache = { | ||
| /** | ||
| * Identifier used in logs and diagnostics (`'fs'`, `'memory'`). | ||
| */ | ||
| readonly name: string | ||
| /** | ||
| * Returns the snapshot stored under `key`, or `null` on a miss. A backend never | ||
| * throws on a miss or a transient failure. It returns `null` so the build falls | ||
| * through to regeneration. | ||
| */ | ||
| restore(params: { key: string }): Promise<CachedSnapshot | null> | ||
| /** | ||
| * Stores `snapshot` under `key`. Only called after a successful build with no | ||
| * error diagnostics. | ||
| */ | ||
| persist(params: { key: string; snapshot: CachedSnapshot }): Promise<void> | ||
| /** | ||
| * Optional teardown called after the build. Use to flush buffers or close | ||
| * connections. | ||
| */ | ||
| dispose?(): Promise<void> | ||
| } | ||
| /** | ||
| * Defines a custom cache backend. The builder receives user options and returns a | ||
| * {@link Cache}. Reach for this when the filesystem backend doesn't fit, for | ||
| * example to store snapshots in Redis or a database. | ||
| * | ||
| * @example In-memory cache (the built-in implementation) | ||
| * ```ts | ||
| * import { createCache } from '@kubb/core' | ||
| * | ||
| * export const memoryCache = createCache(() => { | ||
| * const store = new Map<string, CachedSnapshot>() | ||
| * | ||
| * return { | ||
| * name: 'memory', | ||
| * async restore({ key }) { | ||
| * return store.get(key) ?? null | ||
| * }, | ||
| * async persist({ key, snapshot }) { | ||
| * store.set(key, snapshot) | ||
| * }, | ||
| * } | ||
| * }) | ||
| * ``` | ||
| */ | ||
| export function createCache<TOptions = Record<string, never>>(build: (options: TOptions) => Cache): (options?: TOptions) => Cache { | ||
| return (options) => build(options ?? ({} as TOptions)) | ||
| } |
| import { createHash } from 'node:crypto' | ||
| import { readFile } from 'node:fs/promises' | ||
| import { relative } from 'node:path' | ||
| import { URLPath } from '@internals/utils' | ||
| import type { AdapterSource } from './createAdapter.ts' | ||
| import type { Config } from './types.ts' | ||
| /** | ||
| * Computes the cache key for an incremental build. All methods are static, so call them as | ||
| * `Fingerprint.compute(...)` and `Fingerprint.stringify(...)`. The key holds no absolute | ||
| * paths or modification times, so it never depends on where the project lives on disk. | ||
| */ | ||
| export class Fingerprint { | ||
| /** | ||
| * Bumped when the snapshot format or fingerprint inputs change in an incompatible way, so stale | ||
| * cache entries from older Kubb builds are never reused. | ||
| */ | ||
| static version = 1 | ||
| /** | ||
| * Deterministically serializes a value to JSON: object keys are sorted recursively and | ||
| * `undefined` values and functions are dropped. Two structurally equal configs produce the same | ||
| * string regardless of key order, which keeps the fingerprint stable across machines. | ||
| */ | ||
| static stringify(value: unknown): string { | ||
| return JSON.stringify(Fingerprint.#normalize(value)) | ||
| } | ||
| /** | ||
| * Computes a cache key from everything that affects the generated output: the spec content, the | ||
| * output-shaping config, each plugin's name and options, the running | ||
| * `@kubb/core` version, and the cache format version. Returns `null` when the input can't be | ||
| * fingerprinted (remote URL or no adapter source), which disables caching for that build. | ||
| */ | ||
| static async compute({ config, adapterSource, version }: { config: Config; adapterSource: AdapterSource | null; version: string }): Promise<string | null> { | ||
| if (!adapterSource) { | ||
| return null | ||
| } | ||
| const spec = await Fingerprint.#readSpec(adapterSource, config.root) | ||
| if (spec === null) { | ||
| return null | ||
| } | ||
| const input = { | ||
| cacheVersion: Fingerprint.version, | ||
| version, | ||
| spec, | ||
| name: config.name, | ||
| output: config.output, | ||
| adapter: config.adapter?.name, | ||
| parsers: config.parsers.map((parser) => parser.name), | ||
| plugins: config.plugins.map((plugin) => ({ name: plugin.name, options: plugin.options })), | ||
| } | ||
| return createHash('sha256').update(Fingerprint.stringify(input)).digest('hex') | ||
| } | ||
| static #normalize(value: unknown): unknown { | ||
| if (value === null || typeof value !== 'object') { | ||
| return typeof value === 'function' ? undefined : value | ||
| } | ||
| if (Array.isArray(value)) { | ||
| return value.map((item) => Fingerprint.#normalize(item)) | ||
| } | ||
| const source = value as Record<string, unknown> | ||
| const result: Record<string, unknown> = {} | ||
| for (const key of Object.keys(source).sort()) { | ||
| const normalized = Fingerprint.#normalize(source[key]) | ||
| if (normalized !== undefined) { | ||
| result[key] = normalized | ||
| } | ||
| } | ||
| return result | ||
| } | ||
| /** | ||
| * Reads the spec content that feeds the fingerprint. Returns `null` for a remote URL source | ||
| * (hashing remote content would mean fetching it on every run) or when a file can't be read, so a | ||
| * missing or virtual spec disables caching instead of failing the build. | ||
| */ | ||
| static async #readSpec(source: AdapterSource, root: string): Promise<unknown> { | ||
| if (source.type === 'data') { | ||
| return { kind: 'data', data: typeof source.data === 'string' ? source.data : Fingerprint.stringify(source.data) } | ||
| } | ||
| const paths = source.type === 'paths' ? source.paths : [source.path] | ||
| if (paths.some((path) => new URLPath(path).isURL)) { | ||
| return null | ||
| } | ||
| try { | ||
| const contents = await Promise.all(paths.map(async (path) => ({ path: relative(root, path), content: await readFile(path, 'utf8') }))) | ||
| return { kind: 'path', contents } | ||
| } catch { | ||
| return null | ||
| } | ||
| } | ||
| } |
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
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1
-50%1101176
-7.86%43
-6.52%16107
-7.82%+ Added
+ Added
- Removed
- Removed
Updated