@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.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  buildFormSchemas: () => buildFormSchemas,
34
+ buildMixedAuthoringSchemas: () => buildMixedAuthoringSchemas,
34
35
  categorizationSchema: () => categorizationSchema,
35
36
  categorySchema: () => categorySchema,
36
37
  controlSchema: () => controlSchema,
@@ -386,6 +387,7 @@ function canonicalizeTSDoc(analysis, source) {
386
387
  irVersion: import_core2.IR_VERSION,
387
388
  elements,
388
389
  typeRegistry: analysis.typeRegistry,
390
+ ...analysis.annotations !== void 0 && analysis.annotations.length > 0 && { annotations: analysis.annotations },
389
391
  provenance
390
392
  };
391
393
  }
@@ -462,6 +464,9 @@ function generateJsonSchemaFromIR(ir, options) {
462
464
  const ctx = makeContext(options);
463
465
  for (const [name, typeDef] of Object.entries(ir.typeRegistry)) {
464
466
  ctx.defs[name] = generateTypeNode(typeDef.type, ctx);
467
+ if (typeDef.annotations && typeDef.annotations.length > 0) {
468
+ applyAnnotations(ctx.defs[name], typeDef.annotations, ctx);
469
+ }
465
470
  }
466
471
  const properties = {};
467
472
  const required = [];
@@ -473,6 +478,9 @@ function generateJsonSchemaFromIR(ir, options) {
473
478
  properties,
474
479
  ...uniqueRequired.length > 0 && { required: uniqueRequired }
475
480
  };
481
+ if (ir.annotations && ir.annotations.length > 0) {
482
+ applyAnnotations(result, ir.annotations, ctx);
483
+ }
476
484
  if (Object.keys(ctx.defs).length > 0) {
477
485
  result.$defs = ctx.defs;
478
486
  }
@@ -502,22 +510,51 @@ function collectFields(elements, properties, required, ctx) {
502
510
  }
503
511
  function generateFieldSchema(field, ctx) {
504
512
  const schema = generateTypeNode(field.type, ctx);
513
+ const itemStringSchema = schema.type === "array" && schema.items?.type === "string" ? schema.items : void 0;
505
514
  const directConstraints = [];
515
+ const itemConstraints = [];
506
516
  const pathConstraints = [];
507
517
  for (const c of field.constraints) {
508
518
  if (c.path) {
509
519
  pathConstraints.push(c);
520
+ } else if (itemStringSchema !== void 0 && isStringItemConstraint(c)) {
521
+ itemConstraints.push(c);
510
522
  } else {
511
523
  directConstraints.push(c);
512
524
  }
513
525
  }
514
526
  applyConstraints(schema, directConstraints, ctx);
515
- applyAnnotations(schema, field.annotations, ctx);
527
+ if (itemStringSchema !== void 0) {
528
+ applyConstraints(itemStringSchema, itemConstraints, ctx);
529
+ }
530
+ const rootAnnotations = [];
531
+ const itemAnnotations = [];
532
+ for (const annotation of field.annotations) {
533
+ if (itemStringSchema !== void 0 && annotation.annotationKind === "format") {
534
+ itemAnnotations.push(annotation);
535
+ } else {
536
+ rootAnnotations.push(annotation);
537
+ }
538
+ }
539
+ applyAnnotations(schema, rootAnnotations, ctx);
540
+ if (itemStringSchema !== void 0) {
541
+ applyAnnotations(itemStringSchema, itemAnnotations, ctx);
542
+ }
516
543
  if (pathConstraints.length === 0) {
517
544
  return schema;
518
545
  }
519
546
  return applyPathTargetedConstraints(schema, pathConstraints, ctx);
520
547
  }
548
+ function isStringItemConstraint(constraint) {
549
+ switch (constraint.constraintKind) {
550
+ case "minLength":
551
+ case "maxLength":
552
+ case "pattern":
553
+ return true;
554
+ default:
555
+ return false;
556
+ }
557
+ }
521
558
  function applyPathTargetedConstraints(schema, pathConstraints, ctx) {
522
559
  if (schema.type === "array" && schema.items) {
523
560
  schema.items = applyPathTargetedConstraints(schema.items, pathConstraints, ctx);
@@ -735,6 +772,9 @@ function applyConstraints(schema, constraints, ctx) {
735
772
  case "uniqueItems":
736
773
  schema.uniqueItems = constraint.value;
737
774
  break;
775
+ case "const":
776
+ schema.const = constraint.value;
777
+ break;
738
778
  case "allowedMembers":
739
779
  break;
740
780
  case "custom":
@@ -759,8 +799,14 @@ function applyAnnotations(schema, annotations, ctx) {
759
799
  case "defaultValue":
760
800
  schema.default = annotation.value;
761
801
  break;
802
+ case "format":
803
+ schema.format = annotation.value;
804
+ break;
762
805
  case "deprecated":
763
806
  schema.deprecated = true;
807
+ if (annotation.message !== void 0 && annotation.message !== "") {
808
+ schema["x-formspec-deprecation-description"] = annotation.message;
809
+ }
764
810
  break;
765
811
  case "placeholder":
766
812
  break;
@@ -948,25 +994,31 @@ function createShowRule(fieldName, value) {
948
994
  }
949
995
  };
950
996
  }
997
+ function flattenConditionSchema(scope, schema) {
998
+ if (schema.allOf === void 0) {
999
+ if (scope === "#") {
1000
+ return [schema];
1001
+ }
1002
+ const fieldName = scope.replace("#/properties/", "");
1003
+ return [
1004
+ {
1005
+ properties: {
1006
+ [fieldName]: schema
1007
+ }
1008
+ }
1009
+ ];
1010
+ }
1011
+ return schema.allOf.flatMap((member) => flattenConditionSchema(scope, member));
1012
+ }
951
1013
  function combineRules(parentRule, childRule) {
952
- const parentCondition = parentRule.condition;
953
- const childCondition = childRule.condition;
954
1014
  return {
955
1015
  effect: "SHOW",
956
1016
  condition: {
957
1017
  scope: "#",
958
1018
  schema: {
959
1019
  allOf: [
960
- {
961
- properties: {
962
- [parentCondition.scope.replace("#/properties/", "")]: parentCondition.schema
963
- }
964
- },
965
- {
966
- properties: {
967
- [childCondition.scope.replace("#/properties/", "")]: childCondition.schema
968
- }
969
- }
1020
+ ...flattenConditionSchema(parentRule.condition.scope, parentRule.condition.schema),
1021
+ ...flattenConditionSchema(childRule.condition.scope, childRule.condition.schema)
970
1022
  ]
971
1023
  }
972
1024
  }
@@ -974,10 +1026,14 @@ function combineRules(parentRule, childRule) {
974
1026
  }
975
1027
  function fieldNodeToControl(field, parentRule) {
976
1028
  const displayNameAnnotation = field.annotations.find((a) => a.annotationKind === "displayName");
1029
+ const placeholderAnnotation = field.annotations.find((a) => a.annotationKind === "placeholder");
977
1030
  const control = {
978
1031
  type: "Control",
979
1032
  scope: fieldToScope(field.name),
980
1033
  ...displayNameAnnotation !== void 0 && { label: displayNameAnnotation.value },
1034
+ ...placeholderAnnotation !== void 0 && {
1035
+ options: { placeholder: placeholderAnnotation.value }
1036
+ },
981
1037
  ...parentRule !== void 0 && { rule: parentRule }
982
1038
  };
983
1039
  return control;
@@ -1254,7 +1310,7 @@ var LENGTH_CONSTRAINT_MAP = {
1254
1310
  minItems: "minItems",
1255
1311
  maxItems: "maxItems"
1256
1312
  };
1257
- var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions"]);
1313
+ var TAGS_REQUIRING_RAW_TEXT = /* @__PURE__ */ new Set(["pattern", "enumOptions", "defaultValue"]);
1258
1314
  function createFormSpecTSDocConfig() {
1259
1315
  const config = new import_tsdoc.TSDocConfiguration();
1260
1316
  for (const tagName of Object.keys(import_core3.BUILTIN_CONSTRAINT_DEFINITIONS)) {
@@ -1266,7 +1322,7 @@ function createFormSpecTSDocConfig() {
1266
1322
  })
1267
1323
  );
1268
1324
  }
1269
- for (const tagName of ["displayName", "description"]) {
1325
+ for (const tagName of ["displayName", "description", "format", "placeholder"]) {
1270
1326
  config.addTagDefinition(
1271
1327
  new import_tsdoc.TSDocTagDefinition({
1272
1328
  tagName: "@" + tagName,
@@ -1285,6 +1341,12 @@ function getParser() {
1285
1341
  function parseTSDocTags(node, file = "") {
1286
1342
  const constraints = [];
1287
1343
  const annotations = [];
1344
+ let displayName;
1345
+ let description;
1346
+ let placeholder;
1347
+ let displayNameProvenance;
1348
+ let descriptionProvenance;
1349
+ let placeholderProvenance;
1288
1350
  const sourceFile = node.getSourceFile();
1289
1351
  const sourceText = sourceFile.getFullText();
1290
1352
  const commentRanges = ts2.getLeadingCommentRanges(sourceText, node.getFullStart());
@@ -1304,30 +1366,37 @@ function parseTSDocTags(node, file = "") {
1304
1366
  const docComment = parserContext.docComment;
1305
1367
  for (const block of docComment.customBlocks) {
1306
1368
  const tagName = (0, import_core3.normalizeConstraintTagName)(block.blockTag.tagName.substring(1));
1307
- if (tagName === "displayName" || tagName === "description") {
1369
+ if (tagName === "displayName" || tagName === "description" || tagName === "format" || tagName === "placeholder") {
1308
1370
  const text2 = extractBlockText(block).trim();
1309
1371
  if (text2 === "") continue;
1310
1372
  const provenance2 = provenanceForComment(range, sourceFile, file, tagName);
1311
1373
  if (tagName === "displayName") {
1374
+ if (!isMemberTargetDisplayName(text2) && displayName === void 0) {
1375
+ displayName = text2;
1376
+ displayNameProvenance = provenance2;
1377
+ }
1378
+ } else if (tagName === "format") {
1312
1379
  annotations.push({
1313
1380
  kind: "annotation",
1314
- annotationKind: "displayName",
1381
+ annotationKind: "format",
1315
1382
  value: text2,
1316
1383
  provenance: provenance2
1317
1384
  });
1318
1385
  } else {
1319
- annotations.push({
1320
- kind: "annotation",
1321
- annotationKind: "description",
1322
- value: text2,
1323
- provenance: provenance2
1324
- });
1386
+ if (tagName === "description" && description === void 0) {
1387
+ description = text2;
1388
+ descriptionProvenance = provenance2;
1389
+ } else if (tagName === "placeholder" && placeholder === void 0) {
1390
+ placeholder = text2;
1391
+ placeholderProvenance = provenance2;
1392
+ }
1325
1393
  }
1326
1394
  continue;
1327
1395
  }
1328
1396
  if (TAGS_REQUIRING_RAW_TEXT.has(tagName)) continue;
1329
1397
  const text = extractBlockText(block).trim();
1330
- if (text === "") continue;
1398
+ const expectedType = (0, import_core3.isBuiltinConstraintName)(tagName) ? import_core3.BUILTIN_CONSTRAINT_DEFINITIONS[tagName] : void 0;
1399
+ if (text === "" && expectedType !== "boolean") continue;
1331
1400
  const provenance = provenanceForComment(range, sourceFile, file, tagName);
1332
1401
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1333
1402
  if (constraintNode) {
@@ -1335,14 +1404,47 @@ function parseTSDocTags(node, file = "") {
1335
1404
  }
1336
1405
  }
1337
1406
  if (docComment.deprecatedBlock !== void 0) {
1407
+ const message = extractBlockText(docComment.deprecatedBlock).trim();
1338
1408
  annotations.push({
1339
1409
  kind: "annotation",
1340
1410
  annotationKind: "deprecated",
1411
+ ...message !== "" && { message },
1341
1412
  provenance: provenanceForComment(range, sourceFile, file, "deprecated")
1342
1413
  });
1343
1414
  }
1415
+ if (description === void 0 && docComment.remarksBlock !== void 0) {
1416
+ const remarks = extractBlockText(docComment.remarksBlock).trim();
1417
+ if (remarks !== "") {
1418
+ description = remarks;
1419
+ descriptionProvenance = provenanceForComment(range, sourceFile, file, "remarks");
1420
+ }
1421
+ }
1344
1422
  }
1345
1423
  }
1424
+ if (displayName !== void 0 && displayNameProvenance !== void 0) {
1425
+ annotations.push({
1426
+ kind: "annotation",
1427
+ annotationKind: "displayName",
1428
+ value: displayName,
1429
+ provenance: displayNameProvenance
1430
+ });
1431
+ }
1432
+ if (description !== void 0 && descriptionProvenance !== void 0) {
1433
+ annotations.push({
1434
+ kind: "annotation",
1435
+ annotationKind: "description",
1436
+ value: description,
1437
+ provenance: descriptionProvenance
1438
+ });
1439
+ }
1440
+ if (placeholder !== void 0 && placeholderProvenance !== void 0) {
1441
+ annotations.push({
1442
+ kind: "annotation",
1443
+ annotationKind: "placeholder",
1444
+ value: placeholder,
1445
+ provenance: placeholderProvenance
1446
+ });
1447
+ }
1346
1448
  const jsDocTagsAll = ts2.getJSDocTags(node);
1347
1449
  for (const tag of jsDocTagsAll) {
1348
1450
  const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
@@ -1351,6 +1453,11 @@ function parseTSDocTags(node, file = "") {
1351
1453
  if (commentText === void 0 || commentText.trim() === "") continue;
1352
1454
  const text = commentText.trim();
1353
1455
  const provenance = provenanceForJSDocTag(tag, file);
1456
+ if (tagName === "defaultValue") {
1457
+ const defaultValueNode = parseDefaultValueValue(text, provenance);
1458
+ annotations.push(defaultValueNode);
1459
+ continue;
1460
+ }
1354
1461
  const constraintNode = parseConstraintValue(tagName, text, provenance);
1355
1462
  if (constraintNode) {
1356
1463
  constraints.push(constraintNode);
@@ -1358,6 +1465,28 @@ function parseTSDocTags(node, file = "") {
1358
1465
  }
1359
1466
  return { constraints, annotations };
1360
1467
  }
1468
+ function extractDisplayNameMetadata(node) {
1469
+ let displayName;
1470
+ const memberDisplayNames = /* @__PURE__ */ new Map();
1471
+ for (const tag of ts2.getJSDocTags(node)) {
1472
+ const tagName = (0, import_core3.normalizeConstraintTagName)(tag.tagName.text);
1473
+ if (tagName !== "displayName") continue;
1474
+ const commentText = getTagCommentText(tag);
1475
+ if (commentText === void 0) continue;
1476
+ const text = commentText.trim();
1477
+ if (text === "") continue;
1478
+ const memberTarget = parseMemberTargetDisplayName(text);
1479
+ if (memberTarget) {
1480
+ memberDisplayNames.set(memberTarget.target, memberTarget.label);
1481
+ continue;
1482
+ }
1483
+ displayName ??= text;
1484
+ }
1485
+ return {
1486
+ ...displayName !== void 0 && { displayName },
1487
+ memberDisplayNames
1488
+ };
1489
+ }
1361
1490
  function extractPathTarget(text) {
1362
1491
  const trimmed = text.trimStart();
1363
1492
  const match = /^:([a-zA-Z_]\w*)\s+([\s\S]*)$/.exec(trimmed);
@@ -1420,7 +1549,45 @@ function parseConstraintValue(tagName, text, provenance) {
1420
1549
  }
1421
1550
  return null;
1422
1551
  }
1552
+ if (expectedType === "boolean") {
1553
+ const trimmed = effectiveText.trim();
1554
+ if (trimmed !== "" && trimmed !== "true") {
1555
+ return null;
1556
+ }
1557
+ if (tagName === "uniqueItems") {
1558
+ return {
1559
+ kind: "constraint",
1560
+ constraintKind: "uniqueItems",
1561
+ value: true,
1562
+ ...path3 && { path: path3 },
1563
+ provenance
1564
+ };
1565
+ }
1566
+ return null;
1567
+ }
1423
1568
  if (expectedType === "json") {
1569
+ if (tagName === "const") {
1570
+ const trimmedText = effectiveText.trim();
1571
+ if (trimmedText === "") return null;
1572
+ try {
1573
+ const parsed2 = JSON.parse(trimmedText);
1574
+ return {
1575
+ kind: "constraint",
1576
+ constraintKind: "const",
1577
+ value: parsed2,
1578
+ ...path3 && { path: path3 },
1579
+ provenance
1580
+ };
1581
+ } catch {
1582
+ return {
1583
+ kind: "constraint",
1584
+ constraintKind: "const",
1585
+ value: trimmedText,
1586
+ ...path3 && { path: path3 },
1587
+ provenance
1588
+ };
1589
+ }
1590
+ }
1424
1591
  const parsed = tryParseJson(effectiveText);
1425
1592
  if (!Array.isArray(parsed)) {
1426
1593
  return null;
@@ -1452,6 +1619,34 @@ function parseConstraintValue(tagName, text, provenance) {
1452
1619
  provenance
1453
1620
  };
1454
1621
  }
1622
+ function parseDefaultValueValue(text, provenance) {
1623
+ const trimmed = text.trim();
1624
+ let value;
1625
+ if (trimmed === "null") {
1626
+ value = null;
1627
+ } else if (trimmed === "true") {
1628
+ value = true;
1629
+ } else if (trimmed === "false") {
1630
+ value = false;
1631
+ } else {
1632
+ const parsed = tryParseJson(trimmed);
1633
+ value = parsed !== null ? parsed : trimmed;
1634
+ }
1635
+ return {
1636
+ kind: "annotation",
1637
+ annotationKind: "defaultValue",
1638
+ value,
1639
+ provenance
1640
+ };
1641
+ }
1642
+ function isMemberTargetDisplayName(text) {
1643
+ return parseMemberTargetDisplayName(text) !== null;
1644
+ }
1645
+ function parseMemberTargetDisplayName(text) {
1646
+ const match = /^:([^\s]+)\s+([\s\S]+)$/.exec(text);
1647
+ if (!match?.[1] || !match[2]) return null;
1648
+ return { target: match[1], label: match[2].trim() };
1649
+ }
1455
1650
  function provenanceForComment(range, sourceFile, file, tagName) {
1456
1651
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(range.pos);
1457
1652
  return {
@@ -1533,11 +1728,17 @@ function isObjectType(type) {
1533
1728
  function isTypeReference(type) {
1534
1729
  return !!(type.flags & ts4.TypeFlags.Object) && !!(type.objectFlags & ts4.ObjectFlags.Reference);
1535
1730
  }
1731
+ var RESOLVING_TYPE_PLACEHOLDER = {
1732
+ kind: "object",
1733
+ properties: [],
1734
+ additionalProperties: true
1735
+ };
1536
1736
  function analyzeClassToIR(classDecl, checker, file = "") {
1537
1737
  const name = classDecl.name?.text ?? "AnonymousClass";
1538
1738
  const fields = [];
1539
1739
  const fieldLayouts = [];
1540
1740
  const typeRegistry = {};
1741
+ const annotations = extractJSDocAnnotationNodes(classDecl, file);
1541
1742
  const visiting = /* @__PURE__ */ new Set();
1542
1743
  const instanceMethods = [];
1543
1744
  const staticMethods = [];
@@ -1560,12 +1761,21 @@ function analyzeClassToIR(classDecl, checker, file = "") {
1560
1761
  }
1561
1762
  }
1562
1763
  }
1563
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods, staticMethods };
1764
+ return {
1765
+ name,
1766
+ fields,
1767
+ fieldLayouts,
1768
+ typeRegistry,
1769
+ ...annotations.length > 0 && { annotations },
1770
+ instanceMethods,
1771
+ staticMethods
1772
+ };
1564
1773
  }
1565
1774
  function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1566
1775
  const name = interfaceDecl.name.text;
1567
1776
  const fields = [];
1568
1777
  const typeRegistry = {};
1778
+ const annotations = extractJSDocAnnotationNodes(interfaceDecl, file);
1569
1779
  const visiting = /* @__PURE__ */ new Set();
1570
1780
  for (const member of interfaceDecl.members) {
1571
1781
  if (ts4.isPropertySignature(member)) {
@@ -1576,7 +1786,15 @@ function analyzeInterfaceToIR(interfaceDecl, checker, file = "") {
1576
1786
  }
1577
1787
  }
1578
1788
  const fieldLayouts = fields.map(() => ({}));
1579
- return { name, fields, fieldLayouts, typeRegistry, instanceMethods: [], staticMethods: [] };
1789
+ return {
1790
+ name,
1791
+ fields,
1792
+ fieldLayouts,
1793
+ typeRegistry,
1794
+ ...annotations.length > 0 && { annotations },
1795
+ instanceMethods: [],
1796
+ staticMethods: []
1797
+ };
1580
1798
  }
1581
1799
  function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1582
1800
  if (!ts4.isTypeLiteralNode(typeAlias.type)) {
@@ -1591,6 +1809,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1591
1809
  const name = typeAlias.name.text;
1592
1810
  const fields = [];
1593
1811
  const typeRegistry = {};
1812
+ const annotations = extractJSDocAnnotationNodes(typeAlias, file);
1594
1813
  const visiting = /* @__PURE__ */ new Set();
1595
1814
  for (const member of typeAlias.type.members) {
1596
1815
  if (ts4.isPropertySignature(member)) {
@@ -1607,6 +1826,7 @@ function analyzeTypeAliasToIR(typeAlias, checker, file = "") {
1607
1826
  fields,
1608
1827
  fieldLayouts: fields.map(() => ({})),
1609
1828
  typeRegistry,
1829
+ ...annotations.length > 0 && { annotations },
1610
1830
  instanceMethods: [],
1611
1831
  staticMethods: []
1612
1832
  }
@@ -1620,7 +1840,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1620
1840
  const tsType = checker.getTypeAtLocation(prop);
1621
1841
  const optional = prop.questionToken !== void 0;
1622
1842
  const provenance = provenanceForNode(prop, file);
1623
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1843
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1624
1844
  const constraints = [];
1625
1845
  if (prop.type) {
1626
1846
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1629,7 +1849,7 @@ function analyzeFieldToIR(prop, checker, file, typeRegistry, visiting) {
1629
1849
  let annotations = [];
1630
1850
  annotations.push(...extractJSDocAnnotationNodes(prop, file));
1631
1851
  const defaultAnnotation = extractDefaultValueAnnotation(prop.initializer, file);
1632
- if (defaultAnnotation) {
1852
+ if (defaultAnnotation && !annotations.some((a) => a.annotationKind === "defaultValue")) {
1633
1853
  annotations.push(defaultAnnotation);
1634
1854
  }
1635
1855
  ({ type, annotations } = applyEnumMemberDisplayNames(type, annotations));
@@ -1651,7 +1871,7 @@ function analyzeInterfacePropertyToIR(prop, checker, file, typeRegistry, visitin
1651
1871
  const tsType = checker.getTypeAtLocation(prop);
1652
1872
  const optional = prop.questionToken !== void 0;
1653
1873
  const provenance = provenanceForNode(prop, file);
1654
- let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting);
1874
+ let type = resolveTypeNode(tsType, checker, file, typeRegistry, visiting, prop);
1655
1875
  const constraints = [];
1656
1876
  if (prop.type) {
1657
1877
  constraints.push(...extractTypeAliasConstraintNodes(prop.type, checker, file));
@@ -1732,7 +1952,7 @@ function parseEnumMemberDisplayName(value) {
1732
1952
  if (label === "") return null;
1733
1953
  return { value: match[1], label };
1734
1954
  }
1735
- function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1955
+ function resolveTypeNode(type, checker, file, typeRegistry, visiting, sourceNode) {
1736
1956
  if (type.flags & ts4.TypeFlags.String) {
1737
1957
  return { kind: "primitive", primitiveKind: "string" };
1738
1958
  }
@@ -1761,7 +1981,7 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1761
1981
  };
1762
1982
  }
1763
1983
  if (type.isUnion()) {
1764
- return resolveUnionType(type, checker, file, typeRegistry, visiting);
1984
+ return resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode);
1765
1985
  }
1766
1986
  if (checker.isArrayType(type)) {
1767
1987
  return resolveArrayType(type, checker, file, typeRegistry, visiting);
@@ -1771,70 +1991,102 @@ function resolveTypeNode(type, checker, file, typeRegistry, visiting) {
1771
1991
  }
1772
1992
  return { kind: "primitive", primitiveKind: "string" };
1773
1993
  }
1774
- function resolveUnionType(type, checker, file, typeRegistry, visiting) {
1994
+ function resolveUnionType(type, checker, file, typeRegistry, visiting, sourceNode) {
1995
+ const typeName = getNamedTypeName(type);
1996
+ const namedDecl = getNamedTypeDeclaration(type);
1997
+ if (typeName && typeName in typeRegistry) {
1998
+ return { kind: "reference", name: typeName, typeArguments: [] };
1999
+ }
1775
2000
  const allTypes = type.types;
1776
2001
  const nonNullTypes = allTypes.filter(
1777
2002
  (t) => !(t.flags & (ts4.TypeFlags.Null | ts4.TypeFlags.Undefined))
1778
2003
  );
1779
2004
  const hasNull = allTypes.some((t) => t.flags & ts4.TypeFlags.Null);
2005
+ const memberDisplayNames = /* @__PURE__ */ new Map();
2006
+ if (namedDecl) {
2007
+ for (const [value, label] of extractDisplayNameMetadata(namedDecl).memberDisplayNames) {
2008
+ memberDisplayNames.set(value, label);
2009
+ }
2010
+ }
2011
+ if (sourceNode) {
2012
+ for (const [value, label] of extractDisplayNameMetadata(sourceNode).memberDisplayNames) {
2013
+ memberDisplayNames.set(value, label);
2014
+ }
2015
+ }
2016
+ const registerNamed = (result) => {
2017
+ if (!typeName) {
2018
+ return result;
2019
+ }
2020
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2021
+ typeRegistry[typeName] = {
2022
+ name: typeName,
2023
+ type: result,
2024
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2025
+ provenance: provenanceForDeclaration(namedDecl ?? sourceNode, file)
2026
+ };
2027
+ return { kind: "reference", name: typeName, typeArguments: [] };
2028
+ };
2029
+ const applyMemberLabels = (members2) => members2.map((value) => {
2030
+ const displayName = memberDisplayNames.get(String(value));
2031
+ return displayName !== void 0 ? { value, displayName } : { value };
2032
+ });
1780
2033
  const isBooleanUnion2 = nonNullTypes.length === 2 && nonNullTypes.every((t) => t.flags & ts4.TypeFlags.BooleanLiteral);
1781
2034
  if (isBooleanUnion2) {
1782
2035
  const boolNode = { kind: "primitive", primitiveKind: "boolean" };
1783
- if (hasNull) {
1784
- return {
1785
- kind: "union",
1786
- members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
1787
- };
1788
- }
1789
- return boolNode;
2036
+ const result = hasNull ? {
2037
+ kind: "union",
2038
+ members: [boolNode, { kind: "primitive", primitiveKind: "null" }]
2039
+ } : boolNode;
2040
+ return registerNamed(result);
1790
2041
  }
1791
2042
  const allStringLiterals = nonNullTypes.every((t) => t.isStringLiteral());
1792
2043
  if (allStringLiterals && nonNullTypes.length > 0) {
1793
2044
  const stringTypes = nonNullTypes.filter((t) => t.isStringLiteral());
1794
2045
  const enumNode = {
1795
2046
  kind: "enum",
1796
- members: stringTypes.map((t) => ({ value: t.value }))
2047
+ members: applyMemberLabels(stringTypes.map((t) => t.value))
1797
2048
  };
1798
- if (hasNull) {
1799
- return {
1800
- kind: "union",
1801
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1802
- };
1803
- }
1804
- return enumNode;
2049
+ const result = hasNull ? {
2050
+ kind: "union",
2051
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2052
+ } : enumNode;
2053
+ return registerNamed(result);
1805
2054
  }
1806
2055
  const allNumberLiterals = nonNullTypes.every((t) => t.isNumberLiteral());
1807
2056
  if (allNumberLiterals && nonNullTypes.length > 0) {
1808
2057
  const numberTypes = nonNullTypes.filter((t) => t.isNumberLiteral());
1809
2058
  const enumNode = {
1810
2059
  kind: "enum",
1811
- members: numberTypes.map((t) => ({ value: t.value }))
2060
+ members: applyMemberLabels(numberTypes.map((t) => t.value))
1812
2061
  };
1813
- if (hasNull) {
1814
- return {
1815
- kind: "union",
1816
- members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
1817
- };
1818
- }
1819
- return enumNode;
2062
+ const result = hasNull ? {
2063
+ kind: "union",
2064
+ members: [enumNode, { kind: "primitive", primitiveKind: "null" }]
2065
+ } : enumNode;
2066
+ return registerNamed(result);
1820
2067
  }
1821
2068
  if (nonNullTypes.length === 1 && nonNullTypes[0]) {
1822
- const inner = resolveTypeNode(nonNullTypes[0], checker, file, typeRegistry, visiting);
1823
- if (hasNull) {
1824
- return {
1825
- kind: "union",
1826
- members: [inner, { kind: "primitive", primitiveKind: "null" }]
1827
- };
1828
- }
1829
- return inner;
2069
+ const inner = resolveTypeNode(
2070
+ nonNullTypes[0],
2071
+ checker,
2072
+ file,
2073
+ typeRegistry,
2074
+ visiting,
2075
+ sourceNode
2076
+ );
2077
+ const result = hasNull ? {
2078
+ kind: "union",
2079
+ members: [inner, { kind: "primitive", primitiveKind: "null" }]
2080
+ } : inner;
2081
+ return registerNamed(result);
1830
2082
  }
1831
2083
  const members = nonNullTypes.map(
1832
- (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting)
2084
+ (t) => resolveTypeNode(t, checker, file, typeRegistry, visiting, sourceNode)
1833
2085
  );
1834
2086
  if (hasNull) {
1835
2087
  members.push({ kind: "primitive", primitiveKind: "null" });
1836
2088
  }
1837
- return { kind: "union", members };
2089
+ return registerNamed({ kind: "union", members });
1838
2090
  }
1839
2091
  function resolveArrayType(type, checker, file, typeRegistry, visiting) {
1840
2092
  const typeArgs = isTypeReference(type) ? type.typeArguments : void 0;
@@ -1850,30 +2102,84 @@ function tryResolveRecordType(type, checker, file, typeRegistry, visiting) {
1850
2102
  if (!indexInfo) {
1851
2103
  return null;
1852
2104
  }
1853
- if (visiting.has(type)) {
1854
- return null;
1855
- }
1856
- visiting.add(type);
1857
- try {
1858
- const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
1859
- return { kind: "record", valueType };
1860
- } finally {
1861
- visiting.delete(type);
2105
+ const valueType = resolveTypeNode(indexInfo.type, checker, file, typeRegistry, visiting);
2106
+ return { kind: "record", valueType };
2107
+ }
2108
+ function typeNodeContainsReference(type, targetName) {
2109
+ switch (type.kind) {
2110
+ case "reference":
2111
+ return type.name === targetName;
2112
+ case "array":
2113
+ return typeNodeContainsReference(type.items, targetName);
2114
+ case "record":
2115
+ return typeNodeContainsReference(type.valueType, targetName);
2116
+ case "union":
2117
+ return type.members.some((member) => typeNodeContainsReference(member, targetName));
2118
+ case "object":
2119
+ return type.properties.some(
2120
+ (property) => typeNodeContainsReference(property.type, targetName)
2121
+ );
2122
+ case "primitive":
2123
+ case "enum":
2124
+ case "dynamic":
2125
+ case "custom":
2126
+ return false;
2127
+ default: {
2128
+ const _exhaustive = type;
2129
+ return _exhaustive;
2130
+ }
1862
2131
  }
1863
2132
  }
1864
2133
  function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1865
- const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
1866
- if (recordNode) {
1867
- return recordNode;
1868
- }
2134
+ const typeName = getNamedTypeName(type);
2135
+ const namedTypeName = typeName ?? void 0;
2136
+ const namedDecl = getNamedTypeDeclaration(type);
2137
+ const shouldRegisterNamedType = namedTypeName !== void 0 && !(namedTypeName === "Record" && namedDecl?.getSourceFile().fileName !== file);
2138
+ const clearNamedTypeRegistration = () => {
2139
+ if (namedTypeName === void 0 || !shouldRegisterNamedType) {
2140
+ return;
2141
+ }
2142
+ Reflect.deleteProperty(typeRegistry, namedTypeName);
2143
+ };
1869
2144
  if (visiting.has(type)) {
2145
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2146
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2147
+ }
1870
2148
  return { kind: "object", properties: [], additionalProperties: false };
1871
2149
  }
2150
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && !typeRegistry[namedTypeName]) {
2151
+ typeRegistry[namedTypeName] = {
2152
+ name: namedTypeName,
2153
+ type: RESOLVING_TYPE_PLACEHOLDER,
2154
+ provenance: provenanceForDeclaration(namedDecl, file)
2155
+ };
2156
+ }
1872
2157
  visiting.add(type);
1873
- const typeName = getNamedTypeName(type);
1874
- if (typeName && typeName in typeRegistry) {
2158
+ if (namedTypeName !== void 0 && shouldRegisterNamedType && typeRegistry[namedTypeName]?.type !== void 0) {
2159
+ if (typeRegistry[namedTypeName].type !== RESOLVING_TYPE_PLACEHOLDER) {
2160
+ visiting.delete(type);
2161
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2162
+ }
2163
+ }
2164
+ const recordNode = tryResolveRecordType(type, checker, file, typeRegistry, visiting);
2165
+ if (recordNode) {
1875
2166
  visiting.delete(type);
1876
- return { kind: "reference", name: typeName, typeArguments: [] };
2167
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2168
+ const isRecursiveRecord = typeNodeContainsReference(recordNode.valueType, namedTypeName);
2169
+ if (!isRecursiveRecord) {
2170
+ clearNamedTypeRegistration();
2171
+ return recordNode;
2172
+ }
2173
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2174
+ typeRegistry[namedTypeName] = {
2175
+ name: namedTypeName,
2176
+ type: recordNode,
2177
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2178
+ provenance: provenanceForDeclaration(namedDecl, file)
2179
+ };
2180
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
2181
+ }
2182
+ return recordNode;
1877
2183
  }
1878
2184
  const properties = [];
1879
2185
  const fieldInfoMap = getNamedTypeFieldNodeInfoMap(type, checker, file, typeRegistry, visiting);
@@ -1882,7 +2188,14 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1882
2188
  if (!declaration) continue;
1883
2189
  const propType = checker.getTypeOfSymbolAtLocation(prop, declaration);
1884
2190
  const optional = !!(prop.flags & ts4.SymbolFlags.Optional);
1885
- const propTypeNode = resolveTypeNode(propType, checker, file, typeRegistry, visiting);
2191
+ const propTypeNode = resolveTypeNode(
2192
+ propType,
2193
+ checker,
2194
+ file,
2195
+ typeRegistry,
2196
+ visiting,
2197
+ declaration
2198
+ );
1886
2199
  const fieldNodeInfo = fieldInfoMap?.get(prop.name);
1887
2200
  properties.push({
1888
2201
  name: prop.name,
@@ -1899,13 +2212,15 @@ function resolveObjectType(type, checker, file, typeRegistry, visiting) {
1899
2212
  properties,
1900
2213
  additionalProperties: true
1901
2214
  };
1902
- if (typeName) {
1903
- typeRegistry[typeName] = {
1904
- name: typeName,
2215
+ if (namedTypeName !== void 0 && shouldRegisterNamedType) {
2216
+ const annotations = namedDecl ? extractJSDocAnnotationNodes(namedDecl, file) : void 0;
2217
+ typeRegistry[namedTypeName] = {
2218
+ name: namedTypeName,
1905
2219
  type: objectNode,
1906
- provenance: provenanceForFile(file)
2220
+ ...annotations !== void 0 && annotations.length > 0 && { annotations },
2221
+ provenance: provenanceForDeclaration(namedDecl, file)
1907
2222
  };
1908
- return { kind: "reference", name: typeName, typeArguments: [] };
2223
+ return { kind: "reference", name: namedTypeName, typeArguments: [] };
1909
2224
  }
1910
2225
  return objectNode;
1911
2226
  }
@@ -1997,6 +2312,12 @@ function provenanceForNode(node, file) {
1997
2312
  function provenanceForFile(file) {
1998
2313
  return { surface: "tsdoc", file, line: 0, column: 0 };
1999
2314
  }
2315
+ function provenanceForDeclaration(node, file) {
2316
+ if (!node) {
2317
+ return provenanceForFile(file);
2318
+ }
2319
+ return provenanceForNode(node, file);
2320
+ }
2000
2321
  function getNamedTypeName(type) {
2001
2322
  const symbol = type.getSymbol();
2002
2323
  if (symbol?.declarations) {
@@ -2015,6 +2336,20 @@ function getNamedTypeName(type) {
2015
2336
  }
2016
2337
  return null;
2017
2338
  }
2339
+ function getNamedTypeDeclaration(type) {
2340
+ const symbol = type.getSymbol();
2341
+ if (symbol?.declarations) {
2342
+ const decl = symbol.declarations[0];
2343
+ if (decl && (ts4.isClassDeclaration(decl) || ts4.isInterfaceDeclaration(decl) || ts4.isTypeAliasDeclaration(decl))) {
2344
+ return decl;
2345
+ }
2346
+ }
2347
+ const aliasSymbol = type.aliasSymbol;
2348
+ if (aliasSymbol?.declarations) {
2349
+ return aliasSymbol.declarations.find(ts4.isTypeAliasDeclaration);
2350
+ }
2351
+ return void 0;
2352
+ }
2018
2353
  function analyzeMethod(method, checker) {
2019
2354
  if (!ts4.isIdentifier(method.name)) {
2020
2355
  return null;
@@ -2099,6 +2434,205 @@ function generateSchemas(options) {
2099
2434
  );
2100
2435
  }
2101
2436
 
2437
+ // src/generators/mixed-authoring.ts
2438
+ function buildMixedAuthoringSchemas(options) {
2439
+ const { filePath, typeName, overlays, ...schemaOptions } = options;
2440
+ const analysis = analyzeNamedType(filePath, typeName);
2441
+ const composedAnalysis = composeAnalysisWithOverlays(analysis, overlays);
2442
+ const ir = canonicalizeTSDoc(composedAnalysis, { file: filePath });
2443
+ return {
2444
+ jsonSchema: generateJsonSchemaFromIR(ir, schemaOptions),
2445
+ uiSchema: generateUiSchemaFromIR(ir)
2446
+ };
2447
+ }
2448
+ function analyzeNamedType(filePath, typeName) {
2449
+ const ctx = createProgramContext(filePath);
2450
+ const source = { file: filePath };
2451
+ const classDecl = findClassByName(ctx.sourceFile, typeName);
2452
+ if (classDecl !== null) {
2453
+ return analyzeClassToIR(classDecl, ctx.checker, source.file);
2454
+ }
2455
+ const interfaceDecl = findInterfaceByName(ctx.sourceFile, typeName);
2456
+ if (interfaceDecl !== null) {
2457
+ return analyzeInterfaceToIR(interfaceDecl, ctx.checker, source.file);
2458
+ }
2459
+ const typeAlias = findTypeAliasByName(ctx.sourceFile, typeName);
2460
+ if (typeAlias !== null) {
2461
+ const result = analyzeTypeAliasToIR(typeAlias, ctx.checker, source.file);
2462
+ if (result.ok) {
2463
+ return result.analysis;
2464
+ }
2465
+ throw new Error(result.error);
2466
+ }
2467
+ throw new Error(
2468
+ `Type "${typeName}" not found as a class, interface, or type alias in ${filePath}`
2469
+ );
2470
+ }
2471
+ function composeAnalysisWithOverlays(analysis, overlays) {
2472
+ const overlayIR = canonicalizeChainDSL(overlays);
2473
+ const overlayFields = collectOverlayFields(overlayIR.elements);
2474
+ if (overlayFields.length === 0) {
2475
+ return analysis;
2476
+ }
2477
+ const overlayByName = /* @__PURE__ */ new Map();
2478
+ for (const field of overlayFields) {
2479
+ if (overlayByName.has(field.name)) {
2480
+ throw new Error(`Mixed-authoring overlays define "${field.name}" more than once`);
2481
+ }
2482
+ overlayByName.set(field.name, field);
2483
+ }
2484
+ const mergedFields = [];
2485
+ for (const baseField of analysis.fields) {
2486
+ const overlayField = overlayByName.get(baseField.name);
2487
+ if (overlayField === void 0) {
2488
+ mergedFields.push(baseField);
2489
+ continue;
2490
+ }
2491
+ mergedFields.push(mergeFieldOverlay(baseField, overlayField, analysis.typeRegistry));
2492
+ overlayByName.delete(baseField.name);
2493
+ }
2494
+ if (overlayByName.size > 0) {
2495
+ const unknownFields = [...overlayByName.keys()].sort().join(", ");
2496
+ throw new Error(
2497
+ `Mixed-authoring overlays reference fields that are not present in the static model: ${unknownFields}`
2498
+ );
2499
+ }
2500
+ return {
2501
+ ...analysis,
2502
+ fields: mergedFields
2503
+ };
2504
+ }
2505
+ function collectOverlayFields(elements) {
2506
+ const fields = [];
2507
+ for (const element of elements) {
2508
+ switch (element.kind) {
2509
+ case "field":
2510
+ fields.push(element);
2511
+ break;
2512
+ case "group":
2513
+ fields.push(...collectOverlayFields(element.elements));
2514
+ break;
2515
+ case "conditional":
2516
+ fields.push(...collectOverlayFields(element.elements));
2517
+ break;
2518
+ default: {
2519
+ const _exhaustive = element;
2520
+ void _exhaustive;
2521
+ }
2522
+ }
2523
+ }
2524
+ return fields;
2525
+ }
2526
+ function mergeFieldOverlay(baseField, overlayField, typeRegistry) {
2527
+ assertSupportedOverlayField(baseField, overlayField);
2528
+ return {
2529
+ ...baseField,
2530
+ type: mergeFieldType(baseField, overlayField, typeRegistry),
2531
+ annotations: mergeAnnotations(baseField.annotations, overlayField.annotations)
2532
+ };
2533
+ }
2534
+ function assertSupportedOverlayField(baseField, overlayField) {
2535
+ if (overlayField.constraints.length > 0) {
2536
+ throw new Error(
2537
+ `Mixed-authoring overlay for "${baseField.name}" cannot define constraints; keep constraints on the static model`
2538
+ );
2539
+ }
2540
+ if (overlayField.required) {
2541
+ throw new Error(
2542
+ `Mixed-authoring overlay for "${baseField.name}" cannot change requiredness; keep requiredness on the static model`
2543
+ );
2544
+ }
2545
+ }
2546
+ function mergeFieldType(baseField, overlayField, typeRegistry) {
2547
+ const { type: baseType } = baseField;
2548
+ const { type: overlayType } = overlayField;
2549
+ if (overlayType.kind === "object" || overlayType.kind === "array") {
2550
+ throw new Error(
2551
+ `Mixed-authoring overlays do not support nested object or array overlays for "${baseField.name}"`
2552
+ );
2553
+ }
2554
+ if (overlayType.kind === "dynamic") {
2555
+ if (!isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry)) {
2556
+ throw new Error(
2557
+ `Mixed-authoring overlay for "${baseField.name}" is incompatible with the static field type`
2558
+ );
2559
+ }
2560
+ return overlayType;
2561
+ }
2562
+ if (!isSameStaticTypeShape(baseType, overlayType)) {
2563
+ throw new Error(
2564
+ `Mixed-authoring overlay for "${baseField.name}" must preserve the static field type`
2565
+ );
2566
+ }
2567
+ return baseType;
2568
+ }
2569
+ function isCompatibleDynamicOverlay(baseField, overlayField, typeRegistry) {
2570
+ const overlayType = overlayField.type;
2571
+ if (overlayType.kind !== "dynamic") {
2572
+ return false;
2573
+ }
2574
+ const resolvedBaseType = resolveReferenceType(baseField.type, typeRegistry);
2575
+ if (resolvedBaseType === null) {
2576
+ return false;
2577
+ }
2578
+ if (overlayType.dynamicKind === "enum") {
2579
+ return resolvedBaseType.kind === "primitive" ? resolvedBaseType.primitiveKind === "string" : resolvedBaseType.kind === "enum";
2580
+ }
2581
+ return resolvedBaseType.kind === "object" || resolvedBaseType.kind === "record";
2582
+ }
2583
+ function resolveReferenceType(type, typeRegistry, seen = /* @__PURE__ */ new Set()) {
2584
+ if (type.kind !== "reference") {
2585
+ return type;
2586
+ }
2587
+ if (seen.has(type.name)) {
2588
+ return null;
2589
+ }
2590
+ const definition = typeRegistry[type.name];
2591
+ if (definition === void 0) {
2592
+ return null;
2593
+ }
2594
+ seen.add(type.name);
2595
+ return resolveReferenceType(definition.type, typeRegistry, seen);
2596
+ }
2597
+ function isSameStaticTypeShape(baseType, overlayType) {
2598
+ if (baseType.kind !== overlayType.kind) {
2599
+ return false;
2600
+ }
2601
+ switch (baseType.kind) {
2602
+ case "primitive":
2603
+ return overlayType.kind === "primitive" && baseType.primitiveKind === overlayType.primitiveKind;
2604
+ case "enum":
2605
+ return overlayType.kind === "enum";
2606
+ case "dynamic":
2607
+ return overlayType.kind === "dynamic" && baseType.dynamicKind === overlayType.dynamicKind && baseType.sourceKey === overlayType.sourceKey;
2608
+ case "record":
2609
+ return overlayType.kind === "record";
2610
+ case "reference":
2611
+ return overlayType.kind === "reference" && baseType.name === overlayType.name;
2612
+ case "union":
2613
+ return overlayType.kind === "union";
2614
+ case "custom":
2615
+ return overlayType.kind === "custom" && baseType.typeId === overlayType.typeId;
2616
+ case "object":
2617
+ case "array":
2618
+ return true;
2619
+ default: {
2620
+ const _exhaustive = baseType;
2621
+ return _exhaustive;
2622
+ }
2623
+ }
2624
+ }
2625
+ function mergeAnnotations(baseAnnotations, overlayAnnotations) {
2626
+ const baseKeys = new Set(baseAnnotations.map(annotationKey));
2627
+ const overlayOnly = overlayAnnotations.filter(
2628
+ (annotation) => !baseKeys.has(annotationKey(annotation))
2629
+ );
2630
+ return [...overlayOnly, ...baseAnnotations];
2631
+ }
2632
+ function annotationKey(annotation) {
2633
+ return annotation.annotationKind === "custom" ? `${annotation.annotationKind}:${annotation.annotationId}` : annotation.annotationKind;
2634
+ }
2635
+
2102
2636
  // src/index.ts
2103
2637
  function buildFormSchemas(form, options) {
2104
2638
  return {
@@ -2125,6 +2659,7 @@ function writeSchemas(form, options) {
2125
2659
  // Annotate the CommonJS export names for ESM import in node:
2126
2660
  0 && (module.exports = {
2127
2661
  buildFormSchemas,
2662
+ buildMixedAuthoringSchemas,
2128
2663
  categorizationSchema,
2129
2664
  categorySchema,
2130
2665
  controlSchema,