@gmod/gbz-base 0.0.1
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/LICENSE +25 -0
- package/README.md +125 -0
- package/bin/query.js +7 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +140 -0
- package/dist/db.d.ts +72 -0
- package/dist/db.js +208 -0
- package/dist/filehandle.d.ts +6 -0
- package/dist/filehandle.js +1 -0
- package/dist/gbwt/bytecode.d.ts +20 -0
- package/dist/gbwt/bytecode.js +70 -0
- package/dist/gbwt/node.d.ts +15 -0
- package/dist/gbwt/node.js +44 -0
- package/dist/gbwt/record.d.ts +18 -0
- package/dist/gbwt/record.js +133 -0
- package/dist/gbwt/sequence.d.ts +3 -0
- package/dist/gbwt/sequence.js +40 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +6 -0
- package/dist/lcs.d.ts +1 -0
- package/dist/lcs.js +214 -0
- package/dist/query.d.ts +16 -0
- package/dist/query.js +37 -0
- package/dist/sqlite/btree.d.ts +18 -0
- package/dist/sqlite/btree.js +201 -0
- package/dist/sqlite/database.d.ts +29 -0
- package/dist/sqlite/database.js +71 -0
- package/dist/sqlite/pager.d.ts +19 -0
- package/dist/sqlite/pager.js +55 -0
- package/dist/sqlite/record.d.ts +3 -0
- package/dist/sqlite/record.js +85 -0
- package/dist/subgraph.d.ts +122 -0
- package/dist/subgraph.js +760 -0
- package/package.json +53 -0
- package/tools/haplotype-index/Cargo.toml +15 -0
- package/tools/haplotype-index/src/main.rs +319 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { decodeRecord, readVarint } from "./record.js";
|
|
2
|
+
const INTERIOR_INDEX = 0x02;
|
|
3
|
+
const INTERIOR_TABLE = 0x05;
|
|
4
|
+
const LEAF_INDEX = 0x0a;
|
|
5
|
+
const LEAF_TABLE = 0x0d;
|
|
6
|
+
function readHeader(page, start) {
|
|
7
|
+
const view = new DataView(page.buffer, page.byteOffset, page.byteLength);
|
|
8
|
+
const type = page[start];
|
|
9
|
+
if (type !== INTERIOR_INDEX && type !== INTERIOR_TABLE && type !== LEAF_INDEX && type !== LEAF_TABLE) {
|
|
10
|
+
throw new Error(`SQLite page has unknown b-tree type ${type}`);
|
|
11
|
+
}
|
|
12
|
+
const interior = type === INTERIOR_INDEX || type === INTERIOR_TABLE;
|
|
13
|
+
return {
|
|
14
|
+
type,
|
|
15
|
+
cellCount: view.getUint16(start + 3),
|
|
16
|
+
rightChild: interior ? view.getUint32(start + 8) : 0,
|
|
17
|
+
cellPointers: start + (interior ? 12 : 8),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function cellOffset(page, header, index) {
|
|
21
|
+
const view = new DataView(page.buffer, page.byteOffset, page.byteLength);
|
|
22
|
+
return view.getUint16(header.cellPointers + 2 * index);
|
|
23
|
+
}
|
|
24
|
+
function readUint32(page, offset) {
|
|
25
|
+
const view = new DataView(page.buffer, page.byteOffset, page.byteLength);
|
|
26
|
+
return view.getUint32(offset);
|
|
27
|
+
}
|
|
28
|
+
export class BTree {
|
|
29
|
+
pager;
|
|
30
|
+
usable;
|
|
31
|
+
constructor(pager, reservedBytes) {
|
|
32
|
+
this.pager = pager;
|
|
33
|
+
this.usable = pager.pageSize - reservedBytes;
|
|
34
|
+
}
|
|
35
|
+
localPayloadSize(total, isIndex) {
|
|
36
|
+
const usable = this.usable;
|
|
37
|
+
const maxLocal = isIndex ? Math.floor(((usable - 12) * 64) / 255) - 23 : usable - 35;
|
|
38
|
+
if (total <= maxLocal) {
|
|
39
|
+
return total;
|
|
40
|
+
}
|
|
41
|
+
const minLocal = Math.floor(((usable - 12) * 32) / 255) - 23;
|
|
42
|
+
const spill = minLocal + ((total - minLocal) % (usable - 4));
|
|
43
|
+
return spill <= maxLocal ? spill : minLocal;
|
|
44
|
+
}
|
|
45
|
+
async payload(page, offset, total, isIndex) {
|
|
46
|
+
const local = this.localPayloadSize(total, isIndex);
|
|
47
|
+
if (local === total) {
|
|
48
|
+
return page.subarray(offset, offset + total);
|
|
49
|
+
}
|
|
50
|
+
const result = new Uint8Array(total);
|
|
51
|
+
result.set(page.subarray(offset, offset + local), 0);
|
|
52
|
+
let filled = local;
|
|
53
|
+
let next = readUint32(page, offset + local);
|
|
54
|
+
while (next !== 0 && filled < total) {
|
|
55
|
+
const overflow = await this.pager.page(next);
|
|
56
|
+
const chunk = overflow.subarray(4, 4 + Math.min(this.usable - 4, total - filled));
|
|
57
|
+
result.set(chunk, filled);
|
|
58
|
+
filled += chunk.length;
|
|
59
|
+
next = readUint32(overflow, 0);
|
|
60
|
+
}
|
|
61
|
+
if (filled !== total) {
|
|
62
|
+
throw new Error('SQLite overflow chain ended before the payload was complete');
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
async pageAt(pageNumber) {
|
|
67
|
+
const page = await this.pager.page(pageNumber);
|
|
68
|
+
return { page, header: readHeader(page, pageNumber === 1 ? 100 : 0) };
|
|
69
|
+
}
|
|
70
|
+
async tableRowid(root, rowid) {
|
|
71
|
+
let pageNumber = root;
|
|
72
|
+
for (;;) {
|
|
73
|
+
const { page, header } = await this.pageAt(pageNumber);
|
|
74
|
+
if (header.type === LEAF_TABLE) {
|
|
75
|
+
let low = 0;
|
|
76
|
+
let high = header.cellCount;
|
|
77
|
+
while (low < high) {
|
|
78
|
+
const mid = (low + high) >> 1;
|
|
79
|
+
const offset = cellOffset(page, header, mid);
|
|
80
|
+
const [size, afterSize] = readVarint(page, offset);
|
|
81
|
+
const [key, afterKey] = readVarint(page, afterSize);
|
|
82
|
+
if (key === rowid) {
|
|
83
|
+
return decodeRecord(await this.payload(page, afterKey, size, false));
|
|
84
|
+
}
|
|
85
|
+
if (key < rowid) {
|
|
86
|
+
low = mid + 1;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
high = mid;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
if (header.type !== INTERIOR_TABLE) {
|
|
95
|
+
throw new Error('SQLite table b-tree contains an index page');
|
|
96
|
+
}
|
|
97
|
+
let low = 0;
|
|
98
|
+
let high = header.cellCount;
|
|
99
|
+
while (low < high) {
|
|
100
|
+
const mid = (low + high) >> 1;
|
|
101
|
+
const offset = cellOffset(page, header, mid);
|
|
102
|
+
const [key] = readVarint(page, offset + 4);
|
|
103
|
+
if (key < rowid) {
|
|
104
|
+
low = mid + 1;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
high = mid;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
pageNumber = low === header.cellCount ? header.rightChild : readUint32(page, cellOffset(page, header, low));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async *tableScan(root) {
|
|
114
|
+
const { page, header } = await this.pageAt(root);
|
|
115
|
+
if (header.type === LEAF_TABLE) {
|
|
116
|
+
for (let i = 0; i < header.cellCount; i++) {
|
|
117
|
+
const offset = cellOffset(page, header, i);
|
|
118
|
+
const [size, afterSize] = readVarint(page, offset);
|
|
119
|
+
const [rowid, afterKey] = readVarint(page, afterSize);
|
|
120
|
+
yield { rowid, values: decodeRecord(await this.payload(page, afterKey, size, false)) };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
for (let i = 0; i < header.cellCount; i++) {
|
|
125
|
+
yield* this.tableScan(readUint32(page, cellOffset(page, header, i)));
|
|
126
|
+
}
|
|
127
|
+
yield* this.tableScan(header.rightChild);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async indexCell(page, header, index) {
|
|
131
|
+
const offset = cellOffset(page, header, index);
|
|
132
|
+
const interior = header.type === INTERIOR_INDEX;
|
|
133
|
+
const [size, afterSize] = readVarint(page, interior ? offset + 4 : offset);
|
|
134
|
+
const values = decodeRecord(await this.payload(page, afterSize, size, true));
|
|
135
|
+
return { values, leftChild: interior ? readUint32(page, offset) : 0 };
|
|
136
|
+
}
|
|
137
|
+
async *indexScanFrom(root, low) {
|
|
138
|
+
const { page, header } = await this.pageAt(root);
|
|
139
|
+
let first = 0;
|
|
140
|
+
let high = header.cellCount;
|
|
141
|
+
while (first < high) {
|
|
142
|
+
const mid = (first + high) >> 1;
|
|
143
|
+
const cell = await this.indexCell(page, header, mid);
|
|
144
|
+
if (compareKey(cell.values, low) < 0) {
|
|
145
|
+
first = mid + 1;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
high = mid;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
for (let i = first; i < header.cellCount; i++) {
|
|
152
|
+
const cell = await this.indexCell(page, header, i);
|
|
153
|
+
if (header.type === INTERIOR_INDEX) {
|
|
154
|
+
yield* this.indexScanFrom(cell.leftChild, low);
|
|
155
|
+
}
|
|
156
|
+
yield cell.values;
|
|
157
|
+
}
|
|
158
|
+
if (header.type === INTERIOR_INDEX) {
|
|
159
|
+
yield* this.indexScanFrom(header.rightChild, low);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async indexSeekLE(root, key) {
|
|
163
|
+
let best;
|
|
164
|
+
let pageNumber = root;
|
|
165
|
+
for (;;) {
|
|
166
|
+
const { page, header } = await this.pageAt(pageNumber);
|
|
167
|
+
let low = 0;
|
|
168
|
+
let high = header.cellCount;
|
|
169
|
+
let child = 0;
|
|
170
|
+
while (low < high) {
|
|
171
|
+
const mid = (low + high) >> 1;
|
|
172
|
+
const cell = await this.indexCell(page, header, mid);
|
|
173
|
+
if (compareKey(cell.values, key) <= 0) {
|
|
174
|
+
best = cell.values;
|
|
175
|
+
low = mid + 1;
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
child = cell.leftChild;
|
|
179
|
+
high = mid;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (header.type === LEAF_INDEX) {
|
|
183
|
+
return best;
|
|
184
|
+
}
|
|
185
|
+
pageNumber = low === header.cellCount ? header.rightChild : child;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function compareKey(values, key) {
|
|
190
|
+
for (let i = 0; i < key.length; i++) {
|
|
191
|
+
const a = values[i];
|
|
192
|
+
const b = key[i];
|
|
193
|
+
if (typeof a !== 'number' || b === undefined) {
|
|
194
|
+
throw new Error('SQLite index key is not numeric');
|
|
195
|
+
}
|
|
196
|
+
if (a !== b) {
|
|
197
|
+
return a < b ? -1 : 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ByteSource } from '../filehandle.ts';
|
|
2
|
+
import { BTree } from './btree.ts';
|
|
3
|
+
import { Pager } from './pager.ts';
|
|
4
|
+
import type { PagerOptions } from './pager.ts';
|
|
5
|
+
import type { SqlValue } from './record.ts';
|
|
6
|
+
export interface SqliteObject {
|
|
7
|
+
type: string;
|
|
8
|
+
name: string;
|
|
9
|
+
tableName: string;
|
|
10
|
+
rootPage: number;
|
|
11
|
+
sql: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class SqliteDatabase {
|
|
14
|
+
readonly pager: Pager;
|
|
15
|
+
readonly btree: BTree;
|
|
16
|
+
readonly objects: Map<string, SqliteObject>;
|
|
17
|
+
private constructor();
|
|
18
|
+
static open(source: ByteSource, opts?: PagerOptions): Promise<SqliteDatabase>;
|
|
19
|
+
rootPage(name: string): number;
|
|
20
|
+
indexOn(tableName: string): number;
|
|
21
|
+
byRowid(table: string, rowid: number): Promise<SqlValue[] | undefined>;
|
|
22
|
+
scan(table: string): AsyncGenerator<{
|
|
23
|
+
rowid: number;
|
|
24
|
+
values: SqlValue[];
|
|
25
|
+
}, any, any>;
|
|
26
|
+
indexSeekLE(table: string, key: number[]): Promise<SqlValue[] | undefined>;
|
|
27
|
+
indexScanFrom(table: string, low: number[]): AsyncGenerator<SqlValue[], any, any>;
|
|
28
|
+
has(name: string): boolean;
|
|
29
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BTree } from "./btree.js";
|
|
2
|
+
import { Pager } from "./pager.js";
|
|
3
|
+
export class SqliteDatabase {
|
|
4
|
+
pager;
|
|
5
|
+
btree;
|
|
6
|
+
objects;
|
|
7
|
+
constructor(pager, btree, objects) {
|
|
8
|
+
this.pager = pager;
|
|
9
|
+
this.btree = btree;
|
|
10
|
+
this.objects = objects;
|
|
11
|
+
}
|
|
12
|
+
static async open(source, opts = {}) {
|
|
13
|
+
const { size } = await source.stat();
|
|
14
|
+
const firstBlock = await source.read(Math.min(opts.blockSize ?? 65536, size), 0);
|
|
15
|
+
const header = firstBlock.subarray(0, 100);
|
|
16
|
+
const magic = new TextDecoder().decode(header.subarray(0, 15));
|
|
17
|
+
if (magic !== 'SQLite format 3') {
|
|
18
|
+
throw new Error('Not a SQLite database');
|
|
19
|
+
}
|
|
20
|
+
const view = new DataView(header.buffer, header.byteOffset, header.byteLength);
|
|
21
|
+
const rawPageSize = view.getUint16(16);
|
|
22
|
+
const pageSize = rawPageSize === 1 ? 65536 : rawPageSize;
|
|
23
|
+
const reserved = header[20] ?? 0;
|
|
24
|
+
const encoding = view.getUint32(56);
|
|
25
|
+
if (encoding !== 1) {
|
|
26
|
+
throw new Error(`SQLite text encoding ${encoding} is not UTF-8`);
|
|
27
|
+
}
|
|
28
|
+
const pager = new Pager(source, pageSize, size, opts);
|
|
29
|
+
pager.seed(0, firstBlock);
|
|
30
|
+
pager.fetches += 1;
|
|
31
|
+
pager.bytesFetched += firstBlock.length;
|
|
32
|
+
const btree = new BTree(pager, reserved);
|
|
33
|
+
const objects = new Map();
|
|
34
|
+
for await (const { values } of btree.tableScan(1)) {
|
|
35
|
+
const [type, name, tableName, rootPage, sql] = values;
|
|
36
|
+
if (typeof type === 'string' && typeof name === 'string' && typeof tableName === 'string' && typeof rootPage === 'number') {
|
|
37
|
+
objects.set(name, { type, name, tableName, rootPage, sql: typeof sql === 'string' ? sql : '' });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return new SqliteDatabase(pager, btree, objects);
|
|
41
|
+
}
|
|
42
|
+
rootPage(name) {
|
|
43
|
+
const object = this.objects.get(name);
|
|
44
|
+
if (!object) {
|
|
45
|
+
throw new Error(`SQLite database has no object named ${name}`);
|
|
46
|
+
}
|
|
47
|
+
return object.rootPage;
|
|
48
|
+
}
|
|
49
|
+
indexOn(tableName) {
|
|
50
|
+
const index = [...this.objects.values()].find(o => o.type === 'index' && o.tableName === tableName);
|
|
51
|
+
if (!index) {
|
|
52
|
+
throw new Error(`SQLite table ${tableName} has no index`);
|
|
53
|
+
}
|
|
54
|
+
return index.rootPage;
|
|
55
|
+
}
|
|
56
|
+
byRowid(table, rowid) {
|
|
57
|
+
return this.btree.tableRowid(this.rootPage(table), rowid);
|
|
58
|
+
}
|
|
59
|
+
scan(table) {
|
|
60
|
+
return this.btree.tableScan(this.rootPage(table));
|
|
61
|
+
}
|
|
62
|
+
indexSeekLE(table, key) {
|
|
63
|
+
return this.btree.indexSeekLE(this.indexOn(table), key);
|
|
64
|
+
}
|
|
65
|
+
indexScanFrom(table, low) {
|
|
66
|
+
return this.btree.indexScanFrom(this.indexOn(table), low);
|
|
67
|
+
}
|
|
68
|
+
has(name) {
|
|
69
|
+
return this.objects.has(name);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ByteSource } from '../filehandle.ts';
|
|
2
|
+
export interface PagerOptions {
|
|
3
|
+
blockSize?: number;
|
|
4
|
+
maxBlocks?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class Pager {
|
|
7
|
+
private source;
|
|
8
|
+
readonly pageSize: number;
|
|
9
|
+
private fileSize;
|
|
10
|
+
private blocks;
|
|
11
|
+
private readonly blockSize;
|
|
12
|
+
private readonly maxBlocks;
|
|
13
|
+
bytesFetched: number;
|
|
14
|
+
fetches: number;
|
|
15
|
+
constructor(source: ByteSource, pageSize: number, fileSize: number, opts?: PagerOptions);
|
|
16
|
+
seed(index: number, bytes: Uint8Array): void;
|
|
17
|
+
private block;
|
|
18
|
+
page(pageNumber: number): Promise<Uint8Array<ArrayBufferLike>>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class Pager {
|
|
2
|
+
source;
|
|
3
|
+
pageSize;
|
|
4
|
+
fileSize;
|
|
5
|
+
blocks = new Map();
|
|
6
|
+
blockSize;
|
|
7
|
+
maxBlocks;
|
|
8
|
+
bytesFetched = 0;
|
|
9
|
+
fetches = 0;
|
|
10
|
+
constructor(source, pageSize, fileSize, opts = {}) {
|
|
11
|
+
this.source = source;
|
|
12
|
+
this.pageSize = pageSize;
|
|
13
|
+
this.fileSize = fileSize;
|
|
14
|
+
const requested = opts.blockSize ?? 65536;
|
|
15
|
+
this.blockSize = Math.max(pageSize, Math.ceil(requested / pageSize) * pageSize);
|
|
16
|
+
this.maxBlocks = opts.maxBlocks ?? 256;
|
|
17
|
+
}
|
|
18
|
+
seed(index, bytes) {
|
|
19
|
+
if (bytes.length === Math.min(this.blockSize, this.fileSize - index * this.blockSize)) {
|
|
20
|
+
this.blocks.set(index, Promise.resolve(bytes));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
block(index) {
|
|
24
|
+
const cached = this.blocks.get(index);
|
|
25
|
+
if (cached) {
|
|
26
|
+
this.blocks.delete(index);
|
|
27
|
+
this.blocks.set(index, cached);
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
const start = index * this.blockSize;
|
|
31
|
+
const length = Math.min(this.blockSize, this.fileSize - start);
|
|
32
|
+
const pending = this.source.read(length, start).then(bytes => {
|
|
33
|
+
this.bytesFetched += bytes.length;
|
|
34
|
+
return bytes;
|
|
35
|
+
});
|
|
36
|
+
this.fetches += 1;
|
|
37
|
+
this.blocks.set(index, pending);
|
|
38
|
+
if (this.blocks.size > this.maxBlocks) {
|
|
39
|
+
const oldest = this.blocks.keys().next().value;
|
|
40
|
+
if (oldest !== undefined) {
|
|
41
|
+
this.blocks.delete(oldest);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return pending;
|
|
45
|
+
}
|
|
46
|
+
async page(pageNumber) {
|
|
47
|
+
const offset = (pageNumber - 1) * this.pageSize;
|
|
48
|
+
if (pageNumber < 1 || offset + this.pageSize > this.fileSize) {
|
|
49
|
+
throw new Error(`SQLite page ${pageNumber} is outside the file`);
|
|
50
|
+
}
|
|
51
|
+
const bytes = await this.block(Math.floor(offset / this.blockSize));
|
|
52
|
+
const within = offset % this.blockSize;
|
|
53
|
+
return bytes.subarray(within, within + this.pageSize);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export function readVarint(bytes, offset) {
|
|
2
|
+
let value = 0;
|
|
3
|
+
for (let i = 0; i < 8; i++) {
|
|
4
|
+
const byte = bytes[offset + i];
|
|
5
|
+
if (byte === undefined) {
|
|
6
|
+
throw new Error('SQLite varint runs past the end of the buffer');
|
|
7
|
+
}
|
|
8
|
+
value = value * 128 + (byte & 0x7f);
|
|
9
|
+
if ((byte & 0x80) === 0) {
|
|
10
|
+
return [value, offset + i + 1];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const last = bytes[offset + 8];
|
|
14
|
+
if (last === undefined) {
|
|
15
|
+
throw new Error('SQLite varint runs past the end of the buffer');
|
|
16
|
+
}
|
|
17
|
+
value = value * 256 + last;
|
|
18
|
+
if (!Number.isSafeInteger(value)) {
|
|
19
|
+
throw new Error('SQLite varint exceeds the safe integer range');
|
|
20
|
+
}
|
|
21
|
+
return [value, offset + 9];
|
|
22
|
+
}
|
|
23
|
+
function readSignedInt(view, offset, width) {
|
|
24
|
+
switch (width) {
|
|
25
|
+
case 1:
|
|
26
|
+
return view.getInt8(offset);
|
|
27
|
+
case 2:
|
|
28
|
+
return view.getInt16(offset);
|
|
29
|
+
case 3:
|
|
30
|
+
return (view.getInt8(offset) << 16) | view.getUint16(offset + 1);
|
|
31
|
+
case 4:
|
|
32
|
+
return view.getInt32(offset);
|
|
33
|
+
case 6:
|
|
34
|
+
return view.getInt16(offset) * 4294967296 + view.getUint32(offset + 2);
|
|
35
|
+
default: {
|
|
36
|
+
const big = view.getBigInt64(offset);
|
|
37
|
+
if (big > BigInt(Number.MAX_SAFE_INTEGER) || big < BigInt(Number.MIN_SAFE_INTEGER)) {
|
|
38
|
+
throw new Error(`SQLite integer ${big} exceeds the safe integer range`);
|
|
39
|
+
}
|
|
40
|
+
return Number(big);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const decoder = new TextDecoder();
|
|
45
|
+
export function decodeRecord(payload) {
|
|
46
|
+
const [headerSize, firstType] = readVarint(payload, 0);
|
|
47
|
+
const types = [];
|
|
48
|
+
let offset = firstType;
|
|
49
|
+
while (offset < headerSize) {
|
|
50
|
+
const [type, next] = readVarint(payload, offset);
|
|
51
|
+
types.push(type);
|
|
52
|
+
offset = next;
|
|
53
|
+
}
|
|
54
|
+
const view = new DataView(payload.buffer, payload.byteOffset, payload.byteLength);
|
|
55
|
+
let body = headerSize;
|
|
56
|
+
return types.map(type => {
|
|
57
|
+
if (type === 0) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
if (type >= 1 && type <= 6) {
|
|
61
|
+
const width = type <= 4 ? type : type === 5 ? 6 : 8;
|
|
62
|
+
const value = readSignedInt(view, body, width);
|
|
63
|
+
body += width;
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
if (type === 7) {
|
|
67
|
+
const value = view.getFloat64(body);
|
|
68
|
+
body += 8;
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
if (type === 8) {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
if (type === 9) {
|
|
75
|
+
return 1;
|
|
76
|
+
}
|
|
77
|
+
if (type >= 12) {
|
|
78
|
+
const length = (type - 12) >> 1;
|
|
79
|
+
const slice = payload.subarray(body, body + length);
|
|
80
|
+
body += length;
|
|
81
|
+
return type % 2 === 0 ? slice : decoder.decode(slice);
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`SQLite serial type ${type} is reserved`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { GBZBase, PathName } from './db.ts';
|
|
2
|
+
import type { Pos } from './gbwt/record.ts';
|
|
3
|
+
import type { Orientation } from './gbwt/node.ts';
|
|
4
|
+
export type HaplotypeOutput = 'all' | 'distinct' | 'reference-only' | 'none';
|
|
5
|
+
export interface PathPosition {
|
|
6
|
+
seqOffset: number;
|
|
7
|
+
handle: number;
|
|
8
|
+
nodeOffset: number;
|
|
9
|
+
gbwtOffset: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ReferencePath {
|
|
12
|
+
position: PathPosition;
|
|
13
|
+
name: PathName;
|
|
14
|
+
handle: number;
|
|
15
|
+
}
|
|
16
|
+
export interface PathIdentity {
|
|
17
|
+
pathHandle: number;
|
|
18
|
+
name: PathName;
|
|
19
|
+
orientation: Orientation;
|
|
20
|
+
hapStart: number;
|
|
21
|
+
hapEnd: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SubgraphPath {
|
|
24
|
+
name: string;
|
|
25
|
+
weight?: number;
|
|
26
|
+
cigar?: string;
|
|
27
|
+
path: {
|
|
28
|
+
id: string;
|
|
29
|
+
is_reverse: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
export interface SubgraphJson {
|
|
33
|
+
nodes: {
|
|
34
|
+
id: string;
|
|
35
|
+
sequence: string;
|
|
36
|
+
}[];
|
|
37
|
+
edges: {
|
|
38
|
+
from: string;
|
|
39
|
+
from_is_reverse: boolean;
|
|
40
|
+
to: string;
|
|
41
|
+
to_is_reverse: boolean;
|
|
42
|
+
}[];
|
|
43
|
+
paths: SubgraphPath[];
|
|
44
|
+
}
|
|
45
|
+
export interface HaplotypeAlignment {
|
|
46
|
+
pathHandle: number | undefined;
|
|
47
|
+
name: PathName | undefined;
|
|
48
|
+
strand: '+' | '-';
|
|
49
|
+
hapStart: number | undefined;
|
|
50
|
+
hapEnd: number | undefined;
|
|
51
|
+
refStart: number;
|
|
52
|
+
refEnd: number;
|
|
53
|
+
cigar: string;
|
|
54
|
+
weight: number | undefined;
|
|
55
|
+
path: number[];
|
|
56
|
+
start: Pos;
|
|
57
|
+
}
|
|
58
|
+
export interface ToJsonOptions {
|
|
59
|
+
names?: 'anonymous' | 'resolved';
|
|
60
|
+
}
|
|
61
|
+
export declare class Subgraph {
|
|
62
|
+
private db;
|
|
63
|
+
private records;
|
|
64
|
+
private paths;
|
|
65
|
+
private refId;
|
|
66
|
+
private refPath;
|
|
67
|
+
private refHandle;
|
|
68
|
+
private refInterval;
|
|
69
|
+
private refIndexCache;
|
|
70
|
+
private refPrefixCache;
|
|
71
|
+
limit: number | undefined;
|
|
72
|
+
readonly stats: {
|
|
73
|
+
orderedAlignments: number;
|
|
74
|
+
lcsAlignments: number;
|
|
75
|
+
identificationSteps: number;
|
|
76
|
+
identificationFetches: number;
|
|
77
|
+
};
|
|
78
|
+
constructor(db: GBZBase);
|
|
79
|
+
get nodeCount(): number;
|
|
80
|
+
get pathCount(): number;
|
|
81
|
+
get referenceInterval(): {
|
|
82
|
+
name: PathName;
|
|
83
|
+
start: number;
|
|
84
|
+
end: number;
|
|
85
|
+
} | undefined;
|
|
86
|
+
hasNode(id: number): boolean;
|
|
87
|
+
hasHandle(handle: number): boolean;
|
|
88
|
+
private record;
|
|
89
|
+
private sortedHandles;
|
|
90
|
+
private addNode;
|
|
91
|
+
private ensureNode;
|
|
92
|
+
private clearPaths;
|
|
93
|
+
pathPosition(query: PathName): Promise<ReferencePath>;
|
|
94
|
+
private findPathPosition;
|
|
95
|
+
aroundPosition(handle: number, nodeOffset: number, context: number): Promise<{
|
|
96
|
+
inserted: number;
|
|
97
|
+
removed: number;
|
|
98
|
+
}>;
|
|
99
|
+
aroundInterval(start: PathPosition, len: number, context: number): Promise<{
|
|
100
|
+
inserted: number;
|
|
101
|
+
removed: number;
|
|
102
|
+
}>;
|
|
103
|
+
aroundNodes(nodes: Iterable<number>, context: number): Promise<{
|
|
104
|
+
inserted: number;
|
|
105
|
+
removed: number;
|
|
106
|
+
}>;
|
|
107
|
+
private insertContext;
|
|
108
|
+
extractPaths(reference: ReferencePath | undefined, output: HaplotypeOutput): void;
|
|
109
|
+
private distinctPaths;
|
|
110
|
+
identifyPaths(): Promise<void>;
|
|
111
|
+
private refIndex;
|
|
112
|
+
private refPrefix;
|
|
113
|
+
private orderedMatches;
|
|
114
|
+
private pathLen;
|
|
115
|
+
private prefixMatches;
|
|
116
|
+
private suffixMatches;
|
|
117
|
+
private align;
|
|
118
|
+
private edits;
|
|
119
|
+
alignToRef(pathIndex: number): string | undefined;
|
|
120
|
+
alignments(): HaplotypeAlignment[];
|
|
121
|
+
toJSON(cigar: boolean, opts?: ToJsonOptions): SubgraphJson;
|
|
122
|
+
}
|