@formspec/build 0.1.0-alpha.15 → 0.1.0-alpha.16

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.
Files changed (42) hide show
  1. package/dist/__tests__/fixtures/edge-cases.d.ts +11 -0
  2. package/dist/__tests__/fixtures/edge-cases.d.ts.map +1 -1
  3. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts +30 -0
  4. package/dist/__tests__/fixtures/mixed-authoring-shipping-address.d.ts.map +1 -0
  5. package/dist/__tests__/mixed-authoring.test.d.ts +2 -0
  6. package/dist/__tests__/mixed-authoring.test.d.ts.map +1 -0
  7. package/dist/__tests__/parity/utils.d.ts +5 -3
  8. package/dist/__tests__/parity/utils.d.ts.map +1 -1
  9. package/dist/analyzer/class-analyzer.d.ts +4 -2
  10. package/dist/analyzer/class-analyzer.d.ts.map +1 -1
  11. package/dist/analyzer/tsdoc-parser.d.ts +20 -2
  12. package/dist/analyzer/tsdoc-parser.d.ts.map +1 -1
  13. package/dist/browser.cjs +172 -17
  14. package/dist/browser.cjs.map +1 -1
  15. package/dist/browser.d.ts.map +1 -1
  16. package/dist/browser.js +172 -17
  17. package/dist/browser.js.map +1 -1
  18. package/dist/build.d.ts +39 -1
  19. package/dist/canonicalize/tsdoc-canonicalizer.d.ts.map +1 -1
  20. package/dist/cli.cjs +634 -88
  21. package/dist/cli.cjs.map +1 -1
  22. package/dist/cli.js +634 -88
  23. package/dist/cli.js.map +1 -1
  24. package/dist/generators/mixed-authoring.d.ts +45 -0
  25. package/dist/generators/mixed-authoring.d.ts.map +1 -0
  26. package/dist/index.cjs +622 -87
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.ts +2 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +621 -87
  31. package/dist/index.js.map +1 -1
  32. package/dist/internals.cjs +526 -91
  33. package/dist/internals.cjs.map +1 -1
  34. package/dist/internals.js +526 -91
  35. package/dist/internals.js.map +1 -1
  36. package/dist/json-schema/ir-generator.d.ts +3 -2
  37. package/dist/json-schema/ir-generator.d.ts.map +1 -1
  38. package/dist/ui-schema/ir-generator.d.ts.map +1 -1
  39. package/dist/validate/constraint-validator.d.ts.map +1 -1
  40. package/package.json +3 -3
  41. package/dist/__tests__/jsdoc-constraints.test.d.ts +0 -9
  42. package/dist/__tests__/jsdoc-constraints.test.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -325,6 +325,7 @@ function canonicalizeTSDoc(analysis, source) {
325
325
  irVersion: IR_VERSION2,
326
326
  elements,
327
327
  typeRegistry: analysis.typeRegistry,
328
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
328
329
  provenance
329
330
  };
330
331
  }
@@ -401,6 +402,9 @@ function generateJsonSchemaFromIR(ir, options) {
401
402
  const ctx = makeContext(options);
402
403
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
403
404
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
405
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
406
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
407
+ }
404
408
  }
405
409
  const properties = {};
406
410
  const required = [];
@@ -412,6 +416,9 @@ function generateJsonSchemaFromIR(ir, options) {
412
416
  properties,
413
417
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
414
418
  };
419
+ if (ir.annotations && ir.annotations.length > 0) {
420
+ applyAnnotations(result, ir.annotations, ctx);
421
+ }
415
422
  if (Object.keys(ctx.defs).length > 0) {
416
423
  result.$defs = ctx.defs;
417
424
  }
@@ -441,22 +448,51 @@ function collectFields(elements, properties, required, ctx) {
441
448
  }
442
449
  function generateFieldSchema(field, ctx) {
443
450
  const schema = generateTypeNode(field.type, ctx);
451
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
444
452
  const directConstraints = [];
453
+ const itemConstraints = [];
445
454
  const pathConstraints = [];
446
455
  for (const c of field.constraints) {
447
456
  if (c.path) {
448
457
  pathConstraints.push(c);
458
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
459
+ itemConstraints.push(c);
449
460
  } else {
450
461
  directConstraints.push(c);
451
462
  }
452
463
  }
453
464
  applyConstraints(schema, directConstraints, ctx);
454
- applyAnnotations(schema, field.annotations, ctx);
465
+ if (itemStringSchema !== void 0) {
466
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
467
+ }
468
+ const rootAnnotations = [];
469
+ const itemAnnotations = [];
470
+ for (const annotation of field.annotations) {
471
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
472
+ itemAnnotations.push(annotation);
473
+ } else {
474
+ rootAnnotations.push(annotation);
475
+ }
476
+ }
477
+ applyAnnotations(schema, rootAnnotations, ctx);
478
+ if (itemStringSchema !== void 0) {
479
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
480
+ }
455
481
  if (pathConstraints.length === 0) {
456
482
  return schema;
457
483
  }
458
484
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
459
485
  }
486
+ function isStringItemConstraint(constraint) {
487
+ switch (constraint.constraintKind) {
488
+ case "minLength":
489
+ case "maxLength":
490
+ case "pattern":
491
+ return true;
492
+ default:
493
+ return false;
494
+ }
495
+ }
460
496
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
461
497
  if (schema.type === "array" && schema.items) {
462
498
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -674,6 +710,9 @@ function applyConstraints(schema, constraints, ctx) {
674
710
  case "uniqueItems":
675
711
  schema.uniqueItems = constraint.value;
676
712
  break;
713
+ case "const":
714
+ schema.const = constraint.value;
715
+ break;
677
716
  case "allowedMembers":
678
717
  break;
679
718
  case "custom":
@@ -698,8 +737,14 @@ function applyAnnotations(schema, annotations, ctx) {
698
737
  case "defaultValue":
699
738
  schema.default = annotation.value;
700
739
  break;
740
+ case "format":
741
+ schema.format = annotation.value;
742
+ break;
701
743
  case "deprecated":
702
744
  schema.deprecated = true;
745
+ if (annotation.message !== void 0 && annotation.message !== "") {
746
+ schema["x-formspec-deprecation-description"] = annotation.message;
747
+ }
703
748
  break;
704
749
  case "placeholder":
705
750
  break;
@@ -887,25 +932,31 @@ function createShowRule(fieldName, value) {
887
932
  }
888
933
  };
889
934
  }
935
+ function flattenConditionSchema(scope, schema) {
936
+ if (schema.allOf === void 0) {
937
+ if (scope === "#") {
938
+ return [schema];
939
+ }
940
+ const fieldName = scope.replace("#/properties/", "");
941
+ return [
942
+ {
943
+ properties: {
944
+ [fieldName]: schema
945
+ }
946
+ }
947
+ ];
948
+ }
949
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
950
+ }
890
951
  function combineRules(parentRule, childRule) {
891
- const parentCondition = parentRule.condition;
892
- const childCondition = childRule.condition;
893
952
  return {
894
953
  effect: "SHOW",
895
954
  condition: {
896
955
  scope: "#",
897
956
  schema: {
898
957
  allOf: [
899
- {
900
- properties: {
901
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
902
- }
903
- },
904
- {
905
- properties: {
906
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
907
- }
908
- }
958
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
959
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
909
960
  ]
910
961
  }
911
962
  }
@@ -913,10 +964,14 @@ function combineRules(parentRule, childRule) {
913
964
  }
914
965
  function fieldNodeToControl(field, parentRule) {
915
966
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
967
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
916
968
  const control = {
917
969
  type: "Control",
918
970
  scope: fieldToScope(field.name),
919
971
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
972
+ ...placeholderAnnotation !== void 0 && {
973
+ options: { placeholder: placeholderAnnotation.value }
974
+ },
920
975
  ...parentRule !== void 0 && { rule: parentRule }
921
976
  };
922
977
  return control;
@@ -1205,7 +1260,7 @@ var LENGTH_CONSTRAINT_MAP = {
1205
1260
  minItems: "minItems",
1206
1261
  maxItems: "maxItems"
1207
1262
  };
1208
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1263
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1209
1264
  function createFormSpecTSDocConfig() {
1210
1265
  const config = new TSDocConfiguration();
1211
1266
  for (const tagName of Object.keys(BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1217,7 +1272,7 @@ function createFormSpecTSDocConfig() {
1217
1272
  })
1218
1273
  );
1219
1274
  }
1220
- for (const tagName of ["displayName", "description"]) {
1275
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1221
1276
  config.addTagDefinition(
1222
1277
  new TSDocTagDefinition({
1223
1278
  tagName: "@" + tagName,
@@ -1236,6 +1291,12 @@ function getParser() {
1236
1291
  function parseTSDocTags(node, file = "") {
1237
1292
  const constraints = [];
1238
1293
  const annotations = [];
1294
+ let displayName;
1295
+ let description;
1296
+ let placeholder;
1297
+ let displayNameProvenance;
1298
+ let descriptionProvenance;
1299
+ let placeholderProvenance;
1239
1300
  const sourceFile = node.getSourceFile();
1240
1301
  const sourceText = sourceFile.getFullText();
1241
1302
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1255,30 +1316,37 @@ function parseTSDocTags(node, file = "") {
1255
1316
  const docComment = parserContext.docComment;
1256
1317
  for (const block of docComment.customBlocks) {
1257
1318
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1258
- if (tagName === "displayName" || tagName === "description") {
1319
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1259
1320
  const text2 = extractBlockText(block).trim();
1260
1321
  if (text2 === "") continue;
1261
1322
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1262
1323
  if (tagName === "displayName") {
1324
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1325
+ displayName = text2;
1326
+ displayNameProvenance = provenance2;
1327
+ }
1328
+ } else if (tagName === "format") {
1263
1329
  annotations.push({
1264
1330
  kind: "annotation",
1265
- annotationKind: "displayName",
1331
+ annotationKind: "format",
1266
1332
  value: text2,
1267
1333
  provenance: provenance2
1268
1334
  });
1269
1335
  } else {
1270
- annotations.push({
1271
- kind: "annotation",
1272
- annotationKind: "description",
1273
- value: text2,
1274
- provenance: provenance2
1275
- });
1336
+ if (tagName === "description" && description === void 0) {
1337
+ description = text2;
1338
+ descriptionProvenance = provenance2;
1339
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1340
+ placeholder = text2;
1341
+ placeholderProvenance = provenance2;
1342
+ }
1276
1343
  }
1277
1344
  continue;
1278
1345
  }
1279
1346
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1280
1347
  const text = extractBlockText(block).trim();
1281
- if (text === "") continue;
1348
+ const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1349
+ if (text === "" && expectedType !== "boolean") continue;
1282
1350
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1283
1351
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1284
1352
  if (constraintNode) {
@@ -1286,14 +1354,47 @@ function parseTSDocTags(node, file = "") {
1286
1354
  }
1287
1355
  }
1288
1356
  if (docComment.deprecatedBlock !== void 0) {
1357
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1289
1358
  annotations.push({
1290
1359
  kind: "annotation",
1291
1360
  annotationKind: "deprecated",
1361
+ ...message !== "" && { message },
1292
1362
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1293
1363
  });
1294
1364
  }
1365
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1366
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1367
+ if (remarks !== "") {
1368
+ description = remarks;
1369
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1370
+ }
1371
+ }
1295
1372
  }
1296
1373
  }
1374
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1375
+ annotations.push({
1376
+ kind: "annotation",
1377
+ annotationKind: "displayName",
1378
+ value: displayName,
1379
+ provenance: displayNameProvenance
1380
+ });
1381
+ }
1382
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1383
+ annotations.push({
1384
+ kind: "annotation",
1385
+ annotationKind: "description",
1386
+ value: description,
1387
+ provenance: descriptionProvenance
1388
+ });
1389
+ }
1390
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1391
+ annotations.push({
1392
+ kind: "annotation",
1393
+ annotationKind: "placeholder",
1394
+ value: placeholder,
1395
+ provenance: placeholderProvenance
1396
+ });
1397
+ }
1297
1398
  const jsDocTagsAll = ts2.getJSDocTags(node);
1298
1399
  for (const tag of jsDocTagsAll) {
1299
1400
  const tagName = normalizeConstraintTagName(tag.tagName.text);
@@ -1302,6 +1403,11 @@ function parseTSDocTags(node, file = "") {
1302
1403
  if (commentText === void 0 || commentText.trim() === "") continue;
1303
1404
  const text = commentText.trim();
1304
1405
  const provenance = provenanceForJSDocTag(tag, file);
1406
+ if (tagName === "defaultValue") {
1407
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1408
+ annotations.push(defaultValueNode);
1409
+ continue;
1410
+ }
1305
1411
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1306
1412
  if (constraintNode) {
1307
1413
  constraints.push(constraintNode);
@@ -1309,6 +1415,28 @@ function parseTSDocTags(node, file = "") {
1309
1415
  }
1310
1416
  return { constraints, annotations };
1311
1417
  }
1418
+ function extractDisplayNameMetadata(node) {
1419
+ let displayName;
1420
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1421
+ for (const tag of ts2.getJSDocTags(node)) {
1422
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1423
+ if (tagName !== "displayName") continue;
1424
+ const commentText = getTagCommentText(tag);
1425
+ if (commentText === void 0) continue;
1426
+ const text = commentText.trim();
1427
+ if (text === "") continue;
1428
+ const memberTarget = parseMemberTargetDisplayName(text);
1429
+ if (memberTarget) {
1430
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1431
+ continue;
1432
+ }
1433
+ displayName ??= text;
1434
+ }
1435
+ return {
1436
+ ...displayName !== void 0 && { displayName },
1437
+ memberDisplayNames
1438
+ };
1439
+ }
1312
1440
  function extractPathTarget(text) {
1313
1441
  const trimmed = text.trimStart();
1314
1442
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1371,7 +1499,45 @@ function parseConstraintValue(tagName, text, provenance) {
1371
1499
  }
1372
1500
  return null;
1373
1501
  }
1502
+ if (expectedType === "boolean") {
1503
+ const trimmed = effectiveText.trim();
1504
+ if (trimmed !== "" && trimmed !== "true") {
1505
+ return null;
1506
+ }
1507
+ if (tagName === "uniqueItems") {
1508
+ return {
1509
+ kind: "constraint",
1510
+ constraintKind: "uniqueItems",
1511
+ value: true,
1512
+ ...path3 && { path: path3 },
1513
+ provenance
1514
+ };
1515
+ }
1516
+ return null;
1517
+ }
1374
1518
  if (expectedType === "json") {
1519
+ if (tagName === "const") {
1520
+ const trimmedText = effectiveText.trim();
1521
+ if (trimmedText === "") return null;
1522
+ try {
1523
+ const parsed2 = JSON.parse(trimmedText);
1524
+ return {
1525
+ kind: "constraint",
1526
+ constraintKind: "const",
1527
+ value: parsed2,
1528
+ ...path3 && { path: path3 },
1529
+ provenance
1530
+ };
1531
+ } catch {
1532
+ return {
1533
+ kind: "constraint",
1534
+ constraintKind: "const",
1535
+ value: trimmedText,
1536
+ ...path3 && { path: path3 },
1537
+ provenance
1538
+ };
1539
+ }
1540
+ }
1375
1541
  const parsed = tryParseJson(effectiveText);
1376
1542
  if (!Array.isArray(parsed)) {
1377
1543
  return null;
@@ -1403,6 +1569,34 @@ function parseConstraintValue(tagName, text, provenance) {
1403
1569
  provenance
1404
1570
  };
1405
1571
  }
1572
+ function parseDefaultValueValue(text, provenance) {
1573
+ const trimmed = text.trim();
1574
+ let value;
1575
+ if (trimmed === "null") {
1576
+ value = null;
1577
+ } else if (trimmed === "true") {
1578
+ value = true;
1579
+ } else if (trimmed === "false") {
1580
+ value = false;
1581
+ } else {
1582
+ const parsed = tryParseJson(trimmed);
1583
+ value = parsed !== null ? parsed : trimmed;
1584
+ }
1585
+ return {
1586
+ kind: "annotation",
1587
+ annotationKind: "defaultValue",
1588
+ value,
1589
+ provenance
1590
+ };
1591
+ }
1592
+ function isMemberTargetDisplayName(text) {
1593
+ return parseMemberTargetDisplayName(text) !== null;
1594
+ }
1595
+ function parseMemberTargetDisplayName(text) {
1596
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1597
+ if (!match?.[1] || !match[2]) return null;
1598
+ return { target: match[1], label: match[2].trim() };
1599
+ }
1406
1600
  function provenanceForComment(range, sourceFile, file, tagName) {
1407
1601
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1408
1602
  return {
@@ -1484,11 +1678,17 @@ function isObjectType(type) {
1484
1678
  function isTypeReference(type) {
1485
1679
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1486
1680
  }
1681
+ var RESOLVING_TYPE_PLACEHOLDER = {
1682
+ kind: "object",
1683
+ properties: [],
1684
+ additionalProperties: true
1685
+ };
1487
1686
  function analyzeClassToIR(classDecl, checker, file = "") {
1488
1687
  const name = classDecl.name?.text ?? "AnonymousClass";
1489
1688
  const fields = [];
1490
1689
  const fieldLayouts = [];
1491
1690
  const typeRegistry = {};
1691
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1492
1692
  const visiting = /* @__PURE__ */ new Set();
1493
1693
  const instanceMethods = [];
1494
1694
  const staticMethods = [];
@@ -1511,12 +1711,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1511
1711
  }
1512
1712
  }
1513
1713
  }
1514
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1714
+ return {
1715
+ name,
1716
+ fields,
1717
+ fieldLayouts,
1718
+ typeRegistry,
1719
+ ...annotations.length > 0 && { annotations },
1720
+ instanceMethods,
1721
+ staticMethods
1722
+ };
1515
1723
  }
1516
1724
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1517
1725
  const name = interfaceDecl.name.text;
1518
1726
  const fields = [];
1519
1727
  const typeRegistry = {};
1728
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1520
1729
  const visiting = /* @__PURE__ */ new Set();
1521
1730
  for (const member of interfaceDecl.members) {
1522
1731
  if (ts4.isPropertySignature(member)) {
@@ -1527,7 +1736,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1527
1736
  }
1528
1737
  }
1529
1738
  const fieldLayouts = fields.map(() => ({}));
1530
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1739
+ return {
1740
+ name,
1741
+ fields,
1742
+ fieldLayouts,
1743
+ typeRegistry,
1744
+ ...annotations.length > 0 && { annotations },
1745
+ instanceMethods: [],
1746
+ staticMethods: []
1747
+ };
1531
1748
  }
1532
1749
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1533
1750
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1542,6 +1759,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1542
1759
  const name = typeAlias.name.text;
1543
1760
  const fields = [];
1544
1761
  const typeRegistry = {};
1762
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1545
1763
  const visiting = /* @__PURE__ */ new Set();
1546
1764
  for (const member of typeAlias.type.members) {
1547
1765
  if (ts4.isPropertySignature(member)) {
@@ -1558,6 +1776,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1558
1776
  fields,
1559
1777
  fieldLayouts: fields.map(() => ({})),
1560
1778
  typeRegistry,
1779
+ ...annotations.length > 0 && { annotations },
1561
1780
  instanceMethods: [],
1562
1781
  staticMethods: []
1563
1782
  }
@@ -1571,7 +1790,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1571
1790
  const tsType = checker.getTypeAtLocation(prop);
1572
1791
  const optional = prop.questionToken !== void 0;
1573
1792
  const provenance = provenanceForNode(prop, file);
1574
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1793
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1575
1794
  const constraints = [];
1576
1795
  if (prop.type) {
1577
1796
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1580,7 +1799,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1580
1799
  let annotations = [];
1581
1800
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1582
1801
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1583
- if (defaultAnnotation) {
1802
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1584
1803
  annotations.push(defaultAnnotation);
1585
1804
  }
1586
1805
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1602,7 +1821,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1602
1821
  const tsType = checker.getTypeAtLocation(prop);
1603
1822
  const optional = prop.questionToken !== void 0;
1604
1823
  const provenance = provenanceForNode(prop, file);
1605
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1824
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1606
1825
  const constraints = [];
1607
1826
  if (prop.type) {
1608
1827
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1683,7 +1902,7 @@ function parseEnumMemberDisplayName(value) {
1683
1902
  if (label === "") return null;
1684
1903
  return { value: match[1], label };
1685
1904
  }
1686
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1905
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1687
1906
  if (type.flags & ts4.TypeFlags.String) {
1688
1907
  return { kind: "primitive", primitiveKind: "string" };
1689
1908
  }
@@ -1712,7 +1931,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1712
1931
  };
1713
1932
  }
1714
1933
  if (type.isUnion()) {
1715
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
1934
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1716
1935
  }
1717
1936
  if (checker.isArrayType(type)) {
1718
1937
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1722,70 +1941,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1722
1941
  }
1723
1942
  return { kind: "primitive", primitiveKind: "string" };
1724
1943
  }
1725
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
1944
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1945
+ const typeName = getNamedTypeName(type);
1946
+ const namedDecl = getNamedTypeDeclaration(type);
1947
+ if (typeName && typeName in typeRegistry) {
1948
+ return { kind: "reference", name: typeName, typeArguments: [] };
1949
+ }
1726
1950
  const allTypes = type.types;
1727
1951
  const nonNullTypes = allTypes.filter(
1728
1952
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1729
1953
  );
1730
1954
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
1955
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1956
+ if (namedDecl) {
1957
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
1958
+ memberDisplayNames.set(value, label);
1959
+ }
1960
+ }
1961
+ if (sourceNode) {
1962
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
1963
+ memberDisplayNames.set(value, label);
1964
+ }
1965
+ }
1966
+ const registerNamed = (result) => {
1967
+ if (!typeName) {
1968
+ return result;
1969
+ }
1970
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
1971
+ typeRegistry[typeName] = {
1972
+ name: typeName,
1973
+ type: result,
1974
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
1975
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
1976
+ };
1977
+ return { kind: "reference", name: typeName, typeArguments: [] };
1978
+ };
1979
+ const applyMemberLabels = (members2) => members2.map((value) => {
1980
+ const displayName = memberDisplayNames.get(String(value));
1981
+ return displayName !== void 0 ? { value, displayName } : { value };
1982
+ });
1731
1983
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1732
1984
  if (isBooleanUnion2) {
1733
1985
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1734
- if (hasNull) {
1735
- return {
1736
- kind: "union",
1737
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1738
- };
1739
- }
1740
- return boolNode;
1986
+ const result = hasNull ? {
1987
+ kind: "union",
1988
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1989
+ } : boolNode;
1990
+ return registerNamed(result);
1741
1991
  }
1742
1992
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1743
1993
  if (allStringLiterals && nonNullTypes.length > 0) {
1744
1994
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1745
1995
  const enumNode = {
1746
1996
  kind: "enum",
1747
- members: stringTypes.map((t) => ({ value: t.value }))
1997
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1748
1998
  };
1749
- if (hasNull) {
1750
- return {
1751
- kind: "union",
1752
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1753
- };
1754
- }
1755
- return enumNode;
1999
+ const result = hasNull ? {
2000
+ kind: "union",
2001
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2002
+ } : enumNode;
2003
+ return registerNamed(result);
1756
2004
  }
1757
2005
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1758
2006
  if (allNumberLiterals && nonNullTypes.length > 0) {
1759
2007
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1760
2008
  const enumNode = {
1761
2009
  kind: "enum",
1762
- members: numberTypes.map((t) => ({ value: t.value }))
2010
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1763
2011
  };
1764
- if (hasNull) {
1765
- return {
1766
- kind: "union",
1767
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1768
- };
1769
- }
1770
- return enumNode;
2012
+ const result = hasNull ? {
2013
+ kind: "union",
2014
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2015
+ } : enumNode;
2016
+ return registerNamed(result);
1771
2017
  }
1772
2018
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1773
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1774
- if (hasNull) {
1775
- return {
1776
- kind: "union",
1777
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1778
- };
1779
- }
1780
- return inner;
2019
+ const inner = resolveTypeNode(
2020
+ nonNullTypes[0],
2021
+ checker,
2022
+ file,
2023
+ typeRegistry,
2024
+ visiting,
2025
+ sourceNode
2026
+ );
2027
+ const result = hasNull ? {
2028
+ kind: "union",
2029
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2030
+ } : inner;
2031
+ return registerNamed(result);
1781
2032
  }
1782
2033
  const members = nonNullTypes.map(
1783
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2034
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1784
2035
  );
1785
2036
  if (hasNull) {
1786
2037
  members.push({ kind: "primitive", primitiveKind: "null" });
1787
2038
  }
1788
- return { kind: "union", members };
2039
+ return registerNamed({ kind: "union", members });
1789
2040
  }
1790
2041
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1791
2042
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1801,30 +2052,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1801
2052
  if (!indexInfo) {
1802
2053
  return null;
1803
2054
  }
1804
- if (visiting.has(type)) {
1805
- return null;
1806
- }
1807
- visiting.add(type);
1808
- try {
1809
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1810
- return { kind: "record", valueType };
1811
- } finally {
1812
- visiting.delete(type);
2055
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2056
+ return { kind: "record", valueType };
2057
+ }
2058
+ function typeNodeContainsReference(type, targetName) {
2059
+ switch (type.kind) {
2060
+ case "reference":
2061
+ return type.name === targetName;
2062
+ case "array":
2063
+ return typeNodeContainsReference(type.items, targetName);
2064
+ case "record":
2065
+ return typeNodeContainsReference(type.valueType, targetName);
2066
+ case "union":
2067
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2068
+ case "object":
2069
+ return type.properties.some(
2070
+ (property) => typeNodeContainsReference(property.type, targetName)
2071
+ );
2072
+ case "primitive":
2073
+ case "enum":
2074
+ case "dynamic":
2075
+ case "custom":
2076
+ return false;
2077
+ default: {
2078
+ const _exhaustive = type;
2079
+ return _exhaustive;
2080
+ }
1813
2081
  }
1814
2082
  }
1815
2083
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1816
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1817
- if (recordNode) {
1818
- return recordNode;
1819
- }
2084
+ const typeName = getNamedTypeName(type);
2085
+ const namedTypeName = typeName ?? void 0;
2086
+ const namedDecl = getNamedTypeDeclaration(type);
2087
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2088
+ const clearNamedTypeRegistration = () => {
2089
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2090
+ return;
2091
+ }
2092
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2093
+ };
1820
2094
  if (visiting.has(type)) {
2095
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2096
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2097
+ }
1821
2098
  return { kind: "object", properties: [], additionalProperties: false };
1822
2099
  }
2100
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2101
+ typeRegistry[namedTypeName] = {
2102
+ name: namedTypeName,
2103
+ type: RESOLVING_TYPE_PLACEHOLDER,
2104
+ provenance: provenanceForDeclaration(namedDecl, file)
2105
+ };
2106
+ }
1823
2107
  visiting.add(type);
1824
- const typeName = getNamedTypeName(type);
1825
- if (typeName && typeName in typeRegistry) {
2108
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2109
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2110
+ visiting.delete(type);
2111
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2112
+ }
2113
+ }
2114
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2115
+ if (recordNode) {
1826
2116
  visiting.delete(type);
1827
- return { kind: "reference", name: typeName, typeArguments: [] };
2117
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2118
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2119
+ if (!isRecursiveRecord) {
2120
+ clearNamedTypeRegistration();
2121
+ return recordNode;
2122
+ }
2123
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2124
+ typeRegistry[namedTypeName] = {
2125
+ name: namedTypeName,
2126
+ type: recordNode,
2127
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2128
+ provenance: provenanceForDeclaration(namedDecl, file)
2129
+ };
2130
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2131
+ }
2132
+ return recordNode;
1828
2133
  }
1829
2134
  const properties = [];
1830
2135
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1833,7 +2138,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1833
2138
  if (!declaration) continue;
1834
2139
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1835
2140
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1836
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2141
+ const propTypeNode = resolveTypeNode(
2142
+ propType,
2143
+ checker,
2144
+ file,
2145
+ typeRegistry,
2146
+ visiting,
2147
+ declaration
2148
+ );
1837
2149
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1838
2150
  properties.push({
1839
2151
  name: prop.name,
@@ -1850,13 +2162,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1850
2162
  properties,
1851
2163
  additionalProperties: true
1852
2164
  };
1853
- if (typeName) {
1854
- typeRegistry[typeName] = {
1855
- name: typeName,
2165
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2166
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2167
+ typeRegistry[namedTypeName] = {
2168
+ name: namedTypeName,
1856
2169
  type: objectNode,
1857
- provenance: provenanceForFile(file)
2170
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2171
+ provenance: provenanceForDeclaration(namedDecl, file)
1858
2172
  };
1859
- return { kind: "reference", name: typeName, typeArguments: [] };
2173
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1860
2174
  }
1861
2175
  return objectNode;
1862
2176
  }
@@ -1948,6 +2262,12 @@ function provenanceForNode(node, file) {
1948
2262
  function provenanceForFile(file) {
1949
2263
  return { surface: "tsdoc", file, line: 0, column: 0 };
1950
2264
  }
2265
+ function provenanceForDeclaration(node, file) {
2266
+ if (!node) {
2267
+ return provenanceForFile(file);
2268
+ }
2269
+ return provenanceForNode(node, file);
2270
+ }
1951
2271
  function getNamedTypeName(type) {
1952
2272
  const symbol = type.getSymbol();
1953
2273
  if (symbol?.declarations) {
@@ -1966,6 +2286,20 @@ function getNamedTypeName(type) {
1966
2286
  }
1967
2287
  return null;
1968
2288
  }
2289
+ function getNamedTypeDeclaration(type) {
2290
+ const symbol = type.getSymbol();
2291
+ if (symbol?.declarations) {
2292
+ const decl = symbol.declarations[0];
2293
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2294
+ return decl;
2295
+ }
2296
+ }
2297
+ const aliasSymbol = type.aliasSymbol;
2298
+ if (aliasSymbol?.declarations) {
2299
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2300
+ }
2301
+ return void 0;
2302
+ }
1969
2303
  function analyzeMethod(method, checker) {
1970
2304
  if (!ts4.isIdentifier(method.name)) {
1971
2305
  return null;
@@ -2050,6 +2384,205 @@ function generateSchemas(options) {
2050
2384
  );
2051
2385
  }
2052
2386
 
2387
+ // src/generators/mixed-authoring.ts
2388
+ function buildMixedAuthoringSchemas(options) {
2389
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2390
+ const analysis = analyzeNamedType(filePath, typeName);
2391
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2392
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2393
+ return {
2394
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2395
+ uiSchema: generateUiSchemaFromIR(ir)
2396
+ };
2397
+ }
2398
+ function analyzeNamedType(filePath, typeName) {
2399
+ const ctx = createProgramContext(filePath);
2400
+ const source = { file: filePath };
2401
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2402
+ if (classDecl !== null) {
2403
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2404
+ }
2405
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2406
+ if (interfaceDecl !== null) {
2407
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2408
+ }
2409
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2410
+ if (typeAlias !== null) {
2411
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2412
+ if (result.ok) {
2413
+ return result.analysis;
2414
+ }
2415
+ throw new Error(result.error);
2416
+ }
2417
+ throw new Error(
2418
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2419
+ );
2420
+ }
2421
+ function composeAnalysisWithOverlays(analysis, overlays) {
2422
+ const overlayIR = canonicalizeChainDSL(overlays);
2423
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2424
+ if (overlayFields.length === 0) {
2425
+ return analysis;
2426
+ }
2427
+ const overlayByName = /* @__PURE__ */ new Map();
2428
+ for (const field of overlayFields) {
2429
+ if (overlayByName.has(field.name)) {
2430
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2431
+ }
2432
+ overlayByName.set(field.name, field);
2433
+ }
2434
+ const mergedFields = [];
2435
+ for (const baseField of analysis.fields) {
2436
+ const overlayField = overlayByName.get(baseField.name);
2437
+ if (overlayField === void 0) {
2438
+ mergedFields.push(baseField);
2439
+ continue;
2440
+ }
2441
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2442
+ overlayByName.delete(baseField.name);
2443
+ }
2444
+ if (overlayByName.size > 0) {
2445
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2446
+ throw new Error(
2447
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2448
+ );
2449
+ }
2450
+ return {
2451
+ ...analysis,
2452
+ fields: mergedFields
2453
+ };
2454
+ }
2455
+ function collectOverlayFields(elements) {
2456
+ const fields = [];
2457
+ for (const element of elements) {
2458
+ switch (element.kind) {
2459
+ case "field":
2460
+ fields.push(element);
2461
+ break;
2462
+ case "group":
2463
+ fields.push(...collectOverlayFields(element.elements));
2464
+ break;
2465
+ case "conditional":
2466
+ fields.push(...collectOverlayFields(element.elements));
2467
+ break;
2468
+ default: {
2469
+ const _exhaustive = element;
2470
+ void _exhaustive;
2471
+ }
2472
+ }
2473
+ }
2474
+ return fields;
2475
+ }
2476
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2477
+ assertSupportedOverlayField(baseField, overlayField);
2478
+ return {
2479
+ ...baseField,
2480
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2481
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2482
+ };
2483
+ }
2484
+ function assertSupportedOverlayField(baseField, overlayField) {
2485
+ if (overlayField.constraints.length > 0) {
2486
+ throw new Error(
2487
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2488
+ );
2489
+ }
2490
+ if (overlayField.required) {
2491
+ throw new Error(
2492
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2493
+ );
2494
+ }
2495
+ }
2496
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2497
+ const { type: baseType } = baseField;
2498
+ const { type: overlayType } = overlayField;
2499
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2500
+ throw new Error(
2501
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2502
+ );
2503
+ }
2504
+ if (overlayType.kind === "dynamic") {
2505
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2506
+ throw new Error(
2507
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2508
+ );
2509
+ }
2510
+ return overlayType;
2511
+ }
2512
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2513
+ throw new Error(
2514
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2515
+ );
2516
+ }
2517
+ return baseType;
2518
+ }
2519
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2520
+ const overlayType = overlayField.type;
2521
+ if (overlayType.kind !== "dynamic") {
2522
+ return false;
2523
+ }
2524
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2525
+ if (resolvedBaseType === null) {
2526
+ return false;
2527
+ }
2528
+ if (overlayType.dynamicKind === "enum") {
2529
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2530
+ }
2531
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2532
+ }
2533
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2534
+ if (type.kind !== "reference") {
2535
+ return type;
2536
+ }
2537
+ if (seen.has(type.name)) {
2538
+ return null;
2539
+ }
2540
+ const definition = typeRegistry[type.name];
2541
+ if (definition === void 0) {
2542
+ return null;
2543
+ }
2544
+ seen.add(type.name);
2545
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2546
+ }
2547
+ function isSameStaticTypeShape(baseType, overlayType) {
2548
+ if (baseType.kind !== overlayType.kind) {
2549
+ return false;
2550
+ }
2551
+ switch (baseType.kind) {
2552
+ case "primitive":
2553
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2554
+ case "enum":
2555
+ return overlayType.kind === "enum";
2556
+ case "dynamic":
2557
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2558
+ case "record":
2559
+ return overlayType.kind === "record";
2560
+ case "reference":
2561
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2562
+ case "union":
2563
+ return overlayType.kind === "union";
2564
+ case "custom":
2565
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2566
+ case "object":
2567
+ case "array":
2568
+ return true;
2569
+ default: {
2570
+ const _exhaustive = baseType;
2571
+ return _exhaustive;
2572
+ }
2573
+ }
2574
+ }
2575
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2576
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2577
+ const overlayOnly = overlayAnnotations.filter(
2578
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2579
+ );
2580
+ return [...overlayOnly, ...baseAnnotations];
2581
+ }
2582
+ function annotationKey(annotation) {
2583
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2584
+ }
2585
+
2053
2586
  // src/index.ts
2054
2587
  function buildFormSchemas(form, options) {
2055
2588
  return {
@@ -2075,6 +2608,7 @@ function writeSchemas(form, options) {
2075
2608
  }
2076
2609
  export {
2077
2610
  buildFormSchemas,
2611
+ buildMixedAuthoringSchemas,
2078
2612
  categorizationSchema,
2079
2613
  categorySchema,
2080
2614
  controlSchema,