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 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
+ }