@gmod/gbz-base 0.0.1 → 1.1.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.
Files changed (62) hide show
  1. package/README.md +45 -4
  2. package/bin/query.js +3 -1
  3. package/dist/cli.js +85 -19
  4. package/dist/cli.js.map +1 -0
  5. package/dist/db.d.ts +14 -6
  6. package/dist/db.js +92 -28
  7. package/dist/db.js.map +1 -0
  8. package/dist/filehandle.js +1 -0
  9. package/dist/filehandle.js.map +1 -0
  10. package/dist/gbwt/bytecode.d.ts +1 -1
  11. package/dist/gbwt/bytecode.js +7 -3
  12. package/dist/gbwt/bytecode.js.map +1 -0
  13. package/dist/gbwt/node.js +7 -2
  14. package/dist/gbwt/node.js.map +1 -0
  15. package/dist/gbwt/record.d.ts +4 -0
  16. package/dist/gbwt/record.js +30 -2
  17. package/dist/gbwt/record.js.map +1 -0
  18. package/dist/gbwt/sequence.js +8 -1
  19. package/dist/gbwt/sequence.js.map +1 -0
  20. package/dist/graphName.d.ts +9 -0
  21. package/dist/graphName.js +83 -0
  22. package/dist/graphName.js.map +1 -0
  23. package/dist/index.d.ts +5 -4
  24. package/dist/index.js +3 -2
  25. package/dist/index.js.map +1 -0
  26. package/dist/lcs.js +32 -10
  27. package/dist/lcs.js.map +1 -0
  28. package/dist/query.d.ts +4 -2
  29. package/dist/query.js +19 -0
  30. package/dist/query.js.map +1 -0
  31. package/dist/sqlite/btree.d.ts +1 -1
  32. package/dist/sqlite/btree.js +24 -7
  33. package/dist/sqlite/btree.js.map +1 -0
  34. package/dist/sqlite/database.d.ts +1 -1
  35. package/dist/sqlite/database.js +12 -2
  36. package/dist/sqlite/database.js.map +1 -0
  37. package/dist/sqlite/pager.d.ts +8 -3
  38. package/dist/sqlite/pager.js +71 -12
  39. package/dist/sqlite/pager.js.map +1 -0
  40. package/dist/sqlite/record.js +3 -1
  41. package/dist/sqlite/record.js.map +1 -0
  42. package/dist/subgraph.d.ts +12 -2
  43. package/dist/subgraph.js +413 -84
  44. package/dist/subgraph.js.map +1 -0
  45. package/package.json +36 -11
  46. package/src/cli.ts +258 -0
  47. package/src/db.ts +374 -0
  48. package/src/filehandle.ts +4 -0
  49. package/src/gbwt/bytecode.ts +87 -0
  50. package/src/gbwt/node.ts +64 -0
  51. package/src/gbwt/record.ts +177 -0
  52. package/src/gbwt/sequence.ts +50 -0
  53. package/src/graphName.ts +94 -0
  54. package/src/index.ts +34 -0
  55. package/src/lcs.ts +282 -0
  56. package/src/query.ts +106 -0
  57. package/src/sqlite/btree.ts +260 -0
  58. package/src/sqlite/database.ts +117 -0
  59. package/src/sqlite/pager.ts +148 -0
  60. package/src/sqlite/record.ts +100 -0
  61. package/src/subgraph.ts +1353 -0
  62. package/tools/haplotype-index/src/main.rs +197 -163
package/dist/subgraph.js CHANGED
@@ -1,32 +1,64 @@
1
1
  import { formatPathName } from "./db.js";
2
- import { ENDMARKER, edgeIsCanonical, encodeNode, entryOrientation, entrySide, exitOrientation, exitSide, flipSide, isReverse, nodeId, nodeOrientation, pathIsCanonical, } from "./gbwt/node.js";
2
+ import { ENDMARKER, edgeIsCanonical, encodeNode, entryOrientation, entrySide, exitOrientation, exitSide, flipNode, flipSide, isReverse, nodeId, nodeOrientation, pathIsCanonical, } from "./gbwt/node.js";
3
+ import { gfaHeaderLines, sha256Hex, subgraphName } from "./graphName.js";
3
4
  import { weightedLcs } from "./lcs.js";
5
+ function sideBefore(a, b) {
6
+ return (a[0] < b[0] ||
7
+ (a[0] === b[0] && (a[1] < b[1] || (a[1] === b[1] && a[2] < b[2]))));
8
+ }
4
9
  class SideQueue {
5
- items = [];
10
+ heap = [];
6
11
  push(distance, node, side) {
7
- this.items.push([distance, node, side]);
12
+ const heap = this.heap;
13
+ heap.push([distance, node, side]);
14
+ let i = heap.length - 1;
15
+ while (i > 0) {
16
+ const parent = (i - 1) >> 1;
17
+ if (sideBefore(heap[i], heap[parent])) {
18
+ ;
19
+ [heap[i], heap[parent]] = [heap[parent], heap[i]];
20
+ i = parent;
21
+ }
22
+ else {
23
+ break;
24
+ }
25
+ }
8
26
  }
9
27
  pop() {
10
- let best = 0;
11
- for (let i = 1; i < this.items.length; i++) {
12
- const a = this.items[i];
13
- const b = this.items[best];
14
- if (a[0] < b[0] || (a[0] === b[0] && (a[1] < b[1] || (a[1] === b[1] && a[2] < b[2])))) {
15
- best = i;
28
+ const heap = this.heap;
29
+ const top = heap[0];
30
+ const last = heap.pop();
31
+ if (heap.length > 0 && last !== undefined) {
32
+ heap[0] = last;
33
+ let i = 0;
34
+ for (;;) {
35
+ const left = 2 * i + 1;
36
+ const right = left + 1;
37
+ let smallest = i;
38
+ if (left < heap.length && sideBefore(heap[left], heap[smallest])) {
39
+ smallest = left;
40
+ }
41
+ if (right < heap.length && sideBefore(heap[right], heap[smallest])) {
42
+ smallest = right;
43
+ }
44
+ if (smallest === i) {
45
+ break;
46
+ }
47
+ ;
48
+ [heap[i], heap[smallest]] = [heap[smallest], heap[i]];
49
+ i = smallest;
16
50
  }
17
51
  }
18
- const [item] = this.items.splice(best, 1);
19
- return item;
52
+ return top;
20
53
  }
21
54
  get size() {
22
- return this.items.length;
55
+ return this.heap.length;
23
56
  }
24
57
  }
25
58
  function posKey(pos) {
26
59
  return `${pos.node}:${pos.offset}`;
27
60
  }
28
61
  export class Subgraph {
29
- db;
30
62
  records = new Map();
31
63
  paths = [];
32
64
  refId;
@@ -36,7 +68,13 @@ export class Subgraph {
36
68
  refIndexCache;
37
69
  refPrefixCache;
38
70
  limit;
39
- stats = { orderedAlignments: 0, lcsAlignments: 0, identificationSteps: 0, identificationFetches: 0 };
71
+ stats = {
72
+ orderedAlignments: 0,
73
+ lcsAlignments: 0,
74
+ identificationSteps: 0,
75
+ identificationFetches: 0,
76
+ };
77
+ db;
40
78
  constructor(db) {
41
79
  this.db = db;
42
80
  }
@@ -48,7 +86,11 @@ export class Subgraph {
48
86
  }
49
87
  get referenceInterval() {
50
88
  return this.refInterval && this.refPath
51
- ? { name: this.refPath, start: this.refPath.fragment + this.refInterval[0], end: this.refPath.fragment + this.refInterval[1] }
89
+ ? {
90
+ name: this.refPath,
91
+ start: this.refPath.fragment + this.refInterval[0],
92
+ end: this.refPath.fragment + this.refInterval[1],
93
+ }
52
94
  : undefined;
53
95
  }
54
96
  hasNode(id) {
@@ -116,7 +158,12 @@ export class Subgraph {
116
158
  const record = this.record(pos.node);
117
159
  if (pathOffset + record.sequenceLen > queryOffset) {
118
160
  return {
119
- position: { seqOffset: queryOffset, handle: pos.node, nodeOffset: queryOffset - pathOffset, gbwtOffset: pos.offset },
161
+ position: {
162
+ seqOffset: queryOffset,
163
+ handle: pos.node,
164
+ nodeOffset: queryOffset - pathOffset,
165
+ gbwtOffset: pos.offset,
166
+ },
120
167
  name: path.name,
121
168
  handle: path.handle,
122
169
  };
@@ -227,6 +274,139 @@ export class Subgraph {
227
274
  }
228
275
  return { inserted, removed: toRemove.size };
229
276
  }
277
+ async betweenNodes(start, end) {
278
+ this.clearPaths();
279
+ const active = [start, flipNode(end)];
280
+ const visited = new Set([nodeId(start), nodeId(end)]);
281
+ let inserted = 0;
282
+ while (active.length > 0) {
283
+ const curr = active.pop();
284
+ const id = nodeId(curr);
285
+ if (!this.hasNode(id)) {
286
+ await this.addNode(id);
287
+ inserted += 1;
288
+ }
289
+ for (const successor of this.record(curr).successors()) {
290
+ const successorId = nodeId(successor);
291
+ if (!visited.has(successorId)) {
292
+ active.push(successor, flipNode(successor));
293
+ visited.add(successorId);
294
+ }
295
+ }
296
+ }
297
+ return inserted;
298
+ }
299
+ async extractSnarls(snarls) {
300
+ let inserted = 0;
301
+ for (const [start, end] of await this.overlappingSnarls(snarls)) {
302
+ inserted += await this.betweenNodes(start, end);
303
+ }
304
+ return inserted;
305
+ }
306
+ async overlappingSnarls(snarls) {
307
+ const result = [];
308
+ if (snarls !== 'none') {
309
+ let foundLink = false;
310
+ for (const handle of this.sortedHandles()) {
311
+ const record = this.record(handle);
312
+ const next = record.next;
313
+ if (next !== undefined) {
314
+ foundLink = true;
315
+ if (this.hasHandle(next)) {
316
+ if (edgeIsCanonical(handle, next)) {
317
+ result.push([handle, next]);
318
+ }
319
+ }
320
+ else if (snarls === 'overlapping' &&
321
+ this.isSnarlEntryInSubgraph(record)) {
322
+ result.push([handle, next]);
323
+ }
324
+ }
325
+ }
326
+ if (!foundLink &&
327
+ snarls === 'overlapping' &&
328
+ (await this.db.hasChainLinks())) {
329
+ const covering = await this.findCoveringSnarl();
330
+ if (covering) {
331
+ result.push(covering);
332
+ }
333
+ }
334
+ }
335
+ return result;
336
+ }
337
+ isSnarlEntryInSubgraph(record) {
338
+ const successors = record.successors();
339
+ const first = successors.find(handle => this.hasHandle(handle));
340
+ return first === undefined
341
+ ? false
342
+ : successors.length > 1 ||
343
+ this.record(flipNode(first)).successors().length > 1;
344
+ }
345
+ recordReader(onFetch) {
346
+ const outside = new Map();
347
+ return async (handle) => {
348
+ const inside = this.records.get(handle);
349
+ if (inside) {
350
+ return inside;
351
+ }
352
+ let record = outside.get(handle);
353
+ if (!record) {
354
+ record = await this.db.getRecord(handle);
355
+ onFetch?.();
356
+ if (!record) {
357
+ throw new Error(`Node record ${handle} is missing from the database`);
358
+ }
359
+ outside.set(handle, record);
360
+ }
361
+ return record;
362
+ };
363
+ }
364
+ async findCoveringSnarl() {
365
+ const read = this.recordReader();
366
+ const isSnarlEntry = async (record) => {
367
+ const successors = record.successors();
368
+ const first = successors[0];
369
+ return first === undefined
370
+ ? false
371
+ : successors.length > 1 ||
372
+ (await read(flipNode(first))).successors().length > 1;
373
+ };
374
+ const classify = async (handle) => {
375
+ const reverse = await read(flipNode(handle));
376
+ return reverse.next !== undefined
377
+ ? (await isSnarlEntry(reverse))
378
+ ? { kind: 'snarl-exit', snarl: [flipNode(handle), reverse.next] }
379
+ : { kind: 'chain' }
380
+ : (await read(handle)).next !== undefined
381
+ ? { kind: 'chain' }
382
+ : { kind: 'regular' };
383
+ };
384
+ const visited = new Set();
385
+ const queue = this.sortedHandles().flatMap(handle => this.record(handle).successors());
386
+ let result;
387
+ let done = false;
388
+ while (!done && queue.length > 0) {
389
+ const handle = queue.shift();
390
+ const id = nodeId(handle);
391
+ if (!this.hasHandle(handle) && !visited.has(id)) {
392
+ visited.add(id);
393
+ const type = await classify(handle);
394
+ if (type.kind === 'snarl-exit') {
395
+ result = type.snarl;
396
+ done = true;
397
+ }
398
+ else if (type.kind === 'chain') {
399
+ done = true;
400
+ }
401
+ else {
402
+ for (const orientation of ['forward', 'reverse']) {
403
+ queue.push(...(await read(encodeNode(id, orientation))).successors());
404
+ }
405
+ }
406
+ }
407
+ }
408
+ return result;
409
+ }
230
410
  extractPaths(reference, output) {
231
411
  this.clearPaths();
232
412
  if (output === 'none') {
@@ -238,47 +418,65 @@ export class Subgraph {
238
418
  const handles = this.sortedHandles();
239
419
  const successors = new Map();
240
420
  for (const handle of handles) {
241
- successors.set(handle, this.record(handle)
242
- .gbwt()
243
- .decompress()
244
- .map(next => ({ next, hasPredecessor: false })));
421
+ const { nodes, offsets } = this.record(handle).gbwt().decompressArrays();
422
+ successors.set(handle, {
423
+ nodes,
424
+ offsets,
425
+ hasPredecessor: new Uint8Array(nodes.length),
426
+ });
245
427
  }
246
428
  for (const handle of handles) {
247
- for (const { next } of successors.get(handle)) {
248
- const entry = successors.get(next.node)?.[next.offset];
429
+ const { nodes, offsets } = successors.get(handle);
430
+ for (let i = 0; i < nodes.length; i++) {
431
+ const entry = successors.get(nodes[i]);
249
432
  if (entry) {
250
- entry.hasPredecessor = true;
433
+ entry.hasPredecessor[offsets[i]] = 1;
251
434
  }
252
435
  }
253
436
  }
254
437
  let refOffset;
255
438
  for (const handle of handles) {
256
439
  const entries = successors.get(handle);
257
- entries.forEach((entry, offset) => {
258
- if (entry.hasPredecessor) {
259
- return;
260
- }
261
- let curr = { node: handle, offset };
262
- let isRef = false;
263
- const path = [];
264
- const positions = [];
265
- let len = 0;
266
- while (curr) {
267
- if (refPos && curr.node === refPos.handle && curr.offset === refPos.gbwtOffset) {
268
- this.refId = this.paths.length;
269
- refOffset = path.length;
270
- isRef = true;
440
+ for (let offset = 0; offset < entries.nodes.length; offset++) {
441
+ if (entries.hasPredecessor[offset] === 0) {
442
+ let currNode = handle;
443
+ let currOffset = offset;
444
+ let isRef = false;
445
+ const path = [];
446
+ const positions = [];
447
+ let len = 0;
448
+ while (currNode !== undefined) {
449
+ if (currNode === refPos?.handle &&
450
+ currOffset === refPos.gbwtOffset) {
451
+ this.refId = this.paths.length;
452
+ refOffset = path.length;
453
+ isRef = true;
454
+ }
455
+ path.push(currNode);
456
+ positions.push({ node: currNode, offset: currOffset });
457
+ len += this.record(currNode).sequenceLen;
458
+ const step = successors.get(currNode);
459
+ const nextNode = step.nodes[currOffset];
460
+ const nextOffset = step.offsets[currOffset];
461
+ if (nextNode !== ENDMARKER && successors.has(nextNode)) {
462
+ currNode = nextNode;
463
+ currOffset = nextOffset;
464
+ }
465
+ else {
466
+ currNode = undefined;
467
+ }
468
+ }
469
+ if (isRef || pathIsCanonical(path)) {
470
+ this.paths.push({
471
+ path,
472
+ positions,
473
+ len,
474
+ weight: undefined,
475
+ identity: undefined,
476
+ });
271
477
  }
272
- path.push(curr.node);
273
- positions.push(curr);
274
- len += this.record(curr.node).sequenceLen;
275
- const step = successors.get(curr.node)?.[curr.offset];
276
- curr = step && step.next.node !== ENDMARKER && successors.has(step.next.node) ? step.next : undefined;
277
- }
278
- if (isRef || pathIsCanonical(path)) {
279
- this.paths.push({ path, positions, len, weight: undefined, identity: undefined });
280
478
  }
281
- });
479
+ }
282
480
  }
283
481
  if (refPos) {
284
482
  if (refOffset === undefined || this.refId === undefined) {
@@ -292,9 +490,13 @@ export class Subgraph {
292
490
  }
293
491
  const start = refPos.seqOffset - before;
294
492
  this.refInterval = [start, start + info.len];
295
- if (reference) {
296
- info.identity = { pathHandle: reference.handle, name: reference.name, orientation: 'forward', hapStart: start, hapEnd: start + info.len };
297
- }
493
+ info.identity = {
494
+ pathHandle: reference.handle,
495
+ name: reference.name,
496
+ orientation: 'forward',
497
+ hapStart: start,
498
+ hapEnd: start + info.len,
499
+ };
298
500
  }
299
501
  if (output === 'distinct') {
300
502
  this.distinctPaths();
@@ -349,23 +551,9 @@ export class Subgraph {
349
551
  starts.set(posKey(first), index);
350
552
  }
351
553
  });
352
- const outside = new Map();
353
- const recordAt = async (handle) => {
354
- const inside = this.records.get(handle);
355
- if (inside) {
356
- return inside;
357
- }
358
- let record = outside.get(handle);
359
- if (!record) {
360
- record = await this.db.getRecord(handle);
361
- this.stats.identificationFetches += 1;
362
- if (!record) {
363
- throw new Error(`Node record ${handle} is missing from the database`);
364
- }
365
- outside.set(handle, record);
366
- }
367
- return record;
368
- };
554
+ const recordAt = this.recordReader(() => {
555
+ this.stats.identificationFetches += 1;
556
+ });
369
557
  const sampleAt = async (pos) => {
370
558
  if (pos.node >= minHandle && pos.node <= maxHandle) {
371
559
  return samples.get(posKey(pos));
@@ -387,11 +575,27 @@ export class Subgraph {
387
575
  return name;
388
576
  };
389
577
  const anchorFromSample = (sample, counter, nodeLen) => sample.orientation === 'forward'
390
- ? { pathHandle: sample.pathHandle, orientation: 'forward', base: sample.pathOffset - counter }
391
- : { pathHandle: sample.pathHandle, orientation: 'reverse', base: sample.pathOffset + counter + nodeLen };
578
+ ? {
579
+ pathHandle: sample.pathHandle,
580
+ orientation: 'forward',
581
+ base: sample.pathOffset - counter,
582
+ }
583
+ : {
584
+ pathHandle: sample.pathHandle,
585
+ orientation: 'reverse',
586
+ base: sample.pathOffset + counter + nodeLen,
587
+ };
392
588
  const anchorFromIdentity = (identity, counter) => identity.orientation === 'forward'
393
- ? { pathHandle: identity.pathHandle, orientation: 'forward', base: identity.hapStart - counter }
394
- : { pathHandle: identity.pathHandle, orientation: 'reverse', base: identity.hapEnd + counter };
589
+ ? {
590
+ pathHandle: identity.pathHandle,
591
+ orientation: 'forward',
592
+ base: identity.hapStart - counter,
593
+ }
594
+ : {
595
+ pathHandle: identity.pathHandle,
596
+ orientation: 'reverse',
597
+ base: identity.hapEnd + counter,
598
+ };
395
599
  for (let start = 0; start < this.paths.length; start++) {
396
600
  const startInfo = this.paths[start];
397
601
  if (start === this.refId || startInfo.identity) {
@@ -449,7 +653,8 @@ export class Subgraph {
449
653
  break;
450
654
  }
451
655
  this.stats.identificationSteps += 1;
452
- if (counter - chain[chain.length - 1].startBp > 4 * interval + 4 * record.sequenceLen) {
656
+ if (counter - chain[chain.length - 1].startBp >
657
+ 4 * interval + 4 * record.sequenceLen) {
453
658
  break;
454
659
  }
455
660
  counter += record.sequenceLen;
@@ -461,8 +666,20 @@ export class Subgraph {
461
666
  const info = this.paths[index];
462
667
  info.identity =
463
668
  anchor.orientation === 'forward'
464
- ? { pathHandle: anchor.pathHandle, name, orientation: 'forward', hapStart: anchor.base + startBp, hapEnd: anchor.base + startBp + info.len }
465
- : { pathHandle: anchor.pathHandle, name, orientation: 'reverse', hapStart: anchor.base - startBp - info.len, hapEnd: anchor.base - startBp };
669
+ ? {
670
+ pathHandle: anchor.pathHandle,
671
+ name,
672
+ orientation: 'forward',
673
+ hapStart: anchor.base + startBp,
674
+ hapEnd: anchor.base + startBp + info.len,
675
+ }
676
+ : {
677
+ pathHandle: anchor.pathHandle,
678
+ name,
679
+ orientation: 'reverse',
680
+ hapStart: anchor.base - startBp - info.len,
681
+ hapEnd: anchor.base - startBp,
682
+ };
466
683
  }
467
684
  }
468
685
  }
@@ -595,7 +812,9 @@ export class Subgraph {
595
812
  }
596
813
  else {
597
814
  const mismatch = Math.min(pathMiddle, refMiddle);
598
- const mismatchIndel = 4 * mismatch + gapPenalty(pathMiddle - mismatch) + gapPenalty(refMiddle - mismatch);
815
+ const mismatchIndel = 4 * mismatch +
816
+ gapPenalty(pathMiddle - mismatch) +
817
+ gapPenalty(refMiddle - mismatch);
599
818
  const insertionDeletion = gapPenalty(pathMiddle) + gapPenalty(refMiddle);
600
819
  if (mismatchIndel <= insertionDeletion) {
601
820
  appendEdit(edits, 'M', mismatch);
@@ -622,7 +841,8 @@ export class Subgraph {
622
841
  else {
623
842
  this.stats.lcsAlignments += 1;
624
843
  }
625
- const lcs = ordered ?? weightedLcs(info.path, ref, handle => this.record(handle).sequenceLen)[0];
844
+ const lcs = ordered ??
845
+ weightedLcs(info.path, ref, handle => this.record(handle).sequenceLen)[0];
626
846
  const edits = [];
627
847
  let pathOffset = 0;
628
848
  let refOffset = 0;
@@ -636,7 +856,9 @@ export class Subgraph {
636
856
  return edits;
637
857
  }
638
858
  alignToRef(pathIndex) {
639
- return this.edits(pathIndex)?.map(([op, len]) => `${len}${op}`).join('');
859
+ return this.edits(pathIndex)
860
+ ?.map(([op, len]) => `${len}${op}`)
861
+ .join('');
640
862
  }
641
863
  alignments() {
642
864
  const reference = this.referenceInterval;
@@ -664,12 +886,21 @@ export class Subgraph {
664
886
  last -= 1;
665
887
  }
666
888
  const identity = info.identity;
667
- const strand = info.path.some(handle => isReverse(handle)) && !info.path.some(handle => !isReverse(handle)) ? '-' : '+';
889
+ const strand = info.path.some(handle => isReverse(handle)) &&
890
+ !info.path.some(handle => !isReverse(handle))
891
+ ? '-'
892
+ : '+';
668
893
  result.push({
669
894
  pathHandle: identity?.pathHandle,
670
895
  name: identity?.name,
671
- strand: identity ? (identity.orientation === 'forward' ? '+' : '-') : strand,
672
- hapStart: identity ? identity.name.fragment + identity.hapStart : undefined,
896
+ strand: identity
897
+ ? identity.orientation === 'forward'
898
+ ? '+'
899
+ : '-'
900
+ : strand,
901
+ hapStart: identity
902
+ ? identity.name.fragment + identity.hapStart
903
+ : undefined,
673
904
  hapEnd: identity ? identity.name.fragment + identity.hapEnd : undefined,
674
905
  start: info.positions[0],
675
906
  refStart: reference.start + leading,
@@ -684,11 +915,99 @@ export class Subgraph {
684
915
  });
685
916
  return result;
686
917
  }
918
+ canonicalEdges(id) {
919
+ const edges = [];
920
+ for (const orientation of ['forward', 'reverse']) {
921
+ const handle = encodeNode(id, orientation);
922
+ for (const successor of this.record(handle).successors()) {
923
+ if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
924
+ edges.push([
925
+ orientation === 'reverse' ? 1 : 0,
926
+ nodeId(successor),
927
+ isReverse(successor) ? 1 : 0,
928
+ ]);
929
+ }
930
+ }
931
+ }
932
+ edges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
933
+ return edges.filter((edge, i) => i === 0 ||
934
+ edge[0] !== edges[i - 1][0] ||
935
+ edge[1] !== edges[i - 1][1] ||
936
+ edge[2] !== edges[i - 1][2]);
937
+ }
938
+ async stableName() {
939
+ const encoder = new TextEncoder();
940
+ const chunks = [];
941
+ for (const handle of this.sortedHandles()) {
942
+ if (!isReverse(handle)) {
943
+ const id = nodeId(handle);
944
+ let text = `S\t${id}\t${this.record(handle).sequence}\n`;
945
+ for (const [fromReverse, toId, toReverse] of this.canonicalEdges(id)) {
946
+ text += `L\t${id}\t${fromReverse ? '-' : '+'}\t${toId}\t${toReverse ? '-' : '+'}\n`;
947
+ }
948
+ chunks.push(encoder.encode(text));
949
+ }
950
+ }
951
+ return sha256Hex(chunks);
952
+ }
953
+ async toGFA(cigar, opts = {}) {
954
+ const lines = [
955
+ this.refPath ? `H\tVN:Z:1.1\tRS:Z:${this.refPath.sample}` : 'H\tVN:Z:1.1',
956
+ ...gfaHeaderLines(subgraphName(await this.stableName(), await this.db.graphName())),
957
+ ];
958
+ const handles = this.sortedHandles();
959
+ for (const handle of handles) {
960
+ if (!isReverse(handle)) {
961
+ lines.push(`S\t${nodeId(handle)}\t${this.record(handle).sequence}`);
962
+ }
963
+ }
964
+ const sign = (handle) => (isReverse(handle) ? '-' : '+');
965
+ for (const handle of handles) {
966
+ for (const successor of this.record(handle).successors()) {
967
+ if (this.hasHandle(successor) && edgeIsCanonical(handle, successor)) {
968
+ lines.push(`L\t${nodeId(handle)}\t${sign(handle)}\t${nodeId(successor)}\t${sign(successor)}\t0M`);
969
+ }
970
+ }
971
+ }
972
+ const walk = (info, name, end, cigarString) => {
973
+ const steps = info.path
974
+ .map(handle => `${isReverse(handle) ? '<' : '>'}${nodeId(handle)}`)
975
+ .join('');
976
+ const weight = info.weight === undefined ? '' : `\tWT:i:${info.weight}`;
977
+ const cg = cigarString === undefined ? '' : `\tCG:Z:${cigarString}`;
978
+ return `W\t${name.sample}\t${name.haplotype}\t${name.contig}\t${name.fragment}\t${end}\t${steps}${weight}${cg}`;
979
+ };
980
+ const contig = this.refPath?.contig ?? 'unknown';
981
+ if (this.refId !== undefined && this.refPath && this.refInterval) {
982
+ lines.push(walk(this.paths[this.refId], {
983
+ ...this.refPath,
984
+ fragment: this.refPath.fragment + this.refInterval[0],
985
+ }, this.refPath.fragment + this.refInterval[1], undefined));
986
+ }
987
+ let haplotype = 1;
988
+ this.paths.forEach((info, index) => {
989
+ if (index !== this.refId) {
990
+ const resolved = opts.names === 'resolved' ? info.identity : undefined;
991
+ const cigarString = cigar ? this.alignToRef(index) : undefined;
992
+ lines.push(resolved
993
+ ? walk(info, {
994
+ ...resolved.name,
995
+ fragment: resolved.name.fragment + resolved.hapStart,
996
+ }, resolved.name.fragment + resolved.hapEnd, cigarString)
997
+ : walk(info, { sample: 'unknown', contig, haplotype, fragment: 0 }, info.len, cigarString));
998
+ haplotype += 1;
999
+ }
1000
+ });
1001
+ return `${lines.join('\n')}\n`;
1002
+ }
687
1003
  toJSON(cigar, opts = {}) {
688
1004
  const handles = this.sortedHandles();
689
1005
  const nodes = handles
690
1006
  .filter(handle => !isReverse(handle))
691
- .map(handle => ({ id: String(nodeId(handle)), sequence: this.record(handle).sequence }));
1007
+ .map(handle => ({
1008
+ id: String(nodeId(handle)),
1009
+ sequence: this.record(handle).sequence,
1010
+ }));
692
1011
  const edges = [];
693
1012
  for (const handle of handles) {
694
1013
  for (const successor of this.record(handle).successors()) {
@@ -706,7 +1025,10 @@ export class Subgraph {
706
1025
  const contig = this.refPath?.contig ?? 'unknown';
707
1026
  if (this.refId !== undefined && this.refPath && this.refInterval) {
708
1027
  const info = this.paths[this.refId];
709
- const name = { ...this.refPath, fragment: this.refPath.fragment + this.refInterval[0] };
1028
+ const name = {
1029
+ ...this.refPath,
1030
+ fragment: this.refPath.fragment + this.refInterval[0],
1031
+ };
710
1032
  paths.push(jsonPath(info, formatPathName(name, this.refPath.fragment + this.refInterval[1]), undefined));
711
1033
  }
712
1034
  let haplotype = 1;
@@ -716,7 +1038,10 @@ export class Subgraph {
716
1038
  }
717
1039
  const resolved = opts.names === 'resolved' ? info.identity : undefined;
718
1040
  const name = resolved
719
- ? formatPathName({ ...resolved.name, fragment: resolved.name.fragment + resolved.hapStart }, resolved.name.fragment + resolved.hapEnd)
1041
+ ? formatPathName({
1042
+ ...resolved.name,
1043
+ fragment: resolved.name.fragment + resolved.hapStart,
1044
+ }, resolved.name.fragment + resolved.hapEnd)
720
1045
  : formatPathName({ sample: 'unknown', contig, haplotype, fragment: 0 }, info.len);
721
1046
  paths.push(jsonPath(info, name, cigar ? this.alignToRef(index) : undefined));
722
1047
  haplotype += 1;
@@ -729,7 +1054,10 @@ function jsonPath(info, name, cigar) {
729
1054
  name,
730
1055
  ...(info.weight === undefined ? {} : { weight: info.weight }),
731
1056
  ...(cigar === undefined ? {} : { cigar }),
732
- path: info.path.map(handle => ({ id: String(nodeId(handle)), is_reverse: isReverse(handle) })),
1057
+ path: info.path.map(handle => ({
1058
+ id: String(nodeId(handle)),
1059
+ is_reverse: isReverse(handle),
1060
+ })),
733
1061
  };
734
1062
  }
735
1063
  function comparePaths(a, b) {
@@ -748,7 +1076,7 @@ function appendEdit(edits, op, len) {
748
1076
  return;
749
1077
  }
750
1078
  const last = edits[edits.length - 1];
751
- if (last && last[0] === op) {
1079
+ if (last?.[0] === op) {
752
1080
  last[1] += len;
753
1081
  }
754
1082
  else {
@@ -758,3 +1086,4 @@ function appendEdit(edits, op, len) {
758
1086
  function gapPenalty(len) {
759
1087
  return len === 0 ? 0 : 6 + (len - 1);
760
1088
  }
1089
+ //# sourceMappingURL=subgraph.js.map