@graphty/graph-io 0.3.27 → 0.3.29

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 (109) hide show
  1. package/dist/chunks/{choose-DrXtu_96.js → choose-DT5da6OC.js} +2 -2
  2. package/dist/chunks/{choose-DrXtu_96.js.map → choose-DT5da6OC.js.map} +1 -1
  3. package/dist/chunks/{constants-ikG85dT8.js → constants-DowRD9ai.js} +2 -2
  4. package/dist/chunks/{constants-ikG85dT8.js.map → constants-DowRD9ai.js.map} +1 -1
  5. package/dist/chunks/{escape-D1HPTUrP.js → escape-DQC5HWqF.js} +2 -2
  6. package/dist/chunks/{escape-D1HPTUrP.js.map → escape-DQC5HWqF.js.map} +1 -1
  7. package/dist/chunks/{export-CHd4tQE2.js → export-CVi12U7J.js} +81 -60
  8. package/dist/chunks/export-CVi12U7J.js.map +1 -0
  9. package/dist/chunks/{exporter-D23CKNqF.js → exporter-C3Pa2PSd.js} +5 -5
  10. package/dist/chunks/exporter-C3Pa2PSd.js.map +1 -0
  11. package/dist/chunks/{exporter-DfWTgrti.js → exporter-CgsO2nSl.js} +4 -4
  12. package/dist/chunks/{exporter-DfWTgrti.js.map → exporter-CgsO2nSl.js.map} +1 -1
  13. package/dist/chunks/{exporter-IHv4nkCm.js → exporter-Cy_1da4c.js} +72 -48
  14. package/dist/chunks/exporter-Cy_1da4c.js.map +1 -0
  15. package/dist/chunks/{exporter-DhtW6FxK.js → exporter-JubAlNWi.js} +34 -34
  16. package/dist/chunks/exporter-JubAlNWi.js.map +1 -0
  17. package/dist/chunks/{importer-CIAzt-wo.js → importer-BA_hZKo7.js} +4 -4
  18. package/dist/chunks/{importer-CIAzt-wo.js.map → importer-BA_hZKo7.js.map} +1 -1
  19. package/dist/chunks/{importer-B1wmPQjr.js → importer-CDXlxXlS.js} +35 -9
  20. package/dist/chunks/importer-CDXlxXlS.js.map +1 -0
  21. package/dist/chunks/{importer-BVRE--Un.js → importer-DLz2Vb4q.js} +3 -3
  22. package/dist/chunks/{importer-BVRE--Un.js.map → importer-DLz2Vb4q.js.map} +1 -1
  23. package/dist/chunks/{importer-B2W_58pz.js → importer-DVxzzibh.js} +4 -4
  24. package/dist/chunks/{importer-B2W_58pz.js.map → importer-DVxzzibh.js.map} +1 -1
  25. package/dist/chunks/{importer-BSALvkui.js → importer-Dej72hIF.js} +6 -6
  26. package/dist/chunks/importer-Dej72hIF.js.map +1 -0
  27. package/dist/chunks/{importer-z_FL4VMb.js → importer-lLH9gQF_.js} +5 -5
  28. package/dist/chunks/{importer-z_FL4VMb.js.map → importer-lLH9gQF_.js.map} +1 -1
  29. package/dist/chunks/{importer-DpEW5zeG.js → importer-q-R-Q98w.js} +4 -4
  30. package/dist/chunks/{importer-DpEW5zeG.js.map → importer-q-R-Q98w.js.map} +1 -1
  31. package/dist/chunks/{json-elements-BAmNBZok.js → json-elements-1IUzkRNy.js} +3 -3
  32. package/dist/chunks/{json-elements-BAmNBZok.js.map → json-elements-1IUzkRNy.js.map} +1 -1
  33. package/dist/chunks/{ontology-export-Bab4eUA8.js → ontology-export-b0GLNIj-.js} +2 -2
  34. package/dist/chunks/{ontology-export-Bab4eUA8.js.map → ontology-export-b0GLNIj-.js.map} +1 -1
  35. package/dist/chunks/{records-CdlvL4RO.js → records-CdyqwXeY.js} +2 -2
  36. package/dist/chunks/{records-CdlvL4RO.js.map → records-CdyqwXeY.js.map} +1 -1
  37. package/dist/chunks/{text-DzXjAiTl.js → text-CDgjbkLC.js} +2 -2
  38. package/dist/chunks/{text-DzXjAiTl.js.map → text-CDgjbkLC.js.map} +1 -1
  39. package/dist/csv.js +33 -16
  40. package/dist/csv.js.map +1 -1
  41. package/dist/cx.js +1 -1
  42. package/dist/cx2.js +2 -2
  43. package/dist/cys.js +1 -1
  44. package/dist/dot.js +1 -1
  45. package/dist/gexf.js +2 -2
  46. package/dist/gml.js +18 -9
  47. package/dist/gml.js.map +1 -1
  48. package/dist/graph-io.js +26 -26
  49. package/dist/graph-io.js.map +1 -1
  50. package/dist/graphml.js +2 -2
  51. package/dist/json.js +1 -1
  52. package/dist/neo4j.js +3 -3
  53. package/dist/obo.js +1 -1
  54. package/dist/pajek.js +1 -1
  55. package/dist/src/common/declared-types.d.ts.map +1 -1
  56. package/dist/src/common/declared-types.js.map +1 -1
  57. package/dist/src/common/export.d.ts +6 -0
  58. package/dist/src/common/export.d.ts.map +1 -1
  59. package/dist/src/common/export.js +62 -28
  60. package/dist/src/common/export.js.map +1 -1
  61. package/dist/src/common/zip.js.map +1 -1
  62. package/dist/src/formats/csv/exporter.d.ts.map +1 -1
  63. package/dist/src/formats/csv/exporter.js +18 -7
  64. package/dist/src/formats/csv/exporter.js.map +1 -1
  65. package/dist/src/formats/csv/importer.d.ts.map +1 -1
  66. package/dist/src/formats/csv/importer.js +20 -2
  67. package/dist/src/formats/csv/importer.js.map +1 -1
  68. package/dist/src/formats/cx2/exporter.d.ts.map +1 -1
  69. package/dist/src/formats/cx2/exporter.js +84 -33
  70. package/dist/src/formats/cx2/exporter.js.map +1 -1
  71. package/dist/src/formats/dot/exporter.js +30 -4
  72. package/dist/src/formats/dot/exporter.js.map +1 -1
  73. package/dist/src/formats/dot/importer.js.map +1 -1
  74. package/dist/src/formats/gml/exporter.d.ts.map +1 -1
  75. package/dist/src/formats/gml/exporter.js +23 -6
  76. package/dist/src/formats/gml/exporter.js.map +1 -1
  77. package/dist/src/formats/obo/syntax.d.ts +4 -0
  78. package/dist/src/formats/obo/syntax.d.ts.map +1 -1
  79. package/dist/src/formats/obo/syntax.js +35 -31
  80. package/dist/src/formats/obo/syntax.js.map +1 -1
  81. package/dist/src/formats/pajek/importer.js.map +1 -1
  82. package/dist/src/registry.d.ts.map +1 -1
  83. package/dist/src/registry.js.map +1 -1
  84. package/dist/src/sniff.d.ts.map +1 -1
  85. package/dist/src/sniff.js.map +1 -1
  86. package/dist/src/types.d.ts +6 -0
  87. package/dist/src/types.d.ts.map +1 -1
  88. package/dist/xgmml.js +2 -2
  89. package/package.json +2 -2
  90. package/src/common/declared-types.ts +1 -10
  91. package/src/common/export.ts +105 -62
  92. package/src/common/zip.ts +1 -1
  93. package/src/formats/csv/exporter.ts +19 -15
  94. package/src/formats/csv/importer.ts +21 -2
  95. package/src/formats/cx2/exporter.ts +123 -49
  96. package/src/formats/dot/exporter.ts +37 -4
  97. package/src/formats/dot/importer.ts +1 -1
  98. package/src/formats/gml/exporter.ts +24 -6
  99. package/src/formats/obo/syntax.ts +35 -31
  100. package/src/formats/pajek/importer.ts +1 -1
  101. package/src/registry.ts +9 -10
  102. package/src/sniff.ts +2 -21
  103. package/src/types.ts +7 -7
  104. package/dist/chunks/export-CHd4tQE2.js.map +0 -1
  105. package/dist/chunks/exporter-D23CKNqF.js.map +0 -1
  106. package/dist/chunks/exporter-DhtW6FxK.js.map +0 -1
  107. package/dist/chunks/exporter-IHv4nkCm.js.map +0 -1
  108. package/dist/chunks/importer-B1wmPQjr.js.map +0 -1
  109. package/dist/chunks/importer-BSALvkui.js.map +0 -1
@@ -305,6 +305,25 @@ function versionText(text: string): string {
305
305
  return NUMBER_TEXT.test(text) ? text : quoteGmlString(text);
306
306
  }
307
307
 
308
+ /**
309
+ * A node or edge table's label role column, which is written under "label" (the key the importer
310
+ * gives the role back to): the key is marked used so a plain column of that name cannot have it.
311
+ * @param columns - the written columns
312
+ * @param label - the table name
313
+ * @param used - the keys already taken, gaining "label" when there is a label role column
314
+ * @returns the label role column, or undefined
315
+ */
316
+ function labelRoleColumn(columns: readonly Column[], label: string, used: Set<string>): Column | undefined {
317
+ if (label !== "node" && label !== "edge") {
318
+ return undefined;
319
+ }
320
+ const column = columns.find((c) => c.meta.role === "label");
321
+ if (column !== undefined) {
322
+ used.add("label");
323
+ }
324
+ return column;
325
+ }
326
+
308
327
  /**
309
328
  * Select the written columns of a table and assign their keys, recording invalid and reserved
310
329
  * keys as notes (or rewriting them under mangle).
@@ -326,14 +345,13 @@ function selectColumns(
326
345
  ): WrittenColumn[] {
327
346
  const used = new Set<string>(reserved);
328
347
  const out: WrittenColumn[] = [];
329
- for (const column of table) {
330
- if (!isWritten(column) || !filter(column)) {
331
- continue;
332
- }
333
- const preferred = preferredKey(column.meta);
348
+ const columns = [...table].filter((c) => isWritten(c) && filter(c));
349
+ const labelRole = labelRoleColumn(columns, label, used);
350
+ for (const column of columns) {
351
+ const preferred = column === labelRole ? "label" : preferredKey(column.meta);
334
352
  let key = preferred;
335
353
  const valid = isGmlKey(preferred);
336
- const taken = used.has(preferred);
354
+ const taken = column !== labelRole && used.has(preferred);
337
355
  if (!valid || taken) {
338
356
  const code = valid ? RESERVED_KEY_CODE : INVALID_KEY_CODE;
339
357
  let why = "is not a GML key";
@@ -6,6 +6,10 @@
6
6
  *
7
7
  * Every function works on the RAW text (escapes still in place) and unescapes only the pieces it
8
8
  * hands back, so an escaped quote, colon, comma, brace or `!` never ends a construct.
9
+ *
10
+ * The scanners read characters with charAt(), never `text[i]`: the importer's linear-time tests
11
+ * count the characters the import reads through the string methods, and an index read is
12
+ * invisible to them, so a quadratic scan written with it would pass unnoticed.
9
13
  */
10
14
 
11
15
  /** The OBO escapes with a meaning of their own; any other `\x` is `x` (the guides). */
@@ -29,14 +33,14 @@ export function unescapeObo(text: string): string {
29
33
  }
30
34
  let out = "";
31
35
  for (let i = 0; i < text.length; i++) {
32
- const ch = text[i];
36
+ const ch = text.charAt(i);
33
37
  if (ch !== "\\") {
34
38
  out += ch;
35
39
  continue;
36
40
  }
37
41
  i++;
38
42
  if (i < text.length) {
39
- const next = text[i];
43
+ const next = text.charAt(i);
40
44
  out += ESCAPES[next] ?? next;
41
45
  }
42
46
  }
@@ -51,7 +55,7 @@ export function unescapeObo(text: string): string {
51
55
  */
52
56
  function isEscaped(text: string, index: number): boolean {
53
57
  let run = 0;
54
- for (let i = index - 1; i >= 0 && text[i] === "\\"; i--) {
58
+ for (let i = index - 1; i >= 0 && text.charAt(i) === "\\"; i--) {
55
59
  run++;
56
60
  }
57
61
  return run % 2 === 1;
@@ -71,8 +75,8 @@ export function splitFormFeeds(line: string): string[] {
71
75
  const pieces: string[] = [];
72
76
  let quoted = false;
73
77
  let start = 0;
74
- for (let i = 0; i < line.length; i += line[i] === "\\" ? 2 : 1) {
75
- const ch = line[i];
78
+ for (let i = 0; i < line.length; i += line.charAt(i) === "\\" ? 2 : 1) {
79
+ const ch = line.charAt(i);
76
80
  if (ch === '"') {
77
81
  quoted = !quoted;
78
82
  } else if (ch === "\f" && !quoted) {
@@ -110,11 +114,11 @@ interface TagValue {
110
114
  */
111
115
  export function splitTagValue(line: string): TagValue | null {
112
116
  for (let i = 0; i < line.length; i++) {
113
- if (line[i] === "\\") {
117
+ if (line.charAt(i) === "\\") {
114
118
  i++;
115
119
  continue;
116
120
  }
117
- if (line[i] === ":") {
121
+ if (line.charAt(i) === ":") {
118
122
  return { tag: unescapeObo(line.slice(0, i).trim()), rest: line.slice(i + 1).trimStart() };
119
123
  }
120
124
  }
@@ -134,12 +138,12 @@ export function splitTagValue(line: string): TagValue | null {
134
138
  export function stripComment(rest: string): string {
135
139
  let quoted = false;
136
140
  for (let i = 0; i < rest.length; i++) {
137
- const ch = rest[i];
141
+ const ch = rest.charAt(i);
138
142
  if (ch === "\\") {
139
143
  i++;
140
144
  continue;
141
145
  }
142
- const atStart = i === 0 || rest[i - 1] === " " || rest[i - 1] === "\t";
146
+ const atStart = i === 0 || rest.charAt(i - 1) === " " || rest.charAt(i - 1) === "\t";
143
147
  if (ch === '"' && (quoted || atStart)) {
144
148
  quoted = !quoted;
145
149
  } else if (ch === "!" && !quoted && atStart) {
@@ -183,7 +187,7 @@ export function splitQualifiers(value: string): QualifiedValue {
183
187
  const parsed: Qualifiers[] = [];
184
188
  let end = value.length;
185
189
  let badBlock: string | null = null;
186
- while (end > 0 && value[end - 1] === "}" && !isEscaped(value, end - 1)) {
190
+ while (end > 0 && value.charAt(end - 1) === "}" && !isEscaped(value, end - 1)) {
187
191
  const open = blocks.get(end - 1) ?? -1;
188
192
  const block = open < 0 ? null : parseQualifiers(value.slice(open + 1, end - 1));
189
193
  if (block === null) {
@@ -192,7 +196,7 @@ export function splitQualifiers(value: string): QualifiedValue {
192
196
  }
193
197
  parsed.push(block);
194
198
  end = open;
195
- while (end > 0 && /\s/.test(value[end - 1])) {
199
+ while (end > 0 && /\s/.test(value.charAt(end - 1))) {
196
200
  end--;
197
201
  }
198
202
  }
@@ -217,7 +221,7 @@ function braceBlocks(value: string): Map<number, number> {
217
221
  let quoted = false;
218
222
  let open = -1;
219
223
  for (let i = 0; i < value.length; i++) {
220
- const ch = value[i];
224
+ const ch = value.charAt(i);
221
225
  if (ch === "\\") {
222
226
  i++;
223
227
  continue;
@@ -244,8 +248,8 @@ function braceBlocks(value: string): Map<number, number> {
244
248
  export function unclosedBlock(value: string): number {
245
249
  let quoted = false;
246
250
  let open = -1;
247
- for (let i = 0; i < value.length; i += value[i] === "\\" ? 2 : 1) {
248
- const ch = value[i];
251
+ for (let i = 0; i < value.length; i += value.charAt(i) === "\\" ? 2 : 1) {
252
+ const ch = value.charAt(i);
249
253
  if (ch === "\\") {
250
254
  continue;
251
255
  }
@@ -297,7 +301,7 @@ export function parseQualifiers(inner: string): Qualifiers | null {
297
301
  const n = inner.length;
298
302
  let any = false;
299
303
  while (i < n) {
300
- while (i < n && (inner[i] === " " || inner[i] === "\t" || inner[i] === ",")) {
304
+ while (i < n && (inner.charAt(i) === " " || inner.charAt(i) === "\t" || inner.charAt(i) === ",")) {
301
305
  i++;
302
306
  }
303
307
  if (i >= n) {
@@ -312,11 +316,11 @@ export function parseQualifiers(inner: string): Qualifiers | null {
312
316
  return null;
313
317
  }
314
318
  i = eq + 1;
315
- while (i < n && (inner[i] === " " || inner[i] === "\t")) {
319
+ while (i < n && (inner.charAt(i) === " " || inner.charAt(i) === "\t")) {
316
320
  i++;
317
321
  }
318
322
  let value: string;
319
- if (inner[i] === '"') {
323
+ if (inner.charAt(i) === '"') {
320
324
  const end = closingQuote(inner, i + 1);
321
325
  if (end < 0) {
322
326
  return null;
@@ -325,8 +329,8 @@ export function parseQualifiers(inner: string): Qualifiers | null {
325
329
  i = end + 1;
326
330
  } else {
327
331
  let end = i;
328
- while (end < n && inner[end] !== ",") {
329
- end += inner[end] === "\\" ? 2 : 1;
332
+ while (end < n && inner.charAt(end) !== ",") {
333
+ end += inner.charAt(end) === "\\" ? 2 : 1;
330
334
  }
331
335
  value = unescapeObo(inner.slice(i, end).trim());
332
336
  i = end;
@@ -345,9 +349,9 @@ export function parseQualifiers(inner: string): Qualifiers | null {
345
349
  */
346
350
  function closingQuote(text: string, from: number): number {
347
351
  for (let i = from; i < text.length; i++) {
348
- if (text[i] === "\\") {
352
+ if (text.charAt(i) === "\\") {
349
353
  i++;
350
- } else if (text[i] === '"') {
354
+ } else if (text.charAt(i) === '"') {
351
355
  return i;
352
356
  }
353
357
  }
@@ -379,7 +383,7 @@ export function tokenize(value: string): Token[] {
379
383
  const n = value.length;
380
384
  let i = 0;
381
385
  while (i < n) {
382
- const ch = value[i];
386
+ const ch = value.charAt(i);
383
387
  if (ch === " " || ch === "\t") {
384
388
  i++;
385
389
  continue;
@@ -390,8 +394,8 @@ export function tokenize(value: string): Token[] {
390
394
  // a 1.4 language tag glued to the closing quote ("chat"@fr) is kept with the text, as an
391
395
  // unquoted chat@fr is (research-obo.md 4.1: no language is split out)
392
396
  let after = stop + 1;
393
- if (end >= 0 && value[after] === "@") {
394
- while (after < n && value[after] !== " " && value[after] !== "\t") {
397
+ if (end >= 0 && value.charAt(after) === "@") {
398
+ while (after < n && value.charAt(after) !== " " && value.charAt(after) !== "\t") {
395
399
  after++;
396
400
  }
397
401
  }
@@ -412,8 +416,8 @@ export function tokenize(value: string): Token[] {
412
416
  continue;
413
417
  }
414
418
  let end = i;
415
- while (end < n && value[end] !== " " && value[end] !== "\t") {
416
- end += value[end] === "\\" ? 2 : 1;
419
+ while (end < n && value.charAt(end) !== " " && value.charAt(end) !== "\t") {
420
+ end += value.charAt(end) === "\\" ? 2 : 1;
417
421
  }
418
422
  tokens.push({ kind: "word", text: unescapeObo(value.slice(i, Math.min(end, n))), unterminated: false });
419
423
  i = end;
@@ -430,7 +434,7 @@ export function tokenize(value: string): Token[] {
430
434
  function closingBracket(text: string, from: number): number {
431
435
  let quoted = false;
432
436
  for (let i = from; i < text.length; i++) {
433
- const ch = text[i];
437
+ const ch = text.charAt(i);
434
438
  if (ch === "\\") {
435
439
  i++;
436
440
  } else if (ch === '"') {
@@ -468,9 +472,9 @@ export function parseXref(raw: string): Xref | null {
468
472
  const { value, qualifiers } = splitQualifiers(raw.trim());
469
473
  let quote = -1;
470
474
  for (let i = 0; i < value.length; i++) {
471
- if (value[i] === "\\") {
475
+ if (value.charAt(i) === "\\") {
472
476
  i++;
473
- } else if (value[i] === '"') {
477
+ } else if (value.charAt(i) === '"') {
474
478
  quote = i;
475
479
  break;
476
480
  }
@@ -508,7 +512,7 @@ export function parseXrefList(inner: string): Xref[] {
508
512
  }
509
513
  };
510
514
  for (let i = 0; i < inner.length; i++) {
511
- const ch = inner[i];
515
+ const ch = inner.charAt(i);
512
516
  if (ch === "\\") {
513
517
  i++;
514
518
  } else if (ch === '"') {
@@ -538,7 +542,7 @@ export function hasStrayBrace(value: string): boolean {
538
542
  let quoted = false;
539
543
  let listed = false;
540
544
  for (let i = 0; i < value.length; i++) {
541
- const ch = value[i];
545
+ const ch = value.charAt(i);
542
546
  if (ch === "\\") {
543
547
  i++;
544
548
  } else if (ch === '"') {
@@ -1607,7 +1607,7 @@ class PajekParser {
1607
1607
  private extras(tokens: readonly string[], start: number, domain: "node" | "edge", line: number): RowExtras {
1608
1608
  const extras: RowExtras = { keys: [], values: [], spells: null };
1609
1609
  const seen = new Set<string>();
1610
- for (let i = start; i < tokens.length; ) {
1610
+ for (let i = start; i < tokens.length;) {
1611
1611
  const token = tokens[i];
1612
1612
  if (token.startsWith("[")) {
1613
1613
  extras.spells = this.intervals(token, domain);
package/src/registry.ts CHANGED
@@ -421,16 +421,15 @@ export class FormatRegistry {
421
421
  ): Promise<ImportGraphResult> {
422
422
  const opts = options as ImportGraphOptions;
423
423
  const picked = await this.choose(input, opts);
424
- const unknown = this.unknownOptions(opts).map(
425
- (name): ImportIssue =>
426
- Object.freeze({
427
- category: "unsupported",
428
- severity: "warning",
429
- code: UNKNOWN_OPTION_CODE,
430
- message: unknownOptionText(name),
431
- line: null,
432
- element: name,
433
- }),
424
+ const unknown = this.unknownOptions(opts).map((name): ImportIssue =>
425
+ Object.freeze({
426
+ category: "unsupported",
427
+ severity: "warning",
428
+ code: UNKNOWN_OPTION_CODE,
429
+ message: unknownOptionText(name),
430
+ line: null,
431
+ element: name,
432
+ }),
434
433
  );
435
434
  const chosen = unknown.length === 0 ? picked : { ...picked, warnings: [...picked.warnings, ...unknown] };
436
435
  const { importer } = chosen;
package/src/sniff.ts CHANGED
@@ -32,19 +32,7 @@ import { type GraphImporter } from "./types.js";
32
32
  * @category Formats and detection
33
33
  */
34
34
  export type GraphFormatName =
35
- | "gexf"
36
- | "graphml"
37
- | "gml"
38
- | "dot"
39
- | "pajek"
40
- | "csv"
41
- | "json"
42
- | "neo4j"
43
- | "xgmml"
44
- | "cx2"
45
- | "cx"
46
- | "obo"
47
- | "cys";
35
+ "gexf" | "graphml" | "gml" | "dot" | "pajek" | "csv" | "json" | "neo4j" | "xgmml" | "cx2" | "cx" | "obo" | "cys";
48
36
 
49
37
  /**
50
38
  * A format name: one of the built-in names (with editor autocomplete) or the name of a format you
@@ -345,14 +333,7 @@ export function sniffJsonDialectHead(head: Uint8Array | string): JsonImportDiale
345
333
 
346
334
  /** Where a key was seen while scanning a truncated head. */
347
335
  type KeyPath =
348
- | ""
349
- | "graph"
350
- | "options"
351
- | "nodes[0]"
352
- | "edges[0]"
353
- | "links[0]"
354
- | "graphs[0].nodes[]"
355
- | "graphs[0].edges[]";
336
+ "" | "graph" | "options" | "nodes[0]" | "edges[0]" | "links[0]" | "graphs[0].nodes[]" | "graphs[0].edges[]";
356
337
 
357
338
  /**
358
339
  * A partial document rebuilt from the keys a truncated head reveals: every key gets a placeholder
package/src/types.ts CHANGED
@@ -368,6 +368,12 @@ export interface LossNote {
368
368
  readonly column: string | null;
369
369
  /** How many nodes, edges or values are affected, or null when not counted. */
370
370
  readonly count: number | null;
371
+ /**
372
+ * Every column the note is about, when it is about several and `column` is null: the graph
373
+ * attributes a format has no place for, or the node columns a CSV edge table leaves for the node
374
+ * table. Absent on a note about one column or none.
375
+ */
376
+ readonly columns?: readonly string[];
371
377
  }
372
378
 
373
379
  /**
@@ -458,13 +464,7 @@ export interface GraphExporter<Opts = unknown> {
458
464
  * @category Reports and errors
459
465
  */
460
466
  export type IssueCategory =
461
- | "parse-error"
462
- | "missing-value"
463
- | "validation-error"
464
- | "unsupported"
465
- | "precision"
466
- | "coercion"
467
- | "merged";
467
+ "parse-error" | "missing-value" | "validation-error" | "unsupported" | "precision" | "coercion" | "merged";
468
468
 
469
469
  /**
470
470
  * One problem found while importing.