vectorjson 0.4.7 → 0.5.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/README.md +25 -9
- package/dist/engine-wasm.generated.d.ts +1 -1
- package/dist/engine-wasm.generated.d.ts.map +1 -1
- package/dist/engine.wasm +0 -0
- package/dist/index.d.ts +22 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/vectorjson)
|
|
6
6
|
[](https://github.com/teamchong/vectorjson/blob/main/LICENSE)
|
|
7
7
|
|
|
8
|
-
O(n) streaming JSON parser for LLM tool calls, built on WASM SIMD. Agents act faster with field-level streaming, detect wrong outputs early to abort and save tokens
|
|
8
|
+
O(n) streaming JSON parser for LLM tool calls, built on WASM SIMD. Agents act faster with field-level streaming, detect wrong outputs early to abort and save tokens.
|
|
9
9
|
|
|
10
10
|
## The Problem
|
|
11
11
|
|
|
@@ -124,9 +124,7 @@ for await (const chunk of llmStream({ signal: abort.signal })) {
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
-
**Worker offload** —
|
|
128
|
-
|
|
129
|
-
`getTapeBuffer()` exports the SIMD-parsed tape + input as a single packed ArrayBuffer. `postMessage(buf, [buf])` transfers it in O(1). The main thread imports it with `importTape()` — zero parsing, just memcpy into a document slot:
|
|
127
|
+
**Worker offload** — `getTapeBuffer()` + `importTape()` for lazy access without re-parsing:
|
|
130
128
|
|
|
131
129
|
```js
|
|
132
130
|
// In Worker:
|
|
@@ -136,11 +134,11 @@ postMessage(tape, [tape]); // O(1) transfer — moves pointer, no copy
|
|
|
136
134
|
|
|
137
135
|
// On Main thread:
|
|
138
136
|
import { importTape } from "vectorjson";
|
|
139
|
-
const obj = importTape(tape); //
|
|
140
|
-
obj.name; // lazy Proxy
|
|
137
|
+
const obj = importTape(tape); // imports pre-built tape, no parse
|
|
138
|
+
obj.name; // lazy Proxy — only materializes fields you access
|
|
141
139
|
```
|
|
142
140
|
|
|
143
|
-
|
|
141
|
+
**Note:** For full materialization, `JSON.parse` in Worker + structured clone is faster end-to-end — Chrome's C++ structured clone is highly optimized. Tape transfer wins when you only need a few fields on the main thread via lazy Proxy access, avoiding full object materialization.
|
|
144
142
|
|
|
145
143
|
## Benchmarks
|
|
146
144
|
|
|
@@ -169,6 +167,24 @@ Apple-to-apple: both sides produce a materialized partial object on every chunk.
|
|
|
169
167
|
|
|
170
168
|
Stock parsers re-parse the full buffer on every chunk — O(n²). VectorJSON maintains a **live JS object** that grows incrementally on each `feed()`, so `getValue()` is O(1). Total work: O(n).
|
|
171
169
|
|
|
170
|
+
### What about single-shot JSON.parse?
|
|
171
|
+
|
|
172
|
+
For one-shot full materialization, `JSON.parse` is faster — it's highly optimized C++ running inside V8. On small payloads it's orders of magnitude faster. On large payloads (500KB+) VectorJSON approaches parity but never beats it for full access:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
bun --expose-gc bench/parse-stream.mjs (one-shot section, CI results)
|
|
176
|
+
|
|
177
|
+
1 KB JSON.parse 2.16M ops/s VectorJSON 1.37K ops/s JSON.parse wins
|
|
178
|
+
10 KB JSON.parse 9.64K ops/s VectorJSON 1.37K ops/s JSON.parse wins
|
|
179
|
+
500 KB JSON.parse 490 ops/s VectorJSON 466 ops/s ~equal (0.95×)
|
|
180
|
+
2 MB JSON.parse 183 ops/s VectorJSON 172 ops/s ~equal (0.94×)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
VectorJSON is not a replacement for `JSON.parse`. It wins in different scenarios:
|
|
184
|
+
- **Streaming**: O(n) incremental vs O(n²) re-parse on every chunk
|
|
185
|
+
- **Partial access**: read 3 fields from a 100KB payload without materializing the other 97%
|
|
186
|
+
- **Deep compare**: WASM tape-level comparison with zero JS allocations
|
|
187
|
+
|
|
172
188
|
### Why this matters: main thread availability
|
|
173
189
|
|
|
174
190
|
The real cost isn't just CPU time — it's blocking the agent's main thread. Simulating an Anthropic `tool_use` content block (`str_replace_editor`) streamed in ~12-char chunks:
|
|
@@ -186,11 +202,11 @@ Both approaches detect the tool name (`.name`) at the same chunk — the LLM has
|
|
|
186
202
|
|
|
187
203
|
For even more control, use `createEventParser()` for field-level subscriptions or only call `getValue()` once when `feed()` returns `"complete"`.
|
|
188
204
|
|
|
189
|
-
### Worker Transfer
|
|
205
|
+
### Worker Transfer
|
|
190
206
|
|
|
191
207
|
`bun run bench:worker` (requires Playwright + Chromium)
|
|
192
208
|
|
|
193
|
-
Measures
|
|
209
|
+
Measures full round-trip time (postMessage → worker parse → transfer → main receive + field access) in a real browser. For full materialization, `JSON.parse` + structured clone wins — Chrome's C++ clone is faster than WASM parse + tape export/import. Tape transfer is useful when you only access a few fields via lazy Proxy on the main thread.
|
|
194
210
|
|
|
195
211
|
<details>
|
|
196
212
|
<summary>Which products use which parser</summary>
|