@botejs/core 0.1.2 → 0.1.4
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 +69 -0
- package/dist/sources.js +4 -1
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# bote
|
|
2
|
+
|
|
3
|
+
a minimal, ergonomic and low-memory approach to navigating a big JSON:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install @botejs/core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { open, fromFile } from '@botejs/core'
|
|
11
|
+
|
|
12
|
+
import * as z from 'zod' // or bring your own Standard Schema validator
|
|
13
|
+
|
|
14
|
+
const User = z.object({
|
|
15
|
+
id: z.string(),
|
|
16
|
+
email: z.string(),
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
type User = z.infer<typeof User>
|
|
20
|
+
|
|
21
|
+
await using cursor = await open(fromFile('./your-big.json'))
|
|
22
|
+
|
|
23
|
+
// if you want one value
|
|
24
|
+
const user0: unknown = await cursor.get('/1234/users/0')
|
|
25
|
+
|
|
26
|
+
// for .get and .iter, you can supply a validator
|
|
27
|
+
const user1: User = await cursor.get('/1234/users/1', User)
|
|
28
|
+
|
|
29
|
+
// if you want to iterate a list of values
|
|
30
|
+
for await (const user of cursor.iter('/1234/users')) {
|
|
31
|
+
console.log(user)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// if you want to iterate but not fully resolve values
|
|
35
|
+
for await (const userCursor of cursor.walk('/1234/users')) {
|
|
36
|
+
const id = await userCursor.get('/id')
|
|
37
|
+
console.log({ id })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 'await using' would normally clean up resources for you
|
|
41
|
+
// when it goes out of lexical scope. if you hate that,
|
|
42
|
+
// you can do it explicitly as well.
|
|
43
|
+
await cursor.close()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
given a **seekable** source (e.g. a file, an HTTP range) and a JSON pointer, it can retrieve values in a JSON quickly, without loading the whole thing in-memory.
|
|
47
|
+
|
|
48
|
+
here's a run (Apple M1 Pro 2021, 500MB JSON array file, cold-cache, default settings):
|
|
49
|
+
|
|
50
|
+
| operation | approach | time | js heap peak Δ | rust heap peak |
|
|
51
|
+
| ------------ | ---------- | --------: | -------------: | -------------: |
|
|
52
|
+
| items[0] | JSON.parse | 1.75 s | 1.21 GB | n/a |
|
|
53
|
+
| items[len/2] | JSON.parse | 1.82 s | 1.21 GB | n/a |
|
|
54
|
+
| items[len-1] | JSON.parse | 1.76 s | 1.21 GB | n/a |
|
|
55
|
+
| items[0] | bote | 1.43 ms | 25.9 KB | 94.9 KB |
|
|
56
|
+
| items[len/2] | bote | 328.81 ms | 1.3 MB | 56.6 MB |
|
|
57
|
+
| items[len-1] | bote | 636.78 ms | 1.3 MB | 56.6 MB |
|
|
58
|
+
|
|
59
|
+
## sources
|
|
60
|
+
|
|
61
|
+
bote currently only has `fromFile` and `fromHttpRange` as pre-built sources. create your own by implementing the `Source` interface. see [./packages/core/src/sources.ts](./packages/core/src/sources.ts) on how it works.
|
|
62
|
+
|
|
63
|
+
## status
|
|
64
|
+
|
|
65
|
+
pre-1.0 so still in development and APIs may change based on feedback, bugs and holy divinations from the coding gods.
|
|
66
|
+
|
|
67
|
+
## license
|
|
68
|
+
|
|
69
|
+
MIT.
|
package/dist/sources.js
CHANGED
|
@@ -66,7 +66,9 @@ function fromHttpRange(url, options) {
|
|
|
66
66
|
userSignal.addEventListener('abort', () => controller.abort(userSignal.reason), { once: true });
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
|
-
const
|
|
69
|
+
const headHeaders = new Headers(init?.headers);
|
|
70
|
+
headHeaders.set('Accept-Encoding', 'identity');
|
|
71
|
+
const head = await fetch(url, { ...init, headers: headHeaders, method: 'HEAD', signal: controller.signal });
|
|
70
72
|
if (!head.ok) {
|
|
71
73
|
throw new Error(`HEAD ${url} failed: ${head.status} ${head.statusText}`);
|
|
72
74
|
}
|
|
@@ -88,6 +90,7 @@ function fromHttpRange(url, options) {
|
|
|
88
90
|
const end = Math.min(offset + dst.byteLength, size) - 1;
|
|
89
91
|
const headers = new Headers(init?.headers);
|
|
90
92
|
headers.set('Range', `bytes=${offset}-${end}`);
|
|
93
|
+
headers.set('Accept-Encoding', 'identity');
|
|
91
94
|
const res = await fetch(url, { ...init, headers, method: 'GET', signal: controller.signal });
|
|
92
95
|
if (res.status === 206) {
|
|
93
96
|
const body = new Uint8Array(await res.arrayBuffer());
|
package/package.json
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botejs/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/jankdc/bote.git",
|
|
8
|
-
"directory": "packages/
|
|
8
|
+
"directory": "packages/core"
|
|
9
9
|
},
|
|
10
10
|
"main": "dist/index.js",
|
|
11
11
|
"types": "dist/index.d.ts",
|
|
12
12
|
"files": [
|
|
13
|
-
"dist"
|
|
13
|
+
"dist",
|
|
14
|
+
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">= 18.17.0 < 19 || >= 20.3.0 < 21 || >= 21.1.0"
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
"build:debug": "tsc --sourceMap",
|
|
25
26
|
"test": "node --test --experimental-strip-types --no-warnings=ExperimentalWarning __test__/*.spec.ts",
|
|
26
27
|
"lint": "oxlint src",
|
|
27
|
-
"prepublishOnly": "tsc"
|
|
28
|
+
"prepublishOnly": "cp ../../README.md ./README.md && tsc"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@botejs/native": "workspace:*"
|