Skip to content

Commit e91053a

Browse files
jasnellBethGriggs
authored andcommitted
stream: implement TextEncoderStream and TextDecoderStream
Experimental as part of the web streams implementation Signed-off-by: James M Snell <[email protected]> PR-URL: #39347 Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent fc13837 commit e91053a

File tree

5 files changed

+429
-0
lines changed

5 files changed

+429
-0
lines changed

doc/api/webstreams.md

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,5 +1118,104 @@ added: v16.5.0
11181118
* `chunk` {any}
11191119
* Returns: {number}
11201120
1121+
### Class: `TextEncoderStream`
1122+
<!-- YAML
1123+
added: REPLACEME
1124+
-->
1125+
1126+
#### `new TextEncoderStream()`
1127+
<!-- YAML
1128+
added: REPLACEME
1129+
-->
1130+
1131+
Creates a new `TextEncoderStream` instance.
1132+
1133+
#### `textEncoderStream.encoding`
1134+
<!-- YAML
1135+
added: REPLACEME
1136+
-->
1137+
1138+
* Type: {string}
1139+
1140+
The encoding supported by the `TextEncoderStream` instance.
1141+
1142+
#### `textEncoderStream.readable`
1143+
<!-- YAML
1144+
added: REPLACEME
1145+
-->
1146+
1147+
* Type: {ReadableStream}
1148+
1149+
#### `textEncoderStream.writable`
1150+
<!-- YAML
1151+
added: REPLACEME
1152+
-->
1153+
1154+
* Type: {WritableStream}
1155+
1156+
### Class: `TextDecoderStream`
1157+
<!-- YAML
1158+
added: REPLACEME
1159+
-->
1160+
1161+
#### `new TextDecoderStream([encoding[, options]])`
1162+
<!-- YAML
1163+
added: REPLACEME
1164+
-->
1165+
1166+
* `encoding` {string} Identifies the `encoding` that this `TextDecoder` instance
1167+
supports. **Default:** `'utf-8'`.
1168+
* `options` {Object}
1169+
* `fatal` {boolean} `true` if decoding failures are fatal.
1170+
* `ignoreBOM` {boolean} When `true`, the `TextDecoderStream` will include the
1171+
byte order mark in the decoded result. When `false`, the byte order mark
1172+
will be removed from the output. This option is only used when `encoding` is
1173+
`'utf-8'`, `'utf-16be'` or `'utf-16le'`. **Default:** `false`.
1174+
1175+
Creates a new `TextDecoderStream` instance.
1176+
1177+
#### `textDecoderStream.encoding`
1178+
<!-- YAML
1179+
added: REPLACEME
1180+
-->
1181+
1182+
* Type: {string}
1183+
1184+
The encoding supported by the `TextDecoderStream` instance.
1185+
1186+
#### `textDecoderStream.fatal`
1187+
<!-- YAML
1188+
added: REPLACEME
1189+
-->
1190+
1191+
* Type: {boolean}
1192+
1193+
The value will be `true` if decoding errors result in a `TypeError` being
1194+
thrown.
1195+
1196+
#### `textDecoderStream.ignoreBOM`
1197+
<!-- YAML
1198+
added: REPLACEME
1199+
-->
1200+
1201+
* Type: {boolean}
1202+
1203+
The value will be `true` if the decoding result will include the byte order
1204+
mark.
1205+
1206+
#### `textDecoderStream.readable`
1207+
<!-- YAML
1208+
added: REPLACEME
1209+
-->
1210+
1211+
* Type: {ReadableStream}
1212+
1213+
#### `textDecoderStream.writable`
1214+
<!-- YAML
1215+
added: REPLACEME
1216+
-->
1217+
1218+
* Type: {WritableStream}
1219+
11211220
[Streams]: stream.md
11221221
[WHATWG Streams Standard]: https://streams.spec.whatwg.org/

lib/internal/webstreams/encoding.js

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
'use strict';
2+
3+
const {
4+
ObjectDefineProperties,
5+
Symbol,
6+
} = primordials;
7+
8+
const {
9+
TextDecoder,
10+
TextEncoder,
11+
} = require('internal/encoding');
12+
13+
const {
14+
TransformStream,
15+
} = require('internal/webstreams/transformstream');
16+
17+
const {
18+
customInspect,
19+
kEnumerableProperty,
20+
} = require('internal/webstreams/util');
21+
22+
const {
23+
codes: {
24+
ERR_INVALID_THIS,
25+
},
26+
} = require('internal/errors');
27+
28+
const {
29+
customInspectSymbol: kInspect
30+
} = require('internal/util');
31+
32+
const kHandle = Symbol('kHandle');
33+
const kTransform = Symbol('kTransform');
34+
const kType = Symbol('kType');
35+
36+
/**
37+
* @typedef {import('./readablestream').ReadableStream} ReadableStream
38+
* @typedef {import('./writablestream').WritableStream} WritableStream
39+
*/
40+
41+
function isTextEncoderStream(value) {
42+
return typeof value?.[kHandle] === 'object' &&
43+
value?.[kType] === 'TextEncoderStream';
44+
}
45+
46+
function isTextDecoderStream(value) {
47+
return typeof value?.[kHandle] === 'object' &&
48+
value?.[kType] === 'TextDecoderStream';
49+
}
50+
51+
class TextEncoderStream {
52+
constructor() {
53+
this[kType] = 'TextEncoderStream';
54+
this[kHandle] = new TextEncoder();
55+
this[kTransform] = new TransformStream({
56+
transform: (chunk, controller) => {
57+
const value = this[kHandle].encode(chunk);
58+
if (value)
59+
controller.enqueue(value);
60+
},
61+
flush: (controller) => {
62+
const value = this[kHandle].encode();
63+
if (value.byteLength > 0)
64+
controller.enqueue(value);
65+
controller.terminate();
66+
},
67+
});
68+
}
69+
70+
/**
71+
* @readonly
72+
* @type {string}
73+
*/
74+
get encoding() {
75+
if (!isTextEncoderStream(this))
76+
throw new ERR_INVALID_THIS('TextEncoderStream');
77+
return this[kHandle].encoding;
78+
}
79+
80+
/**
81+
* @readonly
82+
* @type {ReadableStream}
83+
*/
84+
get readable() {
85+
if (!isTextEncoderStream(this))
86+
throw new ERR_INVALID_THIS('TextEncoderStream');
87+
return this[kTransform].readable;
88+
}
89+
90+
/**
91+
* @readonly
92+
* @type {WritableStream}
93+
*/
94+
get writable() {
95+
if (!isTextEncoderStream(this))
96+
throw new ERR_INVALID_THIS('TextEncoderStream');
97+
return this[kTransform].writable;
98+
}
99+
100+
[kInspect](depth, options) {
101+
if (!isTextEncoderStream(this))
102+
throw new ERR_INVALID_THIS('TextEncoderStream');
103+
return customInspect(depth, options, 'TextEncoderStream', {
104+
encoding: this[kHandle].encoding,
105+
readable: this[kTransform].readable,
106+
writable: this[kTransform].writable,
107+
});
108+
}
109+
}
110+
111+
class TextDecoderStream {
112+
/**
113+
* @param {string} [encoding]
114+
* @param {{
115+
* fatal? : boolean,
116+
* ignoreBOM? : boolean,
117+
* }} [options]
118+
*/
119+
constructor(encoding = 'utf-8', options = {}) {
120+
this[kType] = 'TextDecoderStream';
121+
this[kHandle] = new TextDecoder(encoding, options);
122+
this[kTransform] = new TransformStream({
123+
transform: (chunk, controller) => {
124+
const value = this[kHandle].decode(chunk, { stream: true });
125+
if (value)
126+
controller.enqueue(value);
127+
},
128+
flush: (controller) => {
129+
const value = this[kHandle].decode();
130+
if (value)
131+
controller.enqueue(value);
132+
controller.terminate();
133+
},
134+
});
135+
}
136+
137+
/**
138+
* @readonly
139+
* @type {string}
140+
*/
141+
get encoding() {
142+
if (!isTextDecoderStream(this))
143+
throw new ERR_INVALID_THIS('TextDecoderStream');
144+
return this[kHandle].encoding;
145+
}
146+
147+
/**
148+
* @readonly
149+
* @type {boolean}
150+
*/
151+
get fatal() {
152+
if (!isTextDecoderStream(this))
153+
throw new ERR_INVALID_THIS('TextDecoderStream');
154+
return this[kHandle].fatal;
155+
}
156+
157+
/**
158+
* @readonly
159+
* @type {boolean}
160+
*/
161+
get ignoreBOM() {
162+
if (!isTextDecoderStream(this))
163+
throw new ERR_INVALID_THIS('TextDecoderStream');
164+
return this[kHandle].ignoreBOM;
165+
}
166+
167+
/**
168+
* @readonly
169+
* @type {ReadableStream}
170+
*/
171+
get readable() {
172+
if (!isTextDecoderStream(this))
173+
throw new ERR_INVALID_THIS('TextDecoderStream');
174+
return this[kTransform].readable;
175+
}
176+
177+
/**
178+
* @readonly
179+
* @type {WritableStream}
180+
*/
181+
get writable() {
182+
if (!isTextDecoderStream(this))
183+
throw new ERR_INVALID_THIS('TextDecoderStream');
184+
return this[kTransform].writable;
185+
}
186+
187+
[kInspect](depth, options) {
188+
if (!isTextDecoderStream(this))
189+
throw new ERR_INVALID_THIS('TextDecoderStream');
190+
return customInspect(depth, options, 'TextDecoderStream', {
191+
encoding: this[kHandle].encoding,
192+
fatal: this[kHandle].fatal,
193+
ignoreBOM: this[kHandle].ignoreBOM,
194+
readable: this[kTransform].readable,
195+
writable: this[kTransform].writable,
196+
});
197+
}
198+
}
199+
200+
ObjectDefineProperties(TextEncoderStream.prototype, {
201+
encoding: kEnumerableProperty,
202+
readable: kEnumerableProperty,
203+
writable: kEnumerableProperty,
204+
});
205+
206+
ObjectDefineProperties(TextDecoderStream.prototype, {
207+
encoding: kEnumerableProperty,
208+
fatal: kEnumerableProperty,
209+
ignoreBOM: kEnumerableProperty,
210+
readable: kEnumerableProperty,
211+
writable: kEnumerableProperty,
212+
});
213+
214+
module.exports = {
215+
TextEncoderStream,
216+
TextDecoderStream,
217+
};

lib/stream/web.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ const {
3131
CountQueuingStrategy,
3232
} = require('internal/webstreams/queuingstrategies');
3333

34+
const {
35+
TextEncoderStream,
36+
TextDecoderStream,
37+
} = require('internal/webstreams/encoding');
38+
3439
module.exports = {
3540
ReadableStream,
3641
ReadableStreamDefaultReader,
@@ -45,4 +50,6 @@ module.exports = {
4550
WritableStreamDefaultController,
4651
ByteLengthQueuingStrategy,
4752
CountQueuingStrategy,
53+
TextEncoderStream,
54+
TextDecoderStream,
4855
};

0 commit comments

Comments
 (0)