@jbrowse/graph-walks 0.0.0-stage → 0.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.
@@ -0,0 +1,579 @@
1
+ import { RegionTooLargeError, bpString, panSNMatchesPrefix } from "./util.js";
2
+ import { placeAndLink } from "./walkFormat2.js";
3
+ // The name column, read without splitting the steps, so a row the cut does
4
+ // not want costs a few indexOf calls
5
+ export function walkRowName(line) {
6
+ let at = -1;
7
+ for (let k = 0; k < 3; k++) {
8
+ at = line.indexOf('\t', at + 1);
9
+ }
10
+ return line.slice(at + 1, line.indexOf('\t', at + 1));
11
+ }
12
+ export function parseWalkRow(line) {
13
+ const c = line.split('\t');
14
+ const row = {
15
+ chunkStart: +c[1],
16
+ name: c[3],
17
+ fragStart: +c[4],
18
+ hapOffset: +c[5],
19
+ piece: +c[6],
20
+ n: +c[7],
21
+ enc: c[8],
22
+ };
23
+ for (let k = 9; k < c.length; k++) {
24
+ const tag = c[k];
25
+ const side = tag.startsWith('pv:')
26
+ ? 'prev'
27
+ : tag.startsWith('nx:')
28
+ ? 'next'
29
+ : undefined;
30
+ if (side) {
31
+ const at = tag.lastIndexOf(':');
32
+ row[side] = {
33
+ refName: tag[3] === 'Z' ? tag.slice(5, at) : c[0],
34
+ start: +tag.slice(at + 1),
35
+ };
36
+ }
37
+ }
38
+ return row;
39
+ }
40
+ function integerTag(line, tag) {
41
+ const match = new RegExp(`(?:^|\\t)${tag}:i:(\\d+)`).exec(line);
42
+ return match ? +match[1] : undefined;
43
+ }
44
+ /**
45
+ * What a walk file's header states: its chunk size in a
46
+ * `#walks\tchunk:i:65536` line, from gfa-to-tabix 0.4.0 a
47
+ * `#reference\tGRCh38` line per reference sample and a
48
+ * `#haplotype\tHG002#1` line per other haplotype with rows, and from 0.5.0
49
+ * the longest node in bp as `maxnode:i:1024` beside the chunk size
50
+ */
51
+ export function walkHeader(lines) {
52
+ var _a, _b, _c;
53
+ const header = { references: [], haplotypes: [] };
54
+ for (const line of lines) {
55
+ const [key, value = ''] = line.split('\t');
56
+ if (key === '#reference') {
57
+ header.references.push(value);
58
+ }
59
+ else if (key === '#haplotype') {
60
+ header.haplotypes.push(value);
61
+ }
62
+ else {
63
+ (_a = header.format) !== null && _a !== void 0 ? _a : (header.format = integerTag(line, 'format'));
64
+ (_b = header.chunk) !== null && _b !== void 0 ? _b : (header.chunk = integerTag(line, 'chunk'));
65
+ (_c = header.maxNode) !== null && _c !== void 0 ? _c : (header.maxNode = integerTag(line, 'maxnode'));
66
+ }
67
+ }
68
+ return header;
69
+ }
70
+ /**
71
+ * How many chunks before the window's own a cut reads. A node is filed under
72
+ * the chunk holding its start, so a node `maxNode` bp long reaching into the
73
+ * window starts at most ceil(maxNode / chunk) chunks back. At least one, since
74
+ * the chunk before carries the detour a haplotype takes into the window's
75
+ * first reference node; one when the header gives no `maxnode:i:`, which holds
76
+ * for vg's graphs, chopped to nodes of at most 1,024 bp.
77
+ */
78
+ export function lookbackChunks(maxNode, chunk) {
79
+ return maxNode === undefined ? 1 : Math.max(1, Math.ceil(maxNode / chunk));
80
+ }
81
+ // The first base a cut queries: the start of the chunk `lookback` chunks
82
+ // before the one holding the window's start
83
+ export function chunkQueryStart(start, chunk, lookback = 1) {
84
+ return Math.max(0, (Math.floor(start / chunk) - lookback) * chunk);
85
+ }
86
+ // Which walks a cut decodes: the reference's, and those `wanted` names by
87
+ // PanSN prefix at sample or haplotype depth; undefined decodes every one
88
+ export function walkNameFilter(wanted, referenceHaplotype) {
89
+ return wanted === undefined || wanted.length === 0
90
+ ? undefined
91
+ : (name) => panSNMatchesPrefix(name, referenceHaplotype) ||
92
+ wanted.some(prefix => panSNMatchesPrefix(name, prefix));
93
+ }
94
+ export function decodeSteps(enc, n, ids = new Int32Array(n), rev = new Uint8Array(n), at = 0) {
95
+ let prev = 0;
96
+ let i = at;
97
+ let v = 0;
98
+ let neg = false;
99
+ for (let p = 0; p <= enc.length; p++) {
100
+ const ch = p < enc.length ? enc.charCodeAt(p) : 44;
101
+ if (ch === 44) {
102
+ const d = neg ? -v : v;
103
+ const r = d & 1;
104
+ rev[i] = r;
105
+ prev += (d - r) / 2;
106
+ ids[i++] = prev;
107
+ v = 0;
108
+ neg = false;
109
+ }
110
+ else if (ch === 45) {
111
+ neg = true;
112
+ }
113
+ else {
114
+ v = v * 10 + (ch - 48);
115
+ }
116
+ }
117
+ return { ids, rev };
118
+ }
119
+ export function joinPieces(rows) {
120
+ const byPath = new Map();
121
+ for (const row of rows) {
122
+ const key = `${row.name}\t${row.fragStart}`;
123
+ const list = byPath.get(key);
124
+ if (list) {
125
+ list.push(row);
126
+ }
127
+ else {
128
+ byPath.set(key, [row]);
129
+ }
130
+ }
131
+ const runs = [];
132
+ for (const pieces of byPath.values()) {
133
+ pieces.sort((a, b) => a.piece - b.piece);
134
+ let from = 0;
135
+ for (let i = 1; i <= pieces.length; i++) {
136
+ if (i === pieces.length ||
137
+ pieces[i].piece !== pieces[i - 1].piece + 1) {
138
+ runs.push(decodeRun(pieces.slice(from, i)));
139
+ from = i;
140
+ }
141
+ }
142
+ }
143
+ return runs;
144
+ }
145
+ function decodeRun(pieces) {
146
+ const n = pieces.reduce((sum, p) => sum + p.n, 0);
147
+ const ids = new Int32Array(n);
148
+ const rev = new Uint8Array(n);
149
+ let at = 0;
150
+ for (const p of pieces) {
151
+ if (p.steps) {
152
+ ids.set(p.steps.ids, at);
153
+ rev.set(p.steps.rev, at);
154
+ }
155
+ else {
156
+ decodeSteps(p.enc, p.n, ids, rev, at);
157
+ }
158
+ at += p.n;
159
+ }
160
+ const first = pieces[0];
161
+ return {
162
+ name: first.name,
163
+ hapOffset: first.hapOffset,
164
+ ids,
165
+ rev,
166
+ prev: first.prev,
167
+ next: pieces.at(-1).next,
168
+ };
169
+ }
170
+ /**
171
+ * The cut a window draws from its walks: the reference inside the window plus
172
+ * its context, and each run from its first to its last step on that stretch.
173
+ * A run that leaves the reference there, through steps off it or across a
174
+ * deletion, is followed to the reference step where it rejoins, when the read
175
+ * holds it, and otherwise through the steps off the reference it holds: at
176
+ * the ABCA7 VNTR four haplotypes leave inside the repeat and rejoin 1.8 kb
177
+ * past the window, and cut at the context they measured as partial walks. The reference is cut out as far as those rejoins reach, and
178
+ * a run then runs on outward while the next step's node is already in the
179
+ * cut. Without the run-on a haplotype whose alternate allele straddles the
180
+ * edge stopped short of a node other walks had brought into the cut, and that
181
+ * node drew without it. The run-on adds no node, so one pass is enough, but
182
+ * it reaches through nodes other walks brought in, so with a window edge inside
183
+ * a collapsed array a row's length depends on which haplotypes are cut.
184
+ */
185
+ export function walkCut(runs, nodes, refName) {
186
+ const onWindow = (id) => { var _a; return ((_a = nodes.get(id)) === null || _a === void 0 ? void 0 : _a.onWindow) === true; };
187
+ const reference = (id) => {
188
+ const node = nodes.get(id);
189
+ return (node === null || node === void 0 ? void 0 : node.rank) === 0 ? node : undefined;
190
+ };
191
+ // the chunks walks cut off at the end of a run go on in
192
+ const open = new Map();
193
+ const goesOn = (chunk) => {
194
+ if (chunk) {
195
+ open.set(`${chunk.refName}:${chunk.start}`, chunk);
196
+ }
197
+ };
198
+ // Where a walk stops being followed from step `at`, going by `dir`: the
199
+ // reference step it rejoins at past any steps off the reference, or across
200
+ // a deletion; else its last step off the reference before the run ends,
201
+ // noting the chunk the path goes on in, or reaches another reference
202
+ // sequence
203
+ const rejoin = (run, at, dir) => {
204
+ const { ids } = run;
205
+ let i = at + dir;
206
+ while (i >= 0 && i < ids.length && reference(ids[i]) === undefined) {
207
+ i += dir;
208
+ }
209
+ if (i < 0 || i >= ids.length) {
210
+ goesOn(dir < 0 ? run.prev : run.next);
211
+ return i - dir;
212
+ }
213
+ const from = reference(ids[at]);
214
+ const to = reference(ids[i]);
215
+ if (to.refName !== refName) {
216
+ return i - dir;
217
+ }
218
+ const adjacent = i === at + dir && (to.start === from.end || to.end === from.start);
219
+ return adjacent ? at : i;
220
+ };
221
+ let reachStart = Infinity;
222
+ let reachEnd = -Infinity;
223
+ const spans = [];
224
+ for (const run of runs) {
225
+ const { ids } = run;
226
+ let first = -1;
227
+ let last = -1;
228
+ for (let i = 0; i < ids.length; i++) {
229
+ if (onWindow(ids[i])) {
230
+ if (first < 0) {
231
+ first = i;
232
+ }
233
+ last = i;
234
+ }
235
+ }
236
+ if (first >= 0) {
237
+ if (run.name !== refName) {
238
+ first = rejoin(run, first, -1);
239
+ last = rejoin(run, last, 1);
240
+ for (const i of [first, last]) {
241
+ const node = reference(ids[i]);
242
+ if (node) {
243
+ reachStart = Math.min(reachStart, node.start);
244
+ reachEnd = Math.max(reachEnd, node.end);
245
+ }
246
+ }
247
+ }
248
+ spans.push({ run, first, last });
249
+ }
250
+ }
251
+ const inReach = (id) => {
252
+ const node = reference(id);
253
+ return (onWindow(id) ||
254
+ ((node === null || node === void 0 ? void 0 : node.refName) === refName &&
255
+ node.end > reachStart &&
256
+ node.start < reachEnd));
257
+ };
258
+ const kept = new Set();
259
+ for (const [id, node] of nodes) {
260
+ if (node.onWindow) {
261
+ kept.add(id);
262
+ }
263
+ }
264
+ for (const span of spans) {
265
+ const { ids } = span.run;
266
+ if (span.run.name === refName) {
267
+ while (span.first > 0 && inReach(ids[span.first - 1])) {
268
+ span.first--;
269
+ }
270
+ while (span.last < ids.length - 1 && inReach(ids[span.last + 1])) {
271
+ span.last++;
272
+ }
273
+ }
274
+ for (let i = span.first; i <= span.last; i++) {
275
+ kept.add(ids[i]);
276
+ }
277
+ }
278
+ const length = (id) => {
279
+ const node = nodes.get(id);
280
+ return node === undefined ? 0 : node.end - node.start;
281
+ };
282
+ const fragments = [];
283
+ for (const { run, first: f, last: l } of spans) {
284
+ const { ids, rev } = run;
285
+ let first = f;
286
+ let last = l;
287
+ while (first > 0 && kept.has(ids[first - 1])) {
288
+ first--;
289
+ }
290
+ while (last < ids.length - 1 && kept.has(ids[last + 1])) {
291
+ last++;
292
+ }
293
+ let hapStart = run.hapOffset;
294
+ for (let i = 0; i < first; i++) {
295
+ hapStart += length(ids[i]);
296
+ }
297
+ let hapEnd = hapStart;
298
+ for (let i = first; i <= last; i++) {
299
+ hapEnd += length(ids[i]);
300
+ }
301
+ fragments.push({
302
+ name: run.name,
303
+ hapStart,
304
+ hapEnd,
305
+ ids: ids.subarray(first, last + 1),
306
+ rev: rev.subarray(first, last + 1),
307
+ });
308
+ }
309
+ return { kept, fragments, open: [...open.values()] };
310
+ }
311
+ // The positions of a line's first `n` tabs, -1 past the last
312
+ function tabs(line, n, out) {
313
+ let at = -1;
314
+ for (let k = 0; k < n; k++) {
315
+ at = at === -1 && k > 0 ? -1 : line.indexOf('\t', at + 1);
316
+ out[k] = at;
317
+ }
318
+ return out;
319
+ }
320
+ /**
321
+ * The node and link rows a walk cut reads, parsed as they arrive, and the
322
+ * tables of the cut they make with its walks. A node filed under two chunks
323
+ * arrives twice and is read once; columns past a node row's eighth (LN:i:,
324
+ * SQ:Z: and whatever follows) are read only for the bases a window asks for.
325
+ */
326
+ export class WalkGraph {
327
+ constructor(window) {
328
+ this.nodes = new Map();
329
+ this.sequences = new Map();
330
+ this.links = [];
331
+ // format 2's chunks, whose nodes off R and links tables() fills in
332
+ this.format2 = [];
333
+ this.t = [];
334
+ this.window = window;
335
+ }
336
+ addNode(line) {
337
+ const t = tabs(line, 8, this.t);
338
+ const id = +line.slice(t[2] + 1, t[3]);
339
+ if (!this.nodes.has(id)) {
340
+ if (this.window.sequences) {
341
+ const at = line.indexOf('\tSQ:Z:');
342
+ if (at >= 0) {
343
+ const end = line.indexOf('\t', at + 1);
344
+ this.sequences.set(id, line.slice(at + 6, end < 0 ? undefined : end));
345
+ }
346
+ }
347
+ this.placeNode(id, {
348
+ rank: +line.slice(t[3] + 1, t[4]),
349
+ refName: line.slice(t[4] + 1, t[5]),
350
+ start: +line.slice(t[5] + 1, t[6]),
351
+ end: +line.slice(t[6] + 1, t[7] === -1 ? undefined : t[7]),
352
+ });
353
+ }
354
+ }
355
+ // A node with its position, unless the cut already holds it
356
+ placeNode(id, node) {
357
+ if (this.nodes.has(id)) {
358
+ return;
359
+ }
360
+ const { window } = this;
361
+ this.nodes.set(id, {
362
+ ...node,
363
+ onWindow: node.rank === 0 &&
364
+ node.refName === window.refName &&
365
+ node.end > window.start - window.context &&
366
+ node.start < window.end + window.context,
367
+ });
368
+ }
369
+ // A link between two endpoints, each id * 2 + 1 when reversed
370
+ addLinkEnds(source, target) {
371
+ this.links.push(source, target);
372
+ }
373
+ addFormat2Chunk(chunk) {
374
+ this.format2.push(chunk);
375
+ }
376
+ // An endpoint is held as id * 2 + 1 when reversed
377
+ addLink(line) {
378
+ const t = tabs(line, 5, this.t);
379
+ this.links.push(endpoint(line, t[2] + 1, t[3]), endpoint(line, t[3] + 1, t[4] === -1 ? line.length : t[4]));
380
+ }
381
+ /**
382
+ * The cut as typed arrays, which the worker transfers rather than copies:
383
+ * nodes by reference position, each link once in the order its L line
384
+ * sorts, then a walk per fragment, the reference's first and the rest by
385
+ * name. graphTablesGFA writes them as the GFA they stand for, the same bytes
386
+ * for the same rows.
387
+ */
388
+ tables(kept, fragments) {
389
+ if (this.format2.length > 0) {
390
+ placeAndLink(this.format2, kept, (id, node) => {
391
+ const held = this.nodes.get(id);
392
+ if (held) {
393
+ this.nodes.set(id, { ...held, ...node });
394
+ }
395
+ }, (a, b) => {
396
+ this.links.push(a, b);
397
+ });
398
+ }
399
+ const held = [];
400
+ for (const id of kept) {
401
+ const node = this.nodes.get(id);
402
+ if (node) {
403
+ held.push({ ...node, id });
404
+ }
405
+ }
406
+ held.sort((x, y) => x.refName < y.refName
407
+ ? -1
408
+ : x.refName > y.refName
409
+ ? 1
410
+ : x.start - y.start || x.id - y.id);
411
+ const index = new Map();
412
+ const names = [];
413
+ const refNames = [];
414
+ const refIndex = new Map();
415
+ const lengths = new Int32Array(held.length);
416
+ const refs = new Int32Array(held.length);
417
+ const starts = new Float64Array(held.length);
418
+ const ranks = new Int32Array(held.length);
419
+ held.forEach(({ id, refName, start, end, rank }, i) => {
420
+ index.set(id, i);
421
+ names.push(String(id));
422
+ let ref = refIndex.get(refName);
423
+ if (ref === undefined) {
424
+ ref = refNames.length;
425
+ refIndex.set(refName, ref);
426
+ refNames.push(refName);
427
+ }
428
+ lengths[i] = end - start;
429
+ refs[i] = ref;
430
+ starts[i] = start;
431
+ ranks[i] = rank;
432
+ });
433
+ // a kept node with no row of its own is named, and not drawn
434
+ const nodeIndex = (id) => {
435
+ let i = index.get(id);
436
+ if (i === undefined) {
437
+ i = names.length;
438
+ index.set(id, i);
439
+ names.push(String(id));
440
+ }
441
+ return i;
442
+ };
443
+ const byLine = new Map();
444
+ for (let i = 0; i < this.links.length; i += 2) {
445
+ const a = this.links[i];
446
+ const b = this.links[i + 1];
447
+ const source = Math.floor(a / 2);
448
+ const target = Math.floor(b / 2);
449
+ if (kept.has(source) && kept.has(target)) {
450
+ byLine.set(`${source}\t${a % 2 ? '-' : '+'}\t${target}\t${b % 2 ? '-' : '+'}`, i);
451
+ }
452
+ }
453
+ const lines = [...byLine.keys()].sort();
454
+ const from = new Int32Array(lines.length);
455
+ const to = new Int32Array(lines.length);
456
+ const strands = new Uint8Array(lines.length);
457
+ lines.forEach((line, k) => {
458
+ const i = byLine.get(line);
459
+ const a = this.links[i];
460
+ const b = this.links[i + 1];
461
+ from[k] = nodeIndex(Math.floor(a / 2));
462
+ to[k] = nodeIndex(Math.floor(b / 2));
463
+ strands[k] = (a % 2) | ((b % 2) << 1);
464
+ });
465
+ const walks = this.referenceFirst(fragments);
466
+ const offsets = new Int32Array(walks.length + 1);
467
+ walks.forEach(({ ids }, p) => {
468
+ offsets[p + 1] = offsets[p] + ids.length;
469
+ });
470
+ const steps = new Int32Array(offsets[walks.length]);
471
+ const reversed = new Uint8Array(steps.length);
472
+ walks.forEach(({ ids, rev }, p) => {
473
+ const at = offsets[p];
474
+ for (let j = 0; j < ids.length; j++) {
475
+ steps[at + j] = nodeIndex(ids[j]);
476
+ }
477
+ reversed.set(rev, at);
478
+ });
479
+ return {
480
+ nodes: { names, lengths, refs, starts, ranks, refNames },
481
+ links: { from, to, strands },
482
+ walks: {
483
+ names: walks.map(f => f.name),
484
+ starts: Float64Array.from(walks, f => f.hapStart),
485
+ ends: Float64Array.from(walks, f => f.hapEnd),
486
+ offsets,
487
+ steps,
488
+ reversed,
489
+ },
490
+ };
491
+ }
492
+ // The view anchors on the first walk when no walk names the assembly, as
493
+ // gbz-base's cut has it; in file order that was whichever haplotype the
494
+ // chunk listed first, CHM13 on a GRCh38 window
495
+ referenceFirst(fragments) {
496
+ const { refName } = this.window;
497
+ return [...fragments].sort((a, b) => Number(b.name === refName) - Number(a.name === refName) ||
498
+ (a.name < b.name ? -1 : a.name > b.name ? 1 : a.hapStart - b.hapStart));
499
+ }
500
+ }
501
+ function endpoint(line, from, to) {
502
+ return (+line.slice(from, to - 1) * 2 + (line.charCodeAt(to - 1) === 45 ? 1 : 0));
503
+ }
504
+ export const BUDGET_NAMES = {
505
+ steps: 'the step budget',
506
+ bytes: 'a byte budget of',
507
+ };
508
+ export function stepBudgetError(rows, stepBudget, readPerKept, window, chunk, filtered, names = BUDGET_NAMES) {
509
+ var _a;
510
+ let total = 0;
511
+ const perChunk = new Map();
512
+ const walks = new Set();
513
+ for (const row of rows) {
514
+ total += row.n;
515
+ perChunk.set(row.chunkStart, ((_a = perChunk.get(row.chunkStart)) !== null && _a !== void 0 ? _a : 0) + row.n);
516
+ walks.add(row.name);
517
+ }
518
+ const budget = stepBudget * readPerKept;
519
+ if (total <= budget) {
520
+ return undefined;
521
+ }
522
+ const windowBp = window.end - window.start;
523
+ let read = 0;
524
+ let fitsBp = 0;
525
+ for (const cs of [...perChunk.keys()].sort((a, b) => a - b)) {
526
+ read += perChunk.get(cs);
527
+ if (read > budget) {
528
+ break;
529
+ }
530
+ fitsBp = Math.max(fitsBp, cs + chunk - window.start);
531
+ }
532
+ const fewerFit = !filtered && walks.size > 2 && (2 * total) / walks.size <= budget;
533
+ return new RegionTooLargeError(fitsBp > 0
534
+ ? `Zoom in to about ${bpString(fitsBp)}${fewerFit ? ', or choose fewer haplotypes,' : ''} to see the graph`
535
+ : `Too many haplotype steps here to draw ${filtered ? 'these haplotypes' : 'every haplotype'} (${read.toLocaleString()} steps read, over ${readPerKept} × ${names.steps} of ${stepBudget.toLocaleString()}); choose fewer haplotypes`, budget, windowBp, Math.max(fitsBp, 1));
536
+ }
537
+ /**
538
+ * The zoom-in notice for a cut that keeps more steps than `budget`, or
539
+ * undefined when it fits. The span that fits is the window scaled by how far
540
+ * over the cut is.
541
+ */
542
+ export function keptStepsError(fragments, budget, window, filtered) {
543
+ const total = fragments.reduce((sum, f) => sum + f.ids.length, 0);
544
+ if (total <= budget) {
545
+ return undefined;
546
+ }
547
+ const windowBp = window.end - window.start;
548
+ const fitsBp = Math.max(1, Math.floor((windowBp * budget) / total));
549
+ return new RegionTooLargeError(`Zoom in to about ${bpString(fitsBp)}${filtered ? '' : ', or choose fewer haplotypes,'} to see the graph`, budget, windowBp, fitsBp);
550
+ }
551
+ /**
552
+ * The zoom-in notice for a cut whose three files would fetch more than
553
+ * `budget` compressed bytes, or undefined when they fit. `bytesTo(end)` is the
554
+ * indexes' estimate for a read from the window's first query base to `end`;
555
+ * the span that fits grows a chunk at a time from the chunk holding the
556
+ * window's start, and when that chunk and those before it are over on their
557
+ * own, no zoom fits.
558
+ */
559
+ export async function byteBudgetError(bytesTo, budget, window, chunk, names = BUDGET_NAMES) {
560
+ const bytes = await bytesTo(window.end);
561
+ if (bytes <= budget) {
562
+ return undefined;
563
+ }
564
+ let fitsBp = 0;
565
+ let least;
566
+ for (let cs = Math.floor(window.start / chunk) * chunk; cs < window.end; cs += chunk) {
567
+ const read = await bytesTo(cs + 1);
568
+ least !== null && least !== void 0 ? least : (least = read);
569
+ if (read > budget) {
570
+ break;
571
+ }
572
+ fitsBp = cs + chunk - window.start;
573
+ }
574
+ const windowBp = window.end - window.start;
575
+ return new RegionTooLargeError(fitsBp > 0
576
+ ? `Zoom in to about ${bpString(fitsBp)} to see the graph`
577
+ : `Too much graph here to fetch (${megabytes(least !== null && least !== void 0 ? least : bytes)} against ${names.bytes} ${megabytes(budget)})`, budget, windowBp, Math.max(fitsBp, 1));
578
+ }
579
+ const megabytes = (bytes) => `${(bytes / 1e6).toFixed(1)} MB`;
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
1
  {
2
2
  "name": "@jbrowse/graph-walks",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Read gfa-to-tabix walk files by range requests and cut a window of a pangenome graph: the reference, every haplotype's walk through it, and the nodes and links they visit",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Colin Diesh",
8
+ "email": "colin.diesh@gmail.com",
9
+ "url": "https://github.com/cmdcolin"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/GMOD/graph-walks.git"
14
+ },
15
+ "keywords": [
16
+ "pangenome",
17
+ "graph",
18
+ "gfa",
19
+ "tabix",
20
+ "haplotype"
21
+ ],
22
+ "type": "module",
23
+ "packageManager": "pnpm@11.20.0",
24
+ "exports": {
25
+ ".": "./src/index.ts"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./esm/index.d.ts",
32
+ "default": "./esm/index.js"
33
+ }
34
+ }
35
+ },
36
+ "files": [
37
+ "esm"
38
+ ],
39
+ "scripts": {
40
+ "build": "rm -rf esm && tsc -p tsconfig.build.json",
41
+ "typecheck": "tsc --noEmit",
42
+ "lint": "eslint --report-unused-disable-directives --max-warnings 0",
43
+ "format": "prettier --write .",
44
+ "format:check": "prettier --check .",
45
+ "test": "vitest run",
46
+ "prepack": "pnpm build",
47
+ "preversion": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build",
48
+ "postversion": "git push --follow-tags"
49
+ },
50
+ "engines": {
51
+ "node": ">=20"
52
+ },
53
+ "devDependencies": {
54
+ "@eslint/js": "^10.0.1",
55
+ "@gmod/tabix": "^3.10.1",
56
+ "eslint": "^10.12.0",
57
+ "eslint-plugin-import-x": "^4.17.1",
58
+ "generic-filehandle2": "^2.5.0",
59
+ "prettier": "^3.9.10",
60
+ "typescript": "^6.0.3",
61
+ "typescript-eslint": "^8.71.1",
62
+ "vitest": "^5.0.3"
63
+ }
64
+ }