@gjsify/web-streams 0.3.21 → 0.4.3

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.
@@ -1,10 +0,0 @@
1
- // Registers: ByteLengthQueuingStrategy, CountQueuingStrategy
2
-
3
- import { ByteLengthQueuingStrategy, CountQueuingStrategy } from '../index.js';
4
-
5
- if (typeof globalThis.ByteLengthQueuingStrategy === 'undefined') {
6
- (globalThis as any).ByteLengthQueuingStrategy = ByteLengthQueuingStrategy;
7
- }
8
- if (typeof globalThis.CountQueuingStrategy === 'undefined') {
9
- (globalThis as any).CountQueuingStrategy = CountQueuingStrategy;
10
- }
@@ -1,16 +0,0 @@
1
- // Registers: ReadableStream
2
-
3
- import { ReadableStream } from '../index.js';
4
-
5
- function isNativeStreamUsable(Ctor: unknown, method: string): boolean {
6
- try {
7
- if (typeof Ctor !== 'function') return false;
8
- return typeof (Ctor as any).prototype[method] === 'function';
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- if (!isNativeStreamUsable(globalThis.ReadableStream, 'getReader')) {
15
- (globalThis as any).ReadableStream = ReadableStream;
16
- }
@@ -1,10 +0,0 @@
1
- // Registers: TextEncoderStream, TextDecoderStream
2
-
3
- import { TextEncoderStream, TextDecoderStream } from '../index.js';
4
-
5
- if (typeof globalThis.TextEncoderStream === 'undefined') {
6
- (globalThis as any).TextEncoderStream = TextEncoderStream;
7
- }
8
- if (typeof globalThis.TextDecoderStream === 'undefined') {
9
- (globalThis as any).TextDecoderStream = TextDecoderStream;
10
- }
@@ -1,16 +0,0 @@
1
- // Registers: TransformStream
2
-
3
- import { TransformStream } from '../index.js';
4
-
5
- function isNativeStreamUsable(Ctor: unknown, method: string): boolean {
6
- try {
7
- if (typeof Ctor !== 'function') return false;
8
- return typeof (Ctor as any).prototype[method] === 'function';
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- if (!isNativeStreamUsable(globalThis.TransformStream, 'readable')) {
15
- (globalThis as any).TransformStream = TransformStream;
16
- }
@@ -1,16 +0,0 @@
1
- // Registers: WritableStream
2
-
3
- import { WritableStream } from '../index.js';
4
-
5
- function isNativeStreamUsable(Ctor: unknown, method: string): boolean {
6
- try {
7
- if (typeof Ctor !== 'function') return false;
8
- return typeof (Ctor as any).prototype[method] === 'function';
9
- } catch {
10
- return false;
11
- }
12
- }
13
-
14
- if (!isNativeStreamUsable(globalThis.WritableStream, 'getWriter')) {
15
- (globalThis as any).WritableStream = WritableStream;
16
- }
package/src/register.ts DELETED
@@ -1,8 +0,0 @@
1
- // Catch-all side-effect module: registers ALL WHATWG Stream globals.
2
- // Prefer granular imports when only specific streams are needed.
3
-
4
- import './register/readable.js';
5
- import './register/writable.js';
6
- import './register/transform.js';
7
- import './register/text-streams.js';
8
- import './register/queuing.js';
@@ -1,114 +0,0 @@
1
- import { run, describe, it, expect } from '@gjsify/unit';
2
-
3
- run({
4
- async StreamsTest() {
5
- await describe('ReadableStream', async () => {
6
- await it('reads enqueued chunks', async () => {
7
- const rs = new ReadableStream({
8
- start(ctrl) { ctrl.enqueue('a'); ctrl.enqueue('b'); ctrl.close(); },
9
- });
10
- const reader = rs.getReader();
11
- expect((await reader.read()).value).toBe('a');
12
- expect((await reader.read()).value).toBe('b');
13
- expect((await reader.read()).done).toBe(true);
14
- });
15
-
16
- await it('supports async iteration', async () => {
17
- const rs = new ReadableStream({
18
- start(ctrl) { ctrl.enqueue(1); ctrl.enqueue(2); ctrl.close(); },
19
- });
20
- const values: number[] = [];
21
- for await (const v of rs) values.push(v);
22
- expect(values).toStrictEqual([1, 2]);
23
- });
24
-
25
- await it('tee() creates two independent streams', async () => {
26
- const rs = new ReadableStream({
27
- start(ctrl) { ctrl.enqueue('x'); ctrl.close(); },
28
- });
29
- const [a, b] = rs.tee();
30
- expect((await a.getReader().read()).value).toBe('x');
31
- expect((await b.getReader().read()).value).toBe('x');
32
- });
33
-
34
- await it('pipeTo() transfers all chunks', async () => {
35
- const received: number[] = [];
36
- const rs = new ReadableStream({
37
- start(ctrl) { ctrl.enqueue(1); ctrl.enqueue(2); ctrl.close(); },
38
- });
39
- const ws = new WritableStream({ write(c) { received.push(c); } });
40
- await rs.pipeTo(ws);
41
- expect(received).toStrictEqual([1, 2]);
42
- });
43
- });
44
-
45
- await describe('WritableStream', async () => {
46
- await it('writes chunks in order', async () => {
47
- const written: string[] = [];
48
- const ws = new WritableStream({ write(chunk) { written.push(chunk); } });
49
- const writer = ws.getWriter();
50
- await writer.write('a');
51
- await writer.write('b');
52
- await writer.close();
53
- expect(written).toStrictEqual(['a', 'b']);
54
- });
55
-
56
- await it('abort() rejects pending writes', async () => {
57
- const ws = new WritableStream();
58
- const writer = ws.getWriter();
59
- writer.abort(new Error('aborted'));
60
- let threw = false;
61
- try { await writer.write('x'); } catch (_) { threw = true; }
62
- expect(threw).toBe(true);
63
- });
64
- });
65
-
66
- await describe('TransformStream', async () => {
67
- await it('transforms chunks', async () => {
68
- const ts = new TransformStream<string, string>({
69
- transform(chunk, ctrl) { ctrl.enqueue(chunk.toUpperCase()); },
70
- });
71
- const writer = ts.writable.getWriter();
72
- writer.write('hello');
73
- writer.close();
74
- expect(await new Response(ts.readable).text()).toBe('HELLO');
75
- });
76
-
77
- await it('passthrough when no transform defined', async () => {
78
- const ts = new TransformStream<string, string>();
79
- const writer = ts.writable.getWriter();
80
- writer.write('pass');
81
- writer.close();
82
- expect(await new Response(ts.readable).text()).toBe('pass');
83
- });
84
- });
85
-
86
- await describe('TextEncoderStream / TextDecoderStream', async () => {
87
- await it('round-trips UTF-8 text', async () => {
88
- const enc = new TextEncoderStream();
89
- const dec = new TextDecoderStream();
90
- enc.readable.pipeTo(dec.writable);
91
- const writer = enc.writable.getWriter();
92
- writer.write('héllo wörld');
93
- writer.close();
94
- expect(await new Response(dec.readable).text()).toBe('héllo wörld');
95
- });
96
- });
97
-
98
- await describe('ByteLengthQueuingStrategy', async () => {
99
- await it('size() returns byteLength', async () => {
100
- const s = new ByteLengthQueuingStrategy({ highWaterMark: 1024 });
101
- expect(s.highWaterMark).toBe(1024);
102
- expect(s.size(new Uint8Array(16))).toBe(16);
103
- });
104
- });
105
-
106
- await describe('CountQueuingStrategy', async () => {
107
- await it('size() always returns 1', async () => {
108
- const s = new CountQueuingStrategy({ highWaterMark: 4 });
109
- expect(s.highWaterMark).toBe(4);
110
- expect(s.size()).toBe(1);
111
- });
112
- });
113
- },
114
- });
package/src/test.mts DELETED
@@ -1,6 +0,0 @@
1
-
2
- import { run } from '@gjsify/unit';
3
-
4
- import testSuite from './index.spec.js';
5
-
6
- run({ testSuite });
@@ -1,183 +0,0 @@
1
- // WHATWG TextDecoderStream for GJS
2
- // Reference: refs/deno/ext/web/08_text_encoding.js (lines 299–412)
3
- // Copyright (c) 2018-2026 the Deno authors. MIT license.
4
- // Reimplemented for GJS — pure TypeScript, no native bindings.
5
- //
6
- // GJS's TextDecoder does not support the `stream` option, so we manually
7
- // buffer incomplete multi-byte UTF-8 sequences across chunks.
8
-
9
- import { TransformStream } from './transform-stream.js';
10
-
11
- /** Check if TextDecoder supports the `stream` option */
12
- function hasStreamingSupport(): boolean {
13
- try {
14
- const td = new TextDecoder();
15
- td.decode(new Uint8Array([0xC3]), { stream: true });
16
- return true;
17
- } catch {
18
- return false;
19
- }
20
- }
21
-
22
- const supportsStreaming = hasStreamingSupport();
23
-
24
- /**
25
- * For UTF-8 without streaming TextDecoder support:
26
- * Determine how many bytes at the end of `buf` form an incomplete
27
- * multi-byte sequence. Returns 0 if the buffer ends on a complete character.
28
- */
29
- function incompleteUtf8Tail(buf: Uint8Array): number {
30
- const len = buf.length;
31
- if (len === 0) return 0;
32
-
33
- // Check last byte: is it a continuation byte or a start byte?
34
- // Start bytes:
35
- // 0xxxxxxx (0x00-0x7F) — single byte, complete
36
- // 110xxxxx (0xC0-0xDF) — starts 2-byte sequence
37
- // 1110xxxx (0xE0-0xEF) — starts 3-byte sequence
38
- // 11110xxx (0xF0-0xF7) — starts 4-byte sequence
39
- // Continuation: 10xxxxxx (0x80-0xBF)
40
-
41
- // Scan back up to 3 bytes to find the start of the last multi-byte sequence
42
- for (let i = 1; i <= Math.min(3, len); i++) {
43
- const b = buf[len - i];
44
- if ((b & 0x80) === 0) {
45
- // ASCII byte — everything before is complete, this byte is complete
46
- return 0;
47
- }
48
- if ((b & 0xC0) !== 0x80) {
49
- // This is a start byte
50
- let expectedLen: number;
51
- if ((b & 0xE0) === 0xC0) expectedLen = 2;
52
- else if ((b & 0xF0) === 0xE0) expectedLen = 3;
53
- else if ((b & 0xF8) === 0xF0) expectedLen = 4;
54
- else return 0; // Invalid start byte, treat as complete
55
-
56
- if (i < expectedLen) {
57
- // We have fewer bytes than the sequence needs — incomplete
58
- return i;
59
- }
60
- // Sequence is complete
61
- return 0;
62
- }
63
- // Continuation byte — keep scanning back
64
- }
65
- // If we scanned back 3 continuation bytes without finding a start byte,
66
- // something is wrong; treat as complete to avoid infinite buffering
67
- return 0;
68
- }
69
-
70
- /**
71
- * TextDecoderStream decodes a stream of bytes into strings.
72
- *
73
- * Uses TextDecoder with `stream: true` when available (Node.js, browsers).
74
- * On GJS where `stream` option is not supported, manually buffers incomplete
75
- * multi-byte UTF-8 sequences across chunks.
76
- */
77
- export class TextDecoderStream {
78
- #decoder: TextDecoder;
79
- #transform: TransformStream;
80
-
81
- constructor(label?: string, options?: TextDecoderOptions) {
82
- this.#decoder = new TextDecoder(label, options);
83
-
84
- if (supportsStreaming) {
85
- // Native streaming support (Node.js, modern browsers)
86
- this.#transform = new TransformStream({
87
- transform: (chunk, controller) => {
88
- const bytes = toUint8Array(chunk);
89
- const decoded = this.#decoder.decode(bytes, { stream: true });
90
- if (decoded) {
91
- controller.enqueue(decoded);
92
- }
93
- },
94
- flush: (controller) => {
95
- const final = this.#decoder.decode();
96
- if (final) {
97
- controller.enqueue(final);
98
- }
99
- },
100
- cancel: () => {
101
- this.#decoder.decode();
102
- },
103
- });
104
- } else {
105
- // GJS fallback: manually buffer incomplete UTF-8 sequences
106
- let pendingBytes = new Uint8Array(0);
107
-
108
- this.#transform = new TransformStream({
109
- transform: (chunk, controller) => {
110
- const incoming = toUint8Array(chunk);
111
-
112
- // Merge pending bytes with new data
113
- let combined: Uint8Array;
114
- if (pendingBytes.length > 0) {
115
- combined = new Uint8Array(pendingBytes.length + incoming.length);
116
- combined.set(pendingBytes, 0);
117
- combined.set(incoming, pendingBytes.length);
118
- } else {
119
- combined = incoming;
120
- }
121
-
122
- // Check for incomplete multi-byte sequence at the end
123
- const tail = incompleteUtf8Tail(combined);
124
- let decodable: Uint8Array;
125
- if (tail > 0) {
126
- decodable = combined.slice(0, combined.length - tail);
127
- pendingBytes = combined.slice(combined.length - tail);
128
- } else {
129
- decodable = combined;
130
- pendingBytes = new Uint8Array(0);
131
- }
132
-
133
- if (decodable.length > 0) {
134
- const decoded = this.#decoder.decode(decodable);
135
- if (decoded) {
136
- controller.enqueue(decoded);
137
- }
138
- }
139
- },
140
- flush: (controller) => {
141
- if (pendingBytes.length > 0) {
142
- // Decode remaining bytes (may produce replacement characters)
143
- const decoded = this.#decoder.decode(pendingBytes);
144
- if (decoded) {
145
- controller.enqueue(decoded);
146
- }
147
- pendingBytes = new Uint8Array(0);
148
- }
149
- },
150
- cancel: () => {
151
- pendingBytes = new Uint8Array(0);
152
- },
153
- });
154
- }
155
- }
156
-
157
- get encoding(): string {
158
- return this.#decoder.encoding;
159
- }
160
-
161
- get fatal(): boolean {
162
- return this.#decoder.fatal;
163
- }
164
-
165
- get ignoreBOM(): boolean {
166
- return this.#decoder.ignoreBOM;
167
- }
168
-
169
- get readable(): ReadableStream<string> {
170
- return this.#transform.readable;
171
- }
172
-
173
- get writable(): WritableStream<BufferSource> {
174
- return this.#transform.writable;
175
- }
176
- }
177
-
178
- function toUint8Array(chunk: BufferSource): Uint8Array {
179
- if (chunk instanceof Uint8Array) return chunk;
180
- if (chunk instanceof ArrayBuffer) return new Uint8Array(chunk);
181
- if (ArrayBuffer.isView(chunk)) return new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
182
- throw new TypeError('chunk must be a BufferSource');
183
- }
@@ -1,62 +0,0 @@
1
- // WHATWG TextEncoderStream for GJS
2
- // Reference: refs/deno/ext/web/08_text_encoding.js (lines 414–490)
3
- // Copyright (c) 2018-2026 the Deno authors. MIT license.
4
- // Reimplemented for GJS — pure TypeScript, no native bindings.
5
-
6
- import { TransformStream } from './transform-stream.js';
7
-
8
- /**
9
- * TextEncoderStream encodes a stream of strings into UTF-8 encoded bytes.
10
- *
11
- * Handles surrogate pairs split across chunks: if the last code unit of a
12
- * chunk is a high surrogate (0xD800–0xDBFF), it is held until the next chunk.
13
- * If the stream ends with a pending high surrogate, U+FFFD replacement bytes
14
- * are emitted.
15
- */
16
- export class TextEncoderStream {
17
- #pendingHighSurrogate: string | null = null;
18
- #encoder = new TextEncoder();
19
- #transform: TransformStream;
20
-
21
- constructor() {
22
- this.#transform = new TransformStream({
23
- transform: (chunk: string, controller) => {
24
- chunk = String(chunk);
25
- if (chunk === '') {
26
- return;
27
- }
28
- if (this.#pendingHighSurrogate !== null) {
29
- chunk = this.#pendingHighSurrogate + chunk;
30
- }
31
- const lastCodeUnit = chunk.charCodeAt(chunk.length - 1);
32
- if (0xD800 <= lastCodeUnit && lastCodeUnit <= 0xDBFF) {
33
- this.#pendingHighSurrogate = chunk.slice(-1);
34
- chunk = chunk.slice(0, -1);
35
- } else {
36
- this.#pendingHighSurrogate = null;
37
- }
38
- if (chunk) {
39
- controller.enqueue(this.#encoder.encode(chunk));
40
- }
41
- },
42
- flush: (controller) => {
43
- if (this.#pendingHighSurrogate !== null) {
44
- // U+FFFD replacement character in UTF-8
45
- controller.enqueue(new Uint8Array([0xEF, 0xBF, 0xBD]));
46
- }
47
- },
48
- });
49
- }
50
-
51
- get encoding(): string {
52
- return 'utf-8';
53
- }
54
-
55
- get readable(): ReadableStream<Uint8Array> {
56
- return this.#transform.readable;
57
- }
58
-
59
- get writable(): WritableStream<string> {
60
- return this.#transform.writable;
61
- }
62
- }