@kubb/ast 5.0.0-beta.99 → 5.0.0
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/README.md +5 -14
- package/dist/index.cjs +88 -354
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +33 -155
- package/dist/index.js +85 -340
- package/dist/index.js.map +1 -1
- package/dist/{types-CqXMgUzC.d.ts → types-BPqinVNh.d.ts} +26 -9
- package/dist/types.d.ts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,15 +30,13 @@ Defines the node tree, visitor pattern, factory functions, and type guards used
|
|
|
30
30
|
|
|
31
31
|
| Path | Contents |
|
|
32
32
|
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
33
|
-
| `@kubb/ast` | Runtime: node definitions, guards, visitor, macro engine,
|
|
33
|
+
| `@kubb/ast` | Runtime: node definitions, guards, visitor, macro engine, constants |
|
|
34
34
|
| `ast.factory` (via `@kubb/ast`) | Node constructors (`createSchema`, `createFile`, and friends), the `ts.factory` analogue |
|
|
35
35
|
| `@kubb/ast/types` | Types only: all node interfaces, type aliases, visitor types |
|
|
36
36
|
| `kubb/kit` | Re-exports the `ast` and `factory` namespaces, the way most Kubb code reaches the AST without a direct dependency |
|
|
37
37
|
|
|
38
38
|
`@kubb/ast` is an internal library. Inside the Kubb ecosystem the whole surface travels on the `ast` namespace from `kubb/kit`, so plugins and generators reach it there instead of depending on this package directly. The examples below import from `@kubb/ast` for clarity; through `kubb/kit` the same calls read as `ast.walk`, `ast.factory.createSchema`, and so on.
|
|
39
39
|
|
|
40
|
-
The macro presets (`macroDiscriminatorEnum`, `macroSimplifyUnion`, `macroEnumName`) and the string, identifier, and ref helpers live on the root `@kubb/ast` export. They no longer ship as separate `@kubb/ast/macros` and `@kubb/ast/utils` subpaths.
|
|
41
|
-
|
|
42
40
|
## Node tree
|
|
43
41
|
|
|
44
42
|
```
|
|
@@ -94,14 +92,7 @@ const root = createInput({
|
|
|
94
92
|
### Visitor
|
|
95
93
|
|
|
96
94
|
```ts
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
// Side effects
|
|
100
|
-
await walk(root, {
|
|
101
|
-
schema(node) {
|
|
102
|
-
console.log(node.type)
|
|
103
|
-
},
|
|
104
|
-
})
|
|
95
|
+
import { collectSync, transform } from '@kubb/ast'
|
|
105
96
|
|
|
106
97
|
// Immutable transformation
|
|
107
98
|
const updated = transform(root, {
|
|
@@ -111,7 +102,7 @@ const updated = transform(root, {
|
|
|
111
102
|
})
|
|
112
103
|
|
|
113
104
|
// Extraction
|
|
114
|
-
const types =
|
|
105
|
+
const types = collectSync<string>(root, {
|
|
115
106
|
schema(node) {
|
|
116
107
|
return node.type
|
|
117
108
|
},
|
|
@@ -135,9 +126,9 @@ function process(node: Node) {
|
|
|
135
126
|
### Refs
|
|
136
127
|
|
|
137
128
|
```ts
|
|
138
|
-
import {
|
|
129
|
+
import { resolveRefName } from '@kubb/ast'
|
|
139
130
|
|
|
140
|
-
|
|
131
|
+
resolveRefName({ kind: 'Schema', type: 'ref', ref: '#/components/schemas/Pet' }) // 'Pet'
|
|
141
132
|
```
|
|
142
133
|
|
|
143
134
|
## Adding a node
|
package/dist/index.cjs
CHANGED
|
@@ -25,7 +25,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
25
|
}
|
|
26
26
|
return to;
|
|
27
27
|
};
|
|
28
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
29
29
|
value: mod,
|
|
30
30
|
enumerable: true
|
|
31
31
|
}) : target, mod));
|
|
@@ -363,33 +363,6 @@ const contentDef = defineNode({
|
|
|
363
363
|
*/
|
|
364
364
|
const createContent = contentDef.create;
|
|
365
365
|
//#endregion
|
|
366
|
-
//#region ../../internals/utils/src/casing.ts
|
|
367
|
-
/**
|
|
368
|
-
* Shared implementation for camelCase and PascalCase conversion.
|
|
369
|
-
* Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
|
|
370
|
-
* and capitalizes each word according to `pascal`.
|
|
371
|
-
*
|
|
372
|
-
* When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
|
|
373
|
-
*/
|
|
374
|
-
function toCamelOrPascal(text, pascal) {
|
|
375
|
-
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) => {
|
|
376
|
-
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
377
|
-
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
378
|
-
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Converts `text` to PascalCase.
|
|
382
|
-
*
|
|
383
|
-
* @example Word boundaries
|
|
384
|
-
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
385
|
-
*
|
|
386
|
-
* @example With a suffix
|
|
387
|
-
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
388
|
-
*/
|
|
389
|
-
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
390
|
-
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
391
|
-
}
|
|
392
|
-
//#endregion
|
|
393
366
|
//#region ../../internals/utils/src/fs.ts
|
|
394
367
|
/**
|
|
395
368
|
* Strips the file extension from a path or file name.
|
|
@@ -484,6 +457,17 @@ function extractStringsFromNodes(nodes) {
|
|
|
484
457
|
}
|
|
485
458
|
//#endregion
|
|
486
459
|
//#region src/utils/combineFileMembers.ts
|
|
460
|
+
const IDENTIFIER_RUN = /[\w$]+/g;
|
|
461
|
+
/**
|
|
462
|
+
* How many imports a file needs before indexing the source beats scanning it once per name.
|
|
463
|
+
*/
|
|
464
|
+
const INDEX_ABOVE_IMPORTS = 128;
|
|
465
|
+
/**
|
|
466
|
+
* Every unbroken run of identifier characters in the source.
|
|
467
|
+
*/
|
|
468
|
+
function collectIdentifiers(source) {
|
|
469
|
+
return new Set(source.match(IDENTIFIER_RUN));
|
|
470
|
+
}
|
|
487
471
|
function sourceKey(source) {
|
|
488
472
|
return `${source.name ?? extractStringsFromNodes(source.nodes)}:${source.isExportable ?? false}:${source.isTypeOnly ?? false}`;
|
|
489
473
|
}
|
|
@@ -578,7 +562,8 @@ function combineExports(exports) {
|
|
|
578
562
|
*/
|
|
579
563
|
function combineImports(imports, exports, source) {
|
|
580
564
|
const exportedNames = new Set(exports.flatMap((e) => Array.isArray(e.name) ? e.name : e.name ? [e.name] : []));
|
|
581
|
-
const
|
|
565
|
+
const identifiers = source && imports.length > INDEX_ABOVE_IMPORTS ? collectIdentifiers(source) : null;
|
|
566
|
+
const isUsed = (importName) => !source || identifiers?.has(importName) || source.includes(importName) || exportedNames.has(importName);
|
|
582
567
|
const importNameMemo = /* @__PURE__ */ new Map();
|
|
583
568
|
const canonicalizeName = (n) => {
|
|
584
569
|
if (typeof n === "string") return n;
|
|
@@ -1097,9 +1082,9 @@ function* getChildren(node, recurse) {
|
|
|
1097
1082
|
* context. The result is a replacement node, a collected value, or `undefined`
|
|
1098
1083
|
* when no callback is registered for the kind.
|
|
1099
1084
|
*
|
|
1100
|
-
* Shared by `transform` and `
|
|
1085
|
+
* Shared by `transform` and `collect` so node-kind dispatch lives in one place.
|
|
1101
1086
|
* `TResult` is the caller's expected return: the same node type for `transform`,
|
|
1102
|
-
* the collected value type for `
|
|
1087
|
+
* the collected value type for `collect`.
|
|
1103
1088
|
*/
|
|
1104
1089
|
function applyVisitor(node, visitor, parent) {
|
|
1105
1090
|
const key = VISITOR_KEY_BY_KIND[node.kind];
|
|
@@ -1157,12 +1142,12 @@ function transformChildren(node, visitor, recurse) {
|
|
|
1157
1142
|
}
|
|
1158
1143
|
/**
|
|
1159
1144
|
* Lazy depth-first collection pass. Yields every non-null value returned by
|
|
1160
|
-
* the visitor callbacks. Use `
|
|
1145
|
+
* the visitor callbacks. Use `collectSync` for the eager array form.
|
|
1161
1146
|
*
|
|
1162
1147
|
* @example Collect every operationId
|
|
1163
1148
|
* ```ts
|
|
1164
1149
|
* const ids: string[] = []
|
|
1165
|
-
* for (const id of
|
|
1150
|
+
* for (const id of collect<string>(root, {
|
|
1166
1151
|
* operation(node) {
|
|
1167
1152
|
* return node.operationId
|
|
1168
1153
|
* },
|
|
@@ -1171,7 +1156,7 @@ function transformChildren(node, visitor, recurse) {
|
|
|
1171
1156
|
* }
|
|
1172
1157
|
* ```
|
|
1173
1158
|
*/
|
|
1174
|
-
function*
|
|
1159
|
+
function* collect(node, options) {
|
|
1175
1160
|
const { depth, parent, ...visitor } = options;
|
|
1176
1161
|
yield* collectNode(node, visitor, (depth ?? visitorDepths.deep) === visitorDepths.deep, parent);
|
|
1177
1162
|
}
|
|
@@ -1186,15 +1171,15 @@ function* collectNode(node, visitor, recurse, parent) {
|
|
|
1186
1171
|
*
|
|
1187
1172
|
* @example Collect every operationId
|
|
1188
1173
|
* ```ts
|
|
1189
|
-
* const ids =
|
|
1174
|
+
* const ids = collectSync<string>(root, {
|
|
1190
1175
|
* operation(node) {
|
|
1191
1176
|
* return node.operationId
|
|
1192
1177
|
* },
|
|
1193
1178
|
* })
|
|
1194
1179
|
* ```
|
|
1195
1180
|
*/
|
|
1196
|
-
function
|
|
1197
|
-
return Array.from(
|
|
1181
|
+
function collectSync(node, options) {
|
|
1182
|
+
return Array.from(collect(node, options));
|
|
1198
1183
|
}
|
|
1199
1184
|
//#endregion
|
|
1200
1185
|
//#region src/defineMacro.ts
|
|
@@ -1234,7 +1219,7 @@ function chain({ macros, key, node, context }) {
|
|
|
1234
1219
|
for (const macro of macros) {
|
|
1235
1220
|
const callback = macro[key];
|
|
1236
1221
|
if (!callback) continue;
|
|
1237
|
-
if (macro.
|
|
1222
|
+
if (macro.match && !macro.match(current)) continue;
|
|
1238
1223
|
const next = callback(current, context);
|
|
1239
1224
|
if (next != null) current = next;
|
|
1240
1225
|
}
|
|
@@ -1361,54 +1346,7 @@ function createPrinter(build) {
|
|
|
1361
1346
|
};
|
|
1362
1347
|
}
|
|
1363
1348
|
//#endregion
|
|
1364
|
-
//#region src/utils/mergeAdjacentSchemas.ts
|
|
1365
|
-
/**
|
|
1366
|
-
* Merges a run of adjacent anonymous object members into one. Named or non-object members break the
|
|
1367
|
-
* run and pass through unchanged. The merge follows member order, so callers control which members
|
|
1368
|
-
* combine by where they place them in the sequence.
|
|
1369
|
-
*
|
|
1370
|
-
* @example
|
|
1371
|
-
* ```ts
|
|
1372
|
-
* const merged = [...mergeAdjacentObjectsLazy([objectA, objectB])]
|
|
1373
|
-
* ```
|
|
1374
|
-
*/
|
|
1375
|
-
function* mergeAdjacentObjectsLazy(members) {
|
|
1376
|
-
let acc;
|
|
1377
|
-
for (const member of members) {
|
|
1378
|
-
const objectMember = narrowSchema(member, "object");
|
|
1379
|
-
if (objectMember && !objectMember.name && acc !== void 0) {
|
|
1380
|
-
const accObject = narrowSchema(acc, "object");
|
|
1381
|
-
if (accObject && !accObject.name) {
|
|
1382
|
-
acc = createSchema({
|
|
1383
|
-
...accObject,
|
|
1384
|
-
properties: [...accObject.properties ?? [], ...objectMember.properties ?? []]
|
|
1385
|
-
});
|
|
1386
|
-
continue;
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
if (acc !== void 0) yield acc;
|
|
1390
|
-
acc = member;
|
|
1391
|
-
}
|
|
1392
|
-
if (acc !== void 0) yield acc;
|
|
1393
|
-
}
|
|
1394
|
-
//#endregion
|
|
1395
1349
|
//#region src/utils/refs.ts
|
|
1396
|
-
const plainStringTypes = /* @__PURE__ */ new Set([
|
|
1397
|
-
"string",
|
|
1398
|
-
"uuid",
|
|
1399
|
-
"email",
|
|
1400
|
-
"url",
|
|
1401
|
-
"datetime"
|
|
1402
|
-
]);
|
|
1403
|
-
/**
|
|
1404
|
-
* Returns the last path segment of a reference string.
|
|
1405
|
-
*
|
|
1406
|
-
* @example
|
|
1407
|
-
* `extractRefName('#/components/schemas/Pet') // 'Pet'`
|
|
1408
|
-
*/
|
|
1409
|
-
function extractRefName(ref) {
|
|
1410
|
-
return ref.split("/").at(-1) ?? ref;
|
|
1411
|
-
}
|
|
1412
1350
|
/**
|
|
1413
1351
|
* Resolves the emitted name of the schema a ref node points at. Prefers `targetName` (set when
|
|
1414
1352
|
* the referenced schema was renamed, e.g. to break a collision), then the last segment of `ref`,
|
|
@@ -1425,73 +1363,9 @@ function extractRefName(ref) {
|
|
|
1425
1363
|
function resolveRefName(node) {
|
|
1426
1364
|
if (!node || node.type !== "ref") return null;
|
|
1427
1365
|
if (node.targetName) return node.targetName;
|
|
1428
|
-
if (node.ref) return
|
|
1366
|
+
if (node.ref) return node.ref.split("/").at(-1) ?? node.ref;
|
|
1429
1367
|
return node.name ?? node.schema?.name ?? null;
|
|
1430
1368
|
}
|
|
1431
|
-
/**
|
|
1432
|
-
* Builds a PascalCase child schema name by joining a parent name and property name.
|
|
1433
|
-
* Returns `null` when there is no parent to nest under.
|
|
1434
|
-
*
|
|
1435
|
-
* @example Nested under a parent
|
|
1436
|
-
* `childName('Order', 'shipping_address') // 'OrderShippingAddress'`
|
|
1437
|
-
*
|
|
1438
|
-
* @example No parent
|
|
1439
|
-
* `childName(undefined, 'params') // null`
|
|
1440
|
-
*/
|
|
1441
|
-
function childName(parentName, propName) {
|
|
1442
|
-
return parentName ? pascalCase([parentName, propName].join(" ")) : null;
|
|
1443
|
-
}
|
|
1444
|
-
/**
|
|
1445
|
-
* Builds a PascalCase enum name from the parent name, property name, and a suffix, skipping any
|
|
1446
|
-
* empty parts.
|
|
1447
|
-
*
|
|
1448
|
-
* @example
|
|
1449
|
-
* `enumPropName('Order', 'status', 'enum') // 'OrderStatusEnum'`
|
|
1450
|
-
*/
|
|
1451
|
-
function enumPropName(parentName, propName, enumSuffix) {
|
|
1452
|
-
return pascalCase([
|
|
1453
|
-
parentName,
|
|
1454
|
-
propName,
|
|
1455
|
-
enumSuffix
|
|
1456
|
-
].filter(Boolean).join(" "));
|
|
1457
|
-
}
|
|
1458
|
-
/**
|
|
1459
|
-
* Merges a ref node with its resolved schema, giving usage-site fields precedence.
|
|
1460
|
-
*
|
|
1461
|
-
* Every field set on the ref node except `kind`, `type`, `name`, `ref`, and `schema` overrides the
|
|
1462
|
-
* same field in the resolved `node.schema` (for example `description`, `nullable`, `readOnly`,
|
|
1463
|
-
* `deprecated`). Fields left `undefined` on the ref do not shadow the resolved schema. Non-ref
|
|
1464
|
-
* nodes and refs without a resolved `schema` are returned unchanged.
|
|
1465
|
-
*
|
|
1466
|
-
* @example
|
|
1467
|
-
* ```ts
|
|
1468
|
-
* const ref = createSchema({ type: 'ref', ref: '#/components/schemas/Pet', description: 'A cute pet' })
|
|
1469
|
-
* const merged = syncSchemaRef(ref) // merges with resolved Pet schema
|
|
1470
|
-
* ```
|
|
1471
|
-
*/
|
|
1472
|
-
function syncSchemaRef(node) {
|
|
1473
|
-
const ref = narrowSchema(node, "ref");
|
|
1474
|
-
if (!ref) return node;
|
|
1475
|
-
if (!ref.schema) return node;
|
|
1476
|
-
const { kind: _kind, type: _type, name: _name, ref: _ref, schema: _schema, ...overrides } = ref;
|
|
1477
|
-
const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, v]) => v !== void 0));
|
|
1478
|
-
return createSchema({
|
|
1479
|
-
...ref.schema,
|
|
1480
|
-
...definedOverrides
|
|
1481
|
-
});
|
|
1482
|
-
}
|
|
1483
|
-
/**
|
|
1484
|
-
* Returns `true` when a schema emits as a plain `string` type.
|
|
1485
|
-
*
|
|
1486
|
-
* Covers `string`, `uuid`, `email`, `url`, and `datetime` types. For `date` and `time`
|
|
1487
|
-
* types, returns `true` only when `representation` is `'string'` rather than `'date'`.
|
|
1488
|
-
*/
|
|
1489
|
-
function isStringType(node) {
|
|
1490
|
-
if (plainStringTypes.has(node.type)) return true;
|
|
1491
|
-
const temporal = narrowSchema(node, "date") ?? narrowSchema(node, "time");
|
|
1492
|
-
if (temporal) return temporal.representation !== "date";
|
|
1493
|
-
return false;
|
|
1494
|
-
}
|
|
1495
1369
|
//#endregion
|
|
1496
1370
|
//#region src/utils/schemaGraph.ts
|
|
1497
1371
|
/**
|
|
@@ -1499,7 +1373,7 @@ function isStringType(node) {
|
|
|
1499
1373
|
*/
|
|
1500
1374
|
const collectSchemaRefs = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1501
1375
|
const refs = /* @__PURE__ */ new Set();
|
|
1502
|
-
|
|
1376
|
+
collectSync(node, { schema(child) {
|
|
1503
1377
|
if (child.type === "ref") {
|
|
1504
1378
|
const name = resolveRefName(child);
|
|
1505
1379
|
if (name) refs.add(name);
|
|
@@ -1532,6 +1406,36 @@ function collectReferencedSchemaNames(node, out = /* @__PURE__ */ new Set()) {
|
|
|
1532
1406
|
for (const name of collectSchemaRefs(node)) out.add(name);
|
|
1533
1407
|
return out;
|
|
1534
1408
|
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Collects the de-duplicated target names of every pointer-carrying ref in a node's subtree, in
|
|
1411
|
+
* first-occurrence order. The walk is memoized by node identity, so the subtree is scanned once and
|
|
1412
|
+
* `resolver.imports` reads the same result across the ts, zod, and faker plugins instead of
|
|
1413
|
+
* re-scanning the same schema per plugin.
|
|
1414
|
+
*
|
|
1415
|
+
* Only refs that carry a `$ref` pointer count, so a synthesized ref pointing at a sibling in the
|
|
1416
|
+
* same file (a union member created by name) is left out. That leaves exactly the set
|
|
1417
|
+
* `resolver.imports` emits. This is the ordered, import-facing counterpart to
|
|
1418
|
+
* {@link collectReferencedSchemaNames}, which returns an unordered set for graph analysis.
|
|
1419
|
+
*
|
|
1420
|
+
* @example
|
|
1421
|
+
* ```ts
|
|
1422
|
+
* collectImportedRefNames(petSchema)
|
|
1423
|
+
* // ['Category', 'Tag']
|
|
1424
|
+
* ```
|
|
1425
|
+
*/
|
|
1426
|
+
const collectImportedRefNames = memoize(/* @__PURE__ */ new WeakMap(), (node) => {
|
|
1427
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1428
|
+
const names = [];
|
|
1429
|
+
collectSync(node, { schema(child) {
|
|
1430
|
+
if (child.type !== "ref" || !child.ref) return;
|
|
1431
|
+
const name = resolveRefName(child);
|
|
1432
|
+
if (name && !seen.has(name)) {
|
|
1433
|
+
seen.add(name);
|
|
1434
|
+
names.push(name);
|
|
1435
|
+
}
|
|
1436
|
+
} });
|
|
1437
|
+
return names;
|
|
1438
|
+
});
|
|
1535
1439
|
function computeUsedSchemaNames(operations, schemas) {
|
|
1536
1440
|
const schemaMap = /* @__PURE__ */ new Map();
|
|
1537
1441
|
for (const schema of schemas) if (schema.name) schemaMap.set(schema.name, schema);
|
|
@@ -1544,7 +1448,7 @@ function computeUsedSchemaNames(operations, schemas) {
|
|
|
1544
1448
|
if (namedSchema) visitSchema(namedSchema);
|
|
1545
1449
|
}
|
|
1546
1450
|
}
|
|
1547
|
-
for (const op of operations) for (const schema of
|
|
1451
|
+
for (const op of operations) for (const schema of collect(op, {
|
|
1548
1452
|
depth: "shallow",
|
|
1549
1453
|
schema: (node) => node
|
|
1550
1454
|
})) visitSchema(schema);
|
|
@@ -1574,12 +1478,24 @@ function collectUsedSchemaNames(operations, schemas) {
|
|
|
1574
1478
|
return computeUsedSchemaNames(operations, schemas);
|
|
1575
1479
|
}
|
|
1576
1480
|
const EMPTY_CIRCULAR_SET = /* @__PURE__ */ new Set();
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1481
|
+
/**
|
|
1482
|
+
* Finds every schema that takes part in a circular dependency chain in a schema dependency graph
|
|
1483
|
+
* that maps each schema name to the names it references directly.
|
|
1484
|
+
*
|
|
1485
|
+
* Use this when the graph was already collected during another pass (e.g. the adapter's convert
|
|
1486
|
+
* walk), so the schema nodes are not swept a second time. `findCircularSchemas` builds the graph
|
|
1487
|
+
* from schema nodes and delegates here.
|
|
1488
|
+
*
|
|
1489
|
+
* @example
|
|
1490
|
+
* ```ts
|
|
1491
|
+
* const graph = new Map([
|
|
1492
|
+
* ['Pet', new Set(['Category'])],
|
|
1493
|
+
* ['Category', new Set(['Pet'])],
|
|
1494
|
+
* ])
|
|
1495
|
+
* findCircularSchemasFromGraph(graph) // Set { 'Pet', 'Category' }
|
|
1496
|
+
* ```
|
|
1497
|
+
*/
|
|
1498
|
+
function findCircularSchemasFromGraph(graph) {
|
|
1583
1499
|
const circular = /* @__PURE__ */ new Set();
|
|
1584
1500
|
for (const start of graph.keys()) {
|
|
1585
1501
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -1597,6 +1513,14 @@ const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas)
|
|
|
1597
1513
|
}
|
|
1598
1514
|
}
|
|
1599
1515
|
return circular;
|
|
1516
|
+
}
|
|
1517
|
+
const findCircularSchemasMemo = memoize(/* @__PURE__ */ new WeakMap(), (schemas) => {
|
|
1518
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1519
|
+
for (const schema of schemas) {
|
|
1520
|
+
if (!schema.name) continue;
|
|
1521
|
+
graph.set(schema.name, collectReferencedSchemaNames(schema));
|
|
1522
|
+
}
|
|
1523
|
+
return findCircularSchemasFromGraph(graph);
|
|
1600
1524
|
});
|
|
1601
1525
|
/**
|
|
1602
1526
|
* Finds every schema that takes part in a circular dependency chain, including direct self-loops.
|
|
@@ -1611,174 +1535,6 @@ function findCircularSchemas(schemas) {
|
|
|
1611
1535
|
if (schemas.length === 0) return EMPTY_CIRCULAR_SET;
|
|
1612
1536
|
return findCircularSchemasMemo(schemas);
|
|
1613
1537
|
}
|
|
1614
|
-
/**
|
|
1615
|
-
* Returns `true` when a schema, or anything nested inside it, references a circular schema.
|
|
1616
|
-
*
|
|
1617
|
-
* Pass `excludeName` to skip refs to a specific schema, which helps when self-references are handled
|
|
1618
|
-
* on their own. Pair it with `findCircularSchemas()` to decide where lazy wrappers go.
|
|
1619
|
-
*
|
|
1620
|
-
* @note Stops at the first matching circular ref.
|
|
1621
|
-
*/
|
|
1622
|
-
function containsCircularRef(node, { circularSchemas, excludeName }) {
|
|
1623
|
-
if (!node || circularSchemas.size === 0) return false;
|
|
1624
|
-
for (const _ of collectLazy(node, { schema(child) {
|
|
1625
|
-
if (child.type !== "ref") return null;
|
|
1626
|
-
const name = resolveRefName(child);
|
|
1627
|
-
return name && name !== excludeName && circularSchemas.has(name) ? true : null;
|
|
1628
|
-
} })) return true;
|
|
1629
|
-
return false;
|
|
1630
|
-
}
|
|
1631
|
-
//#endregion
|
|
1632
|
-
//#region src/macros/macroDiscriminatorEnum.ts
|
|
1633
|
-
/**
|
|
1634
|
-
* Builds a macro that replaces a discriminator property's schema with a string enum of the given
|
|
1635
|
-
* values. Object schemas that lack the property are returned unchanged.
|
|
1636
|
-
*
|
|
1637
|
-
* @example
|
|
1638
|
-
* ```ts
|
|
1639
|
-
* const macro = macroDiscriminatorEnum({ propertyName: 'type', values: ['dog', 'cat'] })
|
|
1640
|
-
* const next = applyMacros(objectSchema, [macro], { depth: 'shallow' })
|
|
1641
|
-
* ```
|
|
1642
|
-
*/
|
|
1643
|
-
function macroDiscriminatorEnum({ propertyName, values, enumName }) {
|
|
1644
|
-
return defineMacro({
|
|
1645
|
-
name: "discriminator-enum",
|
|
1646
|
-
schema(node) {
|
|
1647
|
-
const objectNode = narrowSchema(node, "object");
|
|
1648
|
-
if (!objectNode?.properties?.length) return void 0;
|
|
1649
|
-
if (!objectNode.properties.some((prop) => prop.name === propertyName)) return void 0;
|
|
1650
|
-
return createSchema({
|
|
1651
|
-
...objectNode,
|
|
1652
|
-
properties: objectNode.properties.map((prop) => {
|
|
1653
|
-
if (prop.name !== propertyName) return prop;
|
|
1654
|
-
return createProperty({
|
|
1655
|
-
...prop,
|
|
1656
|
-
schema: createSchema({
|
|
1657
|
-
type: "enum",
|
|
1658
|
-
primitive: "string",
|
|
1659
|
-
enumValues: values,
|
|
1660
|
-
name: enumName,
|
|
1661
|
-
readOnly: prop.schema.readOnly,
|
|
1662
|
-
writeOnly: prop.schema.writeOnly
|
|
1663
|
-
})
|
|
1664
|
-
});
|
|
1665
|
-
})
|
|
1666
|
-
});
|
|
1667
|
-
}
|
|
1668
|
-
});
|
|
1669
|
-
}
|
|
1670
|
-
//#endregion
|
|
1671
|
-
//#region src/macros/macroEnumName.ts
|
|
1672
|
-
/**
|
|
1673
|
-
* Builds a macro that names an inline enum schema from its parent and property name. Boolean enums
|
|
1674
|
-
* are left anonymous. Non-enum nodes are returned unchanged.
|
|
1675
|
-
*
|
|
1676
|
-
* @example
|
|
1677
|
-
* ```ts
|
|
1678
|
-
* const macro = macroEnumName({ parentName: 'Pet', propName: 'status', enumSuffix: 'enum' })
|
|
1679
|
-
* const named = applyMacros(propSchema, [macro], { depth: 'shallow' })
|
|
1680
|
-
* ```
|
|
1681
|
-
*/
|
|
1682
|
-
function macroEnumName({ parentName, propName, enumSuffix }) {
|
|
1683
|
-
return defineMacro({
|
|
1684
|
-
name: "enum-name",
|
|
1685
|
-
schema(node) {
|
|
1686
|
-
const enumNode = narrowSchema(node, "enum");
|
|
1687
|
-
if (enumNode?.primitive === "boolean") return {
|
|
1688
|
-
...node,
|
|
1689
|
-
name: null
|
|
1690
|
-
};
|
|
1691
|
-
if (enumNode) return {
|
|
1692
|
-
...node,
|
|
1693
|
-
name: enumPropName(parentName, propName, enumSuffix)
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
});
|
|
1697
|
-
}
|
|
1698
|
-
//#endregion
|
|
1699
|
-
//#region src/macros/macroRenameSchema.ts
|
|
1700
|
-
/**
|
|
1701
|
-
* Builds a macro that renames a schema consistently: the declaration (`name`) and every ref
|
|
1702
|
-
* pointing at it (`targetName`) change together, so imports and printed references stay in
|
|
1703
|
-
* sync. Renaming only one side by hand produces imports for files that are never generated.
|
|
1704
|
-
*
|
|
1705
|
-
* @example
|
|
1706
|
-
* `const macro = macroRenameSchema({ from: 'Order', to: 'StoreOrder' })`
|
|
1707
|
-
*/
|
|
1708
|
-
function macroRenameSchema({ from, to }) {
|
|
1709
|
-
return defineMacro({
|
|
1710
|
-
name: "rename-schema",
|
|
1711
|
-
schema(node) {
|
|
1712
|
-
const refNode = narrowSchema(node, "ref");
|
|
1713
|
-
if (!refNode) return node.name === from ? {
|
|
1714
|
-
...node,
|
|
1715
|
-
name: to
|
|
1716
|
-
} : void 0;
|
|
1717
|
-
const renamesDeclaration = refNode.name === from;
|
|
1718
|
-
const renamesTarget = resolveRefName(refNode) === from;
|
|
1719
|
-
if (!renamesDeclaration && !renamesTarget) return void 0;
|
|
1720
|
-
return {
|
|
1721
|
-
...refNode,
|
|
1722
|
-
...renamesDeclaration ? { name: to } : {},
|
|
1723
|
-
...renamesTarget ? { targetName: to } : {}
|
|
1724
|
-
};
|
|
1725
|
-
}
|
|
1726
|
-
});
|
|
1727
|
-
}
|
|
1728
|
-
//#endregion
|
|
1729
|
-
//#region src/macros/macroSimplifyUnion.ts
|
|
1730
|
-
/**
|
|
1731
|
-
* Scalar primitive schema types used for union simplification and type narrowing.
|
|
1732
|
-
*/
|
|
1733
|
-
const SCALAR_PRIMITIVE_TYPES = /* @__PURE__ */ new Set([
|
|
1734
|
-
"string",
|
|
1735
|
-
"number",
|
|
1736
|
-
"integer",
|
|
1737
|
-
"bigint",
|
|
1738
|
-
"boolean"
|
|
1739
|
-
]);
|
|
1740
|
-
function isScalarPrimitive(type) {
|
|
1741
|
-
return SCALAR_PRIMITIVE_TYPES.has(type);
|
|
1742
|
-
}
|
|
1743
|
-
/**
|
|
1744
|
-
* Filters union members, dropping enum members that a broader scalar primitive already covers.
|
|
1745
|
-
*/
|
|
1746
|
-
function simplifyUnionMembers(members) {
|
|
1747
|
-
const scalarPrimitives = new Set(members.filter((member) => isScalarPrimitive(member.type)).map((m) => m.type));
|
|
1748
|
-
if (!scalarPrimitives.size) return members;
|
|
1749
|
-
return members.filter((member) => {
|
|
1750
|
-
const enumNode = narrowSchema(member, "enum");
|
|
1751
|
-
if (!enumNode) return true;
|
|
1752
|
-
const primitive = enumNode.primitive;
|
|
1753
|
-
if (!primitive) return true;
|
|
1754
|
-
if ((enumNode.namedEnumValues?.length ?? enumNode.enumValues?.length ?? 0) <= 1) return true;
|
|
1755
|
-
if (scalarPrimitives.has(primitive)) return false;
|
|
1756
|
-
if ((primitive === "integer" || primitive === "number") && (scalarPrimitives.has("integer") || scalarPrimitives.has("number"))) return false;
|
|
1757
|
-
return true;
|
|
1758
|
-
});
|
|
1759
|
-
}
|
|
1760
|
-
/**
|
|
1761
|
-
* Removes union members a broader scalar primitive already covers, such as a multi-value string enum
|
|
1762
|
-
* sitting next to a plain `string`. Single-value enums are kept.
|
|
1763
|
-
*
|
|
1764
|
-
* @example
|
|
1765
|
-
* ```ts
|
|
1766
|
-
* const next = applyMacros(unionSchema, [macroSimplifyUnion], { depth: 'shallow' })
|
|
1767
|
-
* ```
|
|
1768
|
-
*/
|
|
1769
|
-
const macroSimplifyUnion = defineMacro({
|
|
1770
|
-
name: "simplify-union",
|
|
1771
|
-
schema(node) {
|
|
1772
|
-
const unionNode = narrowSchema(node, "union");
|
|
1773
|
-
if (!unionNode?.members?.length) return void 0;
|
|
1774
|
-
const simplified = simplifyUnionMembers(unionNode.members);
|
|
1775
|
-
if (simplified.length === unionNode.members.length) return void 0;
|
|
1776
|
-
return {
|
|
1777
|
-
...unionNode,
|
|
1778
|
-
members: simplified
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
|
-
});
|
|
1782
1538
|
//#endregion
|
|
1783
1539
|
//#region src/factory.ts
|
|
1784
1540
|
var factory_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1833,37 +1589,27 @@ var exports_exports = /* @__PURE__ */ __exportAll({
|
|
|
1833
1589
|
applyMacros: () => applyMacros,
|
|
1834
1590
|
arrowFunctionDef: () => arrowFunctionDef,
|
|
1835
1591
|
breakDef: () => breakDef,
|
|
1836
|
-
childName: () => childName,
|
|
1837
1592
|
collect: () => collect,
|
|
1593
|
+
collectImportedRefNames: () => collectImportedRefNames,
|
|
1594
|
+
collectSync: () => collectSync,
|
|
1838
1595
|
collectUsedSchemaNames: () => collectUsedSchemaNames,
|
|
1839
|
-
combineExports: () => combineExports,
|
|
1840
|
-
combineImports: () => combineImports,
|
|
1841
|
-
combineSources: () => combineSources,
|
|
1842
1596
|
composeMacros: () => composeMacros,
|
|
1843
1597
|
constDef: () => constDef,
|
|
1844
|
-
containsCircularRef: () => containsCircularRef,
|
|
1845
1598
|
contentDef: () => contentDef,
|
|
1846
1599
|
createPrinter: () => createPrinter,
|
|
1847
1600
|
defineMacro: () => defineMacro,
|
|
1848
1601
|
defineNode: () => defineNode,
|
|
1849
|
-
enumPropName: () => enumPropName,
|
|
1850
1602
|
exportDef: () => exportDef,
|
|
1851
|
-
extractRefName: () => extractRefName,
|
|
1852
1603
|
extractStringsFromNodes: () => extractStringsFromNodes,
|
|
1853
1604
|
factory: () => factory_exports,
|
|
1854
1605
|
fileDef: () => fileDef,
|
|
1855
1606
|
findCircularSchemas: () => findCircularSchemas,
|
|
1607
|
+
findCircularSchemasFromGraph: () => findCircularSchemasFromGraph,
|
|
1856
1608
|
functionDef: () => functionDef,
|
|
1857
1609
|
importDef: () => importDef,
|
|
1858
1610
|
inputDef: () => inputDef,
|
|
1859
1611
|
isHttpOperationNode: () => isHttpOperationNode,
|
|
1860
|
-
isStringType: () => isStringType,
|
|
1861
1612
|
jsxDef: () => jsxDef,
|
|
1862
|
-
macroDiscriminatorEnum: () => macroDiscriminatorEnum,
|
|
1863
|
-
macroEnumName: () => macroEnumName,
|
|
1864
|
-
macroRenameSchema: () => macroRenameSchema,
|
|
1865
|
-
macroSimplifyUnion: () => macroSimplifyUnion,
|
|
1866
|
-
mergeAdjacentObjectsLazy: () => mergeAdjacentObjectsLazy,
|
|
1867
1613
|
narrowSchema: () => narrowSchema,
|
|
1868
1614
|
nodeDefs: () => nodeDefs,
|
|
1869
1615
|
operationDef: () => operationDef,
|
|
@@ -1877,7 +1623,6 @@ var exports_exports = /* @__PURE__ */ __exportAll({
|
|
|
1877
1623
|
schemaDef: () => schemaDef,
|
|
1878
1624
|
schemaTypes: () => schemaTypes,
|
|
1879
1625
|
sourceDef: () => sourceDef,
|
|
1880
|
-
syncSchemaRef: () => syncSchemaRef,
|
|
1881
1626
|
textDef: () => textDef,
|
|
1882
1627
|
transform: () => transform,
|
|
1883
1628
|
typeDef: () => typeDef
|
|
@@ -1892,22 +1637,17 @@ Object.defineProperty(exports, "ast", {
|
|
|
1892
1637
|
}
|
|
1893
1638
|
});
|
|
1894
1639
|
exports.breakDef = breakDef;
|
|
1895
|
-
exports.childName = childName;
|
|
1896
1640
|
exports.collect = collect;
|
|
1641
|
+
exports.collectImportedRefNames = collectImportedRefNames;
|
|
1642
|
+
exports.collectSync = collectSync;
|
|
1897
1643
|
exports.collectUsedSchemaNames = collectUsedSchemaNames;
|
|
1898
|
-
exports.combineExports = combineExports;
|
|
1899
|
-
exports.combineImports = combineImports;
|
|
1900
|
-
exports.combineSources = combineSources;
|
|
1901
1644
|
exports.composeMacros = composeMacros;
|
|
1902
1645
|
exports.constDef = constDef;
|
|
1903
|
-
exports.containsCircularRef = containsCircularRef;
|
|
1904
1646
|
exports.contentDef = contentDef;
|
|
1905
1647
|
exports.createPrinter = createPrinter;
|
|
1906
1648
|
exports.defineMacro = defineMacro;
|
|
1907
1649
|
exports.defineNode = defineNode;
|
|
1908
|
-
exports.enumPropName = enumPropName;
|
|
1909
1650
|
exports.exportDef = exportDef;
|
|
1910
|
-
exports.extractRefName = extractRefName;
|
|
1911
1651
|
exports.extractStringsFromNodes = extractStringsFromNodes;
|
|
1912
1652
|
Object.defineProperty(exports, "factory", {
|
|
1913
1653
|
enumerable: true,
|
|
@@ -1917,17 +1657,12 @@ Object.defineProperty(exports, "factory", {
|
|
|
1917
1657
|
});
|
|
1918
1658
|
exports.fileDef = fileDef;
|
|
1919
1659
|
exports.findCircularSchemas = findCircularSchemas;
|
|
1660
|
+
exports.findCircularSchemasFromGraph = findCircularSchemasFromGraph;
|
|
1920
1661
|
exports.functionDef = functionDef;
|
|
1921
1662
|
exports.importDef = importDef;
|
|
1922
1663
|
exports.inputDef = inputDef;
|
|
1923
1664
|
exports.isHttpOperationNode = isHttpOperationNode;
|
|
1924
|
-
exports.isStringType = isStringType;
|
|
1925
1665
|
exports.jsxDef = jsxDef;
|
|
1926
|
-
exports.macroDiscriminatorEnum = macroDiscriminatorEnum;
|
|
1927
|
-
exports.macroEnumName = macroEnumName;
|
|
1928
|
-
exports.macroRenameSchema = macroRenameSchema;
|
|
1929
|
-
exports.macroSimplifyUnion = macroSimplifyUnion;
|
|
1930
|
-
exports.mergeAdjacentObjectsLazy = mergeAdjacentObjectsLazy;
|
|
1931
1666
|
exports.narrowSchema = narrowSchema;
|
|
1932
1667
|
exports.nodeDefs = nodeDefs;
|
|
1933
1668
|
exports.operationDef = operationDef;
|
|
@@ -1941,7 +1676,6 @@ exports.responseDef = responseDef;
|
|
|
1941
1676
|
exports.schemaDef = schemaDef;
|
|
1942
1677
|
exports.schemaTypes = schemaTypes;
|
|
1943
1678
|
exports.sourceDef = sourceDef;
|
|
1944
|
-
exports.syncSchemaRef = syncSchemaRef;
|
|
1945
1679
|
exports.textDef = textDef;
|
|
1946
1680
|
exports.transform = transform;
|
|
1947
1681
|
exports.typeDef = typeDef;
|