@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
@@ -0,0 +1,260 @@
1
+ import { decodeRecord, readVarint } from './record.ts'
2
+
3
+ import type { Pager } from './pager.ts'
4
+ import type { SqlValue } from './record.ts'
5
+
6
+ const INTERIOR_INDEX = 0x02
7
+ const INTERIOR_TABLE = 0x05
8
+ const LEAF_INDEX = 0x0a
9
+ const LEAF_TABLE = 0x0d
10
+
11
+ interface PageHeader {
12
+ type: number
13
+ cellCount: number
14
+ rightChild: number
15
+ cellPointers: number
16
+ }
17
+
18
+ function readHeader(page: Uint8Array, start: number): PageHeader {
19
+ const view = new DataView(page.buffer, page.byteOffset, page.byteLength)
20
+ const type = page[start]
21
+ if (
22
+ type !== INTERIOR_INDEX &&
23
+ type !== INTERIOR_TABLE &&
24
+ type !== LEAF_INDEX &&
25
+ type !== LEAF_TABLE
26
+ ) {
27
+ throw new Error(`SQLite page has unknown b-tree type ${type}`)
28
+ }
29
+ const interior = type === INTERIOR_INDEX || type === INTERIOR_TABLE
30
+ return {
31
+ type,
32
+ cellCount: view.getUint16(start + 3),
33
+ rightChild: interior ? view.getUint32(start + 8) : 0,
34
+ cellPointers: start + (interior ? 12 : 8),
35
+ }
36
+ }
37
+
38
+ function cellOffset(page: Uint8Array, header: PageHeader, index: number) {
39
+ const view = new DataView(page.buffer, page.byteOffset, page.byteLength)
40
+ return view.getUint16(header.cellPointers + 2 * index)
41
+ }
42
+
43
+ function readUint32(page: Uint8Array, offset: number) {
44
+ const view = new DataView(page.buffer, page.byteOffset, page.byteLength)
45
+ return view.getUint32(offset)
46
+ }
47
+
48
+ export class BTree {
49
+ private readonly usable: number
50
+ private pager: Pager
51
+
52
+ constructor(pager: Pager, reservedBytes: number) {
53
+ this.pager = pager
54
+ this.usable = pager.pageSize - reservedBytes
55
+ }
56
+
57
+ private localPayloadSize(total: number, isIndex: boolean) {
58
+ const usable = this.usable
59
+ const maxLocal = isIndex
60
+ ? Math.floor(((usable - 12) * 64) / 255) - 23
61
+ : usable - 35
62
+ if (total <= maxLocal) {
63
+ return total
64
+ }
65
+ const minLocal = Math.floor(((usable - 12) * 32) / 255) - 23
66
+ const spill = minLocal + ((total - minLocal) % (usable - 4))
67
+ return spill <= maxLocal ? spill : minLocal
68
+ }
69
+
70
+ private async payload(
71
+ page: Uint8Array,
72
+ offset: number,
73
+ total: number,
74
+ isIndex: boolean,
75
+ ) {
76
+ const local = this.localPayloadSize(total, isIndex)
77
+ if (local === total) {
78
+ return page.subarray(offset, offset + total)
79
+ }
80
+ const result = new Uint8Array(total)
81
+ result.set(page.subarray(offset, offset + local), 0)
82
+ let filled = local
83
+ let next = readUint32(page, offset + local)
84
+ while (next !== 0 && filled < total) {
85
+ const overflow = await this.pager.page(next)
86
+ const chunk = overflow.subarray(
87
+ 4,
88
+ 4 + Math.min(this.usable - 4, total - filled),
89
+ )
90
+ result.set(chunk, filled)
91
+ filled += chunk.length
92
+ next = readUint32(overflow, 0)
93
+ }
94
+ if (filled !== total) {
95
+ throw new Error(
96
+ 'SQLite overflow chain ended before the payload was complete',
97
+ )
98
+ }
99
+ return result
100
+ }
101
+
102
+ private async pageAt(pageNumber: number) {
103
+ const page = await this.pager.page(pageNumber)
104
+ return { page, header: readHeader(page, pageNumber === 1 ? 100 : 0) }
105
+ }
106
+
107
+ async tableRowid(
108
+ root: number,
109
+ rowid: number,
110
+ ): Promise<SqlValue[] | undefined> {
111
+ let pageNumber = root
112
+ for (;;) {
113
+ const { page, header } = await this.pageAt(pageNumber)
114
+ if (header.type === LEAF_TABLE) {
115
+ let low = 0
116
+ let high = header.cellCount
117
+ while (low < high) {
118
+ const mid = (low + high) >> 1
119
+ const offset = cellOffset(page, header, mid)
120
+ const [size, afterSize] = readVarint(page, offset)
121
+ const [key, afterKey] = readVarint(page, afterSize)
122
+ if (key === rowid) {
123
+ return decodeRecord(await this.payload(page, afterKey, size, false))
124
+ }
125
+ if (key < rowid) {
126
+ low = mid + 1
127
+ } else {
128
+ high = mid
129
+ }
130
+ }
131
+ return undefined
132
+ }
133
+ if (header.type !== INTERIOR_TABLE) {
134
+ throw new Error('SQLite table b-tree contains an index page')
135
+ }
136
+ let low = 0
137
+ let high = header.cellCount
138
+ while (low < high) {
139
+ const mid = (low + high) >> 1
140
+ const offset = cellOffset(page, header, mid)
141
+ const [key] = readVarint(page, offset + 4)
142
+ if (key < rowid) {
143
+ low = mid + 1
144
+ } else {
145
+ high = mid
146
+ }
147
+ }
148
+ pageNumber =
149
+ low === header.cellCount
150
+ ? header.rightChild
151
+ : readUint32(page, cellOffset(page, header, low))
152
+ }
153
+ }
154
+
155
+ async *tableScan(
156
+ root: number,
157
+ ): AsyncGenerator<{ rowid: number; values: SqlValue[] }> {
158
+ const { page, header } = await this.pageAt(root)
159
+ if (header.type === LEAF_TABLE) {
160
+ for (let i = 0; i < header.cellCount; i++) {
161
+ const offset = cellOffset(page, header, i)
162
+ const [size, afterSize] = readVarint(page, offset)
163
+ const [rowid, afterKey] = readVarint(page, afterSize)
164
+ yield {
165
+ rowid,
166
+ values: decodeRecord(await this.payload(page, afterKey, size, false)),
167
+ }
168
+ }
169
+ } else {
170
+ const children: number[] = []
171
+ for (let i = 0; i < header.cellCount; i++) {
172
+ children.push(readUint32(page, cellOffset(page, header, i)))
173
+ }
174
+ children.push(header.rightChild)
175
+ this.pager.prefetch(children)
176
+ for (const child of children) {
177
+ yield* this.tableScan(child)
178
+ }
179
+ }
180
+ }
181
+
182
+ private async indexCell(page: Uint8Array, header: PageHeader, index: number) {
183
+ const offset = cellOffset(page, header, index)
184
+ const interior = header.type === INTERIOR_INDEX
185
+ const [size, afterSize] = readVarint(page, interior ? offset + 4 : offset)
186
+ const values = decodeRecord(await this.payload(page, afterSize, size, true))
187
+ return { values, leftChild: interior ? readUint32(page, offset) : 0 }
188
+ }
189
+
190
+ async *indexScanFrom(
191
+ root: number,
192
+ low: number[],
193
+ ): AsyncGenerator<SqlValue[]> {
194
+ const { page, header } = await this.pageAt(root)
195
+ let first = 0
196
+ let high = header.cellCount
197
+ while (first < high) {
198
+ const mid = (first + high) >> 1
199
+ const cell = await this.indexCell(page, header, mid)
200
+ if (compareKey(cell.values, low) < 0) {
201
+ first = mid + 1
202
+ } else {
203
+ high = mid
204
+ }
205
+ }
206
+ for (let i = first; i < header.cellCount; i++) {
207
+ const cell = await this.indexCell(page, header, i)
208
+ if (header.type === INTERIOR_INDEX) {
209
+ yield* this.indexScanFrom(cell.leftChild, low)
210
+ }
211
+ yield cell.values
212
+ }
213
+ if (header.type === INTERIOR_INDEX) {
214
+ yield* this.indexScanFrom(header.rightChild, low)
215
+ }
216
+ }
217
+
218
+ async indexSeekLE(
219
+ root: number,
220
+ key: number[],
221
+ ): Promise<SqlValue[] | undefined> {
222
+ let best: SqlValue[] | undefined
223
+ let pageNumber = root
224
+ for (;;) {
225
+ const { page, header } = await this.pageAt(pageNumber)
226
+ let low = 0
227
+ let high = header.cellCount
228
+ let child = 0
229
+ while (low < high) {
230
+ const mid = (low + high) >> 1
231
+ const cell = await this.indexCell(page, header, mid)
232
+ if (compareKey(cell.values, key) <= 0) {
233
+ best = cell.values
234
+ low = mid + 1
235
+ } else {
236
+ child = cell.leftChild
237
+ high = mid
238
+ }
239
+ }
240
+ if (header.type === LEAF_INDEX) {
241
+ return best
242
+ }
243
+ pageNumber = low === header.cellCount ? header.rightChild : child
244
+ }
245
+ }
246
+ }
247
+
248
+ function compareKey(values: SqlValue[], key: number[]) {
249
+ for (let i = 0; i < key.length; i++) {
250
+ const a = values[i]
251
+ const b = key[i]
252
+ if (typeof a !== 'number' || b === undefined) {
253
+ throw new Error('SQLite index key is not numeric')
254
+ }
255
+ if (a !== b) {
256
+ return a < b ? -1 : 1
257
+ }
258
+ }
259
+ return 0
260
+ }
@@ -0,0 +1,117 @@
1
+ import { BTree } from './btree.ts'
2
+ import { Pager } from './pager.ts'
3
+
4
+ import type { ByteSource } from '../filehandle.ts'
5
+ import type { PagerOptions } from './pager.ts'
6
+ import type { SqlValue } from './record.ts'
7
+
8
+ export interface SqliteObject {
9
+ type: string
10
+ name: string
11
+ tableName: string
12
+ rootPage: number
13
+ sql: string
14
+ }
15
+
16
+ export class SqliteDatabase {
17
+ readonly pager: Pager
18
+ readonly btree: BTree
19
+ readonly objects: Map<string, SqliteObject>
20
+
21
+ private constructor(
22
+ pager: Pager,
23
+ btree: BTree,
24
+ objects: Map<string, SqliteObject>,
25
+ ) {
26
+ this.pager = pager
27
+ this.btree = btree
28
+ this.objects = objects
29
+ }
30
+
31
+ static async open(source: ByteSource, opts: PagerOptions = {}) {
32
+ const { size } = await source.stat()
33
+ const firstBlock = await source.read(
34
+ Math.min(opts.blockSize ?? 65536, size),
35
+ 0,
36
+ )
37
+ const header = firstBlock.subarray(0, 100)
38
+ const magic = new TextDecoder().decode(header.subarray(0, 15))
39
+ if (magic !== 'SQLite format 3') {
40
+ throw new Error('Not a SQLite database')
41
+ }
42
+ const view = new DataView(
43
+ header.buffer,
44
+ header.byteOffset,
45
+ header.byteLength,
46
+ )
47
+ const rawPageSize = view.getUint16(16)
48
+ const pageSize = rawPageSize === 1 ? 65536 : rawPageSize
49
+ const reserved = header[20] ?? 0
50
+ const encoding = view.getUint32(56)
51
+ if (encoding !== 1) {
52
+ throw new Error(`SQLite text encoding ${encoding} is not UTF-8`)
53
+ }
54
+ const pager = new Pager(source, pageSize, size, opts)
55
+ pager.seed(0, firstBlock)
56
+ pager.fetches += 1
57
+ pager.bytesFetched += firstBlock.length
58
+ const btree = new BTree(pager, reserved)
59
+ const objects = new Map<string, SqliteObject>()
60
+ for await (const { values } of btree.tableScan(1)) {
61
+ const [type, name, tableName, rootPage, sql] = values
62
+ if (
63
+ typeof type === 'string' &&
64
+ typeof name === 'string' &&
65
+ typeof tableName === 'string' &&
66
+ typeof rootPage === 'number'
67
+ ) {
68
+ objects.set(name, {
69
+ type,
70
+ name,
71
+ tableName,
72
+ rootPage,
73
+ sql: typeof sql === 'string' ? sql : '',
74
+ })
75
+ }
76
+ }
77
+ return new SqliteDatabase(pager, btree, objects)
78
+ }
79
+
80
+ rootPage(name: string) {
81
+ const object = this.objects.get(name)
82
+ if (!object) {
83
+ throw new Error(`SQLite database has no object named ${name}`)
84
+ }
85
+ return object.rootPage
86
+ }
87
+
88
+ indexOn(tableName: string) {
89
+ const index = [...this.objects.values()].find(
90
+ o => o.type === 'index' && o.tableName === tableName,
91
+ )
92
+ if (!index) {
93
+ throw new Error(`SQLite table ${tableName} has no index`)
94
+ }
95
+ return index.rootPage
96
+ }
97
+
98
+ byRowid(table: string, rowid: number) {
99
+ return this.btree.tableRowid(this.rootPage(table), rowid)
100
+ }
101
+
102
+ scan(table: string) {
103
+ return this.btree.tableScan(this.rootPage(table))
104
+ }
105
+
106
+ indexSeekLE(table: string, key: number[]): Promise<SqlValue[] | undefined> {
107
+ return this.btree.indexSeekLE(this.indexOn(table), key)
108
+ }
109
+
110
+ indexScanFrom(table: string, low: number[]) {
111
+ return this.btree.indexScanFrom(this.indexOn(table), low)
112
+ }
113
+
114
+ has(name: string) {
115
+ return this.objects.has(name)
116
+ }
117
+ }
@@ -0,0 +1,148 @@
1
+ import type { ByteSource } from '../filehandle.ts'
2
+
3
+ export interface PagerOptions {
4
+ blockSize?: number
5
+ maxBlocks?: number
6
+ }
7
+
8
+ export class Pager {
9
+ private blocks = new Map<number, Promise<Uint8Array>>()
10
+ private readonly blockSize: number
11
+ private readonly maxBlocks: number
12
+ bytesFetched = 0
13
+ fetches = 0
14
+
15
+ private source: ByteSource
16
+ readonly pageSize: number
17
+ private fileSize: number
18
+
19
+ constructor(
20
+ source: ByteSource,
21
+ pageSize: number,
22
+ fileSize: number,
23
+ opts: PagerOptions = {},
24
+ ) {
25
+ this.source = source
26
+ this.pageSize = pageSize
27
+ this.fileSize = fileSize
28
+ const requested = opts.blockSize ?? 65536
29
+ this.blockSize = Math.max(
30
+ pageSize,
31
+ Math.ceil(requested / pageSize) * pageSize,
32
+ )
33
+ this.maxBlocks = opts.maxBlocks ?? 256
34
+ }
35
+
36
+ private async read(length: number, start: number) {
37
+ let attempt = 0
38
+ for (;;) {
39
+ try {
40
+ const bytes = await this.source.read(length, start)
41
+ this.bytesFetched += bytes.length
42
+ return bytes
43
+ } catch (error) {
44
+ attempt += 1
45
+ if (attempt >= 3) {
46
+ throw error
47
+ }
48
+ await new Promise(resolve => setTimeout(resolve, 200 * attempt))
49
+ }
50
+ }
51
+ }
52
+
53
+ seed(index: number, bytes: Uint8Array) {
54
+ if (
55
+ bytes.length ===
56
+ Math.min(this.blockSize, this.fileSize - index * this.blockSize)
57
+ ) {
58
+ this.blocks.set(index, Promise.resolve(bytes))
59
+ }
60
+ }
61
+
62
+ private block(index: number) {
63
+ const cached = this.blocks.get(index)
64
+ if (cached) {
65
+ this.blocks.delete(index)
66
+ this.blocks.set(index, cached)
67
+ return cached
68
+ }
69
+ const start = index * this.blockSize
70
+ const length = Math.min(this.blockSize, this.fileSize - start)
71
+ const pending = this.read(length, start)
72
+ this.fetches += 1
73
+ this.blocks.set(index, pending)
74
+ this.forgetOnFailure(pending, [index])
75
+ this.evict()
76
+ return pending
77
+ }
78
+
79
+ prefetch(pageNumbers: number[]) {
80
+ const wanted = [
81
+ ...new Set(
82
+ pageNumbers.map(pageNumber =>
83
+ Math.floor(((pageNumber - 1) * this.pageSize) / this.blockSize),
84
+ ),
85
+ ),
86
+ ]
87
+ .filter(index => !this.blocks.has(index))
88
+ .sort((a, b) => a - b)
89
+ let runStart = 0
90
+ while (runStart < wanted.length) {
91
+ let runEnd = runStart + 1
92
+ while (
93
+ runEnd < wanted.length &&
94
+ wanted[runEnd] === wanted[runEnd - 1]! + 1
95
+ ) {
96
+ runEnd += 1
97
+ }
98
+ this.fetchRun(wanted[runStart]!, runEnd - runStart)
99
+ runStart = runEnd
100
+ }
101
+ }
102
+
103
+ private fetchRun(firstIndex: number, count: number) {
104
+ const start = firstIndex * this.blockSize
105
+ const length = Math.min(count * this.blockSize, this.fileSize - start)
106
+ const pending = this.read(length, start)
107
+ this.fetches += 1
108
+ const indexes: number[] = []
109
+ for (let i = 0; i < count; i++) {
110
+ const within = i * this.blockSize
111
+ indexes.push(firstIndex + i)
112
+ this.blocks.set(
113
+ firstIndex + i,
114
+ pending.then(bytes => bytes.subarray(within, within + this.blockSize)),
115
+ )
116
+ }
117
+ this.forgetOnFailure(pending, indexes)
118
+ this.evict()
119
+ }
120
+
121
+ private forgetOnFailure(pending: Promise<Uint8Array>, indexes: number[]) {
122
+ pending.catch(() => {
123
+ for (const index of indexes) {
124
+ this.blocks.delete(index)
125
+ }
126
+ })
127
+ }
128
+
129
+ private evict() {
130
+ while (this.blocks.size > this.maxBlocks) {
131
+ const oldest = this.blocks.keys().next().value
132
+ if (oldest === undefined) {
133
+ break
134
+ }
135
+ this.blocks.delete(oldest)
136
+ }
137
+ }
138
+
139
+ async page(pageNumber: number) {
140
+ const offset = (pageNumber - 1) * this.pageSize
141
+ if (pageNumber < 1 || offset + this.pageSize > this.fileSize) {
142
+ throw new Error(`SQLite page ${pageNumber} is outside the file`)
143
+ }
144
+ const bytes = await this.block(Math.floor(offset / this.blockSize))
145
+ const within = offset % this.blockSize
146
+ return bytes.subarray(within, within + this.pageSize)
147
+ }
148
+ }
@@ -0,0 +1,100 @@
1
+ export type SqlValue = null | number | string | Uint8Array
2
+
3
+ export function readVarint(
4
+ bytes: Uint8Array,
5
+ offset: number,
6
+ ): [value: number, next: number] {
7
+ let value = 0
8
+ for (let i = 0; i < 8; i++) {
9
+ const byte = bytes[offset + i]
10
+ if (byte === undefined) {
11
+ throw new Error('SQLite varint runs past the end of the buffer')
12
+ }
13
+ value = value * 128 + (byte & 0x7f)
14
+ if ((byte & 0x80) === 0) {
15
+ return [value, offset + i + 1]
16
+ }
17
+ }
18
+ const last = bytes[offset + 8]
19
+ if (last === undefined) {
20
+ throw new Error('SQLite varint runs past the end of the buffer')
21
+ }
22
+ value = value * 256 + last
23
+ if (!Number.isSafeInteger(value)) {
24
+ throw new Error('SQLite varint exceeds the safe integer range')
25
+ }
26
+ return [value, offset + 9]
27
+ }
28
+
29
+ function readSignedInt(view: DataView, offset: number, width: number) {
30
+ switch (width) {
31
+ case 1:
32
+ return view.getInt8(offset)
33
+ case 2:
34
+ return view.getInt16(offset)
35
+ case 3:
36
+ return (view.getInt8(offset) << 16) | view.getUint16(offset + 1)
37
+ case 4:
38
+ return view.getInt32(offset)
39
+ case 6:
40
+ return view.getInt16(offset) * 4294967296 + view.getUint32(offset + 2)
41
+ default: {
42
+ const big = view.getBigInt64(offset)
43
+ if (
44
+ big > BigInt(Number.MAX_SAFE_INTEGER) ||
45
+ big < BigInt(Number.MIN_SAFE_INTEGER)
46
+ ) {
47
+ throw new Error(`SQLite integer ${big} exceeds the safe integer range`)
48
+ }
49
+ return Number(big)
50
+ }
51
+ }
52
+ }
53
+
54
+ const decoder = new TextDecoder()
55
+
56
+ export function decodeRecord(payload: Uint8Array): SqlValue[] {
57
+ const [headerSize, firstType] = readVarint(payload, 0)
58
+ const types: number[] = []
59
+ let offset = firstType
60
+ while (offset < headerSize) {
61
+ const [type, next] = readVarint(payload, offset)
62
+ types.push(type)
63
+ offset = next
64
+ }
65
+ const view = new DataView(
66
+ payload.buffer,
67
+ payload.byteOffset,
68
+ payload.byteLength,
69
+ )
70
+ let body = headerSize
71
+ return types.map(type => {
72
+ if (type === 0) {
73
+ return null
74
+ }
75
+ if (type >= 1 && type <= 6) {
76
+ const width = type <= 4 ? type : type === 5 ? 6 : 8
77
+ const value = readSignedInt(view, body, width)
78
+ body += width
79
+ return value
80
+ }
81
+ if (type === 7) {
82
+ const value = view.getFloat64(body)
83
+ body += 8
84
+ return value
85
+ }
86
+ if (type === 8) {
87
+ return 0
88
+ }
89
+ if (type === 9) {
90
+ return 1
91
+ }
92
+ if (type >= 12) {
93
+ const length = (type - 12) >> 1
94
+ const slice = payload.subarray(body, body + length)
95
+ body += length
96
+ return type % 2 === 0 ? slice : decoder.decode(slice)
97
+ }
98
+ throw new Error(`SQLite serial type ${type} is reserved`)
99
+ })
100
+ }