@statewalker/webrun-msgpack 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 statewalker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # @statewalker/webrun-msgpack
2
+
3
+ Length-prefixed MessagePack frame codec for async iterables. Streams-safe `encode`/`decode` for arbitrary values, plus zero-copy specialisations for `Float32Array`.
4
+
5
+ ## Why it exists
6
+
7
+ Consumers that pipe values across transports (scanners writing chunks to a store, chat pipelines streaming embeddings, etc.) need a way to serialise a stream of objects into a byte stream and reassemble it on the other side without truncation surprises.
8
+
9
+ A raw MessagePack stream has no frame boundaries: a decoder can only succeed if the chunk boundaries happen to line up with the payload boundaries. Length-prefix framing fixes this — the decoder buffers incoming bytes and only yields when a complete `[length][payload]` pair is available. Partial trailing frames are NEVER emitted, so callers can detect truncation by comparing observed count to expected.
10
+
11
+ Previously the codec lived inside `@repo/streams` (private, unpublished). It's been extracted here so (a) consumers that only need framing don't pull in the broader `webrun-streams` surface, and (b) the `@ygoe/msgpack` dependency lives in exactly one place.
12
+
13
+ ## How to use
14
+
15
+ ```sh
16
+ npm install @statewalker/webrun-msgpack
17
+ ```
18
+
19
+ Four exports — one encode/decode pair for generic values, one for `Float32Array`:
20
+
21
+ | Export | Direction | Use case |
22
+ | --- | --- | --- |
23
+ | `encodeMsgpack<T>(src: AsyncIterable<T>)` | values → bytes | generic JSON-ish values |
24
+ | `decodeMsgpack<T>(src: AsyncIterable<Uint8Array>)` | bytes → values | inverse of `encodeMsgpack` |
25
+ | `encodeFloat32Arrays(src: AsyncIterable<Float32Array>)` | arrays → bytes | zero-copy float streaming |
26
+ | `decodeFloat32Arrays(src: AsyncIterable<Uint8Array>)` | bytes → arrays | inverse of `encodeFloat32Arrays` |
27
+
28
+ ## Examples
29
+
30
+ ### Stream of values
31
+
32
+ ```ts
33
+ import { encodeMsgpack, decodeMsgpack } from "@statewalker/webrun-msgpack";
34
+
35
+ async function* events() {
36
+ yield { type: "start" };
37
+ yield { type: "chunk", text: "hello" };
38
+ yield { type: "done" };
39
+ }
40
+
41
+ // encode
42
+ const bytes: AsyncIterable<Uint8Array> = encodeMsgpack(events());
43
+
44
+ // decode on the other side — handles arbitrary chunk boundaries
45
+ for await (const msg of decodeMsgpack<{ type: string; text?: string }>(bytes)) {
46
+ console.log(msg);
47
+ }
48
+ ```
49
+
50
+ ### Embeddings pipeline
51
+
52
+ ```ts
53
+ import {
54
+ decodeFloat32Arrays,
55
+ encodeFloat32Arrays,
56
+ } from "@statewalker/webrun-msgpack";
57
+
58
+ async function* chunks() {
59
+ yield new Float32Array([0.1, 0.2, 0.3, 0.4]);
60
+ yield new Float32Array([0.5, 0.6, 0.7, 0.8]);
61
+ }
62
+
63
+ // wire-efficient: msgpack `bin` type reinterpreted byte-for-byte as Float32.
64
+ const pipe = decodeFloat32Arrays(encodeFloat32Arrays(chunks()));
65
+ for await (const arr of pipe) console.log(arr.length); // 4, 4
66
+ ```
67
+
68
+ ### Re-framing across transport chunks
69
+
70
+ ```ts
71
+ import { decodeMsgpack, encodeMsgpack } from "@statewalker/webrun-msgpack";
72
+
73
+ // Produce one frame, then split the bytes any way you like:
74
+ const bytes = [];
75
+ for await (const f of encodeMsgpack([{ a: 1, b: "hi" }])) bytes.push(f);
76
+ // Hand the decoder arbitrarily small slices — it buffers until complete:
77
+ async function* byOne() {
78
+ for (const b of bytes) for (const byte of b) yield new Uint8Array([byte]);
79
+ }
80
+ for await (const v of decodeMsgpack<{ a: number; b: string }>(byOne())) {
81
+ console.log(v); // { a: 1, b: "hi" }
82
+ }
83
+ ```
84
+
85
+ ## Internals
86
+
87
+ ### Frame layout
88
+
89
+ ```
90
+ ┌──────────────┬────────────────────────────┐
91
+ │ uint32 BE │ msgpack payload │
92
+ │ (4 bytes) │ (`length` bytes) │
93
+ └──────────────┴────────────────────────────┘
94
+ ```
95
+
96
+ - Big-endian 32-bit length prefix — same convention as Java `DataOutputStream` and most wire protocols.
97
+ - Max payload per frame: 2³²−1 bytes. No fragmentation within a frame (a single call to `serialize` produces the whole payload up-front); very large values will allocate proportionally.
98
+
99
+ ### Decoder state machine
100
+
101
+ The decoder keeps a rolling `Uint8Array` buffer. Each incoming chunk is appended (single allocation per chunk); then:
102
+
103
+ 1. If buffer is shorter than 4 bytes — wait for more.
104
+ 2. Read the 32-bit BE length.
105
+ 3. If buffer doesn't hold `4 + length` bytes — wait for more.
106
+ 4. Slice the payload, deserialise with `@ygoe/msgpack`, yield.
107
+ 5. Advance the buffer past this frame; repeat step 1.
108
+
109
+ Zero-length chunks are tolerated and simply no-op through the loop. Truncated trailing frames are silently dropped — the buffer retains them but the consuming `for await` ends without yielding a partial value.
110
+
111
+ ### Float32Array zero-copy
112
+
113
+ `encodeFloat32Arrays` constructs a `Uint8Array` view over the `Float32Array`'s underlying buffer and serialises it as a msgpack `bin` payload — no float-by-float conversion. `decodeFloat32Arrays` reinterprets the decoded `Uint8Array` as a `Float32Array`. When the decoded buffer's `byteOffset` is not 4-byte aligned (can happen if `@ygoe/msgpack` returns a view into a larger buffer), we copy into a fresh aligned `Uint8Array` before constructing the `Float32Array`; otherwise the operation is view-only.
114
+
115
+ ### Dependencies
116
+
117
+ - [`@ygoe/msgpack`](https://github.com/ygoe/msgpack.js) — single-file msgpack implementation (≈7 kB gzipped), no transitive deps.
118
+
119
+ Dev: TypeScript, vitest, tsdown, rimraf (catalog versions from the monorepo root).
120
+
121
+ ### Constraints
122
+
123
+ - Big-endian length prefix only — no little-endian variant.
124
+ - `decodeMsgpack` allocates one `Uint8Array` per incoming chunk for the `concat`; long streams with many tiny chunks may benefit from a batched source upstream.
125
+ - `Float32Array` codec is strictly `Float32` — no element-size negotiation.
126
+
127
+ ## Scripts
128
+
129
+ ```sh
130
+ pnpm test # vitest run (25 tests)
131
+ pnpm run build # tsdown
132
+ pnpm lint # biome check
133
+ ```
134
+
135
+ ## License
136
+
137
+ MIT © statewalker
@@ -0,0 +1,23 @@
1
+ //#region src/msgpack.d.ts
2
+ /**
3
+ * Encode each value as a length-prefixed msgpack frame.
4
+ * Frame format: [4-byte big-endian length][msgpack bytes]
5
+ */
6
+ declare function encodeMsgpack<T>(input: AsyncIterable<T>): AsyncGenerator<Uint8Array>;
7
+ /**
8
+ * Decode length-prefixed msgpack frames, reassembling across chunk boundaries.
9
+ */
10
+ declare function decodeMsgpack<T>(input: AsyncIterable<Uint8Array>): AsyncGenerator<T>;
11
+ /**
12
+ * Encode each Float32Array as a msgpack frame.
13
+ * The Float32Array is converted to a Uint8Array view (zero-copy) before encoding as msgpack bin.
14
+ */
15
+ declare function encodeFloat32Arrays(input: AsyncIterable<Float32Array>): AsyncGenerator<Uint8Array>;
16
+ /**
17
+ * Decode msgpack frames back to Float32Array.
18
+ * Each frame contains a msgpack bin value (Uint8Array), reinterpreted as Float32Array.
19
+ */
20
+ declare function decodeFloat32Arrays(input: AsyncIterable<Uint8Array>): AsyncGenerator<Float32Array>;
21
+ //#endregion
22
+ export { decodeFloat32Arrays, decodeMsgpack, encodeFloat32Arrays, encodeMsgpack };
23
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/msgpack.ts"],"mappings":";;;;;iBAQuB,cAAc,GAAG,OAAO,cAAc,KAAK,eAAe;;;;iBAc1D,cAAc,GAAG,OAAO,cAAc,cAAc,eAAe;;;;;iBAsBnE,oBACrB,OAAO,cAAc,gBACpB,eAAe;;;;;iBAgBK,oBACrB,OAAO,cAAc,cACpB,eAAe"}
package/dist/index.mjs ADDED
@@ -0,0 +1,76 @@
1
+ import msgpack from "@ygoe/msgpack";
2
+ //#region src/msgpack.ts
3
+ const { serialize, deserialize } = msgpack;
4
+ /**
5
+ * Encode each value as a length-prefixed msgpack frame.
6
+ * Frame format: [4-byte big-endian length][msgpack bytes]
7
+ */
8
+ async function* encodeMsgpack(input) {
9
+ for await (const item of input) {
10
+ const payload = serialize(item);
11
+ const frame = new Uint8Array(4 + payload.length);
12
+ new DataView(frame.buffer).setUint32(0, payload.length, false);
13
+ frame.set(payload, 4);
14
+ yield frame;
15
+ }
16
+ }
17
+ /**
18
+ * Decode length-prefixed msgpack frames, reassembling across chunk boundaries.
19
+ */
20
+ async function* decodeMsgpack(input) {
21
+ let buffer = /* @__PURE__ */ new Uint8Array(0);
22
+ for await (const chunk of input) {
23
+ buffer = concat(buffer, chunk);
24
+ while (buffer.length >= 4) {
25
+ const frameLen = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength).getUint32(0, false);
26
+ if (buffer.length < 4 + frameLen) break;
27
+ const payload = buffer.subarray(4, 4 + frameLen);
28
+ buffer = buffer.subarray(4 + frameLen);
29
+ yield deserialize(Uint8Array.from(payload));
30
+ }
31
+ }
32
+ }
33
+ /**
34
+ * Encode each Float32Array as a msgpack frame.
35
+ * The Float32Array is converted to a Uint8Array view (zero-copy) before encoding as msgpack bin.
36
+ */
37
+ async function* encodeFloat32Arrays(input) {
38
+ for await (const arr of input) {
39
+ const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
40
+ const payload = serialize(bytes);
41
+ const frame = new Uint8Array(4 + payload.length);
42
+ new DataView(frame.buffer).setUint32(0, payload.length, false);
43
+ frame.set(payload, 4);
44
+ yield frame;
45
+ }
46
+ }
47
+ /**
48
+ * Decode msgpack frames back to Float32Array.
49
+ * Each frame contains a msgpack bin value (Uint8Array), reinterpreted as Float32Array.
50
+ */
51
+ async function* decodeFloat32Arrays(input) {
52
+ for await (const item of decodeMsgpack(wrapIterable(input))) {
53
+ const aligned = alignBuffer(item);
54
+ yield new Float32Array(aligned.buffer, aligned.byteOffset, aligned.byteLength / 4);
55
+ }
56
+ }
57
+ function alignBuffer(bytes) {
58
+ if (bytes.byteOffset % 4 === 0) return bytes;
59
+ const aligned = new Uint8Array(bytes.length);
60
+ aligned.set(bytes);
61
+ return aligned;
62
+ }
63
+ function concat(a, b) {
64
+ if (a.length === 0) return b;
65
+ const result = new Uint8Array(a.length + b.length);
66
+ result.set(a, 0);
67
+ result.set(b, a.length);
68
+ return result;
69
+ }
70
+ async function* wrapIterable(input) {
71
+ yield* input;
72
+ }
73
+ //#endregion
74
+ export { decodeFloat32Arrays, decodeMsgpack, encodeFloat32Arrays, encodeMsgpack };
75
+
76
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/msgpack.ts"],"sourcesContent":["import msgpack from \"@ygoe/msgpack\";\n\nconst { serialize, deserialize } = msgpack;\n\n/**\n * Encode each value as a length-prefixed msgpack frame.\n * Frame format: [4-byte big-endian length][msgpack bytes]\n */\nexport async function* encodeMsgpack<T>(input: AsyncIterable<T>): AsyncGenerator<Uint8Array> {\n for await (const item of input) {\n const payload = serialize(item);\n const frame = new Uint8Array(4 + payload.length);\n const view = new DataView(frame.buffer);\n view.setUint32(0, payload.length, false);\n frame.set(payload, 4);\n yield frame;\n }\n}\n\n/**\n * Decode length-prefixed msgpack frames, reassembling across chunk boundaries.\n */\nexport async function* decodeMsgpack<T>(input: AsyncIterable<Uint8Array>): AsyncGenerator<T> {\n let buffer: Uint8Array = new Uint8Array(0);\n\n for await (const chunk of input) {\n buffer = concat(buffer, chunk);\n\n while (buffer.length >= 4) {\n const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n const frameLen = view.getUint32(0, false);\n if (buffer.length < 4 + frameLen) break;\n\n const payload = buffer.subarray(4, 4 + frameLen);\n buffer = buffer.subarray(4 + frameLen);\n yield deserialize(Uint8Array.from(payload)) as T;\n }\n }\n}\n\n/**\n * Encode each Float32Array as a msgpack frame.\n * The Float32Array is converted to a Uint8Array view (zero-copy) before encoding as msgpack bin.\n */\nexport async function* encodeFloat32Arrays(\n input: AsyncIterable<Float32Array>,\n): AsyncGenerator<Uint8Array> {\n for await (const arr of input) {\n const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);\n const payload = serialize(bytes);\n const frame = new Uint8Array(4 + payload.length);\n const view = new DataView(frame.buffer);\n view.setUint32(0, payload.length, false);\n frame.set(payload, 4);\n yield frame;\n }\n}\n\n/**\n * Decode msgpack frames back to Float32Array.\n * Each frame contains a msgpack bin value (Uint8Array), reinterpreted as Float32Array.\n */\nexport async function* decodeFloat32Arrays(\n input: AsyncIterable<Uint8Array>,\n): AsyncGenerator<Float32Array> {\n for await (const item of decodeMsgpack<Uint8Array>(wrapIterable(input))) {\n const aligned = alignBuffer(item);\n yield new Float32Array(aligned.buffer, aligned.byteOffset, aligned.byteLength / 4);\n }\n}\n\nfunction alignBuffer(bytes: Uint8Array): Uint8Array {\n if (bytes.byteOffset % 4 === 0) return bytes;\n const aligned = new Uint8Array(bytes.length);\n aligned.set(bytes);\n return aligned;\n}\n\nfunction concat(a: Uint8Array, b: Uint8Array): Uint8Array {\n if (a.length === 0) return b;\n const result = new Uint8Array(a.length + b.length);\n result.set(a, 0);\n result.set(b, a.length);\n return result;\n}\n\nasync function* wrapIterable<T>(input: AsyncIterable<T>): AsyncGenerator<T> {\n yield* input;\n}\n"],"mappings":";;AAEA,MAAM,EAAE,WAAW,gBAAgB;;;;;AAMnC,gBAAuB,cAAiB,OAAqD;CAC3F,WAAW,MAAM,QAAQ,OAAO;EAC9B,MAAM,UAAU,UAAU,IAAI;EAC9B,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAQ,MAAM;EAE/C,IADiB,SAAS,MAAM,MAC7B,CAAC,CAAC,UAAU,GAAG,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,SAAS,CAAC;EACpB,MAAM;CACR;AACF;;;;AAKA,gBAAuB,cAAiB,OAAqD;CAC3F,IAAI,yBAAqB,IAAI,WAAW,CAAC;CAEzC,WAAW,MAAM,SAAS,OAAO;EAC/B,SAAS,OAAO,QAAQ,KAAK;EAE7B,OAAO,OAAO,UAAU,GAAG;GAEzB,MAAM,WAAW,IADA,SAAS,OAAO,QAAQ,OAAO,YAAY,OAAO,UAC/C,CAAC,CAAC,UAAU,GAAG,KAAK;GACxC,IAAI,OAAO,SAAS,IAAI,UAAU;GAElC,MAAM,UAAU,OAAO,SAAS,GAAG,IAAI,QAAQ;GAC/C,SAAS,OAAO,SAAS,IAAI,QAAQ;GACrC,MAAM,YAAY,WAAW,KAAK,OAAO,CAAC;EAC5C;CACF;AACF;;;;;AAMA,gBAAuB,oBACrB,OAC4B;CAC5B,WAAW,MAAM,OAAO,OAAO;EAC7B,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAQ,IAAI,YAAY,IAAI,UAAU;EACvE,MAAM,UAAU,UAAU,KAAK;EAC/B,MAAM,QAAQ,IAAI,WAAW,IAAI,QAAQ,MAAM;EAE/C,IADiB,SAAS,MAAM,MAC7B,CAAC,CAAC,UAAU,GAAG,QAAQ,QAAQ,KAAK;EACvC,MAAM,IAAI,SAAS,CAAC;EACpB,MAAM;CACR;AACF;;;;;AAMA,gBAAuB,oBACrB,OAC8B;CAC9B,WAAW,MAAM,QAAQ,cAA0B,aAAa,KAAK,CAAC,GAAG;EACvE,MAAM,UAAU,YAAY,IAAI;EAChC,MAAM,IAAI,aAAa,QAAQ,QAAQ,QAAQ,YAAY,QAAQ,aAAa,CAAC;CACnF;AACF;AAEA,SAAS,YAAY,OAA+B;CAClD,IAAI,MAAM,aAAa,MAAM,GAAG,OAAO;CACvC,MAAM,UAAU,IAAI,WAAW,MAAM,MAAM;CAC3C,QAAQ,IAAI,KAAK;CACjB,OAAO;AACT;AAEA,SAAS,OAAO,GAAe,GAA2B;CACxD,IAAI,EAAE,WAAW,GAAG,OAAO;CAC3B,MAAM,SAAS,IAAI,WAAW,EAAE,SAAS,EAAE,MAAM;CACjD,OAAO,IAAI,GAAG,CAAC;CACf,OAAO,IAAI,GAAG,EAAE,MAAM;CACtB,OAAO;AACT;AAEA,gBAAgB,aAAgB,OAA4C;CAC1E,OAAO;AACT"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@statewalker/webrun-msgpack",
3
+ "version": "0.1.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "Length-prefixed MessagePack frame codec for async iterables. Streams-safe encode/decode for values and Float32Arrays.",
7
+ "homepage": "https://github.com/statewalker/webrun-wire",
8
+ "author": {
9
+ "name": "Mikhail Kotelnikov",
10
+ "email": "mikhail.kotelnikov@gmail.com"
11
+ },
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git@github.com:statewalker/webrun-wire.git",
16
+ "directory": "packages/webrun-msgpack"
17
+ },
18
+ "exports": {
19
+ ".": "./src/index.ts"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "src"
24
+ ],
25
+ "dependencies": {
26
+ "@ygoe/msgpack": "^1.0.3"
27
+ },
28
+ "devDependencies": {
29
+ "rimraf": "^6.1.3",
30
+ "tsdown": "^0.22.14",
31
+ "typescript": "^7.0.2",
32
+ "vitest": "^4.1.10"
33
+ },
34
+ "sideEffects": false,
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "scripts": {
39
+ "build": "tsdown",
40
+ "dev": "tsdown --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "typecheck": "tsc --noEmit",
44
+ "clean": "rimraf dist",
45
+ "lint": "biome check --write .",
46
+ "format": "biome format --write ."
47
+ }
48
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export {
2
+ decodeFloat32Arrays,
3
+ decodeMsgpack,
4
+ encodeFloat32Arrays,
5
+ encodeMsgpack,
6
+ } from "./msgpack.js";
package/src/msgpack.ts ADDED
@@ -0,0 +1,89 @@
1
+ import msgpack from "@ygoe/msgpack";
2
+
3
+ const { serialize, deserialize } = msgpack;
4
+
5
+ /**
6
+ * Encode each value as a length-prefixed msgpack frame.
7
+ * Frame format: [4-byte big-endian length][msgpack bytes]
8
+ */
9
+ export async function* encodeMsgpack<T>(input: AsyncIterable<T>): AsyncGenerator<Uint8Array> {
10
+ for await (const item of input) {
11
+ const payload = serialize(item);
12
+ const frame = new Uint8Array(4 + payload.length);
13
+ const view = new DataView(frame.buffer);
14
+ view.setUint32(0, payload.length, false);
15
+ frame.set(payload, 4);
16
+ yield frame;
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Decode length-prefixed msgpack frames, reassembling across chunk boundaries.
22
+ */
23
+ export async function* decodeMsgpack<T>(input: AsyncIterable<Uint8Array>): AsyncGenerator<T> {
24
+ let buffer: Uint8Array = new Uint8Array(0);
25
+
26
+ for await (const chunk of input) {
27
+ buffer = concat(buffer, chunk);
28
+
29
+ while (buffer.length >= 4) {
30
+ const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
31
+ const frameLen = view.getUint32(0, false);
32
+ if (buffer.length < 4 + frameLen) break;
33
+
34
+ const payload = buffer.subarray(4, 4 + frameLen);
35
+ buffer = buffer.subarray(4 + frameLen);
36
+ yield deserialize(Uint8Array.from(payload)) as T;
37
+ }
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Encode each Float32Array as a msgpack frame.
43
+ * The Float32Array is converted to a Uint8Array view (zero-copy) before encoding as msgpack bin.
44
+ */
45
+ export async function* encodeFloat32Arrays(
46
+ input: AsyncIterable<Float32Array>,
47
+ ): AsyncGenerator<Uint8Array> {
48
+ for await (const arr of input) {
49
+ const bytes = new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
50
+ const payload = serialize(bytes);
51
+ const frame = new Uint8Array(4 + payload.length);
52
+ const view = new DataView(frame.buffer);
53
+ view.setUint32(0, payload.length, false);
54
+ frame.set(payload, 4);
55
+ yield frame;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Decode msgpack frames back to Float32Array.
61
+ * Each frame contains a msgpack bin value (Uint8Array), reinterpreted as Float32Array.
62
+ */
63
+ export async function* decodeFloat32Arrays(
64
+ input: AsyncIterable<Uint8Array>,
65
+ ): AsyncGenerator<Float32Array> {
66
+ for await (const item of decodeMsgpack<Uint8Array>(wrapIterable(input))) {
67
+ const aligned = alignBuffer(item);
68
+ yield new Float32Array(aligned.buffer, aligned.byteOffset, aligned.byteLength / 4);
69
+ }
70
+ }
71
+
72
+ function alignBuffer(bytes: Uint8Array): Uint8Array {
73
+ if (bytes.byteOffset % 4 === 0) return bytes;
74
+ const aligned = new Uint8Array(bytes.length);
75
+ aligned.set(bytes);
76
+ return aligned;
77
+ }
78
+
79
+ function concat(a: Uint8Array, b: Uint8Array): Uint8Array {
80
+ if (a.length === 0) return b;
81
+ const result = new Uint8Array(a.length + b.length);
82
+ result.set(a, 0);
83
+ result.set(b, a.length);
84
+ return result;
85
+ }
86
+
87
+ async function* wrapIterable<T>(input: AsyncIterable<T>): AsyncGenerator<T> {
88
+ yield* input;
89
+ }