@openpkg-ts/spec 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -36,20 +36,121 @@ type SpecSource = {
36
36
  line?: number;
37
37
  url?: string;
38
38
  };
39
- type SpecSchema = unknown;
40
- type SpecExample = Record<string, unknown>;
39
+ type SpecSchemaPrimitive = {
40
+ type: "string";
41
+ format?: string;
42
+ enum?: string[];
43
+ } | {
44
+ type: "number";
45
+ enum?: number[];
46
+ } | {
47
+ type: "boolean";
48
+ enum?: boolean[];
49
+ } | {
50
+ type: "integer";
51
+ format?: string;
52
+ } | {
53
+ type: "null";
54
+ } | {
55
+ type: "undefined";
56
+ } | {
57
+ type: "any";
58
+ } | {
59
+ type: "unknown";
60
+ } | {
61
+ type: "never";
62
+ } | {
63
+ type: "void";
64
+ };
65
+ type SpecSchemaComposite = {
66
+ type: "array";
67
+ items?: SpecSchema;
68
+ } | {
69
+ type: "tuple";
70
+ items: SpecSchema[];
71
+ minItems?: number;
72
+ maxItems?: number;
73
+ } | {
74
+ type: "object";
75
+ properties?: Record<string, SpecSchema>;
76
+ required?: string[];
77
+ additionalProperties?: boolean | SpecSchema;
78
+ description?: string;
79
+ } | {
80
+ type: "function";
81
+ signatures?: SpecSignature[];
82
+ };
83
+ type SpecSchemaCombinator = {
84
+ anyOf: SpecSchema[];
85
+ discriminator?: {
86
+ propertyName: string;
87
+ };
88
+ } | {
89
+ allOf: SpecSchema[];
90
+ } | {
91
+ oneOf: SpecSchema[];
92
+ };
93
+ type SpecSchemaRef = {
94
+ $ref: string;
95
+ };
96
+ type SpecSchemaFallback = {
97
+ type: string;
98
+ tsType?: string;
99
+ };
100
+ type SpecSchemaGeneric = Record<string, unknown>;
101
+ type SpecSchema = string | SpecSchemaPrimitive | SpecSchemaComposite | SpecSchemaCombinator | SpecSchemaRef | SpecSchemaFallback | SpecSchemaGeneric;
102
+ type SpecExampleLanguage = "ts" | "js" | "tsx" | "jsx" | "shell" | "json";
103
+ type SpecExample = {
104
+ code: string;
105
+ title?: string;
106
+ description?: string;
107
+ language?: SpecExampleLanguage;
108
+ runnable?: boolean;
109
+ expectedOutput?: string;
110
+ tags?: string[];
111
+ };
112
+ type SpecRelationType = "uses" | "returns" | "implements" | "extends" | "see-also" | "companion";
113
+ type SpecRelation = {
114
+ type: SpecRelationType;
115
+ target: string;
116
+ description?: string;
117
+ };
41
118
  type SpecExtension = Record<string, unknown>;
42
- type SpecDocSignal = "description" | "params" | "returns" | "examples";
119
+ /**
120
+ * All possible drift type identifiers.
121
+ */
122
+ type DriftType = "param-mismatch" | "param-type-mismatch" | "return-type-mismatch" | "generic-constraint-mismatch" | "optionality-mismatch" | "deprecated-mismatch" | "visibility-mismatch" | "async-mismatch" | "property-type-drift" | "example-drift" | "example-syntax-error" | "example-runtime-error" | "example-assertion-failed" | "broken-link";
43
123
  type SpecDocDrift = {
44
- type: "param-mismatch" | "param-type-mismatch" | "return-type-mismatch" | "generic-constraint-mismatch" | "optionality-mismatch" | "deprecated-mismatch" | "visibility-mismatch" | "async-mismatch" | "property-type-drift" | "example-drift" | "example-syntax-error" | "example-runtime-error" | "example-assertion-failed" | "broken-link";
124
+ type: DriftType;
45
125
  target?: string;
46
126
  issue: string;
47
127
  suggestion?: string;
48
128
  };
129
+ /**
130
+ * Drift categories group related drift types for progressive disclosure.
131
+ *
132
+ * - `structural`: Signature/type mismatches (mostly auto-fixable via JSDoc)
133
+ * - `semantic`: Metadata/visibility/reference issues
134
+ * - `example`: Code example problems
135
+ */
136
+ type DriftCategory = "structural" | "semantic" | "example";
137
+ /**
138
+ * Maps each drift type to its category.
139
+ */
140
+ declare const DRIFT_CATEGORIES: Record<DriftType, DriftCategory>;
141
+ /**
142
+ * Human-readable category labels.
143
+ */
144
+ declare const DRIFT_CATEGORY_LABELS: Record<DriftCategory, string>;
145
+ /**
146
+ * Category descriptions for help text.
147
+ */
148
+ declare const DRIFT_CATEGORY_DESCRIPTIONS: Record<DriftCategory, string>;
49
149
  type SpecVisibility = "public" | "protected" | "private";
50
150
  type SpecDocsMetadata = {
51
151
  coverageScore?: number;
52
- missing?: SpecDocSignal[];
152
+ /** Rule IDs that failed quality checks */
153
+ missing?: string[];
53
154
  drift?: SpecDocDrift[];
54
155
  };
55
156
  type SpecTypeParameter = {
@@ -109,8 +210,7 @@ type SpecExport = {
109
210
  type?: string | SpecSchema;
110
211
  schema?: SpecSchema;
111
212
  description?: string;
112
- examples?: string[];
113
- docs?: SpecDocsMetadata;
213
+ examples?: (string | SpecExample)[];
114
214
  source?: SpecSource;
115
215
  deprecated?: boolean;
116
216
  flags?: Record<string, unknown>;
@@ -123,6 +223,7 @@ type SpecExport = {
123
223
  decorators?: SpecDecorator[];
124
224
  isAugmentation?: boolean;
125
225
  augmentedModule?: string;
226
+ related?: SpecRelation[];
126
227
  };
127
228
  type SpecType = {
128
229
  id: string;
@@ -145,6 +246,7 @@ type SpecType = {
145
246
  typeAliasKind?: SpecTypeAliasKind;
146
247
  conditionalType?: SpecConditionalType;
147
248
  mappedType?: SpecMappedType;
249
+ related?: SpecRelation[];
148
250
  };
149
251
  type OpenPkgMeta = {
150
252
  name: string;
@@ -163,13 +265,23 @@ type OpenPkg = {
163
265
  exports: SpecExport[];
164
266
  types?: SpecType[];
165
267
  examples?: SpecExample[];
166
- docs?: SpecDocsMetadata;
167
268
  extensions?: SpecExtension;
168
269
  };
169
270
  declare const SCHEMA_VERSION: OpenPkgVersion;
170
271
  declare const SCHEMA_URL = "https://unpkg.com/@openpkg-ts/spec/schemas/v0.3.0/openpkg.schema.json";
171
272
  declare const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
172
273
  declare function dereference(spec: OpenPkg): OpenPkg;
274
+ /**
275
+ * Export with optional docs metadata for diff comparison.
276
+ * Pure OpenPkg specs won't have docs; enriched specs will.
277
+ */
278
+ type ExportWithDocs = SpecExport & {
279
+ docs?: SpecDocsMetadata;
280
+ };
281
+ type SpecWithDocs = OpenPkg & {
282
+ docs?: SpecDocsMetadata;
283
+ exports: ExportWithDocs[];
284
+ };
173
285
  type BreakingSeverity = "high" | "medium" | "low";
174
286
  interface CategorizedBreaking {
175
287
  id: string;
@@ -198,7 +310,12 @@ type SpecDiff = {
198
310
  driftIntroduced: number;
199
311
  driftResolved: number;
200
312
  };
201
- declare function diffSpec(oldSpec: OpenPkg, newSpec: OpenPkg): SpecDiff;
313
+ /**
314
+ * Compare two OpenPkg specs and compute differences.
315
+ * If specs are enriched (have docs metadata), coverage changes are tracked.
316
+ * For pure structural specs, coverage fields will be 0.
317
+ */
318
+ declare function diffSpec(oldSpec: SpecWithDocs, newSpec: SpecWithDocs): SpecDiff;
202
319
  /**
203
320
  * Categorize breaking changes by severity
204
321
  *
@@ -208,7 +325,7 @@ declare function diffSpec(oldSpec: OpenPkg, newSpec: OpenPkg): SpecDiff;
208
325
  * @param memberChanges - Optional member-level changes for classes
209
326
  * @returns Categorized breaking changes sorted by severity (high first)
210
327
  */
211
- declare function categorizeBreakingChanges(breaking: string[], oldSpec: OpenPkg, newSpec: OpenPkg, memberChanges?: MemberChangeInfo[]): CategorizedBreaking[];
328
+ declare function categorizeBreakingChanges(breaking: string[], oldSpec: SpecWithDocs, newSpec: SpecWithDocs, memberChanges?: MemberChangeInfo[]): CategorizedBreaking[];
212
329
  declare function normalize(spec: OpenPkg): OpenPkg;
213
330
  /** Supported schema versions */
214
331
  type SchemaVersion = "0.1.0" | "0.2.0" | "0.3.0" | "latest";
@@ -246,4 +363,4 @@ declare function assertSpec(spec: unknown, version?: SchemaVersion): asserts spe
246
363
  * @returns Array of validation errors (empty if valid)
247
364
  */
248
365
  declare function getValidationErrors(spec: unknown, version?: SchemaVersion): SpecError[];
249
- export { validateSpec, normalize, getValidationErrors, diffSpec, dereference, categorizeBreakingChanges, assertSpec, SpecVisibility, SpecTypeParameter, SpecTypeKind, SpecTypeAliasKind, SpecType, SpecThrows, SpecTag, SpecSource, SpecSignatureReturn, SpecSignatureParameter, SpecSignature, SpecSchema, SpecMember, SpecMappedType, SpecExtension, SpecExportKind, SpecExport, SpecExample, SpecDocsMetadata, SpecDocSignal, SpecDocDrift, SpecDiff, SpecDecorator, SpecConditionalType, SCHEMA_VERSION, SCHEMA_URL, OpenPkgVersion, OpenPkgMeta, OpenPkg, MemberChangeInfo, JSON_SCHEMA_DRAFT, CategorizedBreaking, BreakingSeverity };
366
+ export { validateSpec, normalize, getValidationErrors, diffSpec, dereference, categorizeBreakingChanges, assertSpec, SpecVisibility, SpecTypeParameter, SpecTypeKind, SpecTypeAliasKind, SpecType, SpecThrows, SpecTag, SpecSource, SpecSignatureReturn, SpecSignatureParameter, SpecSignature, SpecSchemaRef, SpecSchemaPrimitive, SpecSchemaGeneric, SpecSchemaFallback, SpecSchemaComposite, SpecSchemaCombinator, SpecSchema, SpecRelationType, SpecRelation, SpecMember, SpecMappedType, SpecExtension, SpecExportKind, SpecExport, SpecExampleLanguage, SpecExample, SpecDocsMetadata, SpecDocDrift, SpecDiff, SpecDecorator, SpecConditionalType, SCHEMA_VERSION, SCHEMA_URL, OpenPkgVersion, OpenPkgMeta, OpenPkg, MemberChangeInfo, JSON_SCHEMA_DRAFT, DriftType, DriftCategory, DRIFT_CATEGORY_LABELS, DRIFT_CATEGORY_DESCRIPTIONS, DRIFT_CATEGORIES, CategorizedBreaking, BreakingSeverity };
package/dist/index.js CHANGED
@@ -318,6 +318,33 @@ function normalizeType(item) {
318
318
  }
319
319
  return clone;
320
320
  }
321
+ // src/types.ts
322
+ var DRIFT_CATEGORIES = {
323
+ "param-mismatch": "structural",
324
+ "param-type-mismatch": "structural",
325
+ "return-type-mismatch": "structural",
326
+ "optionality-mismatch": "structural",
327
+ "generic-constraint-mismatch": "structural",
328
+ "property-type-drift": "structural",
329
+ "async-mismatch": "structural",
330
+ "deprecated-mismatch": "semantic",
331
+ "visibility-mismatch": "semantic",
332
+ "broken-link": "semantic",
333
+ "example-drift": "example",
334
+ "example-syntax-error": "example",
335
+ "example-runtime-error": "example",
336
+ "example-assertion-failed": "example"
337
+ };
338
+ var DRIFT_CATEGORY_LABELS = {
339
+ structural: "Signature mismatches",
340
+ semantic: "Metadata issues",
341
+ example: "Example problems"
342
+ };
343
+ var DRIFT_CATEGORY_DESCRIPTIONS = {
344
+ structural: "JSDoc types or parameters don't match the actual code signature",
345
+ semantic: "Deprecation, visibility, or reference issues",
346
+ example: "@example code has errors or doesn't work correctly"
347
+ };
321
348
  // src/validate.ts
322
349
  import Ajv from "ajv/dist/2020.js";
323
350
  import addFormats from "ajv-formats";
@@ -1244,7 +1271,7 @@ var openpkg_schema_default3 = {
1244
1271
  type: "array",
1245
1272
  description: "Usage examples from documentation",
1246
1273
  items: {
1247
- type: "string"
1274
+ $ref: "#/$defs/example"
1248
1275
  }
1249
1276
  },
1250
1277
  signatures: {
@@ -1310,6 +1337,13 @@ var openpkg_schema_default3 = {
1310
1337
  augmentedModule: {
1311
1338
  type: "string",
1312
1339
  description: "The module being augmented (e.g., 'express')"
1340
+ },
1341
+ related: {
1342
+ type: "array",
1343
+ description: "Related exports/types (auto-detected + @see tags)",
1344
+ items: {
1345
+ $ref: "#/$defs/relation"
1346
+ }
1313
1347
  }
1314
1348
  }
1315
1349
  },
@@ -1396,6 +1430,13 @@ var openpkg_schema_default3 = {
1396
1430
  mappedType: {
1397
1431
  $ref: "#/$defs/mappedType",
1398
1432
  description: "Structural details for mapped types"
1433
+ },
1434
+ related: {
1435
+ type: "array",
1436
+ description: "Related exports/types (auto-detected + @see tags)",
1437
+ items: {
1438
+ $ref: "#/$defs/relation"
1439
+ }
1399
1440
  }
1400
1441
  }
1401
1442
  },
@@ -1542,28 +1583,39 @@ var openpkg_schema_default3 = {
1542
1583
  }
1543
1584
  },
1544
1585
  schema: {
1545
- anyOf: [
1546
- {
1547
- type: "boolean"
1548
- },
1586
+ description: "Flexible JSON Schema for type representation",
1587
+ oneOf: [
1588
+ { type: "string" },
1589
+ { type: "boolean" },
1549
1590
  {
1550
1591
  type: "object",
1551
1592
  properties: {
1552
- $ref: {
1553
- type: "string",
1554
- description: "Reference to another type",
1555
- pattern: "^#/types/[A-Za-z0-9_.-]+$"
1556
- }
1557
- },
1558
- required: ["$ref"],
1559
- additionalProperties: false
1560
- },
1561
- {
1562
- type: "object",
1563
- not: {
1564
- required: ["$ref"]
1565
- },
1566
- additionalProperties: true
1593
+ type: { type: "string" },
1594
+ format: { type: "string" },
1595
+ enum: { type: "array" },
1596
+ items: { $ref: "#/$defs/schema" },
1597
+ properties: {
1598
+ type: "object",
1599
+ additionalProperties: { $ref: "#/$defs/schema" }
1600
+ },
1601
+ required: { type: "array", items: { type: "string" } },
1602
+ additionalProperties: {
1603
+ oneOf: [{ type: "boolean" }, { $ref: "#/$defs/schema" }]
1604
+ },
1605
+ anyOf: { type: "array", items: { $ref: "#/$defs/schema" } },
1606
+ allOf: { type: "array", items: { $ref: "#/$defs/schema" } },
1607
+ oneOf: { type: "array", items: { $ref: "#/$defs/schema" } },
1608
+ $ref: { type: "string" },
1609
+ discriminator: {
1610
+ type: "object",
1611
+ properties: { propertyName: { type: "string" } }
1612
+ },
1613
+ tsType: { type: "string" },
1614
+ description: { type: "string" },
1615
+ minItems: { type: "integer" },
1616
+ maxItems: { type: "integer" },
1617
+ signatures: { type: "array" }
1618
+ }
1567
1619
  }
1568
1620
  ]
1569
1621
  },
@@ -1674,6 +1726,76 @@ var openpkg_schema_default3 = {
1674
1726
  }
1675
1727
  },
1676
1728
  additionalProperties: false
1729
+ },
1730
+ exampleLanguage: {
1731
+ type: "string",
1732
+ description: "Programming language for an example",
1733
+ enum: ["ts", "js", "tsx", "jsx", "shell", "json"]
1734
+ },
1735
+ example: {
1736
+ description: "Usage example - can be a simple string or structured object",
1737
+ oneOf: [
1738
+ { type: "string" },
1739
+ {
1740
+ type: "object",
1741
+ required: ["code"],
1742
+ properties: {
1743
+ code: {
1744
+ type: "string",
1745
+ description: "The example code"
1746
+ },
1747
+ title: {
1748
+ type: "string",
1749
+ description: "Short title for the example"
1750
+ },
1751
+ description: {
1752
+ type: "string",
1753
+ description: "Longer description of what the example demonstrates"
1754
+ },
1755
+ language: {
1756
+ $ref: "#/$defs/exampleLanguage"
1757
+ },
1758
+ runnable: {
1759
+ type: "boolean",
1760
+ description: "Whether this example can be run/tested automatically"
1761
+ },
1762
+ expectedOutput: {
1763
+ type: "string",
1764
+ description: "Expected output when the example is run"
1765
+ },
1766
+ tags: {
1767
+ type: "array",
1768
+ description: "Categorization tags (e.g., 'basic', 'advanced', 'error-handling')",
1769
+ items: { type: "string" }
1770
+ }
1771
+ },
1772
+ additionalProperties: false
1773
+ }
1774
+ ]
1775
+ },
1776
+ relationType: {
1777
+ type: "string",
1778
+ description: "Type of relationship between exports",
1779
+ enum: ["uses", "returns", "implements", "extends", "see-also", "companion"]
1780
+ },
1781
+ relation: {
1782
+ type: "object",
1783
+ description: "Relationship to another export or type",
1784
+ required: ["type", "target"],
1785
+ properties: {
1786
+ type: {
1787
+ $ref: "#/$defs/relationType"
1788
+ },
1789
+ target: {
1790
+ type: "string",
1791
+ description: "Target export/type ID or reference"
1792
+ },
1793
+ description: {
1794
+ type: "string",
1795
+ description: "Optional description of the relationship"
1796
+ }
1797
+ },
1798
+ additionalProperties: false
1677
1799
  }
1678
1800
  }
1679
1801
  };
@@ -1746,5 +1868,8 @@ export {
1746
1868
  assertSpec,
1747
1869
  SCHEMA_VERSION,
1748
1870
  SCHEMA_URL,
1749
- JSON_SCHEMA_DRAFT
1871
+ JSON_SCHEMA_DRAFT,
1872
+ DRIFT_CATEGORY_LABELS,
1873
+ DRIFT_CATEGORY_DESCRIPTIONS,
1874
+ DRIFT_CATEGORIES
1750
1875
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/spec",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Shared schema, validation, and diff utilities for OpenPkg specs",
5
5
  "keywords": [
6
6
  "openpkg",
@@ -192,7 +192,7 @@
192
192
  "type": "array",
193
193
  "description": "Usage examples from documentation",
194
194
  "items": {
195
- "type": "string"
195
+ "$ref": "#/$defs/example"
196
196
  }
197
197
  },
198
198
  "signatures": {
@@ -258,6 +258,13 @@
258
258
  "augmentedModule": {
259
259
  "type": "string",
260
260
  "description": "The module being augmented (e.g., 'express')"
261
+ },
262
+ "related": {
263
+ "type": "array",
264
+ "description": "Related exports/types (auto-detected + @see tags)",
265
+ "items": {
266
+ "$ref": "#/$defs/relation"
267
+ }
261
268
  }
262
269
  }
263
270
  },
@@ -344,6 +351,13 @@
344
351
  "mappedType": {
345
352
  "$ref": "#/$defs/mappedType",
346
353
  "description": "Structural details for mapped types"
354
+ },
355
+ "related": {
356
+ "type": "array",
357
+ "description": "Related exports/types (auto-detected + @see tags)",
358
+ "items": {
359
+ "$ref": "#/$defs/relation"
360
+ }
347
361
  }
348
362
  }
349
363
  },
@@ -490,28 +504,39 @@
490
504
  }
491
505
  },
492
506
  "schema": {
493
- "anyOf": [
494
- {
495
- "type": "boolean"
496
- },
507
+ "description": "Flexible JSON Schema for type representation",
508
+ "oneOf": [
509
+ { "type": "string" },
510
+ { "type": "boolean" },
497
511
  {
498
512
  "type": "object",
499
513
  "properties": {
500
- "$ref": {
501
- "type": "string",
502
- "description": "Reference to another type",
503
- "pattern": "^#/types/[A-Za-z0-9_.-]+$"
504
- }
505
- },
506
- "required": ["$ref"],
507
- "additionalProperties": false
508
- },
509
- {
510
- "type": "object",
511
- "not": {
512
- "required": ["$ref"]
513
- },
514
- "additionalProperties": true
514
+ "type": { "type": "string" },
515
+ "format": { "type": "string" },
516
+ "enum": { "type": "array" },
517
+ "items": { "$ref": "#/$defs/schema" },
518
+ "properties": {
519
+ "type": "object",
520
+ "additionalProperties": { "$ref": "#/$defs/schema" }
521
+ },
522
+ "required": { "type": "array", "items": { "type": "string" } },
523
+ "additionalProperties": {
524
+ "oneOf": [{ "type": "boolean" }, { "$ref": "#/$defs/schema" }]
525
+ },
526
+ "anyOf": { "type": "array", "items": { "$ref": "#/$defs/schema" } },
527
+ "allOf": { "type": "array", "items": { "$ref": "#/$defs/schema" } },
528
+ "oneOf": { "type": "array", "items": { "$ref": "#/$defs/schema" } },
529
+ "$ref": { "type": "string" },
530
+ "discriminator": {
531
+ "type": "object",
532
+ "properties": { "propertyName": { "type": "string" } }
533
+ },
534
+ "tsType": { "type": "string" },
535
+ "description": { "type": "string" },
536
+ "minItems": { "type": "integer" },
537
+ "maxItems": { "type": "integer" },
538
+ "signatures": { "type": "array" }
539
+ }
515
540
  }
516
541
  ]
517
542
  },
@@ -622,6 +647,76 @@
622
647
  }
623
648
  },
624
649
  "additionalProperties": false
650
+ },
651
+ "exampleLanguage": {
652
+ "type": "string",
653
+ "description": "Programming language for an example",
654
+ "enum": ["ts", "js", "tsx", "jsx", "shell", "json"]
655
+ },
656
+ "example": {
657
+ "description": "Usage example - can be a simple string or structured object",
658
+ "oneOf": [
659
+ { "type": "string" },
660
+ {
661
+ "type": "object",
662
+ "required": ["code"],
663
+ "properties": {
664
+ "code": {
665
+ "type": "string",
666
+ "description": "The example code"
667
+ },
668
+ "title": {
669
+ "type": "string",
670
+ "description": "Short title for the example"
671
+ },
672
+ "description": {
673
+ "type": "string",
674
+ "description": "Longer description of what the example demonstrates"
675
+ },
676
+ "language": {
677
+ "$ref": "#/$defs/exampleLanguage"
678
+ },
679
+ "runnable": {
680
+ "type": "boolean",
681
+ "description": "Whether this example can be run/tested automatically"
682
+ },
683
+ "expectedOutput": {
684
+ "type": "string",
685
+ "description": "Expected output when the example is run"
686
+ },
687
+ "tags": {
688
+ "type": "array",
689
+ "description": "Categorization tags (e.g., 'basic', 'advanced', 'error-handling')",
690
+ "items": { "type": "string" }
691
+ }
692
+ },
693
+ "additionalProperties": false
694
+ }
695
+ ]
696
+ },
697
+ "relationType": {
698
+ "type": "string",
699
+ "description": "Type of relationship between exports",
700
+ "enum": ["uses", "returns", "implements", "extends", "see-also", "companion"]
701
+ },
702
+ "relation": {
703
+ "type": "object",
704
+ "description": "Relationship to another export or type",
705
+ "required": ["type", "target"],
706
+ "properties": {
707
+ "type": {
708
+ "$ref": "#/$defs/relationType"
709
+ },
710
+ "target": {
711
+ "type": "string",
712
+ "description": "Target export/type ID or reference"
713
+ },
714
+ "description": {
715
+ "type": "string",
716
+ "description": "Optional description of the relationship"
717
+ }
718
+ },
719
+ "additionalProperties": false
625
720
  }
626
721
  }
627
722
  }