@kubb/ast 5.0.0-beta.30 → 5.0.0-beta.32
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 +452 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +220 -101
- package/dist/index.js +450 -56
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dedupe.ts +200 -0
- package/src/guards.ts +1 -46
- package/src/index.ts +2 -0
- package/src/refs.ts +0 -49
- package/src/signature.ts +232 -0
- package/src/types.ts +1 -0
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./chunk
|
|
1
|
+
import "./chunk-C0LytTxp.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
//#region src/constants.ts
|
|
@@ -201,60 +201,6 @@ const mediaTypes = {
|
|
|
201
201
|
videoMp4: "video/mp4"
|
|
202
202
|
};
|
|
203
203
|
//#endregion
|
|
204
|
-
//#region src/dialect.ts
|
|
205
|
-
/**
|
|
206
|
-
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
207
|
-
* `defineParser`, it adds no runtime behavior — it pins the dialect's type for
|
|
208
|
-
* inference and gives adapter authors a discoverable anchor.
|
|
209
|
-
*
|
|
210
|
-
* @example
|
|
211
|
-
* ```ts
|
|
212
|
-
* export const oasDialect = defineSchemaDialect({
|
|
213
|
-
* name: 'oas',
|
|
214
|
-
* isNullable,
|
|
215
|
-
* isReference,
|
|
216
|
-
* isDiscriminator,
|
|
217
|
-
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
218
|
-
* resolveRef,
|
|
219
|
-
* })
|
|
220
|
-
* ```
|
|
221
|
-
*/
|
|
222
|
-
function defineSchemaDialect(dialect) {
|
|
223
|
-
return dialect;
|
|
224
|
-
}
|
|
225
|
-
//#endregion
|
|
226
|
-
//#region src/dispatch.ts
|
|
227
|
-
/**
|
|
228
|
-
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
229
|
-
*
|
|
230
|
-
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
231
|
-
* The contract an adapter follows is intentionally minimal:
|
|
232
|
-
*
|
|
233
|
-
* context → [rule.match → rule.convert] → node
|
|
234
|
-
*
|
|
235
|
-
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
236
|
-
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
237
|
-
* context type and a new rules table — the traversal here is reused unchanged.
|
|
238
|
-
*
|
|
239
|
-
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
240
|
-
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
241
|
-
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
242
|
-
* returns `null`, leaving the caller to apply its own fallback.
|
|
243
|
-
*
|
|
244
|
-
* @example
|
|
245
|
-
* ```ts
|
|
246
|
-
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
247
|
-
* ```
|
|
248
|
-
*/
|
|
249
|
-
function dispatch(rules, context) {
|
|
250
|
-
for (const rule of rules) {
|
|
251
|
-
if (!rule.match(context)) continue;
|
|
252
|
-
const node = rule.convert(context);
|
|
253
|
-
if (node !== null && node !== void 0) return node;
|
|
254
|
-
}
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
//#endregion
|
|
258
204
|
//#region ../../internals/utils/src/casing.ts
|
|
259
205
|
/**
|
|
260
206
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -2115,6 +2061,454 @@ function createJsx(value) {
|
|
|
2115
2061
|
};
|
|
2116
2062
|
}
|
|
2117
2063
|
//#endregion
|
|
2064
|
+
//#region src/signature.ts
|
|
2065
|
+
/**
|
|
2066
|
+
* The shape-affecting flags shared by every node kind: base primitive, format, and `nullable`.
|
|
2067
|
+
* Documentation and usage-slot flags (`optional`/`nullish`/`readOnly`/`writeOnly`) are
|
|
2068
|
+
* intentionally excluded — they describe the property slot, not the type.
|
|
2069
|
+
*/
|
|
2070
|
+
function flagsDescriptor(node) {
|
|
2071
|
+
return `${node.primitive ?? ""};${node.format ?? ""};${node.nullable ? 1 : 0}`;
|
|
2072
|
+
}
|
|
2073
|
+
function refTargetName(node) {
|
|
2074
|
+
if (node.ref) return extractRefName(node.ref);
|
|
2075
|
+
return node.name ?? "";
|
|
2076
|
+
}
|
|
2077
|
+
const arrayTupleFields = [
|
|
2078
|
+
{
|
|
2079
|
+
kind: "children",
|
|
2080
|
+
key: "items",
|
|
2081
|
+
prefix: "i"
|
|
2082
|
+
},
|
|
2083
|
+
{
|
|
2084
|
+
kind: "child",
|
|
2085
|
+
key: "rest",
|
|
2086
|
+
prefix: "r"
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
kind: "scalar",
|
|
2090
|
+
key: "min",
|
|
2091
|
+
prefix: "mn"
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
kind: "scalar",
|
|
2095
|
+
key: "max",
|
|
2096
|
+
prefix: "mx"
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
kind: "bool",
|
|
2100
|
+
key: "unique",
|
|
2101
|
+
prefix: "u"
|
|
2102
|
+
}
|
|
2103
|
+
];
|
|
2104
|
+
const numericFields = [
|
|
2105
|
+
{
|
|
2106
|
+
kind: "scalar",
|
|
2107
|
+
key: "min",
|
|
2108
|
+
prefix: "mn"
|
|
2109
|
+
},
|
|
2110
|
+
{
|
|
2111
|
+
kind: "scalar",
|
|
2112
|
+
key: "max",
|
|
2113
|
+
prefix: "mx"
|
|
2114
|
+
},
|
|
2115
|
+
{
|
|
2116
|
+
kind: "scalar",
|
|
2117
|
+
key: "exclusiveMinimum",
|
|
2118
|
+
prefix: "emn"
|
|
2119
|
+
},
|
|
2120
|
+
{
|
|
2121
|
+
kind: "scalar",
|
|
2122
|
+
key: "exclusiveMaximum",
|
|
2123
|
+
prefix: "emx"
|
|
2124
|
+
},
|
|
2125
|
+
{
|
|
2126
|
+
kind: "scalar",
|
|
2127
|
+
key: "multipleOf",
|
|
2128
|
+
prefix: "mo"
|
|
2129
|
+
}
|
|
2130
|
+
];
|
|
2131
|
+
const rangeFields = [{
|
|
2132
|
+
kind: "scalar",
|
|
2133
|
+
key: "min",
|
|
2134
|
+
prefix: "mn"
|
|
2135
|
+
}, {
|
|
2136
|
+
kind: "scalar",
|
|
2137
|
+
key: "max",
|
|
2138
|
+
prefix: "mx"
|
|
2139
|
+
}];
|
|
2140
|
+
/**
|
|
2141
|
+
* Maps each schema node `type` to the ordered list of shape-contributing fields.
|
|
2142
|
+
* Node types absent from this map (scalar types like boolean, null, any, etc.) fall
|
|
2143
|
+
* back to `${type}|${flags}` with no additional fields.
|
|
2144
|
+
*/
|
|
2145
|
+
const SHAPE_KEYS = {
|
|
2146
|
+
object: [
|
|
2147
|
+
{ kind: "objectProps" },
|
|
2148
|
+
{ kind: "additionalProps" },
|
|
2149
|
+
{ kind: "patternProps" },
|
|
2150
|
+
{
|
|
2151
|
+
kind: "scalar",
|
|
2152
|
+
key: "minProperties",
|
|
2153
|
+
prefix: "mn"
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
kind: "scalar",
|
|
2157
|
+
key: "maxProperties",
|
|
2158
|
+
prefix: "mx"
|
|
2159
|
+
}
|
|
2160
|
+
],
|
|
2161
|
+
array: arrayTupleFields,
|
|
2162
|
+
tuple: arrayTupleFields,
|
|
2163
|
+
union: [
|
|
2164
|
+
{
|
|
2165
|
+
kind: "scalar",
|
|
2166
|
+
key: "strategy",
|
|
2167
|
+
prefix: "s"
|
|
2168
|
+
},
|
|
2169
|
+
{
|
|
2170
|
+
kind: "scalar",
|
|
2171
|
+
key: "discriminatorPropertyName",
|
|
2172
|
+
prefix: "d"
|
|
2173
|
+
},
|
|
2174
|
+
{
|
|
2175
|
+
kind: "children",
|
|
2176
|
+
key: "members",
|
|
2177
|
+
prefix: "m"
|
|
2178
|
+
}
|
|
2179
|
+
],
|
|
2180
|
+
intersection: [{
|
|
2181
|
+
kind: "children",
|
|
2182
|
+
key: "members",
|
|
2183
|
+
prefix: "m"
|
|
2184
|
+
}],
|
|
2185
|
+
enum: [{ kind: "enumValues" }],
|
|
2186
|
+
ref: [{ kind: "refTarget" }],
|
|
2187
|
+
string: [
|
|
2188
|
+
{
|
|
2189
|
+
kind: "scalar",
|
|
2190
|
+
key: "min",
|
|
2191
|
+
prefix: "mn"
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
kind: "scalar",
|
|
2195
|
+
key: "max",
|
|
2196
|
+
prefix: "mx"
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
kind: "scalar",
|
|
2200
|
+
key: "pattern",
|
|
2201
|
+
prefix: "pt"
|
|
2202
|
+
}
|
|
2203
|
+
],
|
|
2204
|
+
number: numericFields,
|
|
2205
|
+
integer: numericFields,
|
|
2206
|
+
bigint: numericFields,
|
|
2207
|
+
url: [
|
|
2208
|
+
{
|
|
2209
|
+
kind: "scalar",
|
|
2210
|
+
key: "path",
|
|
2211
|
+
prefix: "path"
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
kind: "scalar",
|
|
2215
|
+
key: "min",
|
|
2216
|
+
prefix: "mn"
|
|
2217
|
+
},
|
|
2218
|
+
{
|
|
2219
|
+
kind: "scalar",
|
|
2220
|
+
key: "max",
|
|
2221
|
+
prefix: "mx"
|
|
2222
|
+
}
|
|
2223
|
+
],
|
|
2224
|
+
uuid: rangeFields,
|
|
2225
|
+
email: rangeFields,
|
|
2226
|
+
datetime: [{
|
|
2227
|
+
kind: "bool",
|
|
2228
|
+
key: "offset",
|
|
2229
|
+
prefix: "o"
|
|
2230
|
+
}, {
|
|
2231
|
+
kind: "bool",
|
|
2232
|
+
key: "local",
|
|
2233
|
+
prefix: "l"
|
|
2234
|
+
}],
|
|
2235
|
+
date: [{
|
|
2236
|
+
kind: "scalar",
|
|
2237
|
+
key: "representation",
|
|
2238
|
+
prefix: "rep"
|
|
2239
|
+
}],
|
|
2240
|
+
time: [{
|
|
2241
|
+
kind: "scalar",
|
|
2242
|
+
key: "representation",
|
|
2243
|
+
prefix: "rep"
|
|
2244
|
+
}]
|
|
2245
|
+
};
|
|
2246
|
+
function serializeShapeField(field, node, record) {
|
|
2247
|
+
switch (field.kind) {
|
|
2248
|
+
case "scalar": return `${field.prefix}:${record[field.key] ?? ""}`;
|
|
2249
|
+
case "bool": return `${field.prefix}:${record[field.key] ? 1 : 0}`;
|
|
2250
|
+
case "child": {
|
|
2251
|
+
const child = record[field.key];
|
|
2252
|
+
return `${field.prefix}:${child ? signatureOf(child) : ""}`;
|
|
2253
|
+
}
|
|
2254
|
+
case "children": {
|
|
2255
|
+
const children = record[field.key] ?? [];
|
|
2256
|
+
return `${field.prefix}[${children.map((c) => signatureOf(c)).join(",")}]`;
|
|
2257
|
+
}
|
|
2258
|
+
case "objectProps": return `p[${(node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema)}`).join(",")}]`;
|
|
2259
|
+
case "additionalProps": {
|
|
2260
|
+
const obj = node;
|
|
2261
|
+
if (typeof obj.additionalProperties === "boolean") return `ab:${obj.additionalProperties}`;
|
|
2262
|
+
if (obj.additionalProperties) return `as:${signatureOf(obj.additionalProperties)}`;
|
|
2263
|
+
return "";
|
|
2264
|
+
}
|
|
2265
|
+
case "patternProps": {
|
|
2266
|
+
const obj = node;
|
|
2267
|
+
return `pp[${obj.patternProperties ? Object.keys(obj.patternProperties).sort().map((key) => `${key}=${signatureOf(obj.patternProperties[key])}`).join(",") : ""}]`;
|
|
2268
|
+
}
|
|
2269
|
+
case "enumValues": {
|
|
2270
|
+
const en = node;
|
|
2271
|
+
let values = "";
|
|
2272
|
+
if (en.namedEnumValues?.length) values = en.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
|
|
2273
|
+
else if (en.enumValues?.length) values = en.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
|
|
2274
|
+
return `v[${values}]`;
|
|
2275
|
+
}
|
|
2276
|
+
case "refTarget": return `->${refTargetName(node)}`;
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
/**
|
|
2280
|
+
* Builds the local, shape-only descriptor for a node: its kind, flags, constraints, and its
|
|
2281
|
+
* children's signatures. {@link signatureOf} hashes this string; children contribute their
|
|
2282
|
+
* fixed-length signature rather than their own full descriptor, which keeps the result bounded.
|
|
2283
|
+
*/
|
|
2284
|
+
function describeShape(node) {
|
|
2285
|
+
const flags = flagsDescriptor(node);
|
|
2286
|
+
const fields = SHAPE_KEYS[node.type];
|
|
2287
|
+
if (!fields) return `${node.type}|${flags}`;
|
|
2288
|
+
const record = node;
|
|
2289
|
+
const parts = [`${node.type}|${flags}`];
|
|
2290
|
+
for (const field of fields) parts.push(serializeShapeField(field, node, record));
|
|
2291
|
+
return parts.join("|");
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Persistent hash-consing cache: `SchemaNode` → signature digest, keyed by node identity.
|
|
2295
|
+
*
|
|
2296
|
+
* A `WeakMap` so entries are released once the node is garbage-collected, and so a node hashed
|
|
2297
|
+
* during dedupe planning is not re-hashed when the same tree is rewritten during streaming
|
|
2298
|
+
* (where `schemaSignature` and `applyDedupe` would otherwise each walk it from scratch). Reuse
|
|
2299
|
+
* across calls is sound because a signature depends only on a node's content, and schema nodes
|
|
2300
|
+
* are immutable once created — transforms allocate new objects rather than mutating in place.
|
|
2301
|
+
*/
|
|
2302
|
+
const signatureCache = /* @__PURE__ */ new WeakMap();
|
|
2303
|
+
/**
|
|
2304
|
+
* Hash-consing: each node's signature is a fixed-length digest of its local shape plus its
|
|
2305
|
+
* children's digests (a Merkle hash). Children contribute their 64-char hash instead of their
|
|
2306
|
+
* full nested descriptor, so a signature stays bounded regardless of subtree depth, and the
|
|
2307
|
+
* digest is identical across calls because it depends only on content — never on traversal
|
|
2308
|
+
* order. This keeps the keys built during planning consistent with the ones recomputed later
|
|
2309
|
+
* during streaming. {@link signatureCache} memoizes node → digest across every computation.
|
|
2310
|
+
*/
|
|
2311
|
+
function signatureOf(node) {
|
|
2312
|
+
const cached = signatureCache.get(node);
|
|
2313
|
+
if (cached !== void 0) return cached;
|
|
2314
|
+
const signature = createHash("sha256").update(describeShape(node)).digest("hex");
|
|
2315
|
+
signatureCache.set(node, signature);
|
|
2316
|
+
return signature;
|
|
2317
|
+
}
|
|
2318
|
+
/**
|
|
2319
|
+
* Computes a deterministic, shape-only signature (a fixed-length content hash) for a schema node.
|
|
2320
|
+
*
|
|
2321
|
+
* Two schemas share a signature when they are structurally identical, ignoring
|
|
2322
|
+
* documentation (`name`, `title`, `description`, `example`, `default`, `deprecated`)
|
|
2323
|
+
* and usage-slot flags (`optional`, `nullish`, `readOnly`, `writeOnly`). `nullable`
|
|
2324
|
+
* is kept because it changes the produced type. `ref` nodes compare by target name,
|
|
2325
|
+
* which also keeps the algorithm terminating on circular shapes.
|
|
2326
|
+
*
|
|
2327
|
+
* @example Two enums with different descriptions share a signature
|
|
2328
|
+
* ```ts
|
|
2329
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'], description: 'x' })) ===
|
|
2330
|
+
* schemaSignature(createSchema({ type: 'enum', primitive: 'string', enumValues: ['a', 'b'] }))
|
|
2331
|
+
* ```
|
|
2332
|
+
*/
|
|
2333
|
+
function schemaSignature(node) {
|
|
2334
|
+
return signatureOf(node);
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Returns `true` when two schema nodes are structurally identical under shape-only equality.
|
|
2338
|
+
*
|
|
2339
|
+
* @example
|
|
2340
|
+
* ```ts
|
|
2341
|
+
* isSchemaEqual(a, b) // a and b produce the same TypeScript type
|
|
2342
|
+
* ```
|
|
2343
|
+
*/
|
|
2344
|
+
function isSchemaEqual(a, b) {
|
|
2345
|
+
return schemaSignature(a) === schemaSignature(b);
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/dedupe.ts
|
|
2349
|
+
/**
|
|
2350
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
2351
|
+
* usage-slot and documentation fields that are not part of the canonical type.
|
|
2352
|
+
*/
|
|
2353
|
+
function createRefNode(node, canonical) {
|
|
2354
|
+
return createSchema({
|
|
2355
|
+
type: "ref",
|
|
2356
|
+
name: canonical.name,
|
|
2357
|
+
ref: canonical.ref,
|
|
2358
|
+
optional: node.optional,
|
|
2359
|
+
nullish: node.nullish,
|
|
2360
|
+
readOnly: node.readOnly,
|
|
2361
|
+
writeOnly: node.writeOnly,
|
|
2362
|
+
deprecated: node.deprecated,
|
|
2363
|
+
description: node.description,
|
|
2364
|
+
default: node.default,
|
|
2365
|
+
example: node.example
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2368
|
+
function applyDedupe(node, canonicalBySignature, skipRootMatch = false) {
|
|
2369
|
+
if (canonicalBySignature.size === 0) return node;
|
|
2370
|
+
const root = node;
|
|
2371
|
+
return transform(node, { schema(schemaNode) {
|
|
2372
|
+
const signature = signatureOf(schemaNode);
|
|
2373
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
2374
|
+
const canonical = canonicalBySignature.get(signature);
|
|
2375
|
+
if (!canonical) return void 0;
|
|
2376
|
+
return createRefNode(schemaNode, canonical);
|
|
2377
|
+
} });
|
|
2378
|
+
}
|
|
2379
|
+
/**
|
|
2380
|
+
* Strips usage-slot flags from a hoisted definition and applies its canonical name.
|
|
2381
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
2382
|
+
*/
|
|
2383
|
+
function cleanDefinition(node, name) {
|
|
2384
|
+
return {
|
|
2385
|
+
...node,
|
|
2386
|
+
name,
|
|
2387
|
+
optional: void 0,
|
|
2388
|
+
nullish: void 0
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2392
|
+
* Scans a forest of schema and operation nodes and produces a {@link DedupePlan}.
|
|
2393
|
+
*
|
|
2394
|
+
* A shape that occurs at least `minOccurrences` times is deduplicated: if any occurrence
|
|
2395
|
+
* is a named top-level schema, that name becomes the canonical (so other top-level duplicates
|
|
2396
|
+
* and inline copies turn into references to it); otherwise a new definition is hoisted using
|
|
2397
|
+
* `nameFor`. The plan is then applied per node with {@link applyDedupe}.
|
|
2398
|
+
*
|
|
2399
|
+
* @example
|
|
2400
|
+
* ```ts
|
|
2401
|
+
* const plan = buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2402
|
+
* isCandidate: (node) => node.type === 'enum' || node.type === 'object',
|
|
2403
|
+
* nameFor: (node) => node.name ?? null,
|
|
2404
|
+
* refFor: (name) => `#/components/schemas/${name}`,
|
|
2405
|
+
* })
|
|
2406
|
+
* ```
|
|
2407
|
+
*/
|
|
2408
|
+
function buildDedupePlan(roots, options) {
|
|
2409
|
+
const { isCandidate, nameFor, refFor, minOccurrences = 2 } = options;
|
|
2410
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
2411
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2412
|
+
function record(schemaNode) {
|
|
2413
|
+
const signature = signatureOf(schemaNode);
|
|
2414
|
+
if (!isCandidate(schemaNode)) return;
|
|
2415
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
2416
|
+
const group = groups.get(signature);
|
|
2417
|
+
if (group) {
|
|
2418
|
+
group.count++;
|
|
2419
|
+
if (isTopLevel && !group.topLevelName) group.topLevelName = schemaNode.name;
|
|
2420
|
+
} else groups.set(signature, {
|
|
2421
|
+
count: 1,
|
|
2422
|
+
representative: schemaNode,
|
|
2423
|
+
topLevelName: isTopLevel ? schemaNode.name : void 0
|
|
2424
|
+
});
|
|
2425
|
+
}
|
|
2426
|
+
for (const root of roots) {
|
|
2427
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
2428
|
+
for (const schemaNode of collectLazy(root, { schema: (node) => node })) record(schemaNode);
|
|
2429
|
+
}
|
|
2430
|
+
const canonicalBySignature = /* @__PURE__ */ new Map();
|
|
2431
|
+
const pendingHoists = [];
|
|
2432
|
+
for (const [signature, group] of groups) {
|
|
2433
|
+
if (group.count < minOccurrences) continue;
|
|
2434
|
+
if (group.topLevelName) {
|
|
2435
|
+
canonicalBySignature.set(signature, {
|
|
2436
|
+
name: group.topLevelName,
|
|
2437
|
+
ref: refFor(group.topLevelName)
|
|
2438
|
+
});
|
|
2439
|
+
continue;
|
|
2440
|
+
}
|
|
2441
|
+
const name = nameFor(group.representative, signature);
|
|
2442
|
+
if (!name) continue;
|
|
2443
|
+
canonicalBySignature.set(signature, {
|
|
2444
|
+
name,
|
|
2445
|
+
ref: refFor(name)
|
|
2446
|
+
});
|
|
2447
|
+
pendingHoists.push({
|
|
2448
|
+
name,
|
|
2449
|
+
representative: group.representative
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
return {
|
|
2453
|
+
canonicalBySignature,
|
|
2454
|
+
hoisted: pendingHoists.map(({ name, representative }) => cleanDefinition(applyDedupe(representative, canonicalBySignature, true), name))
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
//#endregion
|
|
2458
|
+
//#region src/dialect.ts
|
|
2459
|
+
/**
|
|
2460
|
+
* Identity helper that types a {@link SchemaDialect} for an adapter. Like
|
|
2461
|
+
* `defineParser`, it adds no runtime behavior — it pins the dialect's type for
|
|
2462
|
+
* inference and gives adapter authors a discoverable anchor.
|
|
2463
|
+
*
|
|
2464
|
+
* @example
|
|
2465
|
+
* ```ts
|
|
2466
|
+
* export const oasDialect = defineSchemaDialect({
|
|
2467
|
+
* name: 'oas',
|
|
2468
|
+
* isNullable,
|
|
2469
|
+
* isReference,
|
|
2470
|
+
* isDiscriminator,
|
|
2471
|
+
* isBinary: (schema) => schema.type === 'string' && schema.contentMediaType === 'application/octet-stream',
|
|
2472
|
+
* resolveRef,
|
|
2473
|
+
* })
|
|
2474
|
+
* ```
|
|
2475
|
+
*/
|
|
2476
|
+
function defineSchemaDialect(dialect) {
|
|
2477
|
+
return dialect;
|
|
2478
|
+
}
|
|
2479
|
+
//#endregion
|
|
2480
|
+
//#region src/dispatch.ts
|
|
2481
|
+
/**
|
|
2482
|
+
* Walks an ordered list of {@link DispatchRule}s and returns the first node produced.
|
|
2483
|
+
*
|
|
2484
|
+
* This is the shared backbone for spec adapters (OpenAPI today, AsyncAPI and others later).
|
|
2485
|
+
* The contract an adapter follows is intentionally minimal:
|
|
2486
|
+
*
|
|
2487
|
+
* context → [rule.match → rule.convert] → node
|
|
2488
|
+
*
|
|
2489
|
+
* An adapter derives a context from a source spec node, then declares an ordered table of
|
|
2490
|
+
* rules mapping spec shapes onto Kubb AST nodes. To add support for a new spec, write a new
|
|
2491
|
+
* context type and a new rules table — the traversal here is reused unchanged.
|
|
2492
|
+
*
|
|
2493
|
+
* Order is significant: earlier rules win, so list higher-precedence or more specific shapes
|
|
2494
|
+
* first (e.g. composition keywords before plain `type`). A rule whose `match` returns `true`
|
|
2495
|
+
* may still `convert` to `null` to defer to later rules. When no rule produces a node this
|
|
2496
|
+
* returns `null`, leaving the caller to apply its own fallback.
|
|
2497
|
+
*
|
|
2498
|
+
* @example
|
|
2499
|
+
* ```ts
|
|
2500
|
+
* const node = dispatch(schemaRules, schemaContext) ?? createSchema({ type: fallbackType })
|
|
2501
|
+
* ```
|
|
2502
|
+
*/
|
|
2503
|
+
function dispatch(rules, context) {
|
|
2504
|
+
for (const rule of rules) {
|
|
2505
|
+
if (!rule.match(context)) continue;
|
|
2506
|
+
const node = rule.convert(context);
|
|
2507
|
+
if (node !== null && node !== void 0) return node;
|
|
2508
|
+
}
|
|
2509
|
+
return null;
|
|
2510
|
+
}
|
|
2511
|
+
//#endregion
|
|
2118
2512
|
//#region src/printer.ts
|
|
2119
2513
|
/**
|
|
2120
2514
|
* Defines a schema printer: a function that takes a `SchemaNode` and emits
|
|
@@ -2331,6 +2725,6 @@ function setEnumName(propNode, parentName, propName, enumSuffix) {
|
|
|
2331
2725
|
return propNode;
|
|
2332
2726
|
}
|
|
2333
2727
|
//#endregion
|
|
2334
|
-
export { caseParams, childName, collect, collectImports, collectLazy, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createContent, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createRequestBody, createResponse, createSchema, createSource, createStreamInput, createText, createType, definePrinter, defineSchemaDialect, dispatch, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, mergeAdjacentObjectsLazy, narrowSchema, nodeKinds, resolveRefName, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, update, walk };
|
|
2728
|
+
export { applyDedupe, buildDedupePlan, caseParams, childName, collect, collectImports, collectLazy, collectReferencedSchemaNames, collectUsedSchemaNames, containsCircularRef, createArrowFunction, createBreak, createConst, createContent, createDiscriminantNode, createExport, createFile, createFunction, createFunctionParameter, createFunctionParameters, createImport, createInput, createJsx, createOperation, createOperationParams, createOutput, createParameter, createParameterGroup, createParamsType, createPrinterFactory, createProperty, createRequestBody, createResponse, createSchema, createSource, createStreamInput, createText, createType, definePrinter, defineSchemaDialect, dispatch, enumPropName, extractRefName, extractStringsFromNodes, findCircularSchemas, findDiscriminator, httpMethods, isHttpOperationNode, isInputNode, isOperationNode, isOutputNode, isScalarPrimitive, isSchemaEqual, isSchemaNode, isStringType, mediaTypes, mergeAdjacentObjects, mergeAdjacentObjectsLazy, narrowSchema, nodeKinds, resolveRefName, schemaSignature, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, syncOptionality, syncSchemaRef, transform, update, walk };
|
|
2335
2729
|
|
|
2336
2730
|
//# sourceMappingURL=index.js.map
|