@kubb/ast 5.0.0-beta.31 → 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 CHANGED
@@ -2097,66 +2097,245 @@ function refTargetName(node) {
2097
2097
  if (node.ref) return extractRefName(node.ref);
2098
2098
  return node.name ?? "";
2099
2099
  }
2100
- /**
2101
- * Builds the local, shape-only descriptor for a node: its kind, flags, constraints, and its
2102
- * children's signatures. {@link signatureOf} hashes this string; children contribute their
2103
- * fixed-length signature rather than their own full descriptor, which keeps the result bounded.
2104
- */
2105
- function describeShape(node, signatures) {
2106
- const flags = flagsDescriptor(node);
2107
- switch (node.type) {
2108
- case "object": {
2109
- const props = (node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema, signatures)}`).join(",");
2110
- let additional = "";
2111
- if (typeof node.additionalProperties === "boolean") additional = `ab:${node.additionalProperties}`;
2112
- else if (node.additionalProperties) additional = `as:${signatureOf(node.additionalProperties, signatures)}`;
2113
- const pattern = node.patternProperties ? Object.keys(node.patternProperties).sort().map((key) => `${key}=${signatureOf(node.patternProperties[key], signatures)}`).join(",") : "";
2114
- return `object|${flags}|p[${props}]|${additional}|pp[${pattern}]|mn:${node.minProperties ?? ""}|mx:${node.maxProperties ?? ""}`;
2100
+ const arrayTupleFields = [
2101
+ {
2102
+ kind: "children",
2103
+ key: "items",
2104
+ prefix: "i"
2105
+ },
2106
+ {
2107
+ kind: "child",
2108
+ key: "rest",
2109
+ prefix: "r"
2110
+ },
2111
+ {
2112
+ kind: "scalar",
2113
+ key: "min",
2114
+ prefix: "mn"
2115
+ },
2116
+ {
2117
+ kind: "scalar",
2118
+ key: "max",
2119
+ prefix: "mx"
2120
+ },
2121
+ {
2122
+ kind: "bool",
2123
+ key: "unique",
2124
+ prefix: "u"
2125
+ }
2126
+ ];
2127
+ const numericFields = [
2128
+ {
2129
+ kind: "scalar",
2130
+ key: "min",
2131
+ prefix: "mn"
2132
+ },
2133
+ {
2134
+ kind: "scalar",
2135
+ key: "max",
2136
+ prefix: "mx"
2137
+ },
2138
+ {
2139
+ kind: "scalar",
2140
+ key: "exclusiveMinimum",
2141
+ prefix: "emn"
2142
+ },
2143
+ {
2144
+ kind: "scalar",
2145
+ key: "exclusiveMaximum",
2146
+ prefix: "emx"
2147
+ },
2148
+ {
2149
+ kind: "scalar",
2150
+ key: "multipleOf",
2151
+ prefix: "mo"
2152
+ }
2153
+ ];
2154
+ const rangeFields = [{
2155
+ kind: "scalar",
2156
+ key: "min",
2157
+ prefix: "mn"
2158
+ }, {
2159
+ kind: "scalar",
2160
+ key: "max",
2161
+ prefix: "mx"
2162
+ }];
2163
+ /**
2164
+ * Maps each schema node `type` to the ordered list of shape-contributing fields.
2165
+ * Node types absent from this map (scalar types like boolean, null, any, etc.) fall
2166
+ * back to `${type}|${flags}` with no additional fields.
2167
+ */
2168
+ const SHAPE_KEYS = {
2169
+ object: [
2170
+ { kind: "objectProps" },
2171
+ { kind: "additionalProps" },
2172
+ { kind: "patternProps" },
2173
+ {
2174
+ kind: "scalar",
2175
+ key: "minProperties",
2176
+ prefix: "mn"
2177
+ },
2178
+ {
2179
+ kind: "scalar",
2180
+ key: "maxProperties",
2181
+ prefix: "mx"
2182
+ }
2183
+ ],
2184
+ array: arrayTupleFields,
2185
+ tuple: arrayTupleFields,
2186
+ union: [
2187
+ {
2188
+ kind: "scalar",
2189
+ key: "strategy",
2190
+ prefix: "s"
2191
+ },
2192
+ {
2193
+ kind: "scalar",
2194
+ key: "discriminatorPropertyName",
2195
+ prefix: "d"
2196
+ },
2197
+ {
2198
+ kind: "children",
2199
+ key: "members",
2200
+ prefix: "m"
2201
+ }
2202
+ ],
2203
+ intersection: [{
2204
+ kind: "children",
2205
+ key: "members",
2206
+ prefix: "m"
2207
+ }],
2208
+ enum: [{ kind: "enumValues" }],
2209
+ ref: [{ kind: "refTarget" }],
2210
+ string: [
2211
+ {
2212
+ kind: "scalar",
2213
+ key: "min",
2214
+ prefix: "mn"
2215
+ },
2216
+ {
2217
+ kind: "scalar",
2218
+ key: "max",
2219
+ prefix: "mx"
2220
+ },
2221
+ {
2222
+ kind: "scalar",
2223
+ key: "pattern",
2224
+ prefix: "pt"
2225
+ }
2226
+ ],
2227
+ number: numericFields,
2228
+ integer: numericFields,
2229
+ bigint: numericFields,
2230
+ url: [
2231
+ {
2232
+ kind: "scalar",
2233
+ key: "path",
2234
+ prefix: "path"
2235
+ },
2236
+ {
2237
+ kind: "scalar",
2238
+ key: "min",
2239
+ prefix: "mn"
2240
+ },
2241
+ {
2242
+ kind: "scalar",
2243
+ key: "max",
2244
+ prefix: "mx"
2115
2245
  }
2116
- case "array":
2117
- case "tuple": {
2118
- const items = (node.items ?? []).map((item) => signatureOf(item, signatures)).join(",");
2119
- const rest = node.rest ? signatureOf(node.rest, signatures) : "";
2120
- return `${node.type}|${flags}|i[${items}]|r:${rest}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|u:${node.unique ? 1 : 0}`;
2246
+ ],
2247
+ uuid: rangeFields,
2248
+ email: rangeFields,
2249
+ datetime: [{
2250
+ kind: "bool",
2251
+ key: "offset",
2252
+ prefix: "o"
2253
+ }, {
2254
+ kind: "bool",
2255
+ key: "local",
2256
+ prefix: "l"
2257
+ }],
2258
+ date: [{
2259
+ kind: "scalar",
2260
+ key: "representation",
2261
+ prefix: "rep"
2262
+ }],
2263
+ time: [{
2264
+ kind: "scalar",
2265
+ key: "representation",
2266
+ prefix: "rep"
2267
+ }]
2268
+ };
2269
+ function serializeShapeField(field, node, record) {
2270
+ switch (field.kind) {
2271
+ case "scalar": return `${field.prefix}:${record[field.key] ?? ""}`;
2272
+ case "bool": return `${field.prefix}:${record[field.key] ? 1 : 0}`;
2273
+ case "child": {
2274
+ const child = record[field.key];
2275
+ return `${field.prefix}:${child ? signatureOf(child) : ""}`;
2121
2276
  }
2122
- case "union": {
2123
- const members = (node.members ?? []).map((member) => signatureOf(member, signatures)).join(",");
2124
- return `union|${flags}|s:${node.strategy ?? ""}|d:${node.discriminatorPropertyName ?? ""}|m[${members}]`;
2277
+ case "children": {
2278
+ const children = record[field.key] ?? [];
2279
+ return `${field.prefix}[${children.map((c) => signatureOf(c)).join(",")}]`;
2125
2280
  }
2126
- case "intersection": return `intersection|${flags}|m[${(node.members ?? []).map((member) => signatureOf(member, signatures)).join(",")}]`;
2127
- case "enum": {
2281
+ case "objectProps": return `p[${(node.properties ?? []).map((prop) => `${prop.name}${prop.required ? "!" : "?"}${signatureOf(prop.schema)}`).join(",")}]`;
2282
+ case "additionalProps": {
2283
+ const obj = node;
2284
+ if (typeof obj.additionalProperties === "boolean") return `ab:${obj.additionalProperties}`;
2285
+ if (obj.additionalProperties) return `as:${signatureOf(obj.additionalProperties)}`;
2286
+ return "";
2287
+ }
2288
+ case "patternProps": {
2289
+ const obj = node;
2290
+ return `pp[${obj.patternProperties ? Object.keys(obj.patternProperties).sort().map((key) => `${key}=${signatureOf(obj.patternProperties[key])}`).join(",") : ""}]`;
2291
+ }
2292
+ case "enumValues": {
2293
+ const en = node;
2128
2294
  let values = "";
2129
- if (node.namedEnumValues?.length) values = node.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
2130
- else if (node.enumValues?.length) values = node.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
2131
- return `enum|${flags}|v[${values}]`;
2295
+ if (en.namedEnumValues?.length) values = en.namedEnumValues.map((entry) => `${entry.name}=${entry.primitive}:${String(entry.value)}`).join(",");
2296
+ else if (en.enumValues?.length) values = en.enumValues.map((value) => `${value === null ? "null" : typeof value}:${String(value)}`).join(",");
2297
+ return `v[${values}]`;
2132
2298
  }
2133
- case "ref": return `ref|${flags}|->${refTargetName(node)}`;
2134
- case "string": return `string|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|pt:${node.pattern ?? ""}`;
2135
- case "number":
2136
- case "integer":
2137
- case "bigint": return `${node.type}|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}|emn:${node.exclusiveMinimum ?? ""}|emx:${node.exclusiveMaximum ?? ""}|mo:${node.multipleOf ?? ""}`;
2138
- case "url": return `url|${flags}|path:${node.path ?? ""}|mn:${node.min ?? ""}|mx:${node.max ?? ""}`;
2139
- case "uuid":
2140
- case "email": return `${node.type}|${flags}|mn:${node.min ?? ""}|mx:${node.max ?? ""}`;
2141
- case "datetime": return `datetime|${flags}|o:${node.offset ? 1 : 0}|l:${node.local ? 1 : 0}`;
2142
- case "date":
2143
- case "time": return `${node.type}|${flags}|rep:${node.representation}`;
2144
- default: return `${node.type}|${flags}`;
2299
+ case "refTarget": return `->${refTargetName(node)}`;
2145
2300
  }
2146
2301
  }
2147
2302
  /**
2303
+ * Builds the local, shape-only descriptor for a node: its kind, flags, constraints, and its
2304
+ * children's signatures. {@link signatureOf} hashes this string; children contribute their
2305
+ * fixed-length signature rather than their own full descriptor, which keeps the result bounded.
2306
+ */
2307
+ function describeShape(node) {
2308
+ const flags = flagsDescriptor(node);
2309
+ const fields = SHAPE_KEYS[node.type];
2310
+ if (!fields) return `${node.type}|${flags}`;
2311
+ const record = node;
2312
+ const parts = [`${node.type}|${flags}`];
2313
+ for (const field of fields) parts.push(serializeShapeField(field, node, record));
2314
+ return parts.join("|");
2315
+ }
2316
+ /**
2317
+ * Persistent hash-consing cache: `SchemaNode` → signature digest, keyed by node identity.
2318
+ *
2319
+ * A `WeakMap` so entries are released once the node is garbage-collected, and so a node hashed
2320
+ * during dedupe planning is not re-hashed when the same tree is rewritten during streaming
2321
+ * (where `schemaSignature` and `applyDedupe` would otherwise each walk it from scratch). Reuse
2322
+ * across calls is sound because a signature depends only on a node's content, and schema nodes
2323
+ * are immutable once created — transforms allocate new objects rather than mutating in place.
2324
+ */
2325
+ const signatureCache = /* @__PURE__ */ new WeakMap();
2326
+ /**
2148
2327
  * Hash-consing: each node's signature is a fixed-length digest of its local shape plus its
2149
2328
  * children's digests (a Merkle hash). Children contribute their 64-char hash instead of their
2150
2329
  * full nested descriptor, so a signature stays bounded regardless of subtree depth, and the
2151
2330
  * digest is identical across calls because it depends only on content — never on traversal
2152
2331
  * order. This keeps the keys built during planning consistent with the ones recomputed later
2153
- * during streaming. `signatures` memoizes node → digest within a single computation.
2332
+ * during streaming. {@link signatureCache} memoizes node → digest across every computation.
2154
2333
  */
2155
- function signatureOf(node, signatures) {
2156
- const cached = signatures.get(node);
2334
+ function signatureOf(node) {
2335
+ const cached = signatureCache.get(node);
2157
2336
  if (cached !== void 0) return cached;
2158
- const signature = (0, node_crypto.createHash)("sha256").update(describeShape(node, signatures)).digest("hex");
2159
- signatures.set(node, signature);
2337
+ const signature = (0, node_crypto.createHash)("sha256").update(describeShape(node)).digest("hex");
2338
+ signatureCache.set(node, signature);
2160
2339
  return signature;
2161
2340
  }
2162
2341
  /**
@@ -2175,7 +2354,7 @@ function signatureOf(node, signatures) {
2175
2354
  * ```
2176
2355
  */
2177
2356
  function schemaSignature(node) {
2178
- return signatureOf(node, /* @__PURE__ */ new Map());
2357
+ return signatureOf(node);
2179
2358
  }
2180
2359
  /**
2181
2360
  * Returns `true` when two schema nodes are structurally identical under shape-only equality.
@@ -2211,10 +2390,9 @@ function createRefNode(node, canonical) {
2211
2390
  }
2212
2391
  function applyDedupe(node, canonicalBySignature, skipRootMatch = false) {
2213
2392
  if (canonicalBySignature.size === 0) return node;
2214
- const signatures = /* @__PURE__ */ new Map();
2215
2393
  const root = node;
2216
2394
  return transform(node, { schema(schemaNode) {
2217
- const signature = signatureOf(schemaNode, signatures);
2395
+ const signature = signatureOf(schemaNode);
2218
2396
  if (skipRootMatch && schemaNode === root) return void 0;
2219
2397
  const canonical = canonicalBySignature.get(signature);
2220
2398
  if (!canonical) return void 0;
@@ -2252,11 +2430,10 @@ function cleanDefinition(node, name) {
2252
2430
  */
2253
2431
  function buildDedupePlan(roots, options) {
2254
2432
  const { isCandidate, nameFor, refFor, minOccurrences = 2 } = options;
2255
- const signatures = /* @__PURE__ */ new Map();
2256
2433
  const topLevelNodes = /* @__PURE__ */ new Set();
2257
2434
  const groups = /* @__PURE__ */ new Map();
2258
2435
  function record(schemaNode) {
2259
- const signature = signatureOf(schemaNode, signatures);
2436
+ const signature = signatureOf(schemaNode);
2260
2437
  if (!isCandidate(schemaNode)) return;
2261
2438
  const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
2262
2439
  const group = groups.get(signature);