chubakabra 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.
- package/README.md +1 -0
- package/index.d.ts +69 -0
- package/package.json +19 -0
- package/src/browser/index.js +196 -0
- package/src/node/index.js +146 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Example package
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export type ByteRange = [number, number]
|
|
2
|
+
|
|
3
|
+
export interface KvrHeader {
|
|
4
|
+
keySize: number
|
|
5
|
+
indexCount: number
|
|
6
|
+
indexOffset: number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface KvrIndexEntry {
|
|
10
|
+
key: Uint8Array
|
|
11
|
+
range: ByteRange
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface KvrIndex {
|
|
15
|
+
keySize: number
|
|
16
|
+
entries: KvrIndexEntry[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ElementCodec<T = any> {
|
|
20
|
+
size: number
|
|
21
|
+
write(buf: Uint8Array, value: T, offset: number): void
|
|
22
|
+
read(buf: Uint8Array, offset: number): T
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface KvrWriteEntry<T = any> {
|
|
26
|
+
key: T[]
|
|
27
|
+
data: Uint8Array
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function readKvrHeader(
|
|
31
|
+
urlOrPath: string
|
|
32
|
+
): Promise<KvrHeader> | KvrHeader
|
|
33
|
+
|
|
34
|
+
export function readKvrIndex(
|
|
35
|
+
urlOrPath: string
|
|
36
|
+
): Promise<KvrIndex> | KvrIndex
|
|
37
|
+
|
|
38
|
+
export function readKvrRange(
|
|
39
|
+
urlOrPath: string,
|
|
40
|
+
range: ByteRange
|
|
41
|
+
): Promise<Uint8Array> | Uint8Array
|
|
42
|
+
|
|
43
|
+
export function writeKvrOPFS<T>(
|
|
44
|
+
filename: string,
|
|
45
|
+
entries: KvrWriteEntry<T>[],
|
|
46
|
+
elementCodec: ElementCodec<T>
|
|
47
|
+
): Promise<void>
|
|
48
|
+
|
|
49
|
+
export function writeKvr<T>(
|
|
50
|
+
path: string,
|
|
51
|
+
entries: KvrWriteEntry<T>[],
|
|
52
|
+
elementCodec: ElementCodec<T>
|
|
53
|
+
): void
|
|
54
|
+
|
|
55
|
+
export function exportKvr(filename: string): Promise<void>
|
|
56
|
+
|
|
57
|
+
export function packKey<T>(
|
|
58
|
+
keyArray: T[],
|
|
59
|
+
elementCodec: ElementCodec<T>
|
|
60
|
+
): Uint8Array
|
|
61
|
+
|
|
62
|
+
export function unpackKey<T>(
|
|
63
|
+
buf: Uint8Array,
|
|
64
|
+
elementCodec: ElementCodec<T>
|
|
65
|
+
): T[]
|
|
66
|
+
|
|
67
|
+
export function createElementCodec(
|
|
68
|
+
type: "int32" | "uint32" | "char"
|
|
69
|
+
): ElementCodec
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chubakabra",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Browser and Node.js utilities for working with key-addressable byte ranges in large binary files",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"browser": "./src/browser/index.js",
|
|
9
|
+
"default": "./src/node/index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"src/",
|
|
15
|
+
"index.d.ts",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT"
|
|
19
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const MAGIC = "KVR"
|
|
2
|
+
const HEADER_SIZE = 32
|
|
3
|
+
const RANGE_SIZE = 16
|
|
4
|
+
|
|
5
|
+
export async function readKvrHeader(url)
|
|
6
|
+
{
|
|
7
|
+
const buf = await fetchRange(url, 0, HEADER_SIZE - 1)
|
|
8
|
+
if (readString(buf, 0, 3) !== MAGIC || buf[3] !== 1)
|
|
9
|
+
throw new Error("Invalid KVR file")
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
keySize: readUint16LE(buf, 4),
|
|
13
|
+
indexCount: readUint32LE(buf, 6),
|
|
14
|
+
indexOffset: Number(readUint64LE(buf, 10))
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function readKvrIndex(url)
|
|
19
|
+
{
|
|
20
|
+
const h = await readKvrHeader(url)
|
|
21
|
+
const entrySize = h.keySize + RANGE_SIZE
|
|
22
|
+
const size = entrySize * h.indexCount
|
|
23
|
+
|
|
24
|
+
const buf = await fetchRange(
|
|
25
|
+
url,
|
|
26
|
+
h.indexOffset,
|
|
27
|
+
h.indexOffset + size - 1
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
const entries = []
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < h.indexCount; i++)
|
|
33
|
+
{
|
|
34
|
+
const o = i * entrySize
|
|
35
|
+
entries.push({
|
|
36
|
+
key: buf.slice(o, o + h.keySize),
|
|
37
|
+
range: [
|
|
38
|
+
Number(readUint64LE(buf, o + h.keySize)),
|
|
39
|
+
Number(readUint64LE(buf, o + h.keySize + 8))
|
|
40
|
+
]
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { keySize: h.keySize, entries }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function readKvrRange(url, range)
|
|
48
|
+
{
|
|
49
|
+
return fetchRange(url, range[0], range[1] - 1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function writeKvrOPFS(filename, entries, codec)
|
|
53
|
+
{
|
|
54
|
+
const keySize = entries[0].key.length * codec.size
|
|
55
|
+
const root = await navigator.storage.getDirectory()
|
|
56
|
+
const fh = await root.getFileHandle(filename, { create: true })
|
|
57
|
+
const w = await fh.createWritable()
|
|
58
|
+
|
|
59
|
+
const header = new Uint8Array(HEADER_SIZE)
|
|
60
|
+
writeString(header, 0, MAGIC)
|
|
61
|
+
header[3] = 1
|
|
62
|
+
writeUint16LE(header, 4, keySize)
|
|
63
|
+
writeUint32LE(header, 6, entries.length)
|
|
64
|
+
writeUint64LE(header, 10, 0n)
|
|
65
|
+
await w.write(header)
|
|
66
|
+
|
|
67
|
+
let offset = HEADER_SIZE
|
|
68
|
+
const index = []
|
|
69
|
+
|
|
70
|
+
for (const e of entries)
|
|
71
|
+
{
|
|
72
|
+
index.push({
|
|
73
|
+
key: packKey(e.key, codec),
|
|
74
|
+
from: offset,
|
|
75
|
+
to: offset + e.data.byteLength
|
|
76
|
+
})
|
|
77
|
+
await w.write(e.data)
|
|
78
|
+
offset += e.data.byteLength
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const indexOffset = offset
|
|
82
|
+
|
|
83
|
+
for (const i of index)
|
|
84
|
+
{
|
|
85
|
+
const buf = new Uint8Array(keySize + RANGE_SIZE)
|
|
86
|
+
buf.set(i.key, 0)
|
|
87
|
+
writeUint64LE(buf, keySize, BigInt(i.from))
|
|
88
|
+
writeUint64LE(buf, keySize + 8, BigInt(i.to))
|
|
89
|
+
await w.write(buf)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
await w.seek(10)
|
|
93
|
+
const p = new Uint8Array(8)
|
|
94
|
+
writeUint64LE(p, 0, BigInt(indexOffset))
|
|
95
|
+
await w.write(p)
|
|
96
|
+
await w.close()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function exportKvr(filename)
|
|
100
|
+
{
|
|
101
|
+
const root = await navigator.storage.getDirectory()
|
|
102
|
+
const fh = await root.getFileHandle(filename)
|
|
103
|
+
const file = await fh.getFile()
|
|
104
|
+
const url = URL.createObjectURL(file)
|
|
105
|
+
const a = document.createElement("a")
|
|
106
|
+
a.href = url
|
|
107
|
+
a.download = filename
|
|
108
|
+
a.click()
|
|
109
|
+
URL.revokeObjectURL(url)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function packKey(arr, codec)
|
|
113
|
+
{
|
|
114
|
+
const b = new Uint8Array(arr.length * codec.size)
|
|
115
|
+
for (let i = 0; i < arr.length; i++)
|
|
116
|
+
codec.write(b, arr[i], i * codec.size)
|
|
117
|
+
return b
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function unpackKey(buf, codec)
|
|
121
|
+
{
|
|
122
|
+
const n = buf.length / codec.size
|
|
123
|
+
const a = new Array(n)
|
|
124
|
+
for (let i = 0; i < n; i++)
|
|
125
|
+
a[i] = codec.read(buf, i * codec.size)
|
|
126
|
+
return a
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createElementCodec(type)
|
|
130
|
+
{
|
|
131
|
+
if (type === "int32")
|
|
132
|
+
return {
|
|
133
|
+
size: 4,
|
|
134
|
+
write(b, v, o){ new DataView(b.buffer).setInt32(o, v, true) },
|
|
135
|
+
read(b, o){ return new DataView(b.buffer).getInt32(o, true) }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (type === "uint32")
|
|
139
|
+
return {
|
|
140
|
+
size: 4,
|
|
141
|
+
write(b, v, o){ new DataView(b.buffer).setUint32(o, v, true) },
|
|
142
|
+
read(b, o){ return new DataView(b.buffer).getUint32(o, true) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (type === "char")
|
|
146
|
+
return {
|
|
147
|
+
size: 1,
|
|
148
|
+
write(b, v, o){ b[o] = v.charCodeAt(0) },
|
|
149
|
+
read(b, o){ return String.fromCharCode(b[o]) }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new Error("Unsupported codec")
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function fetchRange(url, from, to)
|
|
156
|
+
{
|
|
157
|
+
const r = await fetch(url, {
|
|
158
|
+
headers: { Range: `bytes=${from}-${to}` }
|
|
159
|
+
})
|
|
160
|
+
if (!r.ok && r.status !== 206)
|
|
161
|
+
throw new Error("Range failed")
|
|
162
|
+
return new Uint8Array(await r.arrayBuffer())
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function writeString(b, o, s)
|
|
166
|
+
{
|
|
167
|
+
for (let i = 0; i < s.length; i++)
|
|
168
|
+
b[o + i] = s.charCodeAt(i)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readString(b, o, n)
|
|
172
|
+
{
|
|
173
|
+
return String.fromCharCode(...b.slice(o, o + n))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function writeUint16LE(b, o, v){ new DataView(b.buffer).setUint16(o, v, true) }
|
|
177
|
+
function writeUint32LE(b, o, v){ new DataView(b.buffer).setUint32(o, v, true) }
|
|
178
|
+
function writeUint64LE(b, o, v)
|
|
179
|
+
{
|
|
180
|
+
let x = BigInt(v)
|
|
181
|
+
for (let i = 0; i < 8; i++)
|
|
182
|
+
{
|
|
183
|
+
b[o + i] = Number(x & 255n)
|
|
184
|
+
x >>= 8n
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readUint16LE(b, o){ return b[o] | (b[o+1] << 8) }
|
|
189
|
+
function readUint32LE(b, o){ return b[o] | (b[o+1]<<8) | (b[o+2]<<16) | (b[o+3]<<24) }
|
|
190
|
+
function readUint64LE(b, o)
|
|
191
|
+
{
|
|
192
|
+
let v = 0n
|
|
193
|
+
for (let i = 7; i >= 0; i--)
|
|
194
|
+
v = (v << 8n) | BigInt(b[o + i])
|
|
195
|
+
return v
|
|
196
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import fs from "fs"
|
|
2
|
+
|
|
3
|
+
const MAGIC = "KVR"
|
|
4
|
+
const HEADER_SIZE = 32
|
|
5
|
+
const RANGE_SIZE = 16
|
|
6
|
+
|
|
7
|
+
export function readKvrHeader(path)
|
|
8
|
+
{
|
|
9
|
+
const fd = fs.openSync(path, "r")
|
|
10
|
+
const b = Buffer.alloc(HEADER_SIZE)
|
|
11
|
+
fs.readSync(fd, b, 0, HEADER_SIZE, 0)
|
|
12
|
+
fs.closeSync(fd)
|
|
13
|
+
|
|
14
|
+
if (b.toString("ascii", 0, 3) !== MAGIC || b[3] !== 1)
|
|
15
|
+
throw new Error("Invalid KVR file")
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
keySize: b.readUInt16LE(4),
|
|
19
|
+
indexCount: b.readUInt32LE(6),
|
|
20
|
+
indexOffset: Number(b.readBigUInt64LE(10))
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readKvrIndex(path)
|
|
25
|
+
{
|
|
26
|
+
const h = readKvrHeader(path)
|
|
27
|
+
const entrySize = h.keySize + RANGE_SIZE
|
|
28
|
+
const size = entrySize * h.indexCount
|
|
29
|
+
|
|
30
|
+
const fd = fs.openSync(path, "r")
|
|
31
|
+
const buf = Buffer.alloc(size)
|
|
32
|
+
fs.readSync(fd, buf, 0, size, h.indexOffset)
|
|
33
|
+
fs.closeSync(fd)
|
|
34
|
+
|
|
35
|
+
const entries = []
|
|
36
|
+
|
|
37
|
+
for (let i = 0; i < h.indexCount; i++)
|
|
38
|
+
{
|
|
39
|
+
const o = i * entrySize
|
|
40
|
+
entries.push({
|
|
41
|
+
key: buf.subarray(o, o + h.keySize),
|
|
42
|
+
range: [
|
|
43
|
+
Number(buf.readBigUInt64LE(o + h.keySize)),
|
|
44
|
+
Number(buf.readBigUInt64LE(o + h.keySize + 8))
|
|
45
|
+
]
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { keySize: h.keySize, entries }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function readKvrRange(path, range)
|
|
53
|
+
{
|
|
54
|
+
const fd = fs.openSync(path, "r")
|
|
55
|
+
const size = range[1] - range[0]
|
|
56
|
+
const buf = Buffer.alloc(size)
|
|
57
|
+
fs.readSync(fd, buf, 0, size, range[0])
|
|
58
|
+
fs.closeSync(fd)
|
|
59
|
+
return buf
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function writeKvr(path, entries, codec)
|
|
63
|
+
{
|
|
64
|
+
const keySize = entries[0].key.length * codec.size
|
|
65
|
+
const fd = fs.openSync(path, "w")
|
|
66
|
+
|
|
67
|
+
const header = Buffer.alloc(HEADER_SIZE)
|
|
68
|
+
header.write(MAGIC, 0)
|
|
69
|
+
header[3] = 1
|
|
70
|
+
header.writeUInt16LE(keySize, 4)
|
|
71
|
+
header.writeUInt32LE(entries.length, 6)
|
|
72
|
+
header.writeBigUInt64LE(0n, 10)
|
|
73
|
+
fs.writeSync(fd, header)
|
|
74
|
+
|
|
75
|
+
let offset = HEADER_SIZE
|
|
76
|
+
const index = []
|
|
77
|
+
|
|
78
|
+
for (const e of entries)
|
|
79
|
+
{
|
|
80
|
+
index.push({
|
|
81
|
+
key: packKey(e.key, codec),
|
|
82
|
+
from: offset,
|
|
83
|
+
to: offset + e.data.length
|
|
84
|
+
})
|
|
85
|
+
fs.writeSync(fd, e.data, 0, e.data.length, offset)
|
|
86
|
+
offset += e.data.length
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const indexOffset = offset
|
|
90
|
+
|
|
91
|
+
for (const i of index)
|
|
92
|
+
{
|
|
93
|
+
const b = Buffer.alloc(keySize + RANGE_SIZE)
|
|
94
|
+
i.key.copy(b, 0)
|
|
95
|
+
b.writeBigUInt64LE(BigInt(i.from), keySize)
|
|
96
|
+
b.writeBigUInt64LE(BigInt(i.to), keySize + 8)
|
|
97
|
+
fs.writeSync(fd, b)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
header.writeBigUInt64LE(BigInt(indexOffset), 10)
|
|
101
|
+
fs.writeSync(fd, header, 0, HEADER_SIZE, 0)
|
|
102
|
+
fs.closeSync(fd)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function packKey(arr, codec)
|
|
106
|
+
{
|
|
107
|
+
const b = Buffer.alloc(arr.length * codec.size)
|
|
108
|
+
for (let i = 0; i < arr.length; i++)
|
|
109
|
+
codec.write(b, arr[i], i * codec.size)
|
|
110
|
+
return b
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function unpackKey(buf, codec)
|
|
114
|
+
{
|
|
115
|
+
const n = buf.length / codec.size
|
|
116
|
+
const a = new Array(n)
|
|
117
|
+
for (let i = 0; i < n; i++)
|
|
118
|
+
a[i] = codec.read(buf, i * codec.size)
|
|
119
|
+
return a
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function createElementCodec(type)
|
|
123
|
+
{
|
|
124
|
+
if (type === "int32")
|
|
125
|
+
return {
|
|
126
|
+
size: 4,
|
|
127
|
+
write(b, v, o){ b.writeInt32LE(v, o) },
|
|
128
|
+
read(b, o){ return b.readInt32LE(o) }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (type === "uint32")
|
|
132
|
+
return {
|
|
133
|
+
size: 4,
|
|
134
|
+
write(b, v, o){ b.writeUInt32LE(v, o) },
|
|
135
|
+
read(b, o){ return b.readUInt32LE(o) }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (type === "char")
|
|
139
|
+
return {
|
|
140
|
+
size: 1,
|
|
141
|
+
write(b, v, o){ b[o] = v.charCodeAt(0) },
|
|
142
|
+
read(b, o){ return String.fromCharCode(b[o]) }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error("Unsupported codec")
|
|
146
|
+
}
|