@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.
- package/dist/index.cjs +42 -96
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -26
- package/dist/index.js +41 -93
- package/dist/index.js.map +1 -1
- package/dist/{types-BYujEIxF.d.ts → types-BL7RpQAE.d.ts} +121 -165
- package/dist/types.d.ts +2 -2
- package/dist/{utils-BIcKgbbc.js → utils-0p8ZO287.js} +15 -71
- package/dist/utils-0p8ZO287.js.map +1 -0
- package/dist/{utils-CMRZrT-w.cjs → utils-cdQ6Pzyi.cjs} +14 -82
- package/dist/utils-cdQ6Pzyi.cjs.map +1 -0
- package/dist/utils.cjs +1 -2
- package/dist/utils.d.ts +1 -14
- package/dist/utils.js +2 -2
- package/package.json +1 -1
- package/src/dialect.ts +23 -28
- package/src/factory.ts +2 -3
- package/src/index.ts +1 -3
- package/src/mocks.ts +1 -1
- package/src/nodes/index.ts +6 -4
- package/src/nodes/{root.ts → input.ts} +16 -39
- package/src/nodes/operation.ts +1 -41
- package/src/nodes/requestBody.ts +42 -0
- package/src/signature.ts +23 -48
- package/src/types.ts +0 -2
- package/src/utils/index.ts +3 -1
- package/dist/utils-BIcKgbbc.js.map +0 -1
- package/dist/utils-CMRZrT-w.cjs.map +0 -1
- package/src/dispatch.ts +0 -53
- package/src/resolvers.ts +0 -31
|
@@ -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
|
-
|
|
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')
|
|
224
|
-
*
|
|
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, {
|
|
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')
|
|
239
|
-
*
|
|
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, {
|
|
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-
|
|
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-
|
|
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,
|
|
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 {
|
|
2
|
-
export { buildJSDoc, buildList, buildObject, childName,
|
|
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.
|
|
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
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Identifies the dialect in logs and diagnostics.
|
|
9
|
+
*/
|
|
24
10
|
name: string
|
|
25
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Whether the schema is nullable.
|
|
13
|
+
*/
|
|
26
14
|
isNullable: (schema?: TSchema) => boolean
|
|
27
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Whether the value is a `$ref` pointer.
|
|
17
|
+
*/
|
|
28
18
|
isReference: (value?: unknown) => value is TRef
|
|
29
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Whether the schema carries a discriminator for polymorphism.
|
|
21
|
+
*/
|
|
30
22
|
isDiscriminator: (value?: unknown) => value is TDiscriminated
|
|
31
|
-
/**
|
|
23
|
+
/**
|
|
24
|
+
* Whether the schema is binary data, converted to a `blob` node.
|
|
25
|
+
*/
|
|
32
26
|
isBinary: (schema: TSchema) => boolean
|
|
33
|
-
/**
|
|
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
|
-
*
|
|
39
|
-
*
|
|
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
|
|
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):
|
|
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 {
|
|
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/
|
|
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.
|
package/src/nodes/index.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
|
111
|
-
kind: 'Input'
|
|
90
|
+
export type InputNode<Stream extends boolean = false> = BaseNode & {
|
|
112
91
|
/**
|
|
113
|
-
*
|
|
114
|
-
* multiple plugins can iterate independently without sharing state.
|
|
92
|
+
* Node kind.
|
|
115
93
|
*/
|
|
116
|
-
|
|
94
|
+
kind: 'Input'
|
|
117
95
|
/**
|
|
118
|
-
*
|
|
119
|
-
* multiple plugins can iterate independently without sharing state.
|
|
96
|
+
* All schema nodes in the document.
|
|
120
97
|
*/
|
|
121
|
-
|
|
98
|
+
schemas: Streamable<SchemaNode, Stream>
|
|
122
99
|
/**
|
|
123
|
-
*
|
|
100
|
+
* All operation nodes in the document.
|
|
124
101
|
*/
|
|
125
|
-
|
|
126
|
-
}
|
|
102
|
+
operations: Streamable<OperationNode, Stream>
|
|
103
|
+
} & (Stream extends true ? { meta?: InputMeta } : { meta: InputMeta })
|
package/src/nodes/operation.ts
CHANGED
|
@@ -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
|
+
}
|