@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/cli.cjs CHANGED
@@ -362,6 +362,7 @@ function canonicalizeTSDoc(analysis, source) {
362
362
  irVersion: import_core2.IR_VERSION,
363
363
  elements,
364
364
  typeRegistry: analysis.typeRegistry,
365
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
365
366
  provenance
366
367
  };
367
368
  }
@@ -454,6 +455,9 @@ function generateJsonSchemaFromIR(ir, options) {
454
455
  const ctx = makeContext(options);
455
456
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
456
457
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
458
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
459
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
460
+ }
457
461
  }
458
462
  const properties = {};
459
463
  const required = [];
@@ -465,6 +469,9 @@ function generateJsonSchemaFromIR(ir, options) {
465
469
  properties,
466
470
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
467
471
  };
472
+ if (ir.annotations && ir.annotations.length > 0) {
473
+ applyAnnotations(result, ir.annotations, ctx);
474
+ }
468
475
  if (Object.keys(ctx.defs).length > 0) {
469
476
  result.$defs = ctx.defs;
470
477
  }
@@ -494,22 +501,51 @@ function collectFields(elements, properties, required, ctx) {
494
501
  }
495
502
  function generateFieldSchema(field, ctx) {
496
503
  const schema = generateTypeNode(field.type, ctx);
504
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
497
505
  const directConstraints = [];
506
+ const itemConstraints = [];
498
507
  const pathConstraints = [];
499
508
  for (const c of field.constraints) {
500
509
  if (c.path) {
501
510
  pathConstraints.push(c);
511
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
512
+ itemConstraints.push(c);
502
513
  } else {
503
514
  directConstraints.push(c);
504
515
  }
505
516
  }
506
517
  applyConstraints(schema, directConstraints, ctx);
507
- applyAnnotations(schema, field.annotations, ctx);
518
+ if (itemStringSchema !== void 0) {
519
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
520
+ }
521
+ const rootAnnotations = [];
522
+ const itemAnnotations = [];
523
+ for (const annotation of field.annotations) {
524
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
525
+ itemAnnotations.push(annotation);
526
+ } else {
527
+ rootAnnotations.push(annotation);
528
+ }
529
+ }
530
+ applyAnnotations(schema, rootAnnotations, ctx);
531
+ if (itemStringSchema !== void 0) {
532
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
533
+ }
508
534
  if (pathConstraints.length === 0) {
509
535
  return schema;
510
536
  }
511
537
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
512
538
  }
539
+ function isStringItemConstraint(constraint) {
540
+ switch (constraint.constraintKind) {
541
+ case "minLength":
542
+ case "maxLength":
543
+ case "pattern":
544
+ return true;
545
+ default:
546
+ return false;
547
+ }
548
+ }
513
549
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
514
550
  if (schema.type === "array" && schema.items) {
515
551
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -727,6 +763,9 @@ function applyConstraints(schema, constraints, ctx) {
727
763
  case "uniqueItems":
728
764
  schema.uniqueItems = constraint.value;
729
765
  break;
766
+ case "const":
767
+ schema.const = constraint.value;
768
+ break;
730
769
  case "allowedMembers":
731
770
  break;
732
771
  case "custom":
@@ -751,8 +790,14 @@ function applyAnnotations(schema, annotations, ctx) {
751
790
  case "defaultValue":
752
791
  schema.default = annotation.value;
753
792
  break;
793
+ case "format":
794
+ schema.format = annotation.value;
795
+ break;
754
796
  case "deprecated":
755
797
  schema.deprecated = true;
798
+ if (annotation.message !== void 0 && annotation.message !== "") {
799
+ schema["x-formspec-deprecation-description"] = annotation.message;
800
+ }
756
801
  break;
757
802
  case "placeholder":
758
803
  break;
@@ -957,25 +1002,31 @@ function createShowRule(fieldName, value) {
957
1002
  }
958
1003
  };
959
1004
  }
1005
+ function flattenConditionSchema(scope, schema) {
1006
+ if (schema.allOf === void 0) {
1007
+ if (scope === "#") {
1008
+ return [schema];
1009
+ }
1010
+ const fieldName = scope.replace("#/properties/", "");
1011
+ return [
1012
+ {
1013
+ properties: {
1014
+ [fieldName]: schema
1015
+ }
1016
+ }
1017
+ ];
1018
+ }
1019
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1020
+ }
960
1021
  function combineRules(parentRule, childRule) {
961
- const parentCondition = parentRule.condition;
962
- const childCondition = childRule.condition;
963
1022
  return {
964
1023
  effect: "SHOW",
965
1024
  condition: {
966
1025
  scope: "#",
967
1026
  schema: {
968
1027
  allOf: [
969
- {
970
- properties: {
971
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
972
- }
973
- },
974
- {
975
- properties: {
976
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
977
- }
978
- }
1028
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1029
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
979
1030
  ]
980
1031
  }
981
1032
  }
@@ -983,10 +1034,14 @@ function combineRules(parentRule, childRule) {
983
1034
  }
984
1035
  function fieldNodeToControl(field, parentRule) {
985
1036
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1037
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
986
1038
  const control = {
987
1039
  type: "Control",
988
1040
  scope: fieldToScope(field.name),
989
1041
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1042
+ ...placeholderAnnotation !== void 0 && {
1043
+ options: { placeholder: placeholderAnnotation.value }
1044
+ },
990
1045
  ...parentRule !== void 0 && { rule: parentRule }
991
1046
  };
992
1047
  return control;
@@ -1296,7 +1351,7 @@ function createFormSpecTSDocConfig() {
1296
1351
  })
1297
1352
  );
1298
1353
  }
1299
- for (const tagName of ["displayName", "description"]) {
1354
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1300
1355
  config.addTagDefinition(
1301
1356
  new import_tsdoc.TSDocTagDefinition({
1302
1357
  tagName: "@" + tagName,
@@ -1314,6 +1369,12 @@ function getParser() {
1314
1369
  function parseTSDocTags(node, file = "") {
1315
1370
  const constraints = [];
1316
1371
  const annotations = [];
1372
+ let displayName;
1373
+ let description;
1374
+ let placeholder;
1375
+ let displayNameProvenance;
1376
+ let descriptionProvenance;
1377
+ let placeholderProvenance;
1317
1378
  const sourceFile = node.getSourceFile();
1318
1379
  const sourceText = sourceFile.getFullText();
1319
1380
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1333,30 +1394,37 @@ function parseTSDocTags(node, file = "") {
1333
1394
  const docComment = parserContext.docComment;
1334
1395
  for (const block of docComment.customBlocks) {
1335
1396
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1336
- if (tagName === "displayName" || tagName === "description") {
1397
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1337
1398
  const text2 = extractBlockText(block).trim();
1338
1399
  if (text2 === "") continue;
1339
1400
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1340
1401
  if (tagName === "displayName") {
1402
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1403
+ displayName = text2;
1404
+ displayNameProvenance = provenance2;
1405
+ }
1406
+ } else if (tagName === "format") {
1341
1407
  annotations.push({
1342
1408
  kind: "annotation",
1343
- annotationKind: "displayName",
1409
+ annotationKind: "format",
1344
1410
  value: text2,
1345
1411
  provenance: provenance2
1346
1412
  });
1347
1413
  } else {
1348
- annotations.push({
1349
- kind: "annotation",
1350
- annotationKind: "description",
1351
- value: text2,
1352
- provenance: provenance2
1353
- });
1414
+ if (tagName === "description" && description === void 0) {
1415
+ description = text2;
1416
+ descriptionProvenance = provenance2;
1417
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1418
+ placeholder = text2;
1419
+ placeholderProvenance = provenance2;
1420
+ }
1354
1421
  }
1355
1422
  continue;
1356
1423
  }
1357
1424
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1358
1425
  const text = extractBlockText(block).trim();
1359
- if (text === "") continue;
1426
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1427
+ if (text === "" && expectedType !== "boolean") continue;
1360
1428
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1361
1429
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1362
1430
  if (constraintNode) {
@@ -1364,14 +1432,47 @@ function parseTSDocTags(node, file = "") {
1364
1432
  }
1365
1433
  }
1366
1434
  if (docComment.deprecatedBlock !== void 0) {
1435
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1367
1436
  annotations.push({
1368
1437
  kind: "annotation",
1369
1438
  annotationKind: "deprecated",
1439
+ ...message !== "" && { message },
1370
1440
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1371
1441
  });
1372
1442
  }
1443
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1444
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1445
+ if (remarks !== "") {
1446
+ description = remarks;
1447
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1448
+ }
1449
+ }
1373
1450
  }
1374
1451
  }
1452
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1453
+ annotations.push({
1454
+ kind: "annotation",
1455
+ annotationKind: "displayName",
1456
+ value: displayName,
1457
+ provenance: displayNameProvenance
1458
+ });
1459
+ }
1460
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1461
+ annotations.push({
1462
+ kind: "annotation",
1463
+ annotationKind: "description",
1464
+ value: description,
1465
+ provenance: descriptionProvenance
1466
+ });
1467
+ }
1468
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1469
+ annotations.push({
1470
+ kind: "annotation",
1471
+ annotationKind: "placeholder",
1472
+ value: placeholder,
1473
+ provenance: placeholderProvenance
1474
+ });
1475
+ }
1375
1476
  const jsDocTagsAll = ts2.getJSDocTags(node);
1376
1477
  for (const tag of jsDocTagsAll) {
1377
1478
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1380,6 +1481,11 @@ function parseTSDocTags(node, file = "") {
1380
1481
  if (commentText === void 0 || commentText.trim() === "") continue;
1381
1482
  const text = commentText.trim();
1382
1483
  const provenance = provenanceForJSDocTag(tag, file);
1484
+ if (tagName === "defaultValue") {
1485
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1486
+ annotations.push(defaultValueNode);
1487
+ continue;
1488
+ }
1383
1489
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1384
1490
  if (constraintNode) {
1385
1491
  constraints.push(constraintNode);
@@ -1387,6 +1493,28 @@ function parseTSDocTags(node, file = "") {
1387
1493
  }
1388
1494
  return { constraints, annotations };
1389
1495
  }
1496
+ function extractDisplayNameMetadata(node) {
1497
+ let displayName;
1498
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1499
+ for (const tag of ts2.getJSDocTags(node)) {
1500
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1501
+ if (tagName !== "displayName") continue;
1502
+ const commentText = getTagCommentText(tag);
1503
+ if (commentText === void 0) continue;
1504
+ const text = commentText.trim();
1505
+ if (text === "") continue;
1506
+ const memberTarget = parseMemberTargetDisplayName(text);
1507
+ if (memberTarget) {
1508
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1509
+ continue;
1510
+ }
1511
+ displayName ??= text;
1512
+ }
1513
+ return {
1514
+ ...displayName !== void 0 && { displayName },
1515
+ memberDisplayNames
1516
+ };
1517
+ }
1390
1518
  function extractPathTarget(text) {
1391
1519
  const trimmed = text.trimStart();
1392
1520
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1449,7 +1577,45 @@ function parseConstraintValue(tagName, text, provenance) {
1449
1577
  }
1450
1578
  return null;
1451
1579
  }
1580
+ if (expectedType === "boolean") {
1581
+ const trimmed = effectiveText.trim();
1582
+ if (trimmed !== "" && trimmed !== "true") {
1583
+ return null;
1584
+ }
1585
+ if (tagName === "uniqueItems") {
1586
+ return {
1587
+ kind: "constraint",
1588
+ constraintKind: "uniqueItems",
1589
+ value: true,
1590
+ ...path4 && { path: path4 },
1591
+ provenance
1592
+ };
1593
+ }
1594
+ return null;
1595
+ }
1452
1596
  if (expectedType === "json") {
1597
+ if (tagName === "const") {
1598
+ const trimmedText = effectiveText.trim();
1599
+ if (trimmedText === "") return null;
1600
+ try {
1601
+ const parsed2 = JSON.parse(trimmedText);
1602
+ return {
1603
+ kind: "constraint",
1604
+ constraintKind: "const",
1605
+ value: parsed2,
1606
+ ...path4 && { path: path4 },
1607
+ provenance
1608
+ };
1609
+ } catch {
1610
+ return {
1611
+ kind: "constraint",
1612
+ constraintKind: "const",
1613
+ value: trimmedText,
1614
+ ...path4 && { path: path4 },
1615
+ provenance
1616
+ };
1617
+ }
1618
+ }
1453
1619
  const parsed = tryParseJson(effectiveText);
1454
1620
  if (!Array.isArray(parsed)) {
1455
1621
  return null;
@@ -1481,6 +1647,34 @@ function parseConstraintValue(tagName, text, provenance) {
1481
1647
  provenance
1482
1648
  };
1483
1649
  }
1650
+ function parseDefaultValueValue(text, provenance) {
1651
+ const trimmed = text.trim();
1652
+ let value;
1653
+ if (trimmed === "null") {
1654
+ value = null;
1655
+ } else if (trimmed === "true") {
1656
+ value = true;
1657
+ } else if (trimmed === "false") {
1658
+ value = false;
1659
+ } else {
1660
+ const parsed = tryParseJson(trimmed);
1661
+ value = parsed !== null ? parsed : trimmed;
1662
+ }
1663
+ return {
1664
+ kind: "annotation",
1665
+ annotationKind: "defaultValue",
1666
+ value,
1667
+ provenance
1668
+ };
1669
+ }
1670
+ function isMemberTargetDisplayName(text) {
1671
+ return parseMemberTargetDisplayName(text) !== null;
1672
+ }
1673
+ function parseMemberTargetDisplayName(text) {
1674
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1675
+ if (!match?.[1] || !match[2]) return null;
1676
+ return { target: match[1], label: match[2].trim() };
1677
+ }
1484
1678
  function provenanceForComment(range, sourceFile, file, tagName) {
1485
1679
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1486
1680
  return {
@@ -1532,7 +1726,7 @@ var init_tsdoc_parser = __esm({
1532
1726
  minItems: "minItems",
1533
1727
  maxItems: "maxItems"
1534
1728
  };
1535
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1729
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1536
1730
  }
1537
1731
  });
1538
1732
 
@@ -1599,6 +1793,7 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1599
1793
  const fields = [];
1600
1794
  const fieldLayouts = [];
1601
1795
  const typeRegistry = {};
1796
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1602
1797
  const visiting = /* @__PURE__ */ new Set();
1603
1798
  const instanceMethods = [];
1604
1799
  const staticMethods = [];
@@ -1621,12 +1816,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1621
1816
  }
1622
1817
  }
1623
1818
  }
1624
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1819
+ return {
1820
+ name,
1821
+ fields,
1822
+ fieldLayouts,
1823
+ typeRegistry,
1824
+ ...annotations.length > 0 && { annotations },
1825
+ instanceMethods,
1826
+ staticMethods
1827
+ };
1625
1828
  }
1626
1829
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1627
1830
  const name = interfaceDecl.name.text;
1628
1831
  const fields = [];
1629
1832
  const typeRegistry = {};
1833
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1630
1834
  const visiting = /* @__PURE__ */ new Set();
1631
1835
  for (const member of interfaceDecl.members) {
1632
1836
  if (ts4.isPropertySignature(member)) {
@@ -1637,7 +1841,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1637
1841
  }
1638
1842
  }
1639
1843
  const fieldLayouts = fields.map(() => ({}));
1640
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1844
+ return {
1845
+ name,
1846
+ fields,
1847
+ fieldLayouts,
1848
+ typeRegistry,
1849
+ ...annotations.length > 0 && { annotations },
1850
+ instanceMethods: [],
1851
+ staticMethods: []
1852
+ };
1641
1853
  }
1642
1854
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1643
1855
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1652,6 +1864,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1652
1864
  const name = typeAlias.name.text;
1653
1865
  const fields = [];
1654
1866
  const typeRegistry = {};
1867
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1655
1868
  const visiting = /* @__PURE__ */ new Set();
1656
1869
  for (const member of typeAlias.type.members) {
1657
1870
  if (ts4.isPropertySignature(member)) {
@@ -1668,6 +1881,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1668
1881
  fields,
1669
1882
  fieldLayouts: fields.map(() => ({})),
1670
1883
  typeRegistry,
1884
+ ...annotations.length > 0 && { annotations },
1671
1885
  instanceMethods: [],
1672
1886
  staticMethods: []
1673
1887
  }
@@ -1681,7 +1895,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1681
1895
  const tsType = checker.getTypeAtLocation(prop);
1682
1896
  const optional = prop.questionToken !== void 0;
1683
1897
  const provenance = provenanceForNode(prop, file);
1684
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1898
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1685
1899
  const constraints = [];
1686
1900
  if (prop.type) {
1687
1901
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1690,7 +1904,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1690
1904
  let annotations = [];
1691
1905
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1692
1906
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1693
- if (defaultAnnotation) {
1907
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1694
1908
  annotations.push(defaultAnnotation);
1695
1909
  }
1696
1910
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1712,7 +1926,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1712
1926
  const tsType = checker.getTypeAtLocation(prop);
1713
1927
  const optional = prop.questionToken !== void 0;
1714
1928
  const provenance = provenanceForNode(prop, file);
1715
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1929
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1716
1930
  const constraints = [];
1717
1931
  if (prop.type) {
1718
1932
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1793,7 +2007,7 @@ function parseEnumMemberDisplayName(value) {
1793
2007
  if (label === "") return null;
1794
2008
  return { value: match[1], label };
1795
2009
  }
1796
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
2010
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1797
2011
  if (type.flags & ts4.TypeFlags.String) {
1798
2012
  return { kind: "primitive", primitiveKind: "string" };
1799
2013
  }
@@ -1822,7 +2036,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1822
2036
  };
1823
2037
  }
1824
2038
  if (type.isUnion()) {
1825
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2039
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1826
2040
  }
1827
2041
  if (checker.isArrayType(type)) {
1828
2042
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1832,70 +2046,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1832
2046
  }
1833
2047
  return { kind: "primitive", primitiveKind: "string" };
1834
2048
  }
1835
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2049
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2050
+ const typeName = getNamedTypeName(type);
2051
+ const namedDecl = getNamedTypeDeclaration(type);
2052
+ if (typeName && typeName in typeRegistry) {
2053
+ return { kind: "reference", name: typeName, typeArguments: [] };
2054
+ }
1836
2055
  const allTypes = type.types;
1837
2056
  const nonNullTypes = allTypes.filter(
1838
2057
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1839
2058
  );
1840
2059
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2060
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2061
+ if (namedDecl) {
2062
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2063
+ memberDisplayNames.set(value, label);
2064
+ }
2065
+ }
2066
+ if (sourceNode) {
2067
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2068
+ memberDisplayNames.set(value, label);
2069
+ }
2070
+ }
2071
+ const registerNamed = (result) => {
2072
+ if (!typeName) {
2073
+ return result;
2074
+ }
2075
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2076
+ typeRegistry[typeName] = {
2077
+ name: typeName,
2078
+ type: result,
2079
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2080
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2081
+ };
2082
+ return { kind: "reference", name: typeName, typeArguments: [] };
2083
+ };
2084
+ const applyMemberLabels = (members2) => members2.map((value) => {
2085
+ const displayName = memberDisplayNames.get(String(value));
2086
+ return displayName !== void 0 ? { value, displayName } : { value };
2087
+ });
1841
2088
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1842
2089
  if (isBooleanUnion2) {
1843
2090
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1844
- if (hasNull) {
1845
- return {
1846
- kind: "union",
1847
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1848
- };
1849
- }
1850
- return boolNode;
2091
+ const result = hasNull ? {
2092
+ kind: "union",
2093
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2094
+ } : boolNode;
2095
+ return registerNamed(result);
1851
2096
  }
1852
2097
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1853
2098
  if (allStringLiterals && nonNullTypes.length > 0) {
1854
2099
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1855
2100
  const enumNode = {
1856
2101
  kind: "enum",
1857
- members: stringTypes.map((t) => ({ value: t.value }))
2102
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1858
2103
  };
1859
- if (hasNull) {
1860
- return {
1861
- kind: "union",
1862
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1863
- };
1864
- }
1865
- return enumNode;
2104
+ const result = hasNull ? {
2105
+ kind: "union",
2106
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2107
+ } : enumNode;
2108
+ return registerNamed(result);
1866
2109
  }
1867
2110
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1868
2111
  if (allNumberLiterals && nonNullTypes.length > 0) {
1869
2112
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1870
2113
  const enumNode = {
1871
2114
  kind: "enum",
1872
- members: numberTypes.map((t) => ({ value: t.value }))
2115
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1873
2116
  };
1874
- if (hasNull) {
1875
- return {
1876
- kind: "union",
1877
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1878
- };
1879
- }
1880
- return enumNode;
2117
+ const result = hasNull ? {
2118
+ kind: "union",
2119
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2120
+ } : enumNode;
2121
+ return registerNamed(result);
1881
2122
  }
1882
2123
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1883
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1884
- if (hasNull) {
1885
- return {
1886
- kind: "union",
1887
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1888
- };
1889
- }
1890
- return inner;
2124
+ const inner = resolveTypeNode(
2125
+ nonNullTypes[0],
2126
+ checker,
2127
+ file,
2128
+ typeRegistry,
2129
+ visiting,
2130
+ sourceNode
2131
+ );
2132
+ const result = hasNull ? {
2133
+ kind: "union",
2134
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2135
+ } : inner;
2136
+ return registerNamed(result);
1891
2137
  }
1892
2138
  const members = nonNullTypes.map(
1893
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2139
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1894
2140
  );
1895
2141
  if (hasNull) {
1896
2142
  members.push({ kind: "primitive", primitiveKind: "null" });
1897
2143
  }
1898
- return { kind: "union", members };
2144
+ return registerNamed({ kind: "union", members });
1899
2145
  }
1900
2146
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1901
2147
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1911,30 +2157,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1911
2157
  if (!indexInfo) {
1912
2158
  return null;
1913
2159
  }
1914
- if (visiting.has(type)) {
1915
- return null;
1916
- }
1917
- visiting.add(type);
1918
- try {
1919
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1920
- return { kind: "record", valueType };
1921
- } finally {
1922
- visiting.delete(type);
2160
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2161
+ return { kind: "record", valueType };
2162
+ }
2163
+ function typeNodeContainsReference(type, targetName) {
2164
+ switch (type.kind) {
2165
+ case "reference":
2166
+ return type.name === targetName;
2167
+ case "array":
2168
+ return typeNodeContainsReference(type.items, targetName);
2169
+ case "record":
2170
+ return typeNodeContainsReference(type.valueType, targetName);
2171
+ case "union":
2172
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2173
+ case "object":
2174
+ return type.properties.some(
2175
+ (property) => typeNodeContainsReference(property.type, targetName)
2176
+ );
2177
+ case "primitive":
2178
+ case "enum":
2179
+ case "dynamic":
2180
+ case "custom":
2181
+ return false;
2182
+ default: {
2183
+ const _exhaustive = type;
2184
+ return _exhaustive;
2185
+ }
1923
2186
  }
1924
2187
  }
1925
2188
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1926
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1927
- if (recordNode) {
1928
- return recordNode;
1929
- }
2189
+ const typeName = getNamedTypeName(type);
2190
+ const namedTypeName = typeName ?? void 0;
2191
+ const namedDecl = getNamedTypeDeclaration(type);
2192
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2193
+ const clearNamedTypeRegistration = () => {
2194
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2195
+ return;
2196
+ }
2197
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2198
+ };
1930
2199
  if (visiting.has(type)) {
2200
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2201
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2202
+ }
1931
2203
  return { kind: "object", properties: [], additionalProperties: false };
1932
2204
  }
2205
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2206
+ typeRegistry[namedTypeName] = {
2207
+ name: namedTypeName,
2208
+ type: RESOLVING_TYPE_PLACEHOLDER,
2209
+ provenance: provenanceForDeclaration(namedDecl, file)
2210
+ };
2211
+ }
1933
2212
  visiting.add(type);
1934
- const typeName = getNamedTypeName(type);
1935
- if (typeName && typeName in typeRegistry) {
2213
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2214
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2215
+ visiting.delete(type);
2216
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2217
+ }
2218
+ }
2219
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2220
+ if (recordNode) {
1936
2221
  visiting.delete(type);
1937
- return { kind: "reference", name: typeName, typeArguments: [] };
2222
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2223
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2224
+ if (!isRecursiveRecord) {
2225
+ clearNamedTypeRegistration();
2226
+ return recordNode;
2227
+ }
2228
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2229
+ typeRegistry[namedTypeName] = {
2230
+ name: namedTypeName,
2231
+ type: recordNode,
2232
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2233
+ provenance: provenanceForDeclaration(namedDecl, file)
2234
+ };
2235
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2236
+ }
2237
+ return recordNode;
1938
2238
  }
1939
2239
  const properties = [];
1940
2240
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1943,7 +2243,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1943
2243
  if (!declaration) continue;
1944
2244
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1945
2245
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1946
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2246
+ const propTypeNode = resolveTypeNode(
2247
+ propType,
2248
+ checker,
2249
+ file,
2250
+ typeRegistry,
2251
+ visiting,
2252
+ declaration
2253
+ );
1947
2254
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1948
2255
  properties.push({
1949
2256
  name: prop.name,
@@ -1960,13 +2267,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1960
2267
  properties,
1961
2268
  additionalProperties: true
1962
2269
  };
1963
- if (typeName) {
1964
- typeRegistry[typeName] = {
1965
- name: typeName,
2270
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2271
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2272
+ typeRegistry[namedTypeName] = {
2273
+ name: namedTypeName,
1966
2274
  type: objectNode,
1967
- provenance: provenanceForFile(file)
2275
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2276
+ provenance: provenanceForDeclaration(namedDecl, file)
1968
2277
  };
1969
- return { kind: "reference", name: typeName, typeArguments: [] };
2278
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1970
2279
  }
1971
2280
  return objectNode;
1972
2281
  }
@@ -2057,6 +2366,12 @@ function provenanceForNode(node, file) {
2057
2366
  function provenanceForFile(file) {
2058
2367
  return { surface: "tsdoc", file, line: 0, column: 0 };
2059
2368
  }
2369
+ function provenanceForDeclaration(node, file) {
2370
+ if (!node) {
2371
+ return provenanceForFile(file);
2372
+ }
2373
+ return provenanceForNode(node, file);
2374
+ }
2060
2375
  function getNamedTypeName(type) {
2061
2376
  const symbol = type.getSymbol();
2062
2377
  if (symbol?.declarations) {
@@ -2075,6 +2390,20 @@ function getNamedTypeName(type) {
2075
2390
  }
2076
2391
  return null;
2077
2392
  }
2393
+ function getNamedTypeDeclaration(type) {
2394
+ const symbol = type.getSymbol();
2395
+ if (symbol?.declarations) {
2396
+ const decl = symbol.declarations[0];
2397
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2398
+ return decl;
2399
+ }
2400
+ }
2401
+ const aliasSymbol = type.aliasSymbol;
2402
+ if (aliasSymbol?.declarations) {
2403
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2404
+ }
2405
+ return void 0;
2406
+ }
2078
2407
  function analyzeMethod(method, checker) {
2079
2408
  if (!ts4.isIdentifier(method.name)) {
2080
2409
  return null;
@@ -2115,12 +2444,18 @@ function detectFormSpecReference(typeNode) {
2115
2444
  }
2116
2445
  return null;
2117
2446
  }
2118
- var ts4, MAX_ALIAS_CHAIN_DEPTH;
2447
+ var ts4, RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2119
2448
  var init_class_analyzer = __esm({
2120
2449
  "src/analyzer/class-analyzer.ts"() {
2121
2450
  "use strict";
2122
2451
  ts4 = __toESM(require("typescript"), 1);
2123
2452
  init_jsdoc_constraints();
2453
+ init_tsdoc_parser();
2454
+ RESOLVING_TYPE_PLACEHOLDER = {
2455
+ kind: "object",
2456
+ properties: [],
2457
+ additionalProperties: true
2458
+ };
2124
2459
  MAX_ALIAS_CHAIN_DEPTH = 8;
2125
2460
  }
2126
2461
  });
@@ -2178,10 +2513,220 @@ var init_class_schema = __esm({
2178
2513
  }
2179
2514
  });
2180
2515
 
2516
+ // src/generators/mixed-authoring.ts
2517
+ function buildMixedAuthoringSchemas(options) {
2518
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2519
+ const analysis = analyzeNamedType(filePath, typeName);
2520
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2521
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2522
+ return {
2523
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2524
+ uiSchema: generateUiSchemaFromIR(ir)
2525
+ };
2526
+ }
2527
+ function analyzeNamedType(filePath, typeName) {
2528
+ const ctx = createProgramContext(filePath);
2529
+ const source = { file: filePath };
2530
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2531
+ if (classDecl !== null) {
2532
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2533
+ }
2534
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2535
+ if (interfaceDecl !== null) {
2536
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2537
+ }
2538
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2539
+ if (typeAlias !== null) {
2540
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2541
+ if (result.ok) {
2542
+ return result.analysis;
2543
+ }
2544
+ throw new Error(result.error);
2545
+ }
2546
+ throw new Error(
2547
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2548
+ );
2549
+ }
2550
+ function composeAnalysisWithOverlays(analysis, overlays) {
2551
+ const overlayIR = canonicalizeChainDSL(overlays);
2552
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2553
+ if (overlayFields.length === 0) {
2554
+ return analysis;
2555
+ }
2556
+ const overlayByName = /* @__PURE__ */ new Map();
2557
+ for (const field of overlayFields) {
2558
+ if (overlayByName.has(field.name)) {
2559
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2560
+ }
2561
+ overlayByName.set(field.name, field);
2562
+ }
2563
+ const mergedFields = [];
2564
+ for (const baseField of analysis.fields) {
2565
+ const overlayField = overlayByName.get(baseField.name);
2566
+ if (overlayField === void 0) {
2567
+ mergedFields.push(baseField);
2568
+ continue;
2569
+ }
2570
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2571
+ overlayByName.delete(baseField.name);
2572
+ }
2573
+ if (overlayByName.size > 0) {
2574
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2575
+ throw new Error(
2576
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2577
+ );
2578
+ }
2579
+ return {
2580
+ ...analysis,
2581
+ fields: mergedFields
2582
+ };
2583
+ }
2584
+ function collectOverlayFields(elements) {
2585
+ const fields = [];
2586
+ for (const element of elements) {
2587
+ switch (element.kind) {
2588
+ case "field":
2589
+ fields.push(element);
2590
+ break;
2591
+ case "group":
2592
+ fields.push(...collectOverlayFields(element.elements));
2593
+ break;
2594
+ case "conditional":
2595
+ fields.push(...collectOverlayFields(element.elements));
2596
+ break;
2597
+ default: {
2598
+ const _exhaustive = element;
2599
+ void _exhaustive;
2600
+ }
2601
+ }
2602
+ }
2603
+ return fields;
2604
+ }
2605
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2606
+ assertSupportedOverlayField(baseField, overlayField);
2607
+ return {
2608
+ ...baseField,
2609
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2610
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2611
+ };
2612
+ }
2613
+ function assertSupportedOverlayField(baseField, overlayField) {
2614
+ if (overlayField.constraints.length > 0) {
2615
+ throw new Error(
2616
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2617
+ );
2618
+ }
2619
+ if (overlayField.required) {
2620
+ throw new Error(
2621
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2622
+ );
2623
+ }
2624
+ }
2625
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2626
+ const { type: baseType } = baseField;
2627
+ const { type: overlayType } = overlayField;
2628
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2629
+ throw new Error(
2630
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2631
+ );
2632
+ }
2633
+ if (overlayType.kind === "dynamic") {
2634
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2635
+ throw new Error(
2636
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2637
+ );
2638
+ }
2639
+ return overlayType;
2640
+ }
2641
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2642
+ throw new Error(
2643
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2644
+ );
2645
+ }
2646
+ return baseType;
2647
+ }
2648
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2649
+ const overlayType = overlayField.type;
2650
+ if (overlayType.kind !== "dynamic") {
2651
+ return false;
2652
+ }
2653
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2654
+ if (resolvedBaseType === null) {
2655
+ return false;
2656
+ }
2657
+ if (overlayType.dynamicKind === "enum") {
2658
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2659
+ }
2660
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2661
+ }
2662
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2663
+ if (type.kind !== "reference") {
2664
+ return type;
2665
+ }
2666
+ if (seen.has(type.name)) {
2667
+ return null;
2668
+ }
2669
+ const definition = typeRegistry[type.name];
2670
+ if (definition === void 0) {
2671
+ return null;
2672
+ }
2673
+ seen.add(type.name);
2674
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2675
+ }
2676
+ function isSameStaticTypeShape(baseType, overlayType) {
2677
+ if (baseType.kind !== overlayType.kind) {
2678
+ return false;
2679
+ }
2680
+ switch (baseType.kind) {
2681
+ case "primitive":
2682
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2683
+ case "enum":
2684
+ return overlayType.kind === "enum";
2685
+ case "dynamic":
2686
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2687
+ case "record":
2688
+ return overlayType.kind === "record";
2689
+ case "reference":
2690
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2691
+ case "union":
2692
+ return overlayType.kind === "union";
2693
+ case "custom":
2694
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2695
+ case "object":
2696
+ case "array":
2697
+ return true;
2698
+ default: {
2699
+ const _exhaustive = baseType;
2700
+ return _exhaustive;
2701
+ }
2702
+ }
2703
+ }
2704
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2705
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2706
+ const overlayOnly = overlayAnnotations.filter(
2707
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2708
+ );
2709
+ return [...overlayOnly, ...baseAnnotations];
2710
+ }
2711
+ function annotationKey(annotation) {
2712
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2713
+ }
2714
+ var init_mixed_authoring = __esm({
2715
+ "src/generators/mixed-authoring.ts"() {
2716
+ "use strict";
2717
+ init_ir_generator();
2718
+ init_ir_generator2();
2719
+ init_canonicalize();
2720
+ init_program();
2721
+ init_class_analyzer();
2722
+ }
2723
+ });
2724
+
2181
2725
  // src/index.ts
2182
2726
  var index_exports = {};
2183
2727
  __export(index_exports, {
2184
2728
  buildFormSchemas: () => buildFormSchemas,
2729
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
2185
2730
  categorizationSchema: () => categorizationSchema,
2186
2731
  categorySchema: () => categorySchema,
2187
2732
  controlSchema: () => controlSchema,
@@ -2247,6 +2792,7 @@ var init_index = __esm({
2247
2792
  init_ir_generator();
2248
2793
  init_generator2();
2249
2794
  init_class_schema();
2795
+ init_mixed_authoring();
2250
2796
  }
2251
2797
  });
2252
2798