Support stream/consumers arrayBuffer

- Support `arrayBuffer()` from `node:stream/consumers`
- Print ArrayBuffer and SharedArrayBuffer values like Node
- Mark the stream consumers as partially supported in the compatibility data
This commit is contained in:
Chris Tate
2026-10-13 08:33:55 -05:00
parent 725812afea
commit 5a7fc7225b
18 changed files with 309 additions and 84 deletions
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "32dbd982601194038ff9",
"artifactVersion": "a3c8ef2ce07eb09a4739",
"rowCount": 3662
}
+15 -15
View File
@@ -854,9 +854,9 @@
"apiEntries": 88,
"static": {
"supported": 0,
"partial": 3,
"refused": 2,
"not-implemented": 83,
"partial": 7,
"refused": 1,
"not-implemented": 80,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -76400,9 +76400,9 @@
"inherited": true
},
"static": {
"status": "refused",
"detail": "Explicitly rejected by a named compiler diagnostic.",
"verification": "explicit-refusal"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -76446,9 +76446,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -76492,9 +76492,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -76515,9 +76515,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the documented static async-iteration subset.",
"verification": "test-backed"
}
},
{
@@ -7,9 +7,9 @@
"tierItemCount": 3288,
"tiers": {
"static": {
"replace-refusal": 42,
"verify-gap": 2417,
"audit-partial": 747,
"replace-refusal": 41,
"verify-gap": 2414,
"audit-partial": 751,
"classify": 2,
"implement": 80
}
@@ -76094,12 +76094,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "refused",
"action": "replace-refusal",
"verification": "explicit-refusal",
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:unsupported:node-builtin.stream.consumers.arrayBuffer",
"tests": []
"source": "compiler-feature:webstreams.streamConsumers.arrayBuffer",
"tests": [
"tests/corpus/4870-stream-consumers-array-buffer.ts"
]
}
}
},
@@ -76144,12 +76146,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webstreams",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webstreams.streamConsumers.buffer",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
}
}
},
@@ -76194,12 +76199,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webstreams",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webstreams.streamConsumers.json",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
}
}
},
@@ -76219,12 +76227,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:webstreams",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "compiler-feature:webstreams.streamConsumers.text",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
}
}
},
@@ -911,9 +911,9 @@
"apiEntries": 88,
"static": {
"supported": 0,
"partial": 3,
"refused": 2,
"not-implemented": 83,
"partial": 7,
"refused": 1,
"not-implemented": 80,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -101671,7 +101671,10 @@
"status": "partial",
"evidence": "derived:descendants",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts",
"tests/corpus/2862-web-readable-async-iteration.ts",
"tests/corpus/4870-stream-consumers-array-buffer.ts",
"tests/harness/fetch-conformance.test.ts"
]
},
@@ -101787,8 +101790,11 @@
"inherited": true
},
"static": {
"status": "refused",
"evidence": "surface-manifest:unsupported:node-builtin.stream.consumers.arrayBuffer"
"status": "partial",
"evidence": "compiler-feature:webstreams.streamConsumers.arrayBuffer",
"tests": [
"tests/corpus/4870-stream-consumers-array-buffer.ts"
]
},
"anchorSource": "exact"
},
@@ -101833,8 +101839,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webstreams"
"status": "partial",
"evidence": "compiler-feature:webstreams.streamConsumers.buffer",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
},
"anchorSource": "exact"
},
@@ -101879,8 +101889,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webstreams"
"status": "partial",
"evidence": "compiler-feature:webstreams.streamConsumers.json",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
},
"anchorSource": "exact"
},
@@ -101902,8 +101916,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:webstreams"
"status": "partial",
"evidence": "compiler-feature:webstreams.streamConsumers.text",
"tests": [
"tests/corpus/2629-stream-consumers.ts",
"tests/corpus/2630-stream-consumers-errors.ts"
]
},
"anchorSource": "exact"
},
@@ -224,6 +224,8 @@
{ "chapter": "stream", "signatures": ["`writable` Type: {boolean}"], "status": "partial", "evidence": ["tests/corpus/2893-child-stdin-duplex.ts", "tests/corpus/2895-child-stdin-write-after-end.ts"] },
{ "chapter": "stream", "signatures": ["Event: `'drain'`"], "status": "partial", "evidence": ["tests/corpus/2894-child-stdin-backpressure.ts"] },
{ "chapter": "webstreams", "signatures": ["`readableStream.values([options])`"], "status": "partial", "evidence": ["tests/corpus/2862-web-readable-async-iteration.ts"] },
{ "chapter": "webstreams", "signatures": ["`streamConsumers.text(stream)`", "`streamConsumers.json(stream)`", "`streamConsumers.buffer(stream)`"], "status": "partial", "evidence": ["tests/corpus/2629-stream-consumers.ts", "tests/corpus/2630-stream-consumers-errors.ts"] },
{ "chapter": "webstreams", "signatures": ["`streamConsumers.arrayBuffer(stream)`"], "status": "partial", "evidence": ["tests/corpus/4870-stream-consumers-array-buffer.ts"] },
{ "chapter": "url", "symbols": ["url.fileURLToPath", "url.fileURLToPathBuffer", "url.pathToFileURL"], "status": "partial", "evidence": ["tests/corpus/node24-file-url-buffer.ts", "tests/corpus/node24-file-url-options.ts", "tests/corpus/node24-file-url-errors.js", "tests/fixtures/node-types/file-url-conversion.ts"] },
{ "chapter": "url", "symbols": ["URL.canParse", "URL.parse", "url.toString", "url.toJSON"], "status": "partial", "evidence": ["tests/corpus/node24-url-factories.ts", "tests/corpus/node24-url-factory-errors.ts", "tests/fixtures/node-types/url-factories.ts", "tests/harness/project-config.test.ts"] },
{ "chapter": "url", "signatures": ["Class: `URL`"], "status": "partial", "evidence": ["tests/corpus/url-runtime-base.ts", "tests/corpus/node24-url-mutation.ts", "tests/corpus/node24-url-mutation-coercion.js", "tests/fixtures/node-types/url-mutation.ts"] },
@@ -938,6 +938,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"sc.text": "scr_sc_text",
"sc.json": "scr_sc_json",
"sc.buffer": "scr_sc_buffer",
"sc.arrayBuffer": "scr_sc_array_buffer",
"dc.channel": "scr_dc_channel",
"dc.subscribe": "scr_dc_subscribe",
"dc.unsubscribe": "scr_dc_unsubscribe",
@@ -1139,6 +1140,7 @@ export const USES_TIMERS_LIB_FNS = new Set<string>([
"sc.text",
"sc.json",
"sc.buffer",
"sc.arrayBuffer",
"net.listen",
"net.listenCb",
"net.listenOpts",
@@ -1635,15 +1635,20 @@ export function lowerStreamModuleCall(
}
if (bi.module === "stream/consumers") {
const args = call.arguments;
// text/json/buffer(stream) → a pending promise the accumulate-and-
// settle machinery resolves at the terminal point (the utf8 decode / the parsed
// JSON dyn tree / the concatenated bytes) or rejects — with the stream's
// error, ERR_STREAM_PREMATURE_CLOSE on an early close, or json's
// SyntaxError on malformed text, Node's own rejection set.
// arrayBuffer/blob return null here: neither value has a
// representation, so the module-qualified fence (with the pointed
// hints in BUILTIN_MODULE_FENCE_HINTS) speaks.
if (bi.member === "text" || bi.member === "json" || bi.member === "buffer") {
// text/json/buffer/arrayBuffer(stream) → a pending promise the
// accumulate-and-settle machinery resolves at the terminal point (the
// utf8 decode / the parsed JSON dyn tree / the concatenated bytes / an
// ArrayBuffer dyn over them) or rejects — with the stream's error,
// ERR_STREAM_PREMATURE_CLOSE on an early close, or json's SyntaxError
// on malformed text, Node's own rejection set. blob returns null here:
// Blob has no representation, so the module-qualified fence (with the
// pointed hint in BUILTIN_MODULE_FENCE_HINTS) speaks.
if (
bi.member === "text" ||
bi.member === "json" ||
bi.member === "buffer" ||
bi.member === "arrayBuffer"
) {
if (args.length !== 1) {
lowerer.noLowering(
`stream/consumers.${bi.member} with ${args.length} arguments`,
@@ -1668,7 +1673,7 @@ export function lowerStreamModuleCall(
);
}
const recv: IrExpr = argV;
const inner: IrType = bi.member === "text" ? STRING : bi.member === "json" ? DYN : BYTES;
const inner: IrType = bi.member === "text" ? STRING : bi.member === "buffer" ? BYTES : DYN;
return {
kind: "libCall",
fn: `sc.${bi.member}` as IrLibFn,
@@ -1572,9 +1572,9 @@ export const BUILTIN_MODULE_FNS: Record<
// exists so unlowered members fence module-qualified.
"stream/promises": {},
// node:stream/consumers rides the stream spoke as well (text/json/
// buffer special-case their stream argument); arrayBuffer and blob
// fence module-qualified with the pointed hints below — neither value
// has a representation in a compiled binary.
// buffer/arrayBuffer special-case their stream argument); blob fences
// module-qualified with the pointed hint below — Blob has no
// representation in a compiled binary.
"stream/consumers": {},
// node:timers/promises — the delay-only setTimeout and bare setImmediate
// (void promises the shared timer heap settles). The arity completion
@@ -2384,9 +2384,6 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record<
"the lowered shapes are const bindings over child_process.execFile and fs.readFile; call the bound function directly",
},
"stream/consumers": {
arrayBuffer:
"collecting a stream directly into an ArrayBuffer has no native lowering yet; " +
"buffer(stream) collects the same bytes as a Buffer",
blob:
"Blob values have no representation in a compiled binary — " +
"buffer(stream) collects the same bytes as a Buffer, text(stream) the decoded text",
@@ -229,6 +229,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"sc.text",
"sc.json",
"sc.buffer",
"sc.arrayBuffer",
"readable.newDyn",
"writable.newDyn",
"duplex.newDyn",
@@ -2841,11 +2841,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
* machinery: (s) → a pending promise settled at the terminal point
* with the accumulated result (sc.text: the utf8 decode, sc.json: the
* parsed dyn — malformed input rejects with the parse's SyntaxError,
* sc.buffer: the concatenated bytes) or rejected with the stream's
* sc.buffer: the concatenated bytes, sc.arrayBuffer: an ArrayBuffer
* dyn over them) or rejected with the stream's
* error / ERR_STREAM_PREMATURE_CLOSE. */
"sc.text": { argTypes: [null], result: { kind: "promise", inner: STRING } },
"sc.json": { argTypes: [null], result: { kind: "promise", inner: DYN } },
"sc.buffer": { argTypes: [null], result: { kind: "promise", inner: BYTES_U8 } },
"sc.arrayBuffer": { argTypes: [null], result: { kind: "promise", inner: DYN } },
"readable.newDyn": { argTypes: [DYN], result: VOID },
"writable.newDyn": { argTypes: [DYN], result: VOID },
"duplex.newDyn": { argTypes: [DYN], result: VOID },
-8
View File
@@ -1859,14 +1859,6 @@
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.stream.consumers.arrayBuffer",
"kind": "node-builtin",
"name": "stream/consumers.arrayBuffer",
"status": "unsupported",
"code": "SC2020",
"note": "collecting a stream directly into an ArrayBuffer has no native lowering yet; buffer(stream) collects the same bytes as a Buffer"
},
{
"id": "node-builtin.stream.consumers.blob",
"kind": "node-builtin",
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4870-stream-consumers-array-buffer.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/4871-array-buffer-inspect.ts"
],
"diags": []
}
+58
View File
@@ -650,6 +650,60 @@ ScrStr *scr_insp_buffer(ScrBytes *b) {
return ib_take(&out);
}
/* ArrayBuffer/SharedArrayBuffer: formatArrayBuffer's
* `[Uint8Contents]: <hex pairs>` (the first maxArrayLength = 100 bytes,
* then ` ... N more byte(s)`) followed by the byteLength key, inside the
* class braces. `[ArrayBuffer]` beyond depth. */
static ScrStr *scr_insp_array_buffer(const ScrBytes *h, const char *cls, double recurse,
double depth) {
if (recurse > depth) {
InspBuf out = {0};
ib_char(&out, '[');
ib_cstr(&out, cls);
ib_char(&out, ']');
return ib_take(&out);
}
size_t len = (size_t)scr_bytes_byte_len(h);
size_t shown = len < 100 ? len : 100;
uint8_t head[100];
if (shown) scr_bytes_read(h, 0, head, shown);
static const char hex[] = "0123456789abcdef";
InspBuf contents = {0};
ib_cstr(&contents, "[Uint8Contents]: <");
for (size_t i = 0; i < shown; i++) {
if (i) ib_char(&contents, ' ');
ib_char(&contents, hex[head[i] >> 4]);
ib_char(&contents, hex[head[i] & 15]);
}
if (len > shown) {
char more[64];
size_t remaining = len - shown;
int n = snprintf(more, sizeof more, " ... %zu more byte%s", remaining, remaining > 1 ? "s" : "");
ib_bytes(&contents, more, (size_t)n);
}
ib_char(&contents, '>');
char byte_length[48];
int bn = snprintf(byte_length, sizeof byte_length, "[byteLength]: %zu", len);
scr_insp_begin(recurse + 1);
ScrStr *entry = ib_take(&contents);
scr_insp_entry(entry, false);
scr_str_release(entry);
entry = scr_str_new(byte_length, (size_t)bn);
scr_insp_entry(entry, false);
scr_str_release(entry);
InspBuf open = {0};
ib_cstr(&open, cls);
ib_cstr(&open, " {");
ScrStr *base = scr_str_new("", 0);
ScrStr *b0 = ib_take(&open);
ScrStr *b1 = scr_str_new("}", 1);
ScrStr *out = scr_insp_end(base, b0, b1, recurse + 1, false, false);
scr_str_release(base);
scr_str_release(b0);
scr_str_release(b1);
return out;
}
/* Errors: the STACKLESS rendering — compiled binaries carry no stack, so
* the base is formatError's bracket form `[Name: message]` / `[Name]`
* (Node prints these exact forms for an error whose stack is empty;
@@ -908,6 +962,10 @@ ScrStr *scr_insp_dyn(ScrDyn *d, double recurse, double depth) {
return ib_take(&out);
}
case SCR_DYN_HANDLE: {
if (d->v.handle.tag == SCR_DYNH_ARRAY_BUFFER ||
d->v.handle.tag == SCR_DYNH_SHARED_ARRAY_BUFFER) {
return scr_insp_array_buffer(d->v.handle.ptr, scr_dyn_handle_cls(d), recurse, depth);
}
/* Node prints the class's full property dump (ServerResponse {
* ... }) — internals we do not model. Fence loudly instead of a
* silent-wrong shape (SEMANTICS.md). */
+5 -3
View File
@@ -2922,13 +2922,15 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams);
* and settle at the terminal point (right after 'close', the eos timing
* Node's consumers share) — text answers the utf8 decode, json parses
* the text (malformed input rejects with the parse's SyntaxError; the
* result is a +1 dyn tree), buffer the concatenated bytes. Stream
* errors reject; an early close rejects ERR_STREAM_PREMATURE_CLOSE; a
* stream with no readable side rejects Node's async-iterable TypeError.
* result is a +1 dyn tree), buffer the concatenated bytes, arrayBuffer
* a +1 ArrayBuffer dyn over them. Stream errors reject; an early close
* rejects ERR_STREAM_PREMATURE_CLOSE; a stream with no readable side
* rejects Node's async-iterable TypeError.
* Streams borrowed; +1 promises. */
ScrPromise *scr_sc_text(ScrStream *s);
ScrPromise *scr_sc_json(ScrStream *s);
ScrPromise *scr_sc_buffer(ScrStream *s);
ScrPromise *scr_sc_array_buffer(ScrStream *s);
/* Readable.from(array): +1 fully-seeded object-entry stream (one WHOLE
* chunk per element — strings or Buffers per the flag; hwm 1, already
* EOF'd). Borrows arr. */
+18 -3
View File
@@ -2227,7 +2227,7 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
}
/* ── node:stream/consumers ────────────────────────────────────────────
* text/json/buffer over the readable machinery: a native 'data' listener
* text/json/buffer/arrayBuffer over the readable machinery: a native 'data' listener
* accumulates every chunk (Buffer chunks as-is; string chunks — an
* encoded stream or Readable.from strings — as their utf8 bytes, the
* Blob rule Node's consumers share for whole-stream accumulation), and
@@ -2239,7 +2239,7 @@ ScrPromise *scr_sp_pipeline(double n, ScrStream **streams) {
* closures share the cap layout: caps[0] the pending promise, caps[1]
* the chunk list (Buffer entries). */
enum { SCR_SC_TEXT = 0, SCR_SC_JSON = 1, SCR_SC_BUFFER = 2 };
enum { SCR_SC_TEXT = 0, SCR_SC_JSON = 1, SCR_SC_BUFFER = 2, SCR_SC_ARRAY_BUFFER = 3 };
static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
ScrPromise *p = scr_box_get_ref(cb->caps[0]); /* +1 */
@@ -2251,6 +2251,15 @@ static void scr_sc_settle_ok(ScrClosure *cb, int kind) {
scr_promise_release(p);
return;
}
if (kind == SCR_SC_ARRAY_BUFFER) {
/* The concatenation is a fresh exact-length owner, so the buffer
* wraps it with no second copy (Node copies into a Blob, then out). */
ScrDyn *ab = scr_array_buffer_from_bytes(all);
scr_bytes_release(all);
scr_promise_fulfill_ref(p, ab, scr_dyn_retain_v, scr_dyn_release_v, NULL);
scr_promise_release(p);
return;
}
ScrStr *enc = scr_str_new("utf8", 4);
ScrStr *text = scr_bytes_to_str(all, enc); /* U+FFFD per invalid subpart */
scr_str_release(enc);
@@ -2309,6 +2318,10 @@ static void scr_sc_fin_buffer(ScrClosure *cb, ScrStream *s, ScrError *err) {
(void)s;
scr_sc_fin(cb, err, SCR_SC_BUFFER);
}
static void scr_sc_fin_array_buffer(ScrClosure *cb, ScrStream *s, ScrError *err) {
(void)s;
scr_sc_fin(cb, err, SCR_SC_ARRAY_BUFFER);
}
static ScrClosure *scr_sc_closure(void *fn, ScrPromise *p, ScrArr *chunks) {
ScrClosure *c = scr_closure_new(fn, 2);
@@ -2333,7 +2346,8 @@ static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
ScrArr *chunks = scr_arr_new_ops(&bytes_ops, 4);
ScrStreamErrInv fin_inv = kind == SCR_SC_TEXT ? &scr_sc_fin_text
: kind == SCR_SC_JSON ? &scr_sc_fin_json
: &scr_sc_fin_buffer;
: kind == SCR_SC_BUFFER ? &scr_sc_fin_buffer
: &scr_sc_fin_array_buffer;
/* The terminal watcher first (it marks lifecycle errors handled); the
* promise form exposes no unhook — the cleanup closure drops. */
ScrClosure *cleanup = scr_stream_finished(s, scr_sc_closure((void *)fin_inv, p, chunks), fin_inv);
@@ -2353,6 +2367,7 @@ static ScrPromise *scr_sc_consume(ScrStream *s, int kind) {
ScrPromise *scr_sc_text(ScrStream *s) { return scr_sc_consume(s, SCR_SC_TEXT); }
ScrPromise *scr_sc_json(ScrStream *s) { return scr_sc_consume(s, SCR_SC_JSON); }
ScrPromise *scr_sc_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_BUFFER); }
ScrPromise *scr_sc_array_buffer(ScrStream *s) { return scr_sc_consume(s, SCR_SC_ARRAY_BUFFER); }
/* ── the readable surface ─────────────────────────────────────────────── */
@@ -0,0 +1,81 @@
// stream/consumers.arrayBuffer — the accumulated bytes as a fresh
// ArrayBuffer: pooled small Buffer chunks and a lone chunk never alias
// the result, string chunks contribute their utf8 bytes, an empty stream
// answers a zero-length buffer, and the result works with every view.
// Settlement lands after 'close' and before a later macrotask, and the
// rejection set matches the other consumers: the stream's error and
// ERR_STREAM_PREMATURE_CLOSE.
import { Readable } from "node:stream";
import { arrayBuffer } from "node:stream/consumers";
function source(...chunks: (Buffer | string)[]): Readable {
const r = new Readable({ read() {} });
for (const c of chunks) r.push(c);
r.push(null);
return r;
}
async function main(): Promise<void> {
const ab = await arrayBuffer(source(Buffer.from("hi"), Buffer.from([0xff, 0])));
console.log(ab);
console.log(ab.byteLength, ab instanceof ArrayBuffer);
const u8 = new Uint8Array(ab);
console.log(Array.from(u8), u8.length);
const dv = new DataView(ab);
console.log(dv.getUint16(0), dv.getUint8(2));
console.log(Array.from(new Uint8Array(ab.slice(1, 3))));
console.log(Buffer.from(ab).toString("hex"));
// A single chunk still copies: writes through the result's view do not
// reach the source chunk.
const chunk = Buffer.from([1, 2, 3, 4]);
const lone = await arrayBuffer(source(chunk));
new Uint8Array(lone)[0] = 9;
console.log(chunk[0], new Uint8Array(lone)[0], lone.byteLength);
// String chunks (setEncoding) contribute their utf8 bytes.
const encoded = new Readable({ read() {} });
encoded.setEncoding("utf8");
encoded.push("é");
encoded.push("x");
encoded.push(null);
console.log(Array.from(new Uint8Array(await arrayBuffer(encoded))));
const empty = await arrayBuffer(source());
console.log(empty, empty.byteLength);
// Timing: after 'close', before a setImmediate queued at end.
const timed = new Readable({ read() {} });
const order: string[] = [];
timed.on("close", () => order.push("close"));
const settled = arrayBuffer(timed).then((b) => order.push(`arrayBuffer(${b.byteLength})`));
timed.push("abc");
timed.push(null);
setImmediate(() => order.push("immediate"));
await settled;
await new Promise<void>((resolve) => setImmediate(resolve));
console.log(order.join(","));
const failing = new Readable({ read() {} });
const p1 = arrayBuffer(failing);
failing.push("x");
failing.destroy(new Error("mid-stream failure"));
try {
await p1;
console.log("SHOULD NOT PRINT");
} catch (e) {
if (e instanceof Error) console.log("error:", e.message);
}
const closed = new Readable({ read() {} });
const p2 = arrayBuffer(closed);
closed.destroy();
try {
await p2;
console.log("SHOULD NOT PRINT");
} catch (e) {
console.log("premature:", (e as NodeJS.ErrnoException).code);
}
}
void main();
+27
View File
@@ -0,0 +1,27 @@
// ArrayBuffer and SharedArrayBuffer inspection: Node's formatArrayBuffer
// `[Uint8Contents]: <hex pairs>` entry (the first 100 bytes, then
// `... N more byte(s)`) plus the byteLength entry inside the class
// braces, breaking onto lines past the 80-column budget, nested in
// objects and arrays, `[ArrayBuffer]` beyond the depth limit, and the
// backing buffer of a non-byte typed array.
import { inspect } from "node:util";
for (const n of [0, 1, 3, 16, 17, 18, 19, 20, 24, 100, 101, 102, 300]) {
const ab = new ArrayBuffer(n);
const view = new Uint8Array(ab);
for (let i = 0; i < n; i++) view[i] = (i * 37) & 255;
console.log(n, ab);
}
const small = new ArrayBuffer(2);
console.log({ a: small, b: [small] });
console.log({ deep: { deeper: { deepest: { ab: small } } } });
console.log([new ArrayBuffer(40), 1]);
console.log(inspect(small));
console.log(`${inspect({ x: new ArrayBuffer(4) })}`);
console.log(new SharedArrayBuffer(3));
const f64 = new Float64Array(2);
f64[0] = 1.5;
console.log(f64.buffer);