@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.js CHANGED
@@ -342,6 +342,7 @@ function canonicalizeTSDoc(analysis, source) {
342
342
  irVersion: IR_VERSION2,
343
343
  elements,
344
344
  typeRegistry: analysis.typeRegistry,
345
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
345
346
  provenance
346
347
  };
347
348
  }
@@ -432,6 +433,9 @@ function generateJsonSchemaFromIR(ir, options) {
432
433
  const ctx = makeContext(options);
433
434
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
434
435
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
436
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
437
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
438
+ }
435
439
  }
436
440
  const properties = {};
437
441
  const required = [];
@@ -443,6 +447,9 @@ function generateJsonSchemaFromIR(ir, options) {
443
447
  properties,
444
448
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
445
449
  };
450
+ if (ir.annotations && ir.annotations.length > 0) {
451
+ applyAnnotations(result, ir.annotations, ctx);
452
+ }
446
453
  if (Object.keys(ctx.defs).length > 0) {
447
454
  result.$defs = ctx.defs;
448
455
  }
@@ -472,22 +479,51 @@ function collectFields(elements, properties, required, ctx) {
472
479
  }
473
480
  function generateFieldSchema(field, ctx) {
474
481
  const schema = generateTypeNode(field.type, ctx);
482
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
475
483
  const directConstraints = [];
484
+ const itemConstraints = [];
476
485
  const pathConstraints = [];
477
486
  for (const c of field.constraints) {
478
487
  if (c.path) {
479
488
  pathConstraints.push(c);
489
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
490
+ itemConstraints.push(c);
480
491
  } else {
481
492
  directConstraints.push(c);
482
493
  }
483
494
  }
484
495
  applyConstraints(schema, directConstraints, ctx);
485
- applyAnnotations(schema, field.annotations, ctx);
496
+ if (itemStringSchema !== void 0) {
497
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
498
+ }
499
+ const rootAnnotations = [];
500
+ const itemAnnotations = [];
501
+ for (const annotation of field.annotations) {
502
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
503
+ itemAnnotations.push(annotation);
504
+ } else {
505
+ rootAnnotations.push(annotation);
506
+ }
507
+ }
508
+ applyAnnotations(schema, rootAnnotations, ctx);
509
+ if (itemStringSchema !== void 0) {
510
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
511
+ }
486
512
  if (pathConstraints.length === 0) {
487
513
  return schema;
488
514
  }
489
515
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
490
516
  }
517
+ function isStringItemConstraint(constraint) {
518
+ switch (constraint.constraintKind) {
519
+ case "minLength":
520
+ case "maxLength":
521
+ case "pattern":
522
+ return true;
523
+ default:
524
+ return false;
525
+ }
526
+ }
491
527
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
492
528
  if (schema.type === "array" && schema.items) {
493
529
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -705,6 +741,9 @@ function applyConstraints(schema, constraints, ctx) {
705
741
  case "uniqueItems":
706
742
  schema.uniqueItems = constraint.value;
707
743
  break;
744
+ case "const":
745
+ schema.const = constraint.value;
746
+ break;
708
747
  case "allowedMembers":
709
748
  break;
710
749
  case "custom":
@@ -729,8 +768,14 @@ function applyAnnotations(schema, annotations, ctx) {
729
768
  case "defaultValue":
730
769
  schema.default = annotation.value;
731
770
  break;
771
+ case "format":
772
+ schema.format = annotation.value;
773
+ break;
732
774
  case "deprecated":
733
775
  schema.deprecated = true;
776
+ if (annotation.message !== void 0 && annotation.message !== "") {
777
+ schema["x-formspec-deprecation-description"] = annotation.message;
778
+ }
734
779
  break;
735
780
  case "placeholder":
736
781
  break;
@@ -936,25 +981,31 @@ function createShowRule(fieldName, value) {
936
981
  }
937
982
  };
938
983
  }
984
+ function flattenConditionSchema(scope, schema) {
985
+ if (schema.allOf === void 0) {
986
+ if (scope === "#") {
987
+ return [schema];
988
+ }
989
+ const fieldName = scope.replace("#/properties/", "");
990
+ return [
991
+ {
992
+ properties: {
993
+ [fieldName]: schema
994
+ }
995
+ }
996
+ ];
997
+ }
998
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
999
+ }
939
1000
  function combineRules(parentRule, childRule) {
940
- const parentCondition = parentRule.condition;
941
- const childCondition = childRule.condition;
942
1001
  return {
943
1002
  effect: "SHOW",
944
1003
  condition: {
945
1004
  scope: "#",
946
1005
  schema: {
947
1006
  allOf: [
948
- {
949
- properties: {
950
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
951
- }
952
- },
953
- {
954
- properties: {
955
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
956
- }
957
- }
1007
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1008
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
958
1009
  ]
959
1010
  }
960
1011
  }
@@ -962,10 +1013,14 @@ function combineRules(parentRule, childRule) {
962
1013
  }
963
1014
  function fieldNodeToControl(field, parentRule) {
964
1015
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1016
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
965
1017
  const control = {
966
1018
  type: "Control",
967
1019
  scope: fieldToScope(field.name),
968
1020
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1021
+ ...placeholderAnnotation !== void 0 && {
1022
+ options: { placeholder: placeholderAnnotation.value }
1023
+ },
969
1024
  ...parentRule !== void 0 && { rule: parentRule }
970
1025
  };
971
1026
  return control;
@@ -1287,7 +1342,7 @@ function createFormSpecTSDocConfig() {
1287
1342
  })
1288
1343
  );
1289
1344
  }
1290
- for (const tagName of ["displayName", "description"]) {
1345
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1291
1346
  config.addTagDefinition(
1292
1347
  new TSDocTagDefinition({
1293
1348
  tagName: "@" + tagName,
@@ -1305,6 +1360,12 @@ function getParser() {
1305
1360
  function parseTSDocTags(node, file = "") {
1306
1361
  const constraints = [];
1307
1362
  const annotations = [];
1363
+ let displayName;
1364
+ let description;
1365
+ let placeholder;
1366
+ let displayNameProvenance;
1367
+ let descriptionProvenance;
1368
+ let placeholderProvenance;
1308
1369
  const sourceFile = node.getSourceFile();
1309
1370
  const sourceText = sourceFile.getFullText();
1310
1371
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1324,30 +1385,37 @@ function parseTSDocTags(node, file = "") {
1324
1385
  const docComment = parserContext.docComment;
1325
1386
  for (const block of docComment.customBlocks) {
1326
1387
  const tagName = normalizeConstraintTagName(block.blockTag.tagName.substring(1));
1327
- if (tagName === "displayName" || tagName === "description") {
1388
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1328
1389
  const text2 = extractBlockText(block).trim();
1329
1390
  if (text2 === "") continue;
1330
1391
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1331
1392
  if (tagName === "displayName") {
1393
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1394
+ displayName = text2;
1395
+ displayNameProvenance = provenance2;
1396
+ }
1397
+ } else if (tagName === "format") {
1332
1398
  annotations.push({
1333
1399
  kind: "annotation",
1334
- annotationKind: "displayName",
1400
+ annotationKind: "format",
1335
1401
  value: text2,
1336
1402
  provenance: provenance2
1337
1403
  });
1338
1404
  } else {
1339
- annotations.push({
1340
- kind: "annotation",
1341
- annotationKind: "description",
1342
- value: text2,
1343
- provenance: provenance2
1344
- });
1405
+ if (tagName === "description" && description === void 0) {
1406
+ description = text2;
1407
+ descriptionProvenance = provenance2;
1408
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1409
+ placeholder = text2;
1410
+ placeholderProvenance = provenance2;
1411
+ }
1345
1412
  }
1346
1413
  continue;
1347
1414
  }
1348
1415
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1349
1416
  const text = extractBlockText(block).trim();
1350
- if (text === "") continue;
1417
+ const expectedType = isBuiltinConstraintName(tagName) ? BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1418
+ if (text === "" && expectedType !== "boolean") continue;
1351
1419
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1352
1420
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1353
1421
  if (constraintNode) {
@@ -1355,14 +1423,47 @@ function parseTSDocTags(node, file = "") {
1355
1423
  }
1356
1424
  }
1357
1425
  if (docComment.deprecatedBlock !== void 0) {
1426
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1358
1427
  annotations.push({
1359
1428
  kind: "annotation",
1360
1429
  annotationKind: "deprecated",
1430
+ ...message !== "" && { message },
1361
1431
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1362
1432
  });
1363
1433
  }
1434
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1435
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1436
+ if (remarks !== "") {
1437
+ description = remarks;
1438
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1439
+ }
1440
+ }
1364
1441
  }
1365
1442
  }
1443
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1444
+ annotations.push({
1445
+ kind: "annotation",
1446
+ annotationKind: "displayName",
1447
+ value: displayName,
1448
+ provenance: displayNameProvenance
1449
+ });
1450
+ }
1451
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1452
+ annotations.push({
1453
+ kind: "annotation",
1454
+ annotationKind: "description",
1455
+ value: description,
1456
+ provenance: descriptionProvenance
1457
+ });
1458
+ }
1459
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1460
+ annotations.push({
1461
+ kind: "annotation",
1462
+ annotationKind: "placeholder",
1463
+ value: placeholder,
1464
+ provenance: placeholderProvenance
1465
+ });
1466
+ }
1366
1467
  const jsDocTagsAll = ts2.getJSDocTags(node);
1367
1468
  for (const tag of jsDocTagsAll) {
1368
1469
  const tagName = normalizeConstraintTagName(tag.tagName.text);
@@ -1371,6 +1472,11 @@ function parseTSDocTags(node, file = "") {
1371
1472
  if (commentText === void 0 || commentText.trim() === "") continue;
1372
1473
  const text = commentText.trim();
1373
1474
  const provenance = provenanceForJSDocTag(tag, file);
1475
+ if (tagName === "defaultValue") {
1476
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1477
+ annotations.push(defaultValueNode);
1478
+ continue;
1479
+ }
1374
1480
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1375
1481
  if (constraintNode) {
1376
1482
  constraints.push(constraintNode);
@@ -1378,6 +1484,28 @@ function parseTSDocTags(node, file = "") {
1378
1484
  }
1379
1485
  return { constraints, annotations };
1380
1486
  }
1487
+ function extractDisplayNameMetadata(node) {
1488
+ let displayName;
1489
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1490
+ for (const tag of ts2.getJSDocTags(node)) {
1491
+ const tagName = normalizeConstraintTagName(tag.tagName.text);
1492
+ if (tagName !== "displayName") continue;
1493
+ const commentText = getTagCommentText(tag);
1494
+ if (commentText === void 0) continue;
1495
+ const text = commentText.trim();
1496
+ if (text === "") continue;
1497
+ const memberTarget = parseMemberTargetDisplayName(text);
1498
+ if (memberTarget) {
1499
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1500
+ continue;
1501
+ }
1502
+ displayName ??= text;
1503
+ }
1504
+ return {
1505
+ ...displayName !== void 0 && { displayName },
1506
+ memberDisplayNames
1507
+ };
1508
+ }
1381
1509
  function extractPathTarget(text) {
1382
1510
  const trimmed = text.trimStart();
1383
1511
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1440,7 +1568,45 @@ function parseConstraintValue(tagName, text, provenance) {
1440
1568
  }
1441
1569
  return null;
1442
1570
  }
1571
+ if (expectedType === "boolean") {
1572
+ const trimmed = effectiveText.trim();
1573
+ if (trimmed !== "" && trimmed !== "true") {
1574
+ return null;
1575
+ }
1576
+ if (tagName === "uniqueItems") {
1577
+ return {
1578
+ kind: "constraint",
1579
+ constraintKind: "uniqueItems",
1580
+ value: true,
1581
+ ...path4 && { path: path4 },
1582
+ provenance
1583
+ };
1584
+ }
1585
+ return null;
1586
+ }
1443
1587
  if (expectedType === "json") {
1588
+ if (tagName === "const") {
1589
+ const trimmedText = effectiveText.trim();
1590
+ if (trimmedText === "") return null;
1591
+ try {
1592
+ const parsed2 = JSON.parse(trimmedText);
1593
+ return {
1594
+ kind: "constraint",
1595
+ constraintKind: "const",
1596
+ value: parsed2,
1597
+ ...path4 && { path: path4 },
1598
+ provenance
1599
+ };
1600
+ } catch {
1601
+ return {
1602
+ kind: "constraint",
1603
+ constraintKind: "const",
1604
+ value: trimmedText,
1605
+ ...path4 && { path: path4 },
1606
+ provenance
1607
+ };
1608
+ }
1609
+ }
1444
1610
  const parsed = tryParseJson(effectiveText);
1445
1611
  if (!Array.isArray(parsed)) {
1446
1612
  return null;
@@ -1472,6 +1638,34 @@ function parseConstraintValue(tagName, text, provenance) {
1472
1638
  provenance
1473
1639
  };
1474
1640
  }
1641
+ function parseDefaultValueValue(text, provenance) {
1642
+ const trimmed = text.trim();
1643
+ let value;
1644
+ if (trimmed === "null") {
1645
+ value = null;
1646
+ } else if (trimmed === "true") {
1647
+ value = true;
1648
+ } else if (trimmed === "false") {
1649
+ value = false;
1650
+ } else {
1651
+ const parsed = tryParseJson(trimmed);
1652
+ value = parsed !== null ? parsed : trimmed;
1653
+ }
1654
+ return {
1655
+ kind: "annotation",
1656
+ annotationKind: "defaultValue",
1657
+ value,
1658
+ provenance
1659
+ };
1660
+ }
1661
+ function isMemberTargetDisplayName(text) {
1662
+ return parseMemberTargetDisplayName(text) !== null;
1663
+ }
1664
+ function parseMemberTargetDisplayName(text) {
1665
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1666
+ if (!match?.[1] || !match[2]) return null;
1667
+ return { target: match[1], label: match[2].trim() };
1668
+ }
1475
1669
  function provenanceForComment(range, sourceFile, file, tagName) {
1476
1670
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1477
1671
  return {
@@ -1520,7 +1714,7 @@ var init_tsdoc_parser = __esm({
1520
1714
  minItems: "minItems",
1521
1715
  maxItems: "maxItems"
1522
1716
  };
1523
- TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1717
+ TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1524
1718
  }
1525
1719
  });
1526
1720
 
@@ -1587,6 +1781,7 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1587
1781
  const fields = [];
1588
1782
  const fieldLayouts = [];
1589
1783
  const typeRegistry = {};
1784
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1590
1785
  const visiting = /* @__PURE__ */ new Set();
1591
1786
  const instanceMethods = [];
1592
1787
  const staticMethods = [];
@@ -1609,12 +1804,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1609
1804
  }
1610
1805
  }
1611
1806
  }
1612
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1807
+ return {
1808
+ name,
1809
+ fields,
1810
+ fieldLayouts,
1811
+ typeRegistry,
1812
+ ...annotations.length > 0 && { annotations },
1813
+ instanceMethods,
1814
+ staticMethods
1815
+ };
1613
1816
  }
1614
1817
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1615
1818
  const name = interfaceDecl.name.text;
1616
1819
  const fields = [];
1617
1820
  const typeRegistry = {};
1821
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1618
1822
  const visiting = /* @__PURE__ */ new Set();
1619
1823
  for (const member of interfaceDecl.members) {
1620
1824
  if (ts4.isPropertySignature(member)) {
@@ -1625,7 +1829,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1625
1829
  }
1626
1830
  }
1627
1831
  const fieldLayouts = fields.map(() => ({}));
1628
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1832
+ return {
1833
+ name,
1834
+ fields,
1835
+ fieldLayouts,
1836
+ typeRegistry,
1837
+ ...annotations.length > 0 && { annotations },
1838
+ instanceMethods: [],
1839
+ staticMethods: []
1840
+ };
1629
1841
  }
1630
1842
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1631
1843
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1640,6 +1852,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1640
1852
  const name = typeAlias.name.text;
1641
1853
  const fields = [];
1642
1854
  const typeRegistry = {};
1855
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1643
1856
  const visiting = /* @__PURE__ */ new Set();
1644
1857
  for (const member of typeAlias.type.members) {
1645
1858
  if (ts4.isPropertySignature(member)) {
@@ -1656,6 +1869,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1656
1869
  fields,
1657
1870
  fieldLayouts: fields.map(() => ({})),
1658
1871
  typeRegistry,
1872
+ ...annotations.length > 0 && { annotations },
1659
1873
  instanceMethods: [],
1660
1874
  staticMethods: []
1661
1875
  }
@@ -1669,7 +1883,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1669
1883
  const tsType = checker.getTypeAtLocation(prop);
1670
1884
  const optional = prop.questionToken !== void 0;
1671
1885
  const provenance = provenanceForNode(prop, file);
1672
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1886
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1673
1887
  const constraints = [];
1674
1888
  if (prop.type) {
1675
1889
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1678,7 +1892,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1678
1892
  let annotations = [];
1679
1893
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1680
1894
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1681
- if (defaultAnnotation) {
1895
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1682
1896
  annotations.push(defaultAnnotation);
1683
1897
  }
1684
1898
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1700,7 +1914,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1700
1914
  const tsType = checker.getTypeAtLocation(prop);
1701
1915
  const optional = prop.questionToken !== void 0;
1702
1916
  const provenance = provenanceForNode(prop, file);
1703
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1917
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1704
1918
  const constraints = [];
1705
1919
  if (prop.type) {
1706
1920
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1781,7 +1995,7 @@ function parseEnumMemberDisplayName(value) {
1781
1995
  if (label === "") return null;
1782
1996
  return { value: match[1], label };
1783
1997
  }
1784
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1998
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1785
1999
  if (type.flags & ts4.TypeFlags.String) {
1786
2000
  return { kind: "primitive", primitiveKind: "string" };
1787
2001
  }
@@ -1810,7 +2024,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1810
2024
  };
1811
2025
  }
1812
2026
  if (type.isUnion()) {
1813
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
2027
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1814
2028
  }
1815
2029
  if (checker.isArrayType(type)) {
1816
2030
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1820,70 +2034,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1820
2034
  }
1821
2035
  return { kind: "primitive", primitiveKind: "string" };
1822
2036
  }
1823
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
2037
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
2038
+ const typeName = getNamedTypeName(type);
2039
+ const namedDecl = getNamedTypeDeclaration(type);
2040
+ if (typeName && typeName in typeRegistry) {
2041
+ return { kind: "reference", name: typeName, typeArguments: [] };
2042
+ }
1824
2043
  const allTypes = type.types;
1825
2044
  const nonNullTypes = allTypes.filter(
1826
2045
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1827
2046
  );
1828
2047
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2048
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2049
+ if (namedDecl) {
2050
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2051
+ memberDisplayNames.set(value, label);
2052
+ }
2053
+ }
2054
+ if (sourceNode) {
2055
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2056
+ memberDisplayNames.set(value, label);
2057
+ }
2058
+ }
2059
+ const registerNamed = (result) => {
2060
+ if (!typeName) {
2061
+ return result;
2062
+ }
2063
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2064
+ typeRegistry[typeName] = {
2065
+ name: typeName,
2066
+ type: result,
2067
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2068
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2069
+ };
2070
+ return { kind: "reference", name: typeName, typeArguments: [] };
2071
+ };
2072
+ const applyMemberLabels = (members2) => members2.map((value) => {
2073
+ const displayName = memberDisplayNames.get(String(value));
2074
+ return displayName !== void 0 ? { value, displayName } : { value };
2075
+ });
1829
2076
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1830
2077
  if (isBooleanUnion2) {
1831
2078
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1832
- if (hasNull) {
1833
- return {
1834
- kind: "union",
1835
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1836
- };
1837
- }
1838
- return boolNode;
2079
+ const result = hasNull ? {
2080
+ kind: "union",
2081
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2082
+ } : boolNode;
2083
+ return registerNamed(result);
1839
2084
  }
1840
2085
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1841
2086
  if (allStringLiterals && nonNullTypes.length > 0) {
1842
2087
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1843
2088
  const enumNode = {
1844
2089
  kind: "enum",
1845
- members: stringTypes.map((t) => ({ value: t.value }))
2090
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1846
2091
  };
1847
- if (hasNull) {
1848
- return {
1849
- kind: "union",
1850
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1851
- };
1852
- }
1853
- return enumNode;
2092
+ const result = hasNull ? {
2093
+ kind: "union",
2094
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2095
+ } : enumNode;
2096
+ return registerNamed(result);
1854
2097
  }
1855
2098
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1856
2099
  if (allNumberLiterals && nonNullTypes.length > 0) {
1857
2100
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1858
2101
  const enumNode = {
1859
2102
  kind: "enum",
1860
- members: numberTypes.map((t) => ({ value: t.value }))
2103
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1861
2104
  };
1862
- if (hasNull) {
1863
- return {
1864
- kind: "union",
1865
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1866
- };
1867
- }
1868
- return enumNode;
2105
+ const result = hasNull ? {
2106
+ kind: "union",
2107
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2108
+ } : enumNode;
2109
+ return registerNamed(result);
1869
2110
  }
1870
2111
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1871
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1872
- if (hasNull) {
1873
- return {
1874
- kind: "union",
1875
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1876
- };
1877
- }
1878
- return inner;
2112
+ const inner = resolveTypeNode(
2113
+ nonNullTypes[0],
2114
+ checker,
2115
+ file,
2116
+ typeRegistry,
2117
+ visiting,
2118
+ sourceNode
2119
+ );
2120
+ const result = hasNull ? {
2121
+ kind: "union",
2122
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2123
+ } : inner;
2124
+ return registerNamed(result);
1879
2125
  }
1880
2126
  const members = nonNullTypes.map(
1881
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2127
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1882
2128
  );
1883
2129
  if (hasNull) {
1884
2130
  members.push({ kind: "primitive", primitiveKind: "null" });
1885
2131
  }
1886
- return { kind: "union", members };
2132
+ return registerNamed({ kind: "union", members });
1887
2133
  }
1888
2134
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1889
2135
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1899,30 +2145,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1899
2145
  if (!indexInfo) {
1900
2146
  return null;
1901
2147
  }
1902
- if (visiting.has(type)) {
1903
- return null;
1904
- }
1905
- visiting.add(type);
1906
- try {
1907
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1908
- return { kind: "record", valueType };
1909
- } finally {
1910
- visiting.delete(type);
2148
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2149
+ return { kind: "record", valueType };
2150
+ }
2151
+ function typeNodeContainsReference(type, targetName) {
2152
+ switch (type.kind) {
2153
+ case "reference":
2154
+ return type.name === targetName;
2155
+ case "array":
2156
+ return typeNodeContainsReference(type.items, targetName);
2157
+ case "record":
2158
+ return typeNodeContainsReference(type.valueType, targetName);
2159
+ case "union":
2160
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2161
+ case "object":
2162
+ return type.properties.some(
2163
+ (property) => typeNodeContainsReference(property.type, targetName)
2164
+ );
2165
+ case "primitive":
2166
+ case "enum":
2167
+ case "dynamic":
2168
+ case "custom":
2169
+ return false;
2170
+ default: {
2171
+ const _exhaustive = type;
2172
+ return _exhaustive;
2173
+ }
1911
2174
  }
1912
2175
  }
1913
2176
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1914
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1915
- if (recordNode) {
1916
- return recordNode;
1917
- }
2177
+ const typeName = getNamedTypeName(type);
2178
+ const namedTypeName = typeName ?? void 0;
2179
+ const namedDecl = getNamedTypeDeclaration(type);
2180
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2181
+ const clearNamedTypeRegistration = () => {
2182
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2183
+ return;
2184
+ }
2185
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2186
+ };
1918
2187
  if (visiting.has(type)) {
2188
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2189
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2190
+ }
1919
2191
  return { kind: "object", properties: [], additionalProperties: false };
1920
2192
  }
2193
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2194
+ typeRegistry[namedTypeName] = {
2195
+ name: namedTypeName,
2196
+ type: RESOLVING_TYPE_PLACEHOLDER,
2197
+ provenance: provenanceForDeclaration(namedDecl, file)
2198
+ };
2199
+ }
1921
2200
  visiting.add(type);
1922
- const typeName = getNamedTypeName(type);
1923
- if (typeName && typeName in typeRegistry) {
2201
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2202
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2203
+ visiting.delete(type);
2204
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2205
+ }
2206
+ }
2207
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2208
+ if (recordNode) {
1924
2209
  visiting.delete(type);
1925
- return { kind: "reference", name: typeName, typeArguments: [] };
2210
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2211
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2212
+ if (!isRecursiveRecord) {
2213
+ clearNamedTypeRegistration();
2214
+ return recordNode;
2215
+ }
2216
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2217
+ typeRegistry[namedTypeName] = {
2218
+ name: namedTypeName,
2219
+ type: recordNode,
2220
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2221
+ provenance: provenanceForDeclaration(namedDecl, file)
2222
+ };
2223
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2224
+ }
2225
+ return recordNode;
1926
2226
  }
1927
2227
  const properties = [];
1928
2228
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1931,7 +2231,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1931
2231
  if (!declaration) continue;
1932
2232
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1933
2233
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1934
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2234
+ const propTypeNode = resolveTypeNode(
2235
+ propType,
2236
+ checker,
2237
+ file,
2238
+ typeRegistry,
2239
+ visiting,
2240
+ declaration
2241
+ );
1935
2242
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1936
2243
  properties.push({
1937
2244
  name: prop.name,
@@ -1948,13 +2255,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1948
2255
  properties,
1949
2256
  additionalProperties: true
1950
2257
  };
1951
- if (typeName) {
1952
- typeRegistry[typeName] = {
1953
- name: typeName,
2258
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2259
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2260
+ typeRegistry[namedTypeName] = {
2261
+ name: namedTypeName,
1954
2262
  type: objectNode,
1955
- provenance: provenanceForFile(file)
2263
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2264
+ provenance: provenanceForDeclaration(namedDecl, file)
1956
2265
  };
1957
- return { kind: "reference", name: typeName, typeArguments: [] };
2266
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1958
2267
  }
1959
2268
  return objectNode;
1960
2269
  }
@@ -2045,6 +2354,12 @@ function provenanceForNode(node, file) {
2045
2354
  function provenanceForFile(file) {
2046
2355
  return { surface: "tsdoc", file, line: 0, column: 0 };
2047
2356
  }
2357
+ function provenanceForDeclaration(node, file) {
2358
+ if (!node) {
2359
+ return provenanceForFile(file);
2360
+ }
2361
+ return provenanceForNode(node, file);
2362
+ }
2048
2363
  function getNamedTypeName(type) {
2049
2364
  const symbol = type.getSymbol();
2050
2365
  if (symbol?.declarations) {
@@ -2063,6 +2378,20 @@ function getNamedTypeName(type) {
2063
2378
  }
2064
2379
  return null;
2065
2380
  }
2381
+ function getNamedTypeDeclaration(type) {
2382
+ const symbol = type.getSymbol();
2383
+ if (symbol?.declarations) {
2384
+ const decl = symbol.declarations[0];
2385
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2386
+ return decl;
2387
+ }
2388
+ }
2389
+ const aliasSymbol = type.aliasSymbol;
2390
+ if (aliasSymbol?.declarations) {
2391
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2392
+ }
2393
+ return void 0;
2394
+ }
2066
2395
  function analyzeMethod(method, checker) {
2067
2396
  if (!ts4.isIdentifier(method.name)) {
2068
2397
  return null;
@@ -2103,11 +2432,17 @@ function detectFormSpecReference(typeNode) {
2103
2432
  }
2104
2433
  return null;
2105
2434
  }
2106
- var MAX_ALIAS_CHAIN_DEPTH;
2435
+ var RESOLVING_TYPE_PLACEHOLDER, MAX_ALIAS_CHAIN_DEPTH;
2107
2436
  var init_class_analyzer = __esm({
2108
2437
  "src/analyzer/class-analyzer.ts"() {
2109
2438
  "use strict";
2110
2439
  init_jsdoc_constraints();
2440
+ init_tsdoc_parser();
2441
+ RESOLVING_TYPE_PLACEHOLDER = {
2442
+ kind: "object",
2443
+ properties: [],
2444
+ additionalProperties: true
2445
+ };
2111
2446
  MAX_ALIAS_CHAIN_DEPTH = 8;
2112
2447
  }
2113
2448
  });
@@ -2165,10 +2500,220 @@ var init_class_schema = __esm({
2165
2500
  }
2166
2501
  });
2167
2502
 
2503
+ // src/generators/mixed-authoring.ts
2504
+ function buildMixedAuthoringSchemas(options) {
2505
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2506
+ const analysis = analyzeNamedType(filePath, typeName);
2507
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2508
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2509
+ return {
2510
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2511
+ uiSchema: generateUiSchemaFromIR(ir)
2512
+ };
2513
+ }
2514
+ function analyzeNamedType(filePath, typeName) {
2515
+ const ctx = createProgramContext(filePath);
2516
+ const source = { file: filePath };
2517
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2518
+ if (classDecl !== null) {
2519
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2520
+ }
2521
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2522
+ if (interfaceDecl !== null) {
2523
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2524
+ }
2525
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2526
+ if (typeAlias !== null) {
2527
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2528
+ if (result.ok) {
2529
+ return result.analysis;
2530
+ }
2531
+ throw new Error(result.error);
2532
+ }
2533
+ throw new Error(
2534
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2535
+ );
2536
+ }
2537
+ function composeAnalysisWithOverlays(analysis, overlays) {
2538
+ const overlayIR = canonicalizeChainDSL(overlays);
2539
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2540
+ if (overlayFields.length === 0) {
2541
+ return analysis;
2542
+ }
2543
+ const overlayByName = /* @__PURE__ */ new Map();
2544
+ for (const field of overlayFields) {
2545
+ if (overlayByName.has(field.name)) {
2546
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2547
+ }
2548
+ overlayByName.set(field.name, field);
2549
+ }
2550
+ const mergedFields = [];
2551
+ for (const baseField of analysis.fields) {
2552
+ const overlayField = overlayByName.get(baseField.name);
2553
+ if (overlayField === void 0) {
2554
+ mergedFields.push(baseField);
2555
+ continue;
2556
+ }
2557
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2558
+ overlayByName.delete(baseField.name);
2559
+ }
2560
+ if (overlayByName.size > 0) {
2561
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2562
+ throw new Error(
2563
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2564
+ );
2565
+ }
2566
+ return {
2567
+ ...analysis,
2568
+ fields: mergedFields
2569
+ };
2570
+ }
2571
+ function collectOverlayFields(elements) {
2572
+ const fields = [];
2573
+ for (const element of elements) {
2574
+ switch (element.kind) {
2575
+ case "field":
2576
+ fields.push(element);
2577
+ break;
2578
+ case "group":
2579
+ fields.push(...collectOverlayFields(element.elements));
2580
+ break;
2581
+ case "conditional":
2582
+ fields.push(...collectOverlayFields(element.elements));
2583
+ break;
2584
+ default: {
2585
+ const _exhaustive = element;
2586
+ void _exhaustive;
2587
+ }
2588
+ }
2589
+ }
2590
+ return fields;
2591
+ }
2592
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2593
+ assertSupportedOverlayField(baseField, overlayField);
2594
+ return {
2595
+ ...baseField,
2596
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2597
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2598
+ };
2599
+ }
2600
+ function assertSupportedOverlayField(baseField, overlayField) {
2601
+ if (overlayField.constraints.length > 0) {
2602
+ throw new Error(
2603
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2604
+ );
2605
+ }
2606
+ if (overlayField.required) {
2607
+ throw new Error(
2608
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2609
+ );
2610
+ }
2611
+ }
2612
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2613
+ const { type: baseType } = baseField;
2614
+ const { type: overlayType } = overlayField;
2615
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2616
+ throw new Error(
2617
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2618
+ );
2619
+ }
2620
+ if (overlayType.kind === "dynamic") {
2621
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2622
+ throw new Error(
2623
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2624
+ );
2625
+ }
2626
+ return overlayType;
2627
+ }
2628
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2629
+ throw new Error(
2630
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2631
+ );
2632
+ }
2633
+ return baseType;
2634
+ }
2635
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2636
+ const overlayType = overlayField.type;
2637
+ if (overlayType.kind !== "dynamic") {
2638
+ return false;
2639
+ }
2640
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2641
+ if (resolvedBaseType === null) {
2642
+ return false;
2643
+ }
2644
+ if (overlayType.dynamicKind === "enum") {
2645
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2646
+ }
2647
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2648
+ }
2649
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2650
+ if (type.kind !== "reference") {
2651
+ return type;
2652
+ }
2653
+ if (seen.has(type.name)) {
2654
+ return null;
2655
+ }
2656
+ const definition = typeRegistry[type.name];
2657
+ if (definition === void 0) {
2658
+ return null;
2659
+ }
2660
+ seen.add(type.name);
2661
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2662
+ }
2663
+ function isSameStaticTypeShape(baseType, overlayType) {
2664
+ if (baseType.kind !== overlayType.kind) {
2665
+ return false;
2666
+ }
2667
+ switch (baseType.kind) {
2668
+ case "primitive":
2669
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2670
+ case "enum":
2671
+ return overlayType.kind === "enum";
2672
+ case "dynamic":
2673
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2674
+ case "record":
2675
+ return overlayType.kind === "record";
2676
+ case "reference":
2677
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2678
+ case "union":
2679
+ return overlayType.kind === "union";
2680
+ case "custom":
2681
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2682
+ case "object":
2683
+ case "array":
2684
+ return true;
2685
+ default: {
2686
+ const _exhaustive = baseType;
2687
+ return _exhaustive;
2688
+ }
2689
+ }
2690
+ }
2691
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2692
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2693
+ const overlayOnly = overlayAnnotations.filter(
2694
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2695
+ );
2696
+ return [...overlayOnly, ...baseAnnotations];
2697
+ }
2698
+ function annotationKey(annotation) {
2699
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2700
+ }
2701
+ var init_mixed_authoring = __esm({
2702
+ "src/generators/mixed-authoring.ts"() {
2703
+ "use strict";
2704
+ init_ir_generator();
2705
+ init_ir_generator2();
2706
+ init_canonicalize();
2707
+ init_program();
2708
+ init_class_analyzer();
2709
+ }
2710
+ });
2711
+
2168
2712
  // src/index.ts
2169
2713
  var index_exports = {};
2170
2714
  __export(index_exports, {
2171
2715
  buildFormSchemas: () => buildFormSchemas,
2716
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
2172
2717
  categorizationSchema: () => categorizationSchema,
2173
2718
  categorySchema: () => categorySchema,
2174
2719
  controlSchema: () => controlSchema,
@@ -2233,6 +2778,7 @@ var init_index = __esm({
2233
2778
  init_ir_generator();
2234
2779
  init_generator2();
2235
2780
  init_class_schema();
2781
+ init_mixed_authoring();
2236
2782
  }
2237
2783
  });
2238
2784