@jbrowse/graph-walks 0.1.0 → 1.0.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.
- package/README.md +17 -13
- package/esm/index.d.ts +4 -4
- package/esm/index.js +3 -3
- package/esm/{walkFormat2.d.ts → walkChunk.d.ts} +5 -4
- package/esm/{walkFormat2.js → walkChunk.js} +25 -5
- package/esm/walkReader.d.ts +11 -13
- package/esm/walkReader.js +59 -91
- package/esm/walkRows.d.ts +11 -23
- package/esm/walkRows.js +22 -121
- package/package.json +18 -22
package/README.md
CHANGED
|
@@ -4,11 +4,13 @@ Cuts a window out of a pangenome graph stored as
|
|
|
4
4
|
[gfa-to-tabix](https://github.com/GMOD/gfa-to-tabix) walk files, by range
|
|
5
5
|
requests.
|
|
6
6
|
|
|
7
|
-
- **In:** a `gfa-to-tabix --walks` file
|
|
7
|
+
- **In:** a `gfa-to-tabix --walks` file, `<prefix>.<sample>.walks.bed.gz` with
|
|
8
|
+
its `.tbi`, and a region on its reference
|
|
8
9
|
- **Out:** the reference over the region, every haplotype's walk through it, and
|
|
9
10
|
the nodes and links they visit, as typed arrays
|
|
10
|
-
- Reads format
|
|
11
|
-
format
|
|
11
|
+
- Reads walk format 2, which gfa-to-tabix writes from 1.0. The reader refuses a
|
|
12
|
+
format 1 file, from gfa-to-tabix 0.12 or earlier, with a `WalkFormatError`
|
|
13
|
+
asking for a rebuild
|
|
12
14
|
- Has no runtime dependencies: it reads through any object with
|
|
13
15
|
[@gmod/tabix](https://github.com/GMOD/tabix-js)'s `TabixIndexedFile` methods
|
|
14
16
|
|
|
@@ -32,16 +34,13 @@ import {
|
|
|
32
34
|
} from '@jbrowse/graph-walks'
|
|
33
35
|
import { RemoteFile } from 'generic-filehandle2'
|
|
34
36
|
|
|
35
|
-
const
|
|
37
|
+
const url = 'https://example.org/hprc-v2.1-mc-grch38.GRCh38.walks.bed.gz'
|
|
38
|
+
const reader = new WalkReader(
|
|
36
39
|
new TabixIndexedFile({
|
|
37
40
|
filehandle: new RemoteFile(url),
|
|
38
41
|
tbiFilehandle: new RemoteFile(`${url}.tbi`),
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const reader = new WalkReader({
|
|
42
|
-
walks: file(`${prefix}.walks.bed.gz`),
|
|
43
|
-
nodes: file(`${prefix}.nodes.bed.gz`),
|
|
44
|
-
})
|
|
42
|
+
}),
|
|
43
|
+
)
|
|
45
44
|
|
|
46
45
|
const refName = 'GRCh38#0#chr6'
|
|
47
46
|
const { graph, kept, fragments } = await reader.cut(
|
|
@@ -68,8 +67,8 @@ const tables = graph.tables(kept, fragments)
|
|
|
68
67
|
| `context` | bp of reference kept past each end of the region; default 1000 |
|
|
69
68
|
| `stepBudget` | The most steps a cut keeps; no limit by default |
|
|
70
69
|
| `byteBudget` | The most compressed bytes a cut fetches; no limit by default |
|
|
71
|
-
| `links` | `false`
|
|
72
|
-
| `sequences` | Keep each node's bases in `graph.sequences`, from `--sequences`
|
|
70
|
+
| `links` | `false` lists no links, for a caller that draws no edges |
|
|
71
|
+
| `sequences` | Keep each node's bases in `graph.sequences`, from a `--sequences` file |
|
|
73
72
|
| `signal` | An `AbortSignal` that stops the reads |
|
|
74
73
|
|
|
75
74
|
A cut over a budget throws a `RegionTooLargeError`. Its `fitsBp` holds about how
|
|
@@ -77,9 +76,14 @@ wide a window would fit, and JBrowse shows it as a zoom-in notice.
|
|
|
77
76
|
|
|
78
77
|
## How a cut works
|
|
79
78
|
|
|
79
|
+
- A chunk holds the reference's path through it once, each way a haplotype
|
|
80
|
+
leaves that path once, and every other path's piece as the list of ways it
|
|
81
|
+
takes; gfa-to-tabix's
|
|
82
|
+
[walks-format.md](https://github.com/GMOD/gfa-to-tabix/blob/main/docs/walks-format.md)
|
|
83
|
+
describes the rows
|
|
80
84
|
- A cut reads every chunk under the region, and the chunks before it as far as
|
|
81
85
|
the header's `maxnode` reaches, since a node is filed under the chunk holding
|
|
82
|
-
its start
|
|
86
|
+
its start, and decodes each chunk once
|
|
83
87
|
- A haplotype that leaves the reference near the region is followed to where it
|
|
84
88
|
rejoins, and the reference is cut out as far as those rejoins reach
|
|
85
89
|
- A walk the read cuts off goes on in the chunk its row names (`pv:`/`nx:`
|
package/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart,
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from './walkRows.ts';
|
|
2
2
|
export type { BudgetNames, WalkChunk, WalkFragment, WalkHeader, WalkNode, WalkRow, WalkRun, WalkWindow, } from './walkRows.ts';
|
|
3
|
-
export { WalkReader } from './walkReader.ts';
|
|
4
|
-
export { referenceNodes } from './
|
|
5
|
-
export type { TabixSource, WalkCutOptions,
|
|
3
|
+
export { WalkFormatError, WalkReader } from './walkReader.ts';
|
|
4
|
+
export { referenceNodes } from './walkChunk.ts';
|
|
5
|
+
export type { TabixSource, WalkCutOptions, WalkReaderHeader, } from './walkReader.ts';
|
|
6
6
|
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from './util.ts';
|
|
7
7
|
export type { GraphTables } from './util.ts';
|
package/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart,
|
|
2
|
-
export { WalkReader } from "./walkReader.js";
|
|
3
|
-
export { referenceNodes } from "./
|
|
1
|
+
export { BUDGET_NAMES, WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, walkNameFilter, } from "./walkRows.js";
|
|
2
|
+
export { WalkFormatError, WalkReader } from "./walkReader.js";
|
|
3
|
+
export { referenceNodes } from "./walkChunk.js";
|
|
4
4
|
export { RegionTooLargeError, bpString, panSNHaplotype, panSNMatchesPrefix, } from "./util.js";
|
|
@@ -24,7 +24,7 @@ export declare class RefPath {
|
|
|
24
24
|
}
|
|
25
25
|
export declare class WalkRowError extends Error {
|
|
26
26
|
}
|
|
27
|
-
export interface
|
|
27
|
+
export interface Chunk {
|
|
28
28
|
path: RefPath;
|
|
29
29
|
routes: Route[];
|
|
30
30
|
walks: {
|
|
@@ -34,6 +34,7 @@ export interface Format2Chunk {
|
|
|
34
34
|
lengths: Map<number, number>;
|
|
35
35
|
}
|
|
36
36
|
export interface DecodedChunk {
|
|
37
|
+
sequences: Map<number, string>;
|
|
37
38
|
rows: WalkRow[];
|
|
38
39
|
nodes: Map<number, PlacedNode>;
|
|
39
40
|
ends: {
|
|
@@ -43,13 +44,13 @@ export interface DecodedChunk {
|
|
|
43
44
|
first: number;
|
|
44
45
|
last: number;
|
|
45
46
|
}[];
|
|
46
|
-
chunk:
|
|
47
|
+
chunk: Chunk;
|
|
47
48
|
}
|
|
48
49
|
/**
|
|
49
50
|
* Decodes one chunk's rows, each split on tabs. `keep` names the walk rows
|
|
50
51
|
* whose steps a cut wants, undefined for every one.
|
|
51
52
|
*/
|
|
52
|
-
export declare function decodeChunk(rows: string[][], keep: ((name: string) => boolean) | undefined): DecodedChunk;
|
|
53
|
+
export declare function decodeChunk(rows: string[][], keep: ((name: string) => boolean) | undefined, withSequences?: boolean): DecodedChunk;
|
|
53
54
|
/**
|
|
54
55
|
* The positions of the cut's nodes off R, and the links among the cut's
|
|
55
56
|
* nodes, from the chunks a cut read: `kept` holds the node ids the cut draws.
|
|
@@ -57,7 +58,7 @@ export declare function decodeChunk(rows: string[][], keep: ((name: string) => b
|
|
|
57
58
|
* those visiting it in the chunks read, at its earliest offset, as format 1's
|
|
58
59
|
* node rows had it. Links are R's and the routes', both ends in the cut.
|
|
59
60
|
*/
|
|
60
|
-
export declare function placeAndLink(chunks:
|
|
61
|
+
export declare function placeAndLink(chunks: Chunk[], kept: Set<number>, place: (id: number, node: PlacedNode) => void, link: (a: number, b: number) => void): void;
|
|
61
62
|
export declare function referenceNodes(rows: string[][]): (PlacedNode & {
|
|
62
63
|
id: number;
|
|
63
64
|
})[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
//
|
|
2
|
-
// docs/walks-format.md). A chunk lists its `ref` rows, which joined in
|
|
1
|
+
// A chunk of gfa-to-tabix's walk format 2, one file per reference (that
|
|
2
|
+
// repo's docs/walks-format.md). A chunk lists its `ref` rows, which joined in
|
|
3
3
|
// hapOffset order are the chunk's reference path R; a `nodes` row with the
|
|
4
4
|
// lengths of the nodes its routes visit; `route` rows, each a way off R; and a
|
|
5
5
|
// `walk` row per piece of every other path, naming the routes it takes in
|
|
@@ -286,16 +286,31 @@ function decodeWalk(path, routes, cols, choices, n) {
|
|
|
286
286
|
}
|
|
287
287
|
return { ids, rev };
|
|
288
288
|
}
|
|
289
|
+
// The bases an SQ:Z: tag at or after column `from` joins, one node's per
|
|
290
|
+
// length, as `ids` names them
|
|
291
|
+
function sliceBases(cols, from, ids, lengths, out) {
|
|
292
|
+
const tag = cols.slice(from).find(c => c.startsWith('SQ:Z:'));
|
|
293
|
+
if (tag === undefined) {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
let at = 5;
|
|
297
|
+
for (let k = 0; k < ids.length; k++) {
|
|
298
|
+
const length = lengths[k];
|
|
299
|
+
out.set(ids[k], tag.slice(at, at + length));
|
|
300
|
+
at += length;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
289
303
|
/**
|
|
290
304
|
* Decodes one chunk's rows, each split on tabs. `keep` names the walk rows
|
|
291
305
|
* whose steps a cut wants, undefined for every one.
|
|
292
306
|
*/
|
|
293
|
-
export function decodeChunk(rows, keep) {
|
|
307
|
+
export function decodeChunk(rows, keep, withSequences = false) {
|
|
294
308
|
var _a, _b, _c;
|
|
295
309
|
const path = new RefPath();
|
|
296
310
|
const lengths = new Map();
|
|
297
311
|
const routes = [];
|
|
298
312
|
const out = {
|
|
313
|
+
sequences: new Map(),
|
|
299
314
|
rows: [],
|
|
300
315
|
nodes: new Map(),
|
|
301
316
|
ends: [],
|
|
@@ -309,6 +324,9 @@ export function decodeChunk(rows, keep) {
|
|
|
309
324
|
const nodeLengths = numbers(cols[10]);
|
|
310
325
|
const hapOffset = +cols[6];
|
|
311
326
|
path.add(handles, nodeLengths);
|
|
327
|
+
if (withSequences) {
|
|
328
|
+
sliceBases(cols, 11, handles.map(idOf), nodeLengths, out.sequences);
|
|
329
|
+
}
|
|
312
330
|
let start = hapOffset;
|
|
313
331
|
for (let k = 0; k < handles.length; k++) {
|
|
314
332
|
const id = idOf(handles[k]);
|
|
@@ -339,7 +357,6 @@ export function decodeChunk(rows, keep) {
|
|
|
339
357
|
hapOffset,
|
|
340
358
|
piece: +cols[7],
|
|
341
359
|
n: handles.length,
|
|
342
|
-
enc: '',
|
|
343
360
|
steps: {
|
|
344
361
|
ids: Int32Array.from(handles, idOf),
|
|
345
362
|
rev: Uint8Array.from(handles, h => h & 1),
|
|
@@ -355,8 +372,12 @@ export function decodeChunk(rows, keep) {
|
|
|
355
372
|
let id = 0;
|
|
356
373
|
for (let k = 0; k < ids.length; k++) {
|
|
357
374
|
id += ids[k];
|
|
375
|
+
ids[k] = id;
|
|
358
376
|
lengths.set(id, nodeLengths[k]);
|
|
359
377
|
}
|
|
378
|
+
if (withSequences) {
|
|
379
|
+
sliceBases(cols, 6, ids, nodeLengths, out.sequences);
|
|
380
|
+
}
|
|
360
381
|
}
|
|
361
382
|
else if (cols[3] === 'route') {
|
|
362
383
|
const entry = tokens(cols[4], previous, []);
|
|
@@ -423,7 +444,6 @@ export function decodeChunk(rows, keep) {
|
|
|
423
444
|
hapOffset: +cols[6],
|
|
424
445
|
piece: +cols[7],
|
|
425
446
|
n,
|
|
426
|
-
enc: '',
|
|
427
447
|
steps: decodeWalk(path, routes, cols, choices, n),
|
|
428
448
|
...tags(cols, 12, cols[0]),
|
|
429
449
|
});
|
package/esm/walkReader.d.ts
CHANGED
|
@@ -24,10 +24,8 @@ export interface TabixSource {
|
|
|
24
24
|
signal?: AbortSignal;
|
|
25
25
|
}): Promise<number>;
|
|
26
26
|
}
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
nodes?: TabixSource;
|
|
30
|
-
links?: TabixSource;
|
|
27
|
+
export declare class WalkFormatError extends Error {
|
|
28
|
+
name: string;
|
|
31
29
|
}
|
|
32
30
|
export interface WalkCutOptions {
|
|
33
31
|
signal?: AbortSignal;
|
|
@@ -44,18 +42,18 @@ export type WalkReaderHeader = WalkHeader & {
|
|
|
44
42
|
lookback: number;
|
|
45
43
|
};
|
|
46
44
|
/**
|
|
47
|
-
* A
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
45
|
+
* A `gfa-to-tabix --walks` file, `<prefix>.<sample>.walks.bed.gz`, read a
|
|
46
|
+
* window at a time: one read over whole chunks, from the lookback chunks
|
|
47
|
+
* before the window to its end, each chunk decoded once. The index first
|
|
48
|
+
* estimates the bytes the read fetches, and a window past the byte budget
|
|
49
|
+
* reads no row. Only the walks `keep` accepts are decoded. The step budget
|
|
50
|
+
* bounds the steps the cut keeps, and rows holding READ_STEPS_PER_KEPT times
|
|
51
|
+
* as many stop the read.
|
|
53
52
|
*/
|
|
54
53
|
export declare class WalkReader {
|
|
55
54
|
private headerRead;
|
|
56
|
-
private readonly
|
|
57
|
-
|
|
58
|
-
constructor(files: WalkFileSet, chunkWithoutHeader?: number);
|
|
55
|
+
private readonly walks;
|
|
56
|
+
constructor(walks: TabixSource);
|
|
59
57
|
header(opts?: {
|
|
60
58
|
signal?: AbortSignal;
|
|
61
59
|
}): Promise<WalkReaderHeader>;
|
package/esm/walkReader.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { decodeChunk, pieceLinks } from "./
|
|
2
|
-
import { WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks,
|
|
1
|
+
import { decodeChunk, pieceLinks } from "./walkChunk.js";
|
|
2
|
+
import { WalkGraph, byteBudgetError, chunkQueryStart, joinPieces, keptStepsError, lookbackChunks, stepBudgetError, walkCut, walkHeader, } from "./walkRows.js";
|
|
3
3
|
// A window reads whole chunks and keeps the steps on it, so a narrow window
|
|
4
4
|
// reads several times what it keeps: 464 haplotypes across the 1.5 kb ABCA7
|
|
5
5
|
// repeat read 5.0 M steps and keep 1.16 M, and the cut takes 0.4 s from warm
|
|
@@ -12,6 +12,13 @@ const FOLLOW_ROUNDS = 3;
|
|
|
12
12
|
const FOLLOW_CHUNKS = 4;
|
|
13
13
|
// bp of reference a cut keeps past each end of the window
|
|
14
14
|
const WALK_CONTEXT = 1000;
|
|
15
|
+
// A walk file gfa-to-tabix 0.12 or earlier wrote, which the reader refuses
|
|
16
|
+
export class WalkFormatError extends Error {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.name = 'WalkFormatError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
15
22
|
// @gmod/tabix loops forever on a NaN end and reads nothing for a NaN start
|
|
16
23
|
// (GMOD/tabix-js#157), so every query refuses a range that is not finite first
|
|
17
24
|
function checkRange(refName, start, end) {
|
|
@@ -19,10 +26,6 @@ function checkRange(refName, start, end) {
|
|
|
19
26
|
throw new Error(`${refName}:${start}-${end} is not a finite range to query the graph's index for`);
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
|
-
function getLines(file, refName, start, end, opts) {
|
|
23
|
-
checkRange(refName, start, end);
|
|
24
|
-
return file.getLines(refName, start, end, opts);
|
|
25
|
-
}
|
|
26
29
|
// Settles like `promise` unless `signal` aborts first
|
|
27
30
|
function raceAbort(promise, signal) {
|
|
28
31
|
if (!signal) {
|
|
@@ -42,26 +45,29 @@ function raceAbort(promise, signal) {
|
|
|
42
45
|
});
|
|
43
46
|
}
|
|
44
47
|
/**
|
|
45
|
-
* A
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* A `gfa-to-tabix --walks` file, `<prefix>.<sample>.walks.bed.gz`, read a
|
|
49
|
+
* window at a time: one read over whole chunks, from the lookback chunks
|
|
50
|
+
* before the window to its end, each chunk decoded once. The index first
|
|
51
|
+
* estimates the bytes the read fetches, and a window past the byte budget
|
|
52
|
+
* reads no row. Only the walks `keep` accepts are decoded. The step budget
|
|
53
|
+
* bounds the steps the cut keeps, and rows holding READ_STEPS_PER_KEPT times
|
|
54
|
+
* as many stop the read.
|
|
51
55
|
*/
|
|
52
56
|
export class WalkReader {
|
|
53
|
-
constructor(
|
|
54
|
-
this.
|
|
55
|
-
this.chunkWithoutHeader = chunkWithoutHeader;
|
|
57
|
+
constructor(walks) {
|
|
58
|
+
this.walks = walks;
|
|
56
59
|
}
|
|
57
60
|
// The header, read once for every caller: one caller giving up stops only
|
|
58
61
|
// its own wait, and a read that fails is tried again by the next caller
|
|
59
62
|
header(opts = {}) {
|
|
60
63
|
if (!this.headerRead) {
|
|
61
|
-
const read = this.
|
|
64
|
+
const read = this.walks.getHeaderLines().then(lines => {
|
|
62
65
|
var _a;
|
|
63
66
|
const header = walkHeader(lines);
|
|
64
|
-
|
|
67
|
+
if (header.format !== 2) {
|
|
68
|
+
throw new WalkFormatError('This walk file is format 1, from gfa-to-tabix 0.12 or earlier; rebuild it with gfa-to-tabix 1.0 or later');
|
|
69
|
+
}
|
|
70
|
+
const chunk = (_a = header.chunk) !== null && _a !== void 0 ? _a : 65536;
|
|
65
71
|
return {
|
|
66
72
|
...header,
|
|
67
73
|
chunk,
|
|
@@ -79,36 +85,22 @@ export class WalkReader {
|
|
|
79
85
|
}
|
|
80
86
|
// The reference sequences the walk file holds windows on
|
|
81
87
|
referenceSequences(opts = {}) {
|
|
82
|
-
return this.
|
|
88
|
+
return this.walks.getReferenceSequenceNames(opts);
|
|
83
89
|
}
|
|
84
90
|
async queryStart(start, opts = {}) {
|
|
85
91
|
const { chunk, lookback } = await this.header(opts);
|
|
86
92
|
return chunkQueryStart(start, chunk, lookback);
|
|
87
93
|
}
|
|
88
94
|
async cut(refName, region, opts = {}) {
|
|
89
|
-
|
|
90
|
-
const {
|
|
91
|
-
|
|
92
|
-
const format2 = format === 2;
|
|
93
|
-
const nodeFile = this.files.nodes;
|
|
94
|
-
const linkFile = opts.links === false ? undefined : this.files.links;
|
|
95
|
-
if (!format2 && !nodeFile) {
|
|
96
|
-
throw new Error('A format 1 walk file needs its node file: open it with `nodes`');
|
|
97
|
-
}
|
|
98
|
-
const links = format2 ? opts.links !== false : linkFile !== undefined;
|
|
95
|
+
var _a;
|
|
96
|
+
const { signal, keep, context = WALK_CONTEXT, stepBudget = Infinity, byteBudget = Infinity, links = true, } = opts;
|
|
97
|
+
const { chunk, lookback } = await this.header({ signal });
|
|
99
98
|
const from = chunkQueryStart(region.start, chunk, lookback);
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const bytesFor = async (spans) => {
|
|
106
|
-
if (!budgeted) {
|
|
107
|
-
return 0;
|
|
108
|
-
}
|
|
109
|
-
const sizes = await Promise.all(files.map(file => file.bytesForRegions(spans.map(span => ({ refName, ...span })), { signal })));
|
|
110
|
-
return sizes.reduce((sum, bytes) => sum + bytes, 0);
|
|
111
|
-
};
|
|
99
|
+
const bytesForRegions = (_a = this.walks.bytesForRegions) === null || _a === void 0 ? void 0 : _a.bind(this.walks);
|
|
100
|
+
const budgeted = Number.isFinite(byteBudget) && bytesForRegions !== undefined;
|
|
101
|
+
const bytesFor = async (spans) => budgeted
|
|
102
|
+
? bytesForRegions(spans.map(span => ({ refName, ...span })), { signal })
|
|
103
|
+
: 0;
|
|
112
104
|
const tooLarge = budgeted
|
|
113
105
|
? await byteBudgetError(async (end) => {
|
|
114
106
|
checkRange(refName, from, end);
|
|
@@ -133,49 +125,39 @@ export class WalkReader {
|
|
|
133
125
|
context,
|
|
134
126
|
sequences: opts.sequences,
|
|
135
127
|
});
|
|
136
|
-
const read = (file, lineCallback, start = from, end = region.end) => getLines(file, refName, start, end, {
|
|
137
|
-
signal: reads.signal,
|
|
138
|
-
lineCallback,
|
|
139
|
-
});
|
|
140
|
-
const addRow = (line) => {
|
|
141
|
-
if (keep === undefined || keep(walkRowName(line))) {
|
|
142
|
-
rows.push(parseWalkRow(line));
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
const addNode = (line) => {
|
|
146
|
-
graph.addNode(line);
|
|
147
|
-
};
|
|
148
|
-
const addLink = (line) => {
|
|
149
|
-
graph.addLink(line);
|
|
150
|
-
};
|
|
151
128
|
const decoded = new Set();
|
|
152
129
|
const ends = [];
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
const readChunks = async (start
|
|
130
|
+
// each chunk read is decoded once, into its rows, nodes and the links
|
|
131
|
+
// between pieces
|
|
132
|
+
const readChunks = async (start, end) => {
|
|
133
|
+
checkRange(refName, start, end);
|
|
156
134
|
const byChunk = new Map();
|
|
157
|
-
await
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
held.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
135
|
+
await this.walks.getLines(refName, start, end, {
|
|
136
|
+
signal: reads.signal,
|
|
137
|
+
lineCallback: line => {
|
|
138
|
+
const cols = line.split('\t');
|
|
139
|
+
const cs = +cols[1];
|
|
140
|
+
const held = byChunk.get(cs);
|
|
141
|
+
if (held) {
|
|
142
|
+
held.push(cols);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
byChunk.set(cs, [cols]);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
});
|
|
168
149
|
for (const [cs, chunkRows] of byChunk) {
|
|
169
150
|
if (decoded.has(cs)) {
|
|
170
151
|
continue;
|
|
171
152
|
}
|
|
172
153
|
decoded.add(cs);
|
|
173
|
-
const chunkCut = decodeChunk(chunkRows, keep);
|
|
154
|
+
const chunkCut = decodeChunk(chunkRows, keep, opts.sequences);
|
|
174
155
|
rows.push(...chunkCut.rows);
|
|
175
156
|
for (const [id, node] of chunkCut.nodes) {
|
|
176
157
|
graph.placeNode(id, node);
|
|
177
158
|
}
|
|
178
|
-
graph.
|
|
159
|
+
graph.addSequences(chunkCut.sequences);
|
|
160
|
+
graph.addChunk(chunkCut.chunk);
|
|
179
161
|
ends.push(...chunkCut.ends);
|
|
180
162
|
}
|
|
181
163
|
if (links) {
|
|
@@ -184,20 +166,12 @@ export class WalkReader {
|
|
|
184
166
|
}
|
|
185
167
|
}
|
|
186
168
|
};
|
|
187
|
-
const checkSteps = () => {
|
|
188
|
-
const error = stepBudgetError(rows, stepBudget, READ_STEPS_PER_KEPT, region, chunk, keep !== undefined, opts.budgetNames);
|
|
189
|
-
if (error) {
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
};
|
|
193
169
|
try {
|
|
194
|
-
await (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
...(linkFile ? [read(linkFile, addLink)] : []),
|
|
200
|
-
]));
|
|
170
|
+
await readChunks(from, region.end);
|
|
171
|
+
const tooManyRead = stepBudgetError(rows, stepBudget, READ_STEPS_PER_KEPT, region, chunk, keep !== undefined, opts.budgetNames);
|
|
172
|
+
if (tooManyRead) {
|
|
173
|
+
throw tooManyRead;
|
|
174
|
+
}
|
|
201
175
|
// the chunks read, first to last, which a follow widens to the
|
|
202
176
|
// farthest chunk a cut-off walk goes on in, so the reference between
|
|
203
177
|
// is read too
|
|
@@ -224,13 +198,7 @@ export class WalkReader {
|
|
|
224
198
|
}
|
|
225
199
|
first = Math.min(first, ...starts);
|
|
226
200
|
last = Math.max(last, ...starts);
|
|
227
|
-
await Promise.all(spans.
|
|
228
|
-
? [readChunks(start, end)]
|
|
229
|
-
: [
|
|
230
|
-
read(this.files.walks, addRow, start, end),
|
|
231
|
-
read(nodeFile, addNode, start, end),
|
|
232
|
-
...(linkFile ? [read(linkFile, addLink, start, end)] : []),
|
|
233
|
-
]));
|
|
201
|
+
await Promise.all(spans.map(({ start, end }) => readChunks(start, end)));
|
|
234
202
|
cut = walkCut(joinPieces(rows), graph.nodes, refName);
|
|
235
203
|
}
|
|
236
204
|
}
|
package/esm/walkRows.d.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { RegionTooLargeError } from './util.ts';
|
|
2
2
|
import type { GraphTables } from './util.ts';
|
|
3
|
-
import type {
|
|
3
|
+
import type { Chunk } from './walkChunk.ts';
|
|
4
4
|
export interface WalkRow {
|
|
5
5
|
name: string;
|
|
6
6
|
fragStart: number;
|
|
7
7
|
hapOffset: number;
|
|
8
8
|
piece: number;
|
|
9
9
|
n: number;
|
|
10
|
-
|
|
11
|
-
steps?: {
|
|
10
|
+
steps: {
|
|
12
11
|
ids: Int32Array;
|
|
13
12
|
rev: Uint8Array;
|
|
14
13
|
};
|
|
@@ -34,8 +33,6 @@ export interface WalkWindow {
|
|
|
34
33
|
context: number;
|
|
35
34
|
sequences?: boolean;
|
|
36
35
|
}
|
|
37
|
-
export declare function walkRowName(line: string): string;
|
|
38
|
-
export declare function parseWalkRow(line: string): WalkRow;
|
|
39
36
|
export interface WalkHeader {
|
|
40
37
|
format?: number;
|
|
41
38
|
chunk?: number;
|
|
@@ -44,11 +41,10 @@ export interface WalkHeader {
|
|
|
44
41
|
haplotypes: string[];
|
|
45
42
|
}
|
|
46
43
|
/**
|
|
47
|
-
* What a walk file's header states:
|
|
48
|
-
*
|
|
49
|
-
* `#reference\tGRCh38` line
|
|
50
|
-
* `#haplotype\tHG002#1` line per other haplotype with rows
|
|
51
|
-
* the longest node in bp as `maxnode:i:1024` beside the chunk size
|
|
44
|
+
* What a walk file's header states: a `#walks` line with the format, the
|
|
45
|
+
* chunk size and the longest node in bp, `format:i:2\tchunk:i:65536\tmaxnode:i:1024`,
|
|
46
|
+
* a `#reference\tGRCh38` line naming the reference sample and a
|
|
47
|
+
* `#haplotype\tHG002#1` line per other haplotype with rows
|
|
52
48
|
*/
|
|
53
49
|
export declare function walkHeader(lines: string[]): WalkHeader;
|
|
54
50
|
/**
|
|
@@ -62,10 +58,6 @@ export declare function walkHeader(lines: string[]): WalkHeader;
|
|
|
62
58
|
export declare function lookbackChunks(maxNode: number | undefined, chunk: number): number;
|
|
63
59
|
export declare function chunkQueryStart(start: number, chunk: number, lookback?: number): number;
|
|
64
60
|
export declare function walkNameFilter(wanted: string[] | undefined, referenceHaplotype: string | undefined): ((name: string) => boolean) | undefined;
|
|
65
|
-
export declare function decodeSteps(enc: string, n: number, ids?: Int32Array<ArrayBuffer>, rev?: Uint8Array<ArrayBuffer>, at?: number): {
|
|
66
|
-
ids: Int32Array<ArrayBuffer>;
|
|
67
|
-
rev: Uint8Array<ArrayBuffer>;
|
|
68
|
-
};
|
|
69
61
|
export interface WalkRun {
|
|
70
62
|
name: string;
|
|
71
63
|
hapOffset: number;
|
|
@@ -103,24 +95,20 @@ export declare function walkCut(runs: WalkRun[], nodes: Map<number, WalkNode>, r
|
|
|
103
95
|
open: WalkChunk[];
|
|
104
96
|
};
|
|
105
97
|
/**
|
|
106
|
-
* The
|
|
107
|
-
*
|
|
108
|
-
* arrives twice and is read once; columns past a node row's eighth (LN:i:,
|
|
109
|
-
* SQ:Z: and whatever follows) are read only for the bases a window asks for.
|
|
98
|
+
* The nodes and links of the chunks a cut reads, and the tables of the cut
|
|
99
|
+
* they make with its walks. A node in two chunks is placed once.
|
|
110
100
|
*/
|
|
111
101
|
export declare class WalkGraph {
|
|
112
102
|
readonly nodes: Map<number, WalkNode>;
|
|
113
103
|
readonly sequences: Map<number, string>;
|
|
114
104
|
private links;
|
|
115
|
-
private readonly
|
|
116
|
-
private readonly t;
|
|
105
|
+
private readonly chunks;
|
|
117
106
|
private readonly window;
|
|
118
107
|
constructor(window: WalkWindow);
|
|
119
|
-
addNode(line: string): void;
|
|
120
108
|
placeNode(id: number, node: Omit<WalkNode, 'onWindow'>): void;
|
|
121
109
|
addLinkEnds(source: number, target: number): void;
|
|
122
|
-
|
|
123
|
-
|
|
110
|
+
addChunk(chunk: Chunk): void;
|
|
111
|
+
addSequences(sequences: Map<number, string>): void;
|
|
124
112
|
/**
|
|
125
113
|
* The cut as typed arrays, which the worker transfers rather than copies:
|
|
126
114
|
* nodes by reference position, each link once in the order its L line
|
package/esm/walkRows.js
CHANGED
|
@@ -1,52 +1,14 @@
|
|
|
1
1
|
import { RegionTooLargeError, bpString, panSNMatchesPrefix } from "./util.js";
|
|
2
|
-
import { placeAndLink } from "./
|
|
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
|
-
}
|
|
2
|
+
import { placeAndLink } from "./walkChunk.js";
|
|
40
3
|
function integerTag(line, tag) {
|
|
41
4
|
const match = new RegExp(`(?:^|\\t)${tag}:i:(\\d+)`).exec(line);
|
|
42
5
|
return match ? +match[1] : undefined;
|
|
43
6
|
}
|
|
44
7
|
/**
|
|
45
|
-
* What a walk file's header states:
|
|
46
|
-
*
|
|
47
|
-
* `#reference\tGRCh38` line
|
|
48
|
-
* `#haplotype\tHG002#1` line per other haplotype with rows
|
|
49
|
-
* the longest node in bp as `maxnode:i:1024` beside the chunk size
|
|
8
|
+
* What a walk file's header states: a `#walks` line with the format, the
|
|
9
|
+
* chunk size and the longest node in bp, `format:i:2\tchunk:i:65536\tmaxnode:i:1024`,
|
|
10
|
+
* a `#reference\tGRCh38` line naming the reference sample and a
|
|
11
|
+
* `#haplotype\tHG002#1` line per other haplotype with rows
|
|
50
12
|
*/
|
|
51
13
|
export function walkHeader(lines) {
|
|
52
14
|
var _a, _b, _c;
|
|
@@ -91,31 +53,6 @@ export function walkNameFilter(wanted, referenceHaplotype) {
|
|
|
91
53
|
: (name) => panSNMatchesPrefix(name, referenceHaplotype) ||
|
|
92
54
|
wanted.some(prefix => panSNMatchesPrefix(name, prefix));
|
|
93
55
|
}
|
|
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
56
|
export function joinPieces(rows) {
|
|
120
57
|
const byPath = new Map();
|
|
121
58
|
for (const row of rows) {
|
|
@@ -148,13 +85,8 @@ function decodeRun(pieces) {
|
|
|
148
85
|
const rev = new Uint8Array(n);
|
|
149
86
|
let at = 0;
|
|
150
87
|
for (const p of pieces) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
rev.set(p.steps.rev, at);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
decodeSteps(p.enc, p.n, ids, rev, at);
|
|
157
|
-
}
|
|
88
|
+
ids.set(p.steps.ids, at);
|
|
89
|
+
rev.set(p.steps.rev, at);
|
|
158
90
|
at += p.n;
|
|
159
91
|
}
|
|
160
92
|
const first = pieces[0];
|
|
@@ -308,50 +240,19 @@ export function walkCut(runs, nodes, refName) {
|
|
|
308
240
|
}
|
|
309
241
|
return { kept, fragments, open: [...open.values()] };
|
|
310
242
|
}
|
|
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
243
|
/**
|
|
321
|
-
* The
|
|
322
|
-
*
|
|
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.
|
|
244
|
+
* The nodes and links of the chunks a cut reads, and the tables of the cut
|
|
245
|
+
* they make with its walks. A node in two chunks is placed once.
|
|
325
246
|
*/
|
|
326
247
|
export class WalkGraph {
|
|
327
248
|
constructor(window) {
|
|
328
249
|
this.nodes = new Map();
|
|
329
250
|
this.sequences = new Map();
|
|
330
251
|
this.links = [];
|
|
331
|
-
//
|
|
332
|
-
this.
|
|
333
|
-
this.t = [];
|
|
252
|
+
// the chunks read, whose nodes off R and links tables() fills in
|
|
253
|
+
this.chunks = [];
|
|
334
254
|
this.window = window;
|
|
335
255
|
}
|
|
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
256
|
// A node with its position, unless the cut already holds it
|
|
356
257
|
placeNode(id, node) {
|
|
357
258
|
if (this.nodes.has(id)) {
|
|
@@ -370,13 +271,16 @@ export class WalkGraph {
|
|
|
370
271
|
addLinkEnds(source, target) {
|
|
371
272
|
this.links.push(source, target);
|
|
372
273
|
}
|
|
373
|
-
|
|
374
|
-
this.
|
|
274
|
+
addChunk(chunk) {
|
|
275
|
+
this.chunks.push(chunk);
|
|
375
276
|
}
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
277
|
+
// Bases of nodes a chunk's rows give, for a window that asks for them
|
|
278
|
+
addSequences(sequences) {
|
|
279
|
+
if (this.window.sequences) {
|
|
280
|
+
for (const [id, bases] of sequences) {
|
|
281
|
+
this.sequences.set(id, bases);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
380
284
|
}
|
|
381
285
|
/**
|
|
382
286
|
* The cut as typed arrays, which the worker transfers rather than copies:
|
|
@@ -386,8 +290,8 @@ export class WalkGraph {
|
|
|
386
290
|
* for the same rows.
|
|
387
291
|
*/
|
|
388
292
|
tables(kept, fragments) {
|
|
389
|
-
if (this.
|
|
390
|
-
placeAndLink(this.
|
|
293
|
+
if (this.chunks.length > 0) {
|
|
294
|
+
placeAndLink(this.chunks, kept, (id, node) => {
|
|
391
295
|
const held = this.nodes.get(id);
|
|
392
296
|
if (held) {
|
|
393
297
|
this.nodes.set(id, { ...held, ...node });
|
|
@@ -498,9 +402,6 @@ export class WalkGraph {
|
|
|
498
402
|
(a.name < b.name ? -1 : a.name > b.name ? 1 : a.hapStart - b.hapStart));
|
|
499
403
|
}
|
|
500
404
|
}
|
|
501
|
-
function endpoint(line, from, to) {
|
|
502
|
-
return (+line.slice(from, to - 1) * 2 + (line.charCodeAt(to - 1) === 45 ? 1 : 0));
|
|
503
|
-
}
|
|
504
405
|
export const BUDGET_NAMES = {
|
|
505
406
|
steps: 'the step budget',
|
|
506
407
|
bytes: 'a byte budget of',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/graph-walks",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -20,39 +20,25 @@
|
|
|
20
20
|
"haplotype"
|
|
21
21
|
],
|
|
22
22
|
"type": "module",
|
|
23
|
-
"packageManager": "pnpm@11.20.0",
|
|
24
23
|
"exports": {
|
|
25
|
-
".":
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./esm/index.d.ts",
|
|
26
|
+
"default": "./esm/index.js"
|
|
27
|
+
}
|
|
26
28
|
},
|
|
27
29
|
"publishConfig": {
|
|
28
|
-
"access": "public"
|
|
29
|
-
"exports": {
|
|
30
|
-
".": {
|
|
31
|
-
"types": "./esm/index.d.ts",
|
|
32
|
-
"default": "./esm/index.js"
|
|
33
|
-
}
|
|
34
|
-
}
|
|
30
|
+
"access": "public"
|
|
35
31
|
},
|
|
36
32
|
"files": [
|
|
37
33
|
"esm"
|
|
38
34
|
],
|
|
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
35
|
"engines": {
|
|
51
36
|
"node": ">=20"
|
|
52
37
|
},
|
|
53
38
|
"devDependencies": {
|
|
54
39
|
"@eslint/js": "^10.0.1",
|
|
55
40
|
"@gmod/tabix": "^3.10.1",
|
|
41
|
+
"@types/node": "^26.6.5",
|
|
56
42
|
"eslint": "^10.12.0",
|
|
57
43
|
"eslint-plugin-import-x": "^4.17.1",
|
|
58
44
|
"generic-filehandle2": "^2.5.0",
|
|
@@ -60,5 +46,15 @@
|
|
|
60
46
|
"typescript": "^6.0.3",
|
|
61
47
|
"typescript-eslint": "^8.71.1",
|
|
62
48
|
"vitest": "^5.0.3"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "rm -rf esm && tsc -p tsconfig.build.json",
|
|
52
|
+
"typecheck": "tsc --noEmit",
|
|
53
|
+
"lint": "eslint --report-unused-disable-directives --max-warnings 0",
|
|
54
|
+
"format": "prettier --write .",
|
|
55
|
+
"format:check": "prettier --check .",
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"preversion": "pnpm lint && pnpm format:check && pnpm typecheck && pnpm test && pnpm build",
|
|
58
|
+
"postversion": "git push --follow-tags"
|
|
63
59
|
}
|
|
64
|
-
}
|
|
60
|
+
}
|