@kubb/ast 5.0.0-beta.54 → 5.0.0-beta.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -193,56 +193,31 @@ const INDENT = Array.from({ length: 2 }, () => " ").join("");
193
193
  function toCamelOrPascal(text, pascal) {
194
194
  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) => {
195
195
  if (word.length > 1 && word === word.toUpperCase()) return word;
196
- if (i === 0 && !pascal) return word.charAt(0).toLowerCase() + word.slice(1);
197
- return word.charAt(0).toUpperCase() + word.slice(1);
196
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
198
197
  }).join("").replace(/[^a-zA-Z0-9]/g, "");
199
198
  }
200
199
  /**
201
- * Splits `text` on `.` and applies `transformPart` to each segment.
202
- * The last segment receives `isLast = true`, all earlier segments receive `false`.
203
- * Segments are joined with `/` to form a file path.
204
- *
205
- * Only splits on dots followed by a letter so that version numbers
206
- * embedded in operationIds (e.g. `v2025.0`) are kept intact.
207
- *
208
- * Empty segments are filtered before joining. They arise when the text starts with
209
- * a dot followed immediately by a letter (e.g. `..Schema` splits into `['..', 'Schema']`
210
- * and `'..'` transforms to an empty string). Without this filter the join would produce
211
- * a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
212
- * generated files to escape the configured output directory.
213
- */
214
- function applyToFileParts(text, transformPart) {
215
- const parts = text.split(/\.(?=[a-zA-Z])/);
216
- return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
217
- }
218
- /**
219
200
  * Converts `text` to camelCase.
220
- * When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
221
201
  *
222
- * @example
223
- * camelCase('hello-world') // 'helloWorld'
224
- * camelCase('pet.petId', { isFile: true }) // 'pet/petId'
202
+ * @example Word boundaries
203
+ * `camelCase('hello-world') // 'helloWorld'`
204
+ *
205
+ * @example With a prefix
206
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
225
207
  */
226
- function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
227
- if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
228
- prefix,
229
- suffix
230
- } : {}));
208
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
231
209
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
232
210
  }
233
211
  /**
234
212
  * Converts `text` to PascalCase.
235
- * When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
236
213
  *
237
- * @example
238
- * pascalCase('hello-world') // 'HelloWorld'
239
- * pascalCase('pet.petId', { isFile: true }) // 'pet/PetId'
214
+ * @example Word boundaries
215
+ * `pascalCase('hello-world') // 'HelloWorld'`
216
+ *
217
+ * @example With a suffix
218
+ * `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
240
219
  */
241
- function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
242
- if (isFile) return applyToFileParts(text, (part, isLast) => isLast ? pascalCase(part, {
243
- prefix,
244
- suffix
245
- }) : camelCase(part));
220
+ function pascalCase(text, { prefix = "", suffix = "" } = {}) {
246
221
  return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
247
222
  }
248
223
  //#endregion
@@ -349,22 +324,6 @@ function isValidVarName(name) {
349
324
  return isIdentifier(name);
350
325
  }
351
326
  /**
352
- * Returns `name` when it is already a valid JavaScript variable name, otherwise prefixes it with `_`
353
- * so the result can be used as an identifier. Useful for sanitizing schema names or operation IDs
354
- * that start with a digit (`409`, `504AccountCancel`) or collide with a reserved word.
355
- *
356
- * @example
357
- * ```ts
358
- * ensureValidVarName('409') // '_409'
359
- * ensureValidVarName('Pet') // 'Pet'
360
- * ensureValidVarName('class') // '_class'
361
- * ```
362
- */
363
- function ensureValidVarName(name) {
364
- if (!name || isValidVarName(name)) return name;
365
- return `_${name}`;
366
- }
367
- /**
368
327
  * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
369
328
  *
370
329
  * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
@@ -396,21 +355,6 @@ function singleQuote(value) {
396
355
  if (value === void 0 || value === null) return "''";
397
356
  return `'${String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
398
357
  }
399
- /**
400
- * Strips the file extension from a path or file name.
401
- * Only removes the last `.ext` segment when the dot is not part of a directory name.
402
- *
403
- * @example
404
- * trimExtName('petStore.ts') // 'petStore'
405
- * trimExtName('/src/models/pet.ts') // '/src/models/pet'
406
- * trimExtName('/project.v2/gen/pet.ts') // '/project.v2/gen/pet'
407
- * trimExtName('noExtension') // 'noExtension'
408
- */
409
- function trimExtName(text) {
410
- const dotIndex = text.lastIndexOf(".");
411
- if (dotIndex > 0 && !text.includes("/", dotIndex)) return text.slice(0, dotIndex);
412
- return text;
413
- }
414
358
  //#endregion
415
359
  //#region src/utils/index.ts
416
360
  /**
@@ -688,12 +632,6 @@ Object.defineProperty(exports, "childName", {
688
632
  return childName;
689
633
  }
690
634
  });
691
- Object.defineProperty(exports, "ensureValidVarName", {
692
- enumerable: true,
693
- get: function() {
694
- return ensureValidVarName;
695
- }
696
- });
697
635
  Object.defineProperty(exports, "enumPropName", {
698
636
  enumerable: true,
699
637
  get: function() {
@@ -772,12 +710,6 @@ Object.defineProperty(exports, "toRegExpString", {
772
710
  return toRegExpString;
773
711
  }
774
712
  });
775
- Object.defineProperty(exports, "trimExtName", {
776
- enumerable: true,
777
- get: function() {
778
- return trimExtName;
779
- }
780
- });
781
713
  Object.defineProperty(exports, "trimQuotes", {
782
714
  enumerable: true,
783
715
  get: function() {
@@ -791,4 +723,4 @@ Object.defineProperty(exports, "visitorDepths", {
791
723
  }
792
724
  });
793
725
 
794
- //# sourceMappingURL=utils-CMRZrT-w.cjs.map
726
+ //# sourceMappingURL=utils-cdQ6Pzyi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils-cdQ6Pzyi.cjs","names":[],"sources":["../src/constants.ts","../../../internals/utils/src/casing.ts","../../../internals/utils/src/reserved.ts","../../../internals/utils/src/string.ts","../src/utils/index.ts"],"sourcesContent":["import type { HttpMethod } from './nodes/operation.ts'\nimport type { SchemaType } from './nodes/schema.ts'\n\n/**\n * Traversal depth for AST visitor utilities.\n *\n * - `'shallow'` visits only the immediate node, skipping children.\n * - `'deep'` recursively visits all descendant nodes.\n */\nexport type VisitorDepth = 'shallow' | 'deep'\n\nexport const visitorDepths = {\n shallow: 'shallow',\n deep: 'deep',\n} as const satisfies Record<VisitorDepth, VisitorDepth>\n\n/**\n * Schema type discriminators used by all AST schema nodes.\n *\n * These values serve as stable discriminators across the AST (e.g., `schema.type === schemaTypes.object`).\n * Grouped by category: primitives (`string`, `number`, `boolean`), structural types (`object`, `array`, `union`),\n * and format-specific types (`date`, `uuid`, `email`). Use `isScalarPrimitive()` to check for scalar types.\n */\nexport const schemaTypes = {\n /**\n * Text value.\n */\n string: 'string',\n /**\n * Floating-point number (`float`, `double`).\n */\n number: 'number',\n /**\n * Whole number (`int32`). Use `bigint` for `int64`.\n */\n integer: 'integer',\n /**\n * 64-bit integer (`int64`). Only used when `integerType` is set to `'bigint'`.\n */\n bigint: 'bigint',\n /**\n * Boolean value\n */\n boolean: 'boolean',\n /**\n * Explicit null value.\n */\n null: 'null',\n /**\n * Any value (no type restriction).\n */\n any: 'any',\n /**\n * Unknown value (must be narrowed before usage).\n */\n unknown: 'unknown',\n /**\n * No return value (`void`).\n */\n void: 'void',\n /**\n * Object with named properties.\n */\n object: 'object',\n /**\n * Sequential list of items.\n */\n array: 'array',\n /**\n * Fixed-length list with position-specific items.\n */\n tuple: 'tuple',\n /**\n * \"One of\" multiple schema members.\n */\n union: 'union',\n /**\n * \"All of\" multiple schema members.\n */\n intersection: 'intersection',\n /**\n * Enum schema.\n */\n enum: 'enum',\n /**\n * Reference to another schema.\n */\n ref: 'ref',\n /**\n * Calendar date (for example `2026-03-24`).\n */\n date: 'date',\n /**\n * Date-time value (for example `2026-03-24T09:00:00Z`).\n */\n datetime: 'datetime',\n /**\n * Time-only value (for example `09:00:00`).\n */\n time: 'time',\n /**\n * UUID value.\n */\n uuid: 'uuid',\n /**\n * Email address value.\n */\n email: 'email',\n /**\n * URL value.\n */\n url: 'url',\n /**\n * IPv4 address value.\n */\n ipv4: 'ipv4',\n /**\n * IPv6 address value.\n */\n ipv6: 'ipv6',\n /**\n * Binary/blob value.\n */\n blob: 'blob',\n /**\n * Impossible value (`never`).\n */\n never: 'never',\n} as const satisfies Record<SchemaType, SchemaType>\n\nexport type ScalarPrimitive = 'string' | 'number' | 'integer' | 'bigint' | 'boolean'\n\n/**\n * Scalar primitive schema types used for union simplification and type narrowing.\n *\n * Use `isScalarPrimitive()` to safely check whether a type is a scalar primitive.\n */\nconst SCALAR_PRIMITIVE_TYPES = new Set<ScalarPrimitive>(['string', 'number', 'integer', 'bigint', 'boolean'])\n\n/**\n * Type guard that returns `true` when `type` is a scalar primitive schema type.\n *\n * Use this to check if a schema type can be directly assigned without wrapping (e.g., `string | number | boolean`).\n */\nexport function isScalarPrimitive(type: string): type is ScalarPrimitive {\n return SCALAR_PRIMITIVE_TYPES.has(type as ScalarPrimitive)\n}\n\n/**\n * HTTP method identifiers used by operation nodes.\n *\n * Includes all standard HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, TRACE).\n */\nexport const httpMethods = {\n get: 'GET',\n post: 'POST',\n put: 'PUT',\n patch: 'PATCH',\n delete: 'DELETE',\n head: 'HEAD',\n options: 'OPTIONS',\n trace: 'TRACE',\n} as const satisfies Record<Lowercase<HttpMethod>, HttpMethod>\n\n/**\n * Default concurrency limit for `walk()` traversal utility.\n *\n * Set to 30 to balance I/O-bound resolver parallelism against event loop pressure and memory usage during large spec traversals.\n * Use `WALK_CONCURRENCY` when calling `walk()` or override for different hardware constraints.\n *\n * @example\n * ```ts\n * import { walk, WALK_CONCURRENCY } from '@kubb/ast'\n *\n * walk(root, { concurrency: WALK_CONCURRENCY, root: () => {} })\n * ```\n */\nexport const WALK_CONCURRENCY = 30\n\n/**\n * Number of spaces in one indentation level when assembling multi-line code as strings.\n * Set to 2, 3, … to change the indent width used by `buildObject`/`buildList`.\n */\nconst INDENT_SIZE = 2\n\n/**\n * One indentation level, derived from {@link INDENT_SIZE}.\n */\nexport const INDENT = Array.from({ length: INDENT_SIZE }, () => ' ').join('')\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 * JavaScript and Java reserved words.\n * @link https://github.com/jonschlinkert/reserved/blob/master/index.js\n */\nconst reservedWords = new Set([\n 'abstract',\n 'arguments',\n 'boolean',\n 'break',\n 'byte',\n 'case',\n 'catch',\n 'char',\n 'class',\n 'const',\n 'continue',\n 'debugger',\n 'default',\n 'delete',\n 'do',\n 'double',\n 'else',\n 'enum',\n 'eval',\n 'export',\n 'extends',\n 'false',\n 'final',\n 'finally',\n 'float',\n 'for',\n 'function',\n 'goto',\n 'if',\n 'implements',\n 'import',\n 'in',\n 'instanceof',\n 'int',\n 'interface',\n 'let',\n 'long',\n 'native',\n 'new',\n 'null',\n 'package',\n 'private',\n 'protected',\n 'public',\n 'return',\n 'short',\n 'static',\n 'super',\n 'switch',\n 'synchronized',\n 'this',\n 'throw',\n 'throws',\n 'transient',\n 'true',\n 'try',\n 'typeof',\n 'var',\n 'void',\n 'volatile',\n 'while',\n 'with',\n 'yield',\n 'Array',\n 'Date',\n 'hasOwnProperty',\n 'Infinity',\n 'isFinite',\n 'isNaN',\n 'isPrototypeOf',\n 'length',\n 'Math',\n 'name',\n 'NaN',\n 'Number',\n 'Object',\n 'prototype',\n 'String',\n 'toString',\n 'undefined',\n 'valueOf',\n] as const)\n\n/**\n * Prefixes `word` with `_` when it is a reserved JavaScript/Java identifier or starts with a digit.\n *\n * @example\n * ```ts\n * transformReservedWord('class') // '_class'\n * transformReservedWord('42foo') // '_42foo'\n * transformReservedWord('status') // 'status'\n * ```\n */\nexport function transformReservedWord(word: string): string {\n const firstChar = word.charCodeAt(0)\n if (word && (reservedWords.has(word as 'valueOf') || (firstChar >= 48 && firstChar <= 57))) {\n return `_${word}`\n }\n return word\n}\n\n/**\n * Returns `true` when `name` is a syntactically valid JavaScript variable name.\n *\n * @example\n * ```ts\n * isValidVarName('status') // true\n * isValidVarName('class') // false (reserved word)\n * isValidVarName('42foo') // false (starts with digit)\n * ```\n */\nexport function isValidVarName(name: string): boolean {\n if (!name || reservedWords.has(name as 'valueOf')) {\n return false\n }\n return isIdentifier(name)\n}\n\n/**\n * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.\n *\n * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys\n * even though they are not valid variable names, so use this (not {@link isValidVarName}) when\n * deciding whether an object key needs quoting.\n *\n * @example\n * ```ts\n * isIdentifier('name') // true\n * isIdentifier('x-total')// false\n * ```\n */\nexport function isIdentifier(name: string): boolean {\n return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)\n}\n","/**\n * Wraps a value in single quotes for emitting a single-quoted JavaScript string literal, escaping\n * any backslash or single quote in the content.\n *\n * @example\n * ```ts\n * singleQuote('foo') // \"'foo'\"\n * singleQuote(\"o'clock\") // \"'o\\\\'clock'\"\n * ```\n */\nexport function singleQuote(value: string | number | boolean | undefined | null): string {\n if (value === undefined || value === null) return \"''\"\n const escaped = String(value).replace(/\\\\/g, '\\\\\\\\').replace(/'/g, \"\\\\'\")\n\n return `'${escaped}'`\n}\n","import { isIdentifier, pascalCase, singleQuote } from '@internals/utils'\nimport { INDENT } from '../constants.ts'\n\nexport { isValidVarName } from '@internals/utils'\n\n/**\n * Strips a single matching pair of `\"...\"`, `'...'`, or `` `...` `` from both ends of `text`.\n * Returns the string unchanged when no balanced quote pair is found.\n *\n * @example\n * ```ts\n * trimQuotes('\"hello\"') // 'hello'\n * trimQuotes('hello') // 'hello'\n * ```\n */\nexport function trimQuotes(text: string): string {\n if (text.length >= 2) {\n const first = text[0]\n const last = text[text.length - 1]\n if ((first === '\"' && last === '\"') || (first === \"'\" && last === \"'\") || (first === '`' && last === '`')) {\n return text.slice(1, -1)\n }\n }\n return text\n}\n\n/**\n * Serializes a primitive value to a single-quoted string literal, stripping any surrounding quote\n * characters first. Escaping comes from `JSON.stringify`, then the quote style switches to single\n * quotes so generated code matches the repo style without a formatter.\n *\n * @example\n * ```ts\n * stringify('hello') // \"'hello'\"\n * stringify('\"hello\"') // \"'hello'\"\n * ```\n */\nexport function stringify(value: string | number | boolean | undefined): string {\n if (value === undefined || value === null) return \"''\"\n const json = JSON.stringify(trimQuotes(value.toString()))\n const inner = json.slice(1, -1).replace(/\\\\\"/g, '\"').replace(/'/g, \"\\\\'\")\n return `'${inner}'`\n}\n\n/**\n * Escapes characters that are not allowed inside JS string literals, covering quotes, backslashes,\n * and the Unicode line terminators U+2028 and U+2029.\n *\n * @see http://www.ecma-international.org/ecma-262/5.1/#sec-7.8.4\n *\n * @example\n * ```ts\n * jsStringEscape('say \"hi\"\\nbye') // 'say \\\\\"hi\\\\\"\\\\nbye'\n * ```\n */\nexport function jsStringEscape(input: unknown): string {\n return `${input}`.replace(/[\"'\\\\\\n\\r\\u2028\\u2029]/g, (character) => {\n switch (character) {\n case '\"':\n case \"'\":\n case '\\\\':\n return `\\\\${character}`\n case '\\n':\n return '\\\\n'\n case '\\r':\n return '\\\\r'\n case '\\u2028':\n return '\\\\u2028'\n case '\\u2029':\n return '\\\\u2029'\n default:\n return ''\n }\n })\n}\n\n/**\n * Converts a pattern string into a `new RegExp(...)` constructor call or a regex literal string.\n * Inline flags expressed as a `^(?im)` prefix are extracted and applied to the resulting expression.\n * Pass `null` as the second argument to emit a `/pattern/flags` literal instead.\n *\n * @example\n * ```ts\n * toRegExpString('^(?im)foo') // 'new RegExp(\"^foo\", \"im\")'\n * toRegExpString('^(?im)foo', null) // '/^foo/im'\n * ```\n */\nexport function toRegExpString(text: string, func: string | null = 'RegExp'): string {\n const raw = trimQuotes(text)\n\n const match = raw.match(/^\\^(\\(\\?([igmsuy]+)\\))/i)\n const replacementTarget = match?.[1] ?? ''\n const matchedFlags = match?.[2]\n const cleaned = raw\n .replace(/^\\\\?\\//, '')\n .replace(/\\\\?\\/$/, '')\n .replace(replacementTarget, '')\n\n const { source, flags } = new RegExp(cleaned, matchedFlags)\n\n if (func === null) return `/${source}/${flags}`\n\n return `new ${func}(${JSON.stringify(source)}${flags ? `, ${JSON.stringify(flags)}` : ''})`\n}\n\n/**\n * Renders a plain object as multi-line `key: value` source for embedding in generated code. Nested\n * objects recurse with fixed indentation, so the result drops straight into an object literal\n * without re-parsing.\n *\n * @example\n * ```ts\n * stringifyObject({ foo: 'bar', nested: { a: 1 } })\n * // 'foo: bar,\\nnested: {\\n a: 1\\n }'\n * ```\n */\nexport function stringifyObject(value: Record<string, unknown>): string {\n const items = Object.entries(value)\n .map(([key, val]) => {\n if (val !== null && typeof val === 'object') {\n return `${key}: {\\n ${stringifyObject(val as Record<string, unknown>)}\\n }`\n }\n return `${key}: ${val}`\n })\n .filter(Boolean)\n return items.join(',\\n')\n}\n\n/**\n * Renders a dotted path or string array as an optional-chaining accessor expression rooted at\n * `accessor`. Returns `null` for an empty path.\n *\n * @example\n * ```ts\n * getNestedAccessor('pagination.next.id', 'lastPage')\n * // \"lastPage?.['pagination']?.['next']?.['id']\"\n * ```\n */\nexport function getNestedAccessor(param: string | Array<string>, accessor: string): string | null {\n const parts = Array.isArray(param) ? param : param.split('.')\n if (parts.length === 0 || (parts.length === 1 && parts[0] === '')) return null\n return `${accessor}?.['${`${parts.join(\"']?.['\")}']`}`\n}\n\n/**\n * Builds a JSDoc comment block from an array of lines, returning `fallback` when there are no\n * comments so callers always get a usable string.\n *\n * @example\n * ```ts\n * buildJSDoc(['@type string', '@example hello'])\n * // '/**\\n * @type string\\n * @example hello\\n *\\/\\n '\n * ```\n */\nexport function buildJSDoc(\n comments: Array<string>,\n options: {\n /**\n * String used to indent each comment line.\n * @default ' * '\n */\n indent?: string\n /**\n * String appended after the closing tag.\n * @default '\\n '\n */\n suffix?: string\n /**\n * Returned as-is when `comments` is empty.\n * @default ' '\n */\n fallback?: string\n } = {},\n): string {\n const { indent = ' * ', suffix = '\\n ', fallback = ' ' } = options\n\n if (comments.length === 0) return fallback\n\n return `/**\\n${comments.map((c) => `${indent}${c}`).join('\\n')}\\n */${suffix}`\n}\n\n/**\n * Indents every non-empty line of `text` by one indent level, leaving blank lines empty.\n */\nfunction indentLines(text: string): string {\n if (!text) return ''\n return text\n .split('\\n')\n .map((line) => (line.trim() ? `${INDENT}${line}` : ''))\n .join('\\n')\n}\n\n/**\n * Renders an object key, quoting it with single quotes only when it is not a valid identifier.\n * Reserved words and globals (`name`, `class`, …) are valid bare keys and stay unquoted.\n *\n * @example\n * ```ts\n * objectKey('name') // 'name'\n * objectKey('x-total') // \"'x-total'\"\n * ```\n */\nexport function objectKey(name: string): string {\n return isIdentifier(name) ? name : singleQuote(name)\n}\n\n/**\n * Assembles a multi-line object literal from already-rendered `entries`, indenting each entry one\n * level and closing the brace at column zero. Nested objects built the same way indent cumulatively,\n * so callers never re-parse the generated code. A trailing comma is added per entry to match the\n * formatter's multi-line style.\n *\n * @example\n * ```ts\n * buildObject(['id: z.number()', 'name: z.string()'])\n * // '{\\n id: z.number(),\\n name: z.string(),\\n}'\n * ```\n */\nexport function buildObject(entries: Array<string>): string {\n if (entries.length === 0) return '{}'\n const body = entries.map((entry) => `${indentLines(entry)},`).join('\\n')\n\n return `{\\n${body}\\n}`\n}\n\n/**\n * Assembles a bracketed list (array by default) from already-rendered `items`. Keeps everything on\n * one line when no item spans multiple lines, and otherwise puts each item on its own line, indented\n * one level with a trailing comma and the closing bracket at column zero. Use it for `z.union([…])`,\n * `z.array([…])`, and similar member lists so objects inside them nest correctly.\n *\n * @example\n * ```ts\n * buildList(['z.string()', 'z.number()'])\n * // '[z.string(), z.number()]'\n * ```\n */\nexport function buildList(items: Array<string>, brackets: [open: string, close: string] = ['[', ']']): string {\n const [open, close] = brackets\n if (items.length === 0) return `${open}${close}`\n if (!items.some((item) => item.includes('\\n'))) return `${open}${items.join(', ')}${close}`\n const body = items.map((item) => `${indentLines(item)},`).join('\\n')\n\n return `${open}\\n${body}\\n${close}`\n}\n\n/**\n * Returns the last path segment of a reference string.\n *\n * @example\n * ```ts\n * extractRefName('#/components/schemas/Pet') // 'Pet'\n * ```\n */\nexport function extractRefName(ref: string): string {\n return ref.split('/').at(-1) ?? ref\n}\n\n/**\n * Builds a PascalCase child schema name by joining a parent name and property name.\n * Returns `null` when there is no parent to nest under.\n *\n * @example\n * ```ts\n * childName('Order', 'shipping_address') // 'OrderShippingAddress'\n * childName(undefined, 'params') // null\n * ```\n */\nexport function childName(parentName: string | null | undefined, propName: string): string | null {\n return parentName ? pascalCase([parentName, propName].join(' ')) : null\n}\n\n/**\n * Builds a PascalCase enum name from the parent name, property name, and a suffix, skipping any\n * empty parts.\n *\n * @example\n * ```ts\n * enumPropName('Order', 'status', 'enum') // 'OrderStatusEnum'\n * ```\n */\nexport function enumPropName(parentName: string | null | undefined, propName: string, enumSuffix: string): string {\n return pascalCase([parentName, propName, enumSuffix].filter(Boolean).join(' '))\n}\n\n/**\n * Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.\n *\n * @example\n * ```ts\n * findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'\n * ```\n */\nexport function findDiscriminator(mapping: Record<string, string> | undefined, ref: string | undefined): string | null {\n if (!mapping || !ref) return null\n return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null\n}\n"],"mappings":"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"}
package/dist/utils.cjs CHANGED
@@ -1,10 +1,9 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_utils = require("./utils-CMRZrT-w.cjs");
2
+ const require_utils = require("./utils-cdQ6Pzyi.cjs");
3
3
  exports.buildJSDoc = require_utils.buildJSDoc;
4
4
  exports.buildList = require_utils.buildList;
5
5
  exports.buildObject = require_utils.buildObject;
6
6
  exports.childName = require_utils.childName;
7
- exports.ensureValidVarName = require_utils.ensureValidVarName;
8
7
  exports.enumPropName = require_utils.enumPropName;
9
8
  exports.extractRefName = require_utils.extractRefName;
10
9
  exports.findDiscriminator = require_utils.findDiscriminator;
package/dist/utils.d.ts CHANGED
@@ -12,19 +12,6 @@ import { t as __name } from "./chunk-C0LytTxp.js";
12
12
  * ```
13
13
  */
14
14
  declare function isValidVarName(name: string): boolean;
15
- /**
16
- * Returns `name` when it is already a valid JavaScript variable name, otherwise prefixes it with `_`
17
- * so the result can be used as an identifier. Useful for sanitizing schema names or operation IDs
18
- * that start with a digit (`409`, `504AccountCancel`) or collide with a reserved word.
19
- *
20
- * @example
21
- * ```ts
22
- * ensureValidVarName('409') // '_409'
23
- * ensureValidVarName('Pet') // 'Pet'
24
- * ensureValidVarName('class') // '_class'
25
- * ```
26
- */
27
- declare function ensureValidVarName(name: string): string;
28
15
  //#endregion
29
16
  //#region src/utils/index.d.ts
30
17
  /**
@@ -201,5 +188,5 @@ declare function enumPropName(parentName: string | null | undefined, propName: s
201
188
  */
202
189
  declare function findDiscriminator(mapping: Record<string, string> | undefined, ref: string | undefined): string | null;
203
190
  //#endregion
204
- export { buildJSDoc, buildList, buildObject, childName, ensureValidVarName, enumPropName, extractRefName, findDiscriminator, getNestedAccessor, isValidVarName, jsStringEscape, objectKey, stringify, stringifyObject, toRegExpString, trimQuotes };
191
+ export { buildJSDoc, buildList, buildObject, childName, enumPropName, extractRefName, findDiscriminator, getNestedAccessor, isValidVarName, jsStringEscape, objectKey, stringify, stringifyObject, toRegExpString, trimQuotes };
205
192
  //# sourceMappingURL=utils.d.ts.map
package/dist/utils.js CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as isValidVarName, a as enumPropName, c as getNestedAccessor, d as stringify, f as stringifyObject, g as ensureValidVarName, i as childName, l as jsStringEscape, m as trimQuotes, n as buildList, o as extractRefName, p as toRegExpString, r as buildObject, s as findDiscriminator, t as buildJSDoc, u as objectKey } from "./utils-BIcKgbbc.js";
2
- export { buildJSDoc, buildList, buildObject, childName, ensureValidVarName, enumPropName, extractRefName, findDiscriminator, getNestedAccessor, isValidVarName, jsStringEscape, objectKey, stringify, stringifyObject, toRegExpString, trimQuotes };
1
+ import { a as enumPropName, c as getNestedAccessor, d as stringify, f as stringifyObject, h as isValidVarName, i as childName, l as jsStringEscape, m as trimQuotes, n as buildList, o as extractRefName, p as toRegExpString, r as buildObject, s as findDiscriminator, t as buildJSDoc, u as objectKey } from "./utils-0p8ZO287.js";
2
+ export { buildJSDoc, buildList, buildObject, childName, enumPropName, extractRefName, findDiscriminator, getNestedAccessor, isValidVarName, jsStringEscape, objectKey, stringify, stringifyObject, toRegExpString, trimQuotes };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/ast",
3
- "version": "5.0.0-beta.54",
3
+ "version": "5.0.0-beta.56",
4
4
  "description": "Spec-agnostic AST layer for Kubb. Defines the node tree, visitor pattern, factory functions, and type guards used across all code generation plugins.",
5
5
  "keywords": [
6
6
  "ast",
package/src/dialect.ts CHANGED
@@ -1,43 +1,38 @@
1
1
  /**
2
- * The spec-specific decisions a schema parser makes when converting a source document's schemas
3
- * into Kubb AST nodes. Everything else in an adapter's schema pipeline is generic JSON Schema,
4
- * shared across specs, so the dialect is the one seam where specs differ, like a database driver
5
- * targeting Postgres vs MySQL. Pair it with {@link dispatch}: the rule table picks which converter
6
- * runs, the dialect answers the spec-specific questions inside it.
7
- *
8
- * The guards (`isReference`, `isDiscriminator`) are type predicates, so converters narrow the
9
- * schema after a check and the type parameters carry the narrowed types through.
10
- *
11
- * This is the seam for the JSON Schema family: OpenAPI, AsyncAPI, and plain JSON Schema share
12
- * `$ref`, `allOf`/`oneOf`, `enum`, and `format` and differ only in these few decisions. A spec on
13
- * a different type system (GraphQL, with non-null wrappers and named-type references instead of
14
- * `$ref`) skips `SchemaDialect` and reuses the universal layer directly: the `Adapter` port, the
15
- * AST factories, and {@link dispatch} with its own rule table.
16
- *
17
- * @typeParam TSchema - The adapter's schema object type (e.g. an OpenAPI `SchemaObject`).
18
- * @typeParam TRef - The narrowed `$ref` pointer type `isReference` proves.
19
- * @typeParam TDiscriminated - The narrowed discriminated-schema type `isDiscriminator` proves.
20
- * @typeParam TDocument - The source document `resolveRef` resolves against.
2
+ * The spec-specific questions a schema parser answers while turning a source document into Kubb
3
+ * AST nodes. The rest of the pipeline is generic JSON Schema, so this is the one seam where
4
+ * OpenAPI, AsyncAPI, and plain JSON Schema differ.
21
5
  */
22
6
  export type SchemaDialect<TSchema = unknown, TRef = TSchema, TDiscriminated = TSchema, TDocument = unknown> = {
23
- /** Identifies the dialect in logs and while debugging dispatch. */
7
+ /**
8
+ * Identifies the dialect in logs and diagnostics.
9
+ */
24
10
  name: string
25
- /** Whether a schema should be treated as nullable. */
11
+ /**
12
+ * Whether the schema is nullable.
13
+ */
26
14
  isNullable: (schema?: TSchema) => boolean
27
- /** Whether a value is a `$ref` pointer object. */
15
+ /**
16
+ * Whether the value is a `$ref` pointer.
17
+ */
28
18
  isReference: (value?: unknown) => value is TRef
29
- /** Whether a schema carries a structured discriminator (polymorphism). */
19
+ /**
20
+ * Whether the schema carries a discriminator for polymorphism.
21
+ */
30
22
  isDiscriminator: (value?: unknown) => value is TDiscriminated
31
- /** Whether a schema represents binary data (converted to a `blob` node). */
23
+ /**
24
+ * Whether the schema is binary data, converted to a `blob` node.
25
+ */
32
26
  isBinary: (schema: TSchema) => boolean
33
- /** Resolves a local `$ref` pointer against the document, or nullish when it cannot. */
27
+ /**
28
+ * Resolves a local `$ref` against the document, or nullish when it cannot.
29
+ */
34
30
  resolveRef: <TResolved>(document: TDocument, ref: string) => TResolved | null | undefined
35
31
  }
36
32
 
37
33
  /**
38
- * Identity helper that types a {@link SchemaDialect} for an adapter. Like
39
- * `defineParser`, it adds no runtime behavior, it pins the dialect's type for
40
- * inference and gives adapter authors a discoverable anchor.
34
+ * Types a {@link SchemaDialect} for an adapter. Adds no runtime behavior and only pins the
35
+ * dialect's type for inference.
41
36
  *
42
37
  * @example
43
38
  * ```ts
package/src/factory.ts CHANGED
@@ -17,7 +17,6 @@ import type {
17
17
  ImportNode,
18
18
  InputMeta,
19
19
  InputNode,
20
- InputStreamNode,
21
20
  JsxNode,
22
21
  Node,
23
22
  ObjectSchemaNode,
@@ -127,14 +126,14 @@ export function createInput(overrides: Partial<Omit<InputNode, 'kind'>> = {}): I
127
126
  }
128
127
 
129
128
  /**
130
- * Creates an `InputStreamNode` from pre-built `AsyncIterable` sources.
129
+ * Creates a streaming `InputNode<true>` from pre-built `AsyncIterable` sources.
131
130
  *
132
131
  * @example
133
132
  * ```ts
134
133
  * const node = createStreamInput(schemasIterable, operationsIterable, { title: 'My API' })
135
134
  * ```
136
135
  */
137
- export function createStreamInput(schemas: AsyncIterable<SchemaNode>, operations: AsyncIterable<OperationNode>, meta?: InputMeta): InputStreamNode {
136
+ export function createStreamInput(schemas: AsyncIterable<SchemaNode>, operations: AsyncIterable<OperationNode>, meta?: InputMeta): InputNode<true> {
138
137
  return { kind: 'Input', schemas, operations, meta }
139
138
  }
140
139
 
package/src/index.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export { httpMethods, schemaTypes } from './constants.ts'
2
2
  export { applyDedupe, buildDedupePlan } from './dedupe.ts'
3
3
  export { defineSchemaDialect } from './dialect.ts'
4
- export { dispatch } from './dispatch.ts'
5
4
  export {
6
5
  createArrowFunction,
7
6
  createBreak,
@@ -31,8 +30,7 @@ export {
31
30
  } from './factory.ts'
32
31
  export { isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isSchemaNode, narrowSchema } from './guards.ts'
33
32
  export { createPrinterFactory, definePrinter } from './printer.ts'
34
- export { collectImports } from './resolvers.ts'
35
- export { schemaSignature } from './signature.ts'
33
+ export { signatureOf } from './signature.ts'
36
34
  export { mergeAdjacentObjectsLazy, setDiscriminatorEnum, setEnumName, simplifyUnion } from './transformers.ts'
37
35
  export type * from './types.ts'
38
36
  export {
package/src/mocks.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createInput, createOperation, createParameter, createProperty, createResponse, createSchema } from './factory.ts'
2
- import type { InputNode } from './nodes/root.ts'
2
+ import type { InputNode } from './nodes/input.ts'
3
3
 
4
4
  /**
5
5
  * Builds a minimal sample AST with one `Pet` schema and one `getPetById` operation.
@@ -2,12 +2,13 @@ import type { ArrowFunctionNode, ConstNode, FunctionNode, TypeNode } from './cod
2
2
  import type { ContentNode } from './content.ts'
3
3
  import type { ExportNode, FileNode, ImportNode, SourceNode } from './file.ts'
4
4
  import type { FunctionParamNode, ParamsTypeNode } from './function.ts'
5
- import type { OperationNode, RequestBodyNode } from './operation.ts'
5
+ import type { InputNode } from './input.ts'
6
+ import type { OperationNode } from './operation.ts'
6
7
  import type { OutputNode } from './output.ts'
7
8
  import type { ParameterNode } from './parameter.ts'
8
9
  import type { PropertyNode } from './property.ts'
10
+ import type { RequestBodyNode } from './requestBody.ts'
9
11
  import type { ResponseNode } from './response.ts'
10
- import type { InputNode } from './root.ts'
11
12
  import type { SchemaNode } from './schema.ts'
12
13
 
13
14
  export type { NodeKind } from './base.ts'
@@ -16,12 +17,13 @@ export type { ContentNode } from './content.ts'
16
17
  export type { ExportNode, FileNode, ImportNode, SourceNode } from './file.ts'
17
18
  export type { FunctionNodeType, FunctionParameterNode, FunctionParametersNode, FunctionParamNode, ParameterGroupNode, ParamsTypeNode } from './function.ts'
18
19
  export type { StatusCode } from './http.ts'
19
- export type { GenericOperationNode, HttpMethod, HttpOperationNode, OperationNode, RequestBodyNode } from './operation.ts'
20
+ export type { InputMeta, InputNode } from './input.ts'
21
+ export type { GenericOperationNode, HttpMethod, HttpOperationNode, OperationNode } from './operation.ts'
20
22
  export type { OutputNode } from './output.ts'
21
23
  export type { ParameterLocation, ParameterNode } from './parameter.ts'
22
24
  export type { PropertyNode } from './property.ts'
25
+ export type { RequestBodyNode } from './requestBody.ts'
23
26
  export type { ResponseNode } from './response.ts'
24
- export type { InputMeta, InputNode, InputStreamNode } from './root.ts'
25
27
  export type {
26
28
  ArraySchemaNode,
27
29
  DateSchemaNode,
@@ -1,3 +1,4 @@
1
+ import type { Streamable } from '@internals/utils'
1
2
  import type { BaseNode } from './base.ts'
2
3
  import type { OperationNode } from './operation.ts'
3
4
  import type { SchemaNode } from './schema.ts'
@@ -65,62 +66,38 @@ export type InputMeta = {
65
66
  * Input AST node that contains all schemas and operations for one API document.
66
67
  * Produced by the adapter and consumed by all Kubb plugins.
67
68
  *
69
+ * `Stream` switches `schemas` and `operations` between eager `Array`s (the default) and lazy
70
+ * `AsyncIterable`s. The streaming variant `InputNode<true>` yields nodes one at a time and makes
71
+ * `meta` optional, since the adapter can emit metadata before the first node is parsed.
72
+ *
68
73
  * @example
69
74
  * ```ts
70
75
  * const input: InputNode = {
71
76
  * kind: 'Input',
72
77
  * schemas: [],
73
78
  * operations: [],
79
+ * meta: { circularNames: [], enumNames: [] },
74
80
  * }
75
81
  * ```
76
- */
77
- export type InputNode = BaseNode & {
78
- /**
79
- * Node kind.
80
- */
81
- kind: 'Input'
82
- /**
83
- * All schema nodes in the document.
84
- */
85
- schemas: Array<SchemaNode>
86
- /**
87
- * All operation nodes in the document.
88
- */
89
- operations: Array<OperationNode>
90
- /**
91
- * Document metadata populated by the adapter.
92
- */
93
- meta: InputMeta
94
- }
95
-
96
- /**
97
- * Streaming variant of `InputNode` for memory-efficient processing of large API specs.
98
- *
99
- * `schemas` and `operations` are `AsyncIterable` rather than arrays, each `for await`
100
- * loop creates a fresh parse pass from the cached in-memory document, so multiple
101
- * consumers (plugins) can iterate independently without keeping all nodes in memory.
102
82
  *
103
- * @example
83
+ * @example Streaming variant for large specs
104
84
  * ```ts
105
- * for await (const schema of inputStreamNode.schemas) {
85
+ * for await (const schema of inputNode.schemas) {
106
86
  * // only this one SchemaNode is live here. Previous ones are GC-eligible
107
87
  * }
108
88
  * ```
109
89
  */
110
- export type InputStreamNode = {
111
- kind: 'Input'
90
+ export type InputNode<Stream extends boolean = false> = BaseNode & {
112
91
  /**
113
- * Lazily parsed schema nodes. Each `for await` creates a fresh parse pass, so
114
- * multiple plugins can iterate independently without sharing state.
92
+ * Node kind.
115
93
  */
116
- schemas: AsyncIterable<SchemaNode>
94
+ kind: 'Input'
117
95
  /**
118
- * Lazily parsed operation nodes. Each `for await` creates a fresh parse pass, so
119
- * multiple plugins can iterate independently without sharing state.
96
+ * All schema nodes in the document.
120
97
  */
121
- operations: AsyncIterable<OperationNode>
98
+ schemas: Streamable<SchemaNode, Stream>
122
99
  /**
123
- * Document metadata available immediately, before the first yielded node.
100
+ * All operation nodes in the document.
124
101
  */
125
- meta?: InputMeta
126
- }
102
+ operations: Streamable<OperationNode, Stream>
103
+ } & (Stream extends true ? { meta?: InputMeta } : { meta: InputMeta })
@@ -1,6 +1,6 @@
1
1
  import type { BaseNode } from './base.ts'
2
- import type { ContentNode } from './content.ts'
3
2
  import type { ParameterNode } from './parameter.ts'
3
+ import type { RequestBodyNode } from './requestBody.ts'
4
4
  import type { ResponseNode } from './response.ts'
5
5
 
6
6
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE'
@@ -10,46 +10,6 @@ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' |
10
10
  */
11
11
  type OperationProtocol = 'http'
12
12
 
13
- /**
14
- * AST node representing an operation request body.
15
- *
16
- * Body schemas live exclusively inside the `content` array (one entry per content type),
17
- * mirroring {@link ResponseNode}.
18
- *
19
- * @example
20
- * ```ts
21
- * const requestBody: RequestBodyNode = {
22
- * kind: 'RequestBody',
23
- * required: true,
24
- * content: [{ kind: 'Content', contentType: 'application/json', schema: createSchema({ type: 'string' }) }],
25
- * }
26
- * ```
27
- */
28
- export type RequestBodyNode = BaseNode & {
29
- /**
30
- * Node kind.
31
- */
32
- kind: 'RequestBody'
33
- /**
34
- * Human-readable request body description.
35
- */
36
- description?: string
37
- /**
38
- * Whether the request body is required (`requestBody.required: true` in the spec).
39
- * When `false` or absent, the generated `data` parameter should be optional.
40
- */
41
- required?: boolean
42
- /**
43
- * All available content type entries for this request body.
44
- *
45
- * When the adapter `contentType` option is set, this array contains exactly one entry for
46
- * that content type. Otherwise it contains one entry per content type declared in the spec,
47
- * so that plugins can generate code for every variant (e.g. separate hooks for
48
- * `application/json` and `multipart/form-data`).
49
- */
50
- content?: Array<ContentNode>
51
- }
52
-
53
13
  /**
54
14
  * Fields shared by every operation, regardless of transport.
55
15
  */
@@ -0,0 +1,42 @@
1
+ import type { BaseNode } from './base.ts'
2
+ import type { ContentNode } from './content.ts'
3
+
4
+ /**
5
+ * AST node representing an operation request body.
6
+ *
7
+ * Body schemas live exclusively inside the `content` array (one entry per content type),
8
+ * mirroring {@link ResponseNode}.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * const requestBody: RequestBodyNode = {
13
+ * kind: 'RequestBody',
14
+ * required: true,
15
+ * content: [{ kind: 'Content', contentType: 'application/json', schema: createSchema({ type: 'string' }) }],
16
+ * }
17
+ * ```
18
+ */
19
+ export type RequestBodyNode = BaseNode & {
20
+ /**
21
+ * Node kind.
22
+ */
23
+ kind: 'RequestBody'
24
+ /**
25
+ * Human-readable request body description.
26
+ */
27
+ description?: string
28
+ /**
29
+ * Whether the request body is required (`requestBody.required: true` in the spec).
30
+ * When `false` or absent, the generated `data` parameter should be optional.
31
+ */
32
+ required?: boolean
33
+ /**
34
+ * All available content type entries for this request body.
35
+ *
36
+ * When the adapter `contentType` option is set, this array contains exactly one entry for
37
+ * that content type. Otherwise it contains one entry per content type declared in the spec,
38
+ * so that plugins can generate code for every variant (e.g. separate hooks for
39
+ * `application/json` and `multipart/form-data`).
40
+ */
41
+ content?: Array<ContentNode>
42
+ }