@graphty/graph-io 0.3.16 → 0.3.18

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 (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +29 -3
  3. package/dist/chunks/{escape-D1f9-cwf.js → escape-D-gZWO26.js} +3 -2
  4. package/dist/chunks/{escape-D1f9-cwf.js.map → escape-D-gZWO26.js.map} +1 -1
  5. package/dist/chunks/{importer-D7ZcGCeb.js → importer-Br_QeAeE.js} +4 -3
  6. package/dist/chunks/{importer-D7ZcGCeb.js.map → importer-Br_QeAeE.js.map} +1 -1
  7. package/dist/chunks/{importer-CXEiicAN.js → importer-DHagxvDD.js} +4 -3
  8. package/dist/chunks/{importer-CXEiicAN.js.map → importer-DHagxvDD.js.map} +1 -1
  9. package/dist/chunks/{importer-C7mnGdr_.js → importer-Du5crN9l.js} +510 -26
  10. package/dist/chunks/importer-Du5crN9l.js.map +1 -0
  11. package/dist/chunks/importer-aNJfe0qu.js +1614 -0
  12. package/dist/chunks/importer-aNJfe0qu.js.map +1 -0
  13. package/dist/chunks/{importer-B8lsjFWx.js → importer-d0uQxFp6.js} +4 -3
  14. package/dist/chunks/{importer-B8lsjFWx.js.map → importer-d0uQxFp6.js.map} +1 -1
  15. package/dist/chunks/ontology-BnrJ4I98.js +113 -0
  16. package/dist/chunks/ontology-BnrJ4I98.js.map +1 -0
  17. package/dist/chunks/{records-IHsCfv7s.js → records-Bk9jgodz.js} +2 -2
  18. package/dist/chunks/{records-IHsCfv7s.js.map → records-Bk9jgodz.js.map} +1 -1
  19. package/dist/chunks/{writer-BtWpUaiH.js → report-BOk0p5y8.js} +181 -912
  20. package/dist/chunks/report-BOk0p5y8.js.map +1 -0
  21. package/dist/chunks/writer-GAdltGmC.js +827 -0
  22. package/dist/chunks/writer-GAdltGmC.js.map +1 -0
  23. package/dist/csv.js +4 -3
  24. package/dist/csv.js.map +1 -1
  25. package/dist/dot.js +1 -1
  26. package/dist/gexf.js +3 -2
  27. package/dist/gexf.js.map +1 -1
  28. package/dist/gml.js +3 -2
  29. package/dist/gml.js.map +1 -1
  30. package/dist/graph-io.js +191 -134
  31. package/dist/graph-io.js.map +1 -1
  32. package/dist/graphml.js +1 -1
  33. package/dist/json.js +1 -1
  34. package/dist/neo4j.js +4 -3
  35. package/dist/neo4j.js.map +1 -1
  36. package/dist/obo.d.ts +1 -0
  37. package/dist/obo.js +6 -0
  38. package/dist/obo.js.map +1 -0
  39. package/dist/pajek.js +1 -1
  40. package/dist/src/common/codes.d.ts +43 -0
  41. package/dist/src/common/codes.d.ts.map +1 -1
  42. package/dist/src/common/codes.js +43 -0
  43. package/dist/src/common/codes.js.map +1 -1
  44. package/dist/src/common/input.d.ts +9 -0
  45. package/dist/src/common/input.d.ts.map +1 -1
  46. package/dist/src/common/input.js +16 -0
  47. package/dist/src/common/input.js.map +1 -1
  48. package/dist/src/common/ontology.d.ts +59 -0
  49. package/dist/src/common/ontology.d.ts.map +1 -0
  50. package/dist/src/common/ontology.js +147 -0
  51. package/dist/src/common/ontology.js.map +1 -0
  52. package/dist/src/common/options.d.ts +12 -1
  53. package/dist/src/common/options.d.ts.map +1 -1
  54. package/dist/src/common/options.js +51 -1
  55. package/dist/src/common/options.js.map +1 -1
  56. package/dist/src/formats/json/dialect.d.ts +8 -6
  57. package/dist/src/formats/json/dialect.d.ts.map +1 -1
  58. package/dist/src/formats/json/dialect.js +26 -3
  59. package/dist/src/formats/json/dialect.js.map +1 -1
  60. package/dist/src/formats/json/importer.d.ts +324 -7
  61. package/dist/src/formats/json/importer.d.ts.map +1 -1
  62. package/dist/src/formats/json/importer.js +154 -23
  63. package/dist/src/formats/json/importer.js.map +1 -1
  64. package/dist/src/formats/json/obographs.d.ts +21 -0
  65. package/dist/src/formats/json/obographs.d.ts.map +1 -0
  66. package/dist/src/formats/json/obographs.js +476 -0
  67. package/dist/src/formats/json/obographs.js.map +1 -0
  68. package/dist/src/formats/obo/importer.d.ts +90 -0
  69. package/dist/src/formats/obo/importer.d.ts.map +1 -0
  70. package/dist/src/formats/obo/importer.js +1248 -0
  71. package/dist/src/formats/obo/importer.js.map +1 -0
  72. package/dist/src/formats/obo/index.d.ts +7 -0
  73. package/dist/src/formats/obo/index.d.ts.map +1 -0
  74. package/dist/src/formats/obo/index.js +7 -0
  75. package/dist/src/formats/obo/index.js.map +1 -0
  76. package/dist/src/formats/obo/syntax.d.ts +121 -0
  77. package/dist/src/formats/obo/syntax.d.ts.map +1 -0
  78. package/dist/src/formats/obo/syntax.js +424 -0
  79. package/dist/src/formats/obo/syntax.js.map +1 -0
  80. package/dist/src/index.d.ts +5 -4
  81. package/dist/src/index.d.ts.map +1 -1
  82. package/dist/src/index.js +4 -3
  83. package/dist/src/index.js.map +1 -1
  84. package/dist/src/registry.d.ts +21 -3
  85. package/dist/src/registry.d.ts.map +1 -1
  86. package/dist/src/registry.js +32 -1
  87. package/dist/src/registry.js.map +1 -1
  88. package/dist/src/sniff.d.ts +1 -1
  89. package/dist/src/sniff.d.ts.map +1 -1
  90. package/dist/src/sniff.js +23 -3
  91. package/dist/src/sniff.js.map +1 -1
  92. package/dist/src/types.d.ts +31 -0
  93. package/dist/src/types.d.ts.map +1 -1
  94. package/dist/src/types.js.map +1 -1
  95. package/package.json +6 -1
  96. package/src/common/codes.ts +58 -0
  97. package/src/common/input.ts +16 -0
  98. package/src/common/ontology.ts +169 -0
  99. package/src/common/options.ts +78 -2
  100. package/src/formats/json/dialect.ts +37 -7
  101. package/src/formats/json/importer.ts +206 -28
  102. package/src/formats/json/obographs.ts +563 -0
  103. package/src/formats/obo/importer.ts +1695 -0
  104. package/src/formats/obo/index.ts +7 -0
  105. package/src/formats/obo/syntax.ts +466 -0
  106. package/src/index.ts +6 -0
  107. package/src/registry.ts +38 -3
  108. package/src/sniff.ts +35 -5
  109. package/src/types.ts +40 -1
  110. package/dist/chunks/importer-C7mnGdr_.js.map +0 -1
  111. package/dist/chunks/writer-BtWpUaiH.js.map +0 -1
@@ -32,14 +32,15 @@
32
32
  */
33
33
  import { GraphFormatError, INVALID_INDEX, } from "@graphty/graph-format";
34
34
  import { uniqueColumnName } from "../../common/attributes.js";
35
- import { DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, HYPEREDGE_CODE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, MULTIPLE_GRAPHS_CODE, OPTION_IGNORED_CODE, SYNTAX_CODE, UNKNOWN_ENCODING_CODE, UNKNOWN_PARENT_CODE, } from "../../common/codes.js";
35
+ import { AMBIGUOUS_GRAPH_NAME_CODE, BAD_VALUE_CODE, DANGLING_REFERENCE_CODE, DUPLICATE_EDGE_ID_CODE, DUPLICATE_NODE_CODE, EMPTY_INPUT_CODE, ENCODING_FALLBACK_CODE, GRAPH_NOT_FOUND_CODE, HYPEREDGE_CODE, INVALID_ENCODING_CODE, INVALID_UTF8_CODE, MISSING_ENDPOINT_CODE, MISSING_ID_CODE, MULTIPLE_GRAPHS_CODE, OPTION_IGNORED_CODE, SYNTAX_CODE, TOO_LARGE_CODE, UNKNOWN_ENCODING_CODE, UNKNOWN_PARENT_CODE, } from "../../common/codes.js";
36
36
  import { DirectionResolver } from "../../common/direction.js";
37
37
  import { ID_MERGED_CODE, IdCoercer } from "../../common/ids.js";
38
- import { readText, throwIfAborted } from "../../common/input.js";
39
- import { reportSinkOptions, reportUnusedOptions, resolveImportOptions, SINK_OPTION_CODE, } from "../../common/options.js";
38
+ import { readText, textChunks, throwIfAborted } from "../../common/input.js";
39
+ import { chooseGraph, reportSinkOptions, reportUnusedOptions, resolveImportOptions, SINK_OPTION_CODE, } from "../../common/options.js";
40
40
  import { ImportReportBuilder } from "../../common/report.js";
41
41
  import { weightFromValue } from "../../common/weights.js";
42
42
  import { CLASSES_COLUMN, CYTOSCAPE_ELEMENT_KEYS, CYTOSCAPE_STRUCTURAL_KEYS, DIALECT_DEFAULT_DIRECTED, hasKey, isJsonImportDialect, isJsonObject, JSON_IMPORT_DIALECTS, META_KEY, NODE_LINK_SOURCE_KEYS, NODE_LINK_TARGET_KEYS, PARENT_COLUMN, POSITION_COLUMN, sniffJsonDialect, SUFFIX, } from "./dialect.js";
43
+ import { importObographs } from "./obographs.js";
43
44
  /**
44
45
  * The issue codes the JSON importer records (design section 8.6), by name: the codes shared with
45
46
  * the other importers (src/common/codes.ts) and the JSON-specific ones. A key is the code without
@@ -67,7 +68,7 @@ export const JSON_ISSUE = Object.freeze({
67
68
  /** An index endpoint is not an integer below the node count, or names a skipped node. */
68
69
  BAD_INDEX: "E_BAD_INDEX",
69
70
  /** A declared field has the wrong JSON type (JGF label / relation / metadata, Cytoscape position / classes). */
70
- BAD_VALUE: "E_BAD_VALUE",
71
+ BAD_VALUE: BAD_VALUE_CODE,
71
72
  /** A graph-level flag (`directed`, `multigraph`, graphology `options`) has the wrong type; the default is used. */
72
73
  BAD_FLAG: "W_BAD_FLAG",
73
74
  /** A Cytoscape `data.parent` names an unknown node. */
@@ -80,8 +81,18 @@ export const JSON_ISSUE = Object.freeze({
80
81
  ID_MERGED: ID_MERGED_CODE,
81
82
  /** Edge ids of mixed JSON types were stored as text. */
82
83
  EDGE_ID_STRINGIFIED: "W_EDGE_ID_STRINGIFIED",
83
- /** A JGF `graphs` array holds more than one graph; only `graphIndex` is read. */
84
+ /** A JGF or OBO Graphs `graphs` array holds more than one graph; only the chosen one is read. */
84
85
  MULTIPLE_GRAPHS: MULTIPLE_GRAPHS_CODE,
86
+ /** `graphIndex` is beyond the `graphs` array, or `graphName` names none of its graphs (fatal). */
87
+ GRAPH_NOT_FOUND: GRAPH_NOT_FOUND_CODE,
88
+ /** `graphName` names more than one graph of the `graphs` array (fatal). */
89
+ AMBIGUOUS_GRAPH_NAME: AMBIGUOUS_GRAPH_NAME_CODE,
90
+ /** obographs: an edge uses the outdated `subj` key of the OBO Graphs README; it is read as `sub`. */
91
+ OBOGRAPHS_SUBJ: "W_JSON_OBOGRAPHS_SUBJ",
92
+ /** obographs: an edge endpoint missing from `nodes` (a placeholder node is made, or the edge dropped under addMissingNodes false). */
93
+ DANGLING_REFERENCE: DANGLING_REFERENCE_CODE,
94
+ /** The document is longer than one JavaScript string can hold (fatal; category unsupported). */
95
+ TOO_LARGE: TOO_LARGE_CODE,
85
96
  /** JGF hyperedges under the "error" policy. */
86
97
  HYPEREDGE: HYPEREDGE_CODE,
87
98
  /** JGF hyperedges skipped under the default "skip" policy. */
@@ -180,6 +191,14 @@ function resolveJsonOptions(options) {
180
191
  if (!Number.isInteger(graphIndex) || graphIndex < 0) {
181
192
  throw unsupportedOption("graphIndex", graphIndex, ["a non-negative integer"]);
182
193
  }
194
+ const oboIds = o.oboIds ?? "curie";
195
+ if (oboIds !== "curie" && oboIds !== "iri") {
196
+ throw unsupportedOption("oboIds", oboIds, ["curie", "iri"]);
197
+ }
198
+ const typedefs = o.typedefs ?? "metadata";
199
+ if (typedefs !== "metadata" && typedefs !== "nodes") {
200
+ throw unsupportedOption("typedefs", typedefs, ["metadata", "nodes"]);
201
+ }
183
202
  const nodesPath = pathOption("nodesPath", o.nodesPath);
184
203
  const edgesPath = pathOption("edgesPath", o.edgesPath);
185
204
  if ((nodesPath !== null || edgesPath !== null) && dialect !== "auto" && !PATH_DIALECTS.has(dialect)) {
@@ -193,6 +212,9 @@ function resolveJsonOptions(options) {
193
212
  targetKey: keyOption("targetKey", o.targetKey),
194
213
  indexLinks,
195
214
  graphIndex,
215
+ choice: { graphIndex: o.graphIndex, graphName: o.graphName },
216
+ oboIds,
217
+ typedefs,
196
218
  nodesPath,
197
219
  edgesPath,
198
220
  };
@@ -493,7 +515,7 @@ class AttributeWriter {
493
515
  /**
494
516
  * Everything one import call shares between the dialect readers.
495
517
  */
496
- class ImportContext {
518
+ export class ImportContext {
497
519
  /**
498
520
  * Create the context.
499
521
  * @param sink - the sink
@@ -1906,32 +1928,123 @@ function importJgf(ctx, root) {
1906
1928
  ctx.setMeta(shape, { name: label, ...ctx.weightOriginPatch() });
1907
1929
  }
1908
1930
  /**
1909
- * The graph object of a JGF document: `graph`, or `graphs[graphIndex]`.
1931
+ * The graph object of a JGF document: `graph`, or the graph of `graphs` that graphIndex /
1932
+ * graphName choose (chooseGraph()).
1910
1933
  * @param ctx - the context
1911
1934
  * @param root - the document
1912
1935
  * @returns the graph object; the import fails when there is none
1913
1936
  */
1914
1937
  function jgfGraphOf(ctx, root) {
1938
+ return isJsonObject(root.graph) ? root.graph : chosenGraph(ctx, root, "JGF");
1939
+ }
1940
+ /**
1941
+ * The graph of a `graphs` array (JGF, OBO Graphs) that graphIndex / graphName choose, with
1942
+ * W_MULTIPLE_GRAPHS when the others are skipped; importAll() reads graphs[graphIndex].
1943
+ * @param ctx - the context
1944
+ * @param root - the document
1945
+ * @param what - the dialect's name, for the messages
1946
+ * @returns the graph object; the import fails when there is none
1947
+ */
1948
+ export function chosenGraph(ctx, root, what) {
1915
1949
  const { report } = ctx;
1916
- if (isJsonObject(root.graph)) {
1917
- return root.graph;
1918
- }
1919
1950
  const graphs = arraySection(root.graphs, "graphs", report) ?? [];
1920
1951
  if (graphs.length === 0) {
1921
- report.fail(JSON_ISSUE.SHAPE, "a JGF document needs a graph object or a non-empty graphs array");
1952
+ report.fail(JSON_ISSUE.SHAPE, `a ${what} document needs a graph object or a non-empty graphs array`);
1922
1953
  }
1954
+ const index = ctx.json.all === true ? ctx.json.graphIndex : chooseGraph(graphs.map(graphNameOf), ctx.json.choice, report);
1923
1955
  if (graphs.length > 1 && ctx.json.all !== true) {
1924
- report.warning("unsupported", JSON_ISSUE.MULTIPLE_GRAPHS, `the document holds ${graphs.length} graphs; only graphs[${ctx.json.graphIndex}] is read (${graphs.length - 1} skipped), importAll() reads every one`, { element: "graphs" });
1956
+ report.warning("unsupported", JSON_ISSUE.MULTIPLE_GRAPHS, `the document holds ${graphs.length} graphs; only graphs[${index}] is read (${graphs.length - 1} skipped), importAll() reads every one`, { element: "graphs" });
1925
1957
  }
1926
- if (ctx.json.graphIndex >= graphs.length) {
1927
- report.fail(JSON_ISSUE.SHAPE, `graphIndex ${ctx.json.graphIndex} is beyond the ${graphs.length} graph(s)`);
1928
- }
1929
- const graph = graphs[ctx.json.graphIndex];
1958
+ const graph = graphs[index];
1930
1959
  if (!isJsonObject(graph)) {
1931
- return report.fail(JSON_ISSUE.SHAPE, `graphs[${ctx.json.graphIndex}] is not an object`);
1960
+ return report.fail(JSON_ISSUE.SHAPE, `graphs[${index}] is not an object`);
1932
1961
  }
1933
1962
  return graph;
1934
1963
  }
1964
+ /**
1965
+ * The name a graph of a `graphs` array is listed and chosen by: its `id`, else its `label` (JGF)
1966
+ * or `lbl` (OBO Graphs).
1967
+ * @param graph - the graph
1968
+ * @returns the name, or null
1969
+ */
1970
+ function graphNameOf(graph) {
1971
+ if (!isJsonObject(graph)) {
1972
+ return null;
1973
+ }
1974
+ for (const key of ["id", "label", "lbl"]) {
1975
+ if (typeof graph[key] === "string") {
1976
+ return graph[key];
1977
+ }
1978
+ }
1979
+ return null;
1980
+ }
1981
+ /**
1982
+ * How many elements a nodes or edges section holds: an array's length, an object's key count, 0
1983
+ * when absent, null for anything else.
1984
+ * @param section - the section
1985
+ * @returns the count, or null
1986
+ */
1987
+ function countOf(section) {
1988
+ if (section === undefined || section === null) {
1989
+ return 0;
1990
+ }
1991
+ if (Array.isArray(section)) {
1992
+ return section.length;
1993
+ }
1994
+ return isJsonObject(section) ? Object.keys(section).length : null;
1995
+ }
1996
+ /**
1997
+ * The graphs of a parsed document, for listGraphs(): each entry of a JGF or OBO Graphs `graphs`
1998
+ * array with its name and counts; any other document holds one graph.
1999
+ * @param root - the parsed document
2000
+ * @param dialect - its dialect
2001
+ * @returns the listings
2002
+ */
2003
+ function listingsOf(root, dialect) {
2004
+ if ((dialect === "jgf" || dialect === "obographs") && isJsonObject(root)) {
2005
+ if (Array.isArray(root.graphs) && !isJsonObject(root.graph)) {
2006
+ return root.graphs.map((graph, index) => ({
2007
+ index,
2008
+ name: graphNameOf(graph),
2009
+ nodes: isJsonObject(graph) ? countOf(graph.nodes) : null,
2010
+ edges: isJsonObject(graph) ? countOf(graph.edges) : null,
2011
+ }));
2012
+ }
2013
+ if (isJsonObject(root.graph)) {
2014
+ const { graph } = root;
2015
+ return [{ index: 0, name: graphNameOf(graph), nodes: countOf(graph.nodes), edges: countOf(graph.edges) }];
2016
+ }
2017
+ }
2018
+ return [{ index: 0, name: null, nodes: null, edges: null }];
2019
+ }
2020
+ /** The longest string V8 makes (2^29 - 24 UTF-16 code units); a longer document cannot be one JSON.parse input. */
2021
+ const MAX_TEXT_LENGTH = 2 ** 29 - 24;
2022
+ /**
2023
+ * Read the whole input as one string, failing with E_TOO_LARGE (category unsupported) before the
2024
+ * join when it is longer than one JavaScript string can hold (OBO Graphs files such as
2025
+ * ncbitaxon.json are; design 7.1 defers streaming the JSON reader).
2026
+ * @param input - the input
2027
+ * @param report - the report
2028
+ * @param options - cancellation, progress and encoding
2029
+ * @returns the text
2030
+ */
2031
+ async function readJsonText(input, report, options) {
2032
+ if (typeof input === "string") {
2033
+ return readText(input, report, options);
2034
+ }
2035
+ const parts = [];
2036
+ let length = 0;
2037
+ for await (const chunk of textChunks(input, report, options)) {
2038
+ length += chunk.length;
2039
+ if (length > MAX_TEXT_LENGTH) {
2040
+ const message = `the document is longer than ${MAX_TEXT_LENGTH} characters, the most one JavaScript string holds`;
2041
+ report.error("unsupported", JSON_ISSUE.TOO_LARGE, message);
2042
+ throw report.abort(message, { code: JSON_ISSUE.TOO_LARGE });
2043
+ }
2044
+ parts.push(chunk);
2045
+ }
2046
+ return parts.length === 1 ? parts[0] : parts.join("");
2047
+ }
1935
2048
  /**
1936
2049
  * Push a JGF node: `label` into the declared column, `metadata` as attributes.
1937
2050
  * @param ctx - the context
@@ -2321,11 +2434,27 @@ export const jsonImporter = Object.freeze({
2321
2434
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2322
2435
  const json = resolveJsonOptions(options);
2323
2436
  const report = new ImportReportBuilder("json", resolved.errorLimit);
2324
- const text = await readText(input, report, resolved);
2437
+ const text = await readJsonText(input, report, resolved);
2325
2438
  const { root, dialect } = documentOf(parseDocument(text, report), json, report);
2326
2439
  readGraph(root, dialect, sink, report, resolved, json, options);
2327
2440
  return report.finish();
2328
2441
  },
2442
+ /**
2443
+ * List the graphs of a JSON document without importing them: each entry of a JGF or OBO
2444
+ * Graphs `graphs` array with its name (`id`, else its label) and node and edge counts; any
2445
+ * other document holds one graph.
2446
+ * @param input - the text, bytes or stream
2447
+ * @param options - format-specific and common options
2448
+ * @returns one listing per graph
2449
+ */
2450
+ async listGraphs(input, options) {
2451
+ const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2452
+ const json = resolveJsonOptions(options);
2453
+ const report = new ImportReportBuilder("json", resolved.errorLimit);
2454
+ const text = await readJsonText(input, report, resolved);
2455
+ const { root, dialect } = documentOf(parseDocument(text, report), json, report);
2456
+ return listingsOf(root, dialect);
2457
+ },
2329
2458
  /**
2330
2459
  * Read every graph of a JSON document: each entry of a JGF `graphs` array into its own sink;
2331
2460
  * any other document holds one graph.
@@ -2338,11 +2467,9 @@ export const jsonImporter = Object.freeze({
2338
2467
  const resolved = resolveImportOptions(options, FORMAT_DEFAULTS);
2339
2468
  const json = resolveJsonOptions(options);
2340
2469
  const first = new ImportReportBuilder("json", resolved.errorLimit);
2341
- const text = await readText(input, first, resolved);
2470
+ const text = await readJsonText(input, first, resolved);
2342
2471
  const { root, dialect } = documentOf(parseDocument(text, first), json, first);
2343
- const graphs = dialect === "jgf" && isJsonObject(root) && !isJsonObject(root.graph) && Array.isArray(root.graphs)
2344
- ? root.graphs.length
2345
- : 1;
2472
+ const graphs = listingsOf(root, dialect).length;
2346
2473
  const reports = [];
2347
2474
  for (let i = 0; i < Math.max(graphs, 1); i++) {
2348
2475
  const report = i === 0 ? first : new ImportReportBuilder("json", resolved.errorLimit);
@@ -2364,7 +2491,8 @@ export const jsonImporter = Object.freeze({
2364
2491
  */
2365
2492
  function readGraph(root, dialect, sink, report, resolved, json, options) {
2366
2493
  const ctx = new ImportContext(sink, report, resolved, json, options?.defaultDirected !== undefined);
2367
- reportSinkOptions(sink, options, report);
2494
+ // the obographs reader refuses missing endpoints itself, so addMissingNodes false holds on any sink
2495
+ reportSinkOptions(sink, options, report, dialect === "obographs");
2368
2496
  reportUnusedOptions(options, report, USED_OPTIONS);
2369
2497
  if (dialect === "cytoscape") {
2370
2498
  importCytoscape(ctx, root);
@@ -2394,6 +2522,9 @@ function readGraph(root, dialect, sink, report, resolved, json, options) {
2394
2522
  case "tree":
2395
2523
  importTree(ctx, doc);
2396
2524
  break;
2525
+ case "obographs":
2526
+ importObographs(ctx, doc);
2527
+ break;
2397
2528
  default: {
2398
2529
  const name = dialect;
2399
2530
  throw new GraphFormatError("E_UNSUPPORTED", `unknown dialect ${name}`, {