@kgdiff/core 0.1.0 → 0.3.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.
@@ -0,0 +1,154 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@kgdiff/core/schema/graph.v1.json",
4
+ "title": "kgdiff graph, version 1",
5
+ "description": "A graph in kgdiff JSON, as graphToJSON and `kgdiff apply` write it. kgdiff also reads looser JSON (other field names, numeric ids, loose fields as attributes); this schema describes the canonical form. See https://github.com/iiwish/kgdiff/blob/main/docs/formats.md#kgdiff-json",
6
+ "type": "object",
7
+ "properties": {
8
+ "$schema": {
9
+ "type": "string",
10
+ "description": "The URI of this schema, for editors."
11
+ },
12
+ "format": {
13
+ "const": "kgdiff.graph",
14
+ "description": "Format marker. Optional, together with version; it makes readers recognise the file for certain."
15
+ },
16
+ "version": {
17
+ "const": 1,
18
+ "description": "Version of the format (not of the graph: that is meta.version). Readers reject versions they do not support."
19
+ },
20
+ "meta": { "$ref": "#/$defs/meta" },
21
+ "nodes": {
22
+ "type": "array",
23
+ "items": { "$ref": "#/$defs/node" }
24
+ },
25
+ "edges": {
26
+ "type": "array",
27
+ "items": { "$ref": "#/$defs/edge" }
28
+ }
29
+ },
30
+ "required": ["nodes"],
31
+ "dependentRequired": {
32
+ "format": ["version"],
33
+ "version": ["format"]
34
+ },
35
+ "additionalProperties": false,
36
+ "$defs": {
37
+ "id": {
38
+ "type": "string",
39
+ "minLength": 1,
40
+ "description": "A node id: an IRI, a CURIE such as GO:0008150, or any non-empty string."
41
+ },
42
+ "ids": {
43
+ "type": "array",
44
+ "items": { "$ref": "#/$defs/id" }
45
+ },
46
+ "strings": {
47
+ "type": "array",
48
+ "items": { "type": "string" }
49
+ },
50
+ "attrs": {
51
+ "type": "object",
52
+ "description": "Any other values, by name: strings, numbers, booleans, null, arrays and objects."
53
+ },
54
+ "meta": {
55
+ "type": "object",
56
+ "description": "About the graph. Other keys are kept as they are.",
57
+ "properties": {
58
+ "name": {
59
+ "type": "string",
60
+ "description": "Shown in headers and titles."
61
+ },
62
+ "version": {
63
+ "type": "string",
64
+ "description": "The version of the graph, shown in headers and titles."
65
+ },
66
+ "source": {
67
+ "type": "string",
68
+ "description": "Where the graph came from: a file name, a URL…"
69
+ },
70
+ "predicateLabels": {
71
+ "type": "object",
72
+ "additionalProperties": { "type": "string" },
73
+ "description": "Display names of relations, by predicate."
74
+ }
75
+ }
76
+ },
77
+ "node": {
78
+ "type": "object",
79
+ "properties": {
80
+ "id": { "$ref": "#/$defs/id" },
81
+ "label": {
82
+ "type": "string",
83
+ "description": "The display name; defaults to the id. A changed label is a rename."
84
+ },
85
+ "type": {
86
+ "anyOf": [{ "type": "string" }, { "$ref": "#/$defs/strings" }],
87
+ "description": "A type, or several (class, category, rdf:type)."
88
+ },
89
+ "types": {
90
+ "$ref": "#/$defs/strings",
91
+ "description": "The types, as graphToJSON writes them. Use type or types, not both."
92
+ },
93
+ "aliases": {
94
+ "$ref": "#/$defs/strings",
95
+ "description": "Synonyms."
96
+ },
97
+ "attrs": { "$ref": "#/$defs/attrs" },
98
+ "deprecated": {
99
+ "type": "boolean",
100
+ "description": "True for an obsolete node that is still present."
101
+ },
102
+ "replaces": {
103
+ "$ref": "#/$defs/ids",
104
+ "description": "Ids of the nodes this one replaces in the other version: a strong merge hint."
105
+ },
106
+ "replacedBy": {
107
+ "$ref": "#/$defs/ids",
108
+ "description": "Ids of the nodes that replace this one: a strong merge hint."
109
+ },
110
+ "implicit": {
111
+ "type": "boolean",
112
+ "description": "Written by graphToJSON for a node created at an edge end the graph does not describe."
113
+ },
114
+ "position": {
115
+ "type": "object",
116
+ "properties": {
117
+ "x": { "type": "number" },
118
+ "y": { "type": "number" }
119
+ },
120
+ "required": ["x", "y"],
121
+ "additionalProperties": false,
122
+ "description": "A pinned layout position. Never part of the diff."
123
+ }
124
+ },
125
+ "required": ["id"],
126
+ "dependentSchemas": {
127
+ "types": { "type": "object", "properties": { "type": false } }
128
+ },
129
+ "additionalProperties": false
130
+ },
131
+ "edge": {
132
+ "type": "object",
133
+ "properties": {
134
+ "id": {
135
+ "$ref": "#/$defs/id",
136
+ "description": "An explicit edge id, used for identity only when edges are identified by id."
137
+ },
138
+ "source": { "$ref": "#/$defs/id" },
139
+ "target": { "$ref": "#/$defs/id" },
140
+ "predicate": {
141
+ "type": "string",
142
+ "description": "The relation: an IRI, a CURIE or a plain name. Defaults to the empty string."
143
+ },
144
+ "label": {
145
+ "type": "string",
146
+ "description": "Display label of the relation; defaults to a readable form of the predicate."
147
+ },
148
+ "attrs": { "$ref": "#/$defs/attrs" }
149
+ },
150
+ "required": ["source", "target"],
151
+ "additionalProperties": false
152
+ }
153
+ }
154
+ }
package/src/graph-json.ts CHANGED
@@ -35,8 +35,9 @@ export function edgeToJSON(
35
35
  }
36
36
 
37
37
  /**
38
- * Canonical JSON form of a graph: nodes sorted by id, edges by key, defaults omitted,
39
- * attribute keys sorted. `normalize(graphToJSON(g))` rebuilds `g` (positions excepted).
38
+ * Canonical JSON form of a graph: the `kgdiff.graph` version 1 marker, nodes sorted by id,
39
+ * edges by key, defaults omitted, attribute keys sorted. `normalize(graphToJSON(g))` rebuilds
40
+ * `g` (positions excepted).
40
41
  */
41
42
  export function graphToJSON(graph: KGraph): GraphJSON {
42
43
  const nodes = [...graph.nodes.values()]
@@ -45,7 +46,10 @@ export function graphToJSON(graph: KGraph): GraphJSON {
45
46
  const edges = [...graph.edges.values()]
46
47
  .sort((a, b) => compareStrings(a.key, b.key))
47
48
  .map((edge) => edgeToJSON(edge));
48
- return Object.keys(graph.meta).length ? { meta: graph.meta, nodes, edges } : { nodes, edges };
49
+ const marker = { format: 'kgdiff.graph', version: 1 } as const;
50
+ return Object.keys(graph.meta).length
51
+ ? { ...marker, meta: graph.meta, nodes, edges }
52
+ : { ...marker, nodes, edges };
49
53
  }
50
54
 
51
55
  /**
package/src/index.ts CHANGED
@@ -68,9 +68,11 @@ export function resolveOptions(options?: DiffOptions): ResolvedDiffOptions {
68
68
  *
69
69
  * Labels default to ids; sets are sorted and de-duplicated; duplicate nodes are merged;
70
70
  * parallel edges are keyed `#2`, `#3`… in content order; dangling endpoints follow
71
- * `danglingEdges` (default `create`). Problems are reported in `issues`.
71
+ * `danglingEdges` (default `create`). Problems are reported in `issues`. The kgdiff JSON
72
+ * marker (`format: 'kgdiff.graph'`, `version: 1`) is optional.
72
73
  *
73
- * @throws KgdiffError `invalid-input` for malformed input, `dangling-edge` (policy `error`).
74
+ * @throws KgdiffError `invalid-input` for malformed input, `dangling-edge` (policy `error`),
75
+ * `unsupported-version` for another kgdiff format or graph version.
74
76
  */
75
77
  export function normalize(input: GraphInput, options?: NormalizeOptions): KGraph {
76
78
  return normalizeGraph(input, options);
@@ -129,7 +131,10 @@ export function hashGraph(graph: KGraph): string {
129
131
  return hash(graph);
130
132
  }
131
133
 
132
- /** Canonical JSON form of a graph (sorted, defaults omitted). Accepted by `normalize`. */
134
+ /**
135
+ * Canonical JSON form of a graph: kgdiff JSON with its `kgdiff.graph` version 1 marker, sorted,
136
+ * defaults omitted. Accepted by `normalize`.
137
+ */
133
138
  export function graphToJSON(graph: KGraph): GraphJSON {
134
139
  return toGraphJSON(graph);
135
140
  }
package/src/normalize.ts CHANGED
@@ -77,6 +77,29 @@ function fail(message: string, details?: Record<string, unknown>): never {
77
77
  throw new KgdiffError('invalid-input', message, details);
78
78
  }
79
79
 
80
+ /**
81
+ * Checks the optional kgdiff JSON marker. Only `kgdiff.*` formats are kgdiff's: a `format`
82
+ * field with any other value belongs to someone else and is ignored, like other unknown keys.
83
+ */
84
+ function checkFormat(input: Readonly<Record<string, unknown>>): void {
85
+ const { format, version } = input;
86
+ if (typeof format !== 'string' || !format.startsWith('kgdiff.')) return;
87
+ if (format !== 'kgdiff.graph') {
88
+ throw new KgdiffError(
89
+ 'unsupported-version',
90
+ `Expected a kgdiff graph (format "kgdiff.graph"), got format "${format}"`,
91
+ { format },
92
+ );
93
+ }
94
+ if (version !== 1) {
95
+ throw new KgdiffError(
96
+ 'unsupported-version',
97
+ `Unsupported graph version ${String(version)}; this reader supports version 1`,
98
+ { version },
99
+ );
100
+ }
101
+ }
102
+
80
103
  /** Ids are strings; finite numbers are accepted and stringified. Empty → undefined. */
81
104
  function coerceId(value: unknown): Id | undefined {
82
105
  if (typeof value === 'string') return value === '' ? undefined : value;
@@ -339,10 +362,14 @@ export function isKGraph(value: unknown): value is KGraph {
339
362
  * field-wise. Both cases record a `duplicate-edge` issue.
340
363
  * - Dangling endpoints (`danglingEdges`, default `create`): `create` adds an implicit node,
341
364
  * `drop` discards the edge, `error` throws `KgdiffError('dangling-edge')`.
365
+ * - The kgdiff JSON marker is optional; a `kgdiff.*` format other than `kgdiff.graph`, or a
366
+ * version other than 1, is rejected.
342
367
  *
343
- * @throws KgdiffError `invalid-input` for malformed input, `dangling-edge` (policy `error`).
368
+ * @throws KgdiffError `invalid-input` for malformed input, `dangling-edge` (policy `error`),
369
+ * `unsupported-version` for another kgdiff format or graph version.
344
370
  */
345
371
  export function normalize(input: GraphInput, options: NormalizeOptions = {}): KGraph {
372
+ if (isRecord(input)) checkFormat(input);
346
373
  if (!isRecord(input) || !Array.isArray(input.nodes)) {
347
374
  fail('normalize expects a graph of the form { nodes: [...], edges: [...] }');
348
375
  }
package/src/types.ts CHANGED
@@ -105,6 +105,12 @@ export interface GraphMeta {
105
105
  }
106
106
 
107
107
  export interface GraphInput {
108
+ /**
109
+ * Format marker of kgdiff JSON, as `graphToJSON` writes it. Optional; when present, `version`
110
+ * must be 1 (anything else is rejected with `unsupported-version`).
111
+ */
112
+ readonly format?: 'kgdiff.graph';
113
+ readonly version?: 1;
108
114
  readonly nodes: readonly NodeInput[];
109
115
  readonly edges: readonly EdgeInput[];
110
116
  readonly meta?: GraphMeta;
@@ -618,7 +624,10 @@ export interface DiffResult {
618
624
 
619
625
  // ─── JSON transport ─────────────────────────────────────────────────────────
620
626
 
627
+ /** kgdiff JSON (docs/formats.md §kgdiff JSON), schema `schema/graph.v1.json`. */
621
628
  export interface GraphJSON {
629
+ readonly format: 'kgdiff.graph';
630
+ readonly version: 1;
622
631
  readonly meta?: GraphMeta;
623
632
  readonly nodes: readonly NodeJSON[];
624
633
  readonly edges: readonly EdgeJSON[];