mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
feat(std/node): Add Readable Stream / Writable Stream / errors support (#7569)
This commit is contained in:
parent
ce890f2ae7
commit
a4f27c4d57
16 changed files with 4217 additions and 53 deletions
|
@ -1,52 +1,99 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
/************ NOT IMPLEMENTED
|
||||||
// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
|
* ERR_INVALID_ARG_VALUE
|
||||||
|
* ERR_INVALID_MODULE_SPECIFIER
|
||||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
* ERR_INVALID_PACKAGE_TARGET
|
||||||
// copy of this software and associated documentation files (the
|
* ERR_INVALID_URL_SCHEME
|
||||||
// "Software"), to deal in the Software without restriction, including
|
* ERR_MANIFEST_ASSERT_INTEGRITY
|
||||||
// without limitation the rights to use, copy, modify, merge, publish,
|
* ERR_MISSING_ARGS
|
||||||
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
* ERR_MODULE_NOT_FOUND
|
||||||
// persons to whom the Software is furnished to do so, subject to the
|
* ERR_PACKAGE_PATH_NOT_EXPORTED
|
||||||
// following conditions:
|
* ERR_QUICSESSION_VERSION_NEGOTIATION
|
||||||
|
* ERR_REQUIRE_ESM
|
||||||
// The above copyright notice and this permission notice shall be included
|
* ERR_SOCKET_BAD_PORT
|
||||||
// in all copies or substantial portions of the Software.
|
* ERR_TLS_CERT_ALTNAME_INVALID
|
||||||
|
* ERR_UNHANDLED_ERROR
|
||||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
* ERR_WORKER_INVALID_EXEC_ARGV
|
||||||
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
* ERR_WORKER_PATH
|
||||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
* ERR_QUIC_ERROR
|
||||||
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
* ERR_SOCKET_BUFFER_SIZE //System error, shouldn't ever happen inside Deno
|
||||||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
* ERR_SYSTEM_ERROR //System error, shouldn't ever happen inside Deno
|
||||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
* ERR_TTY_INIT_FAILED //System error, shouldn't ever happen inside Deno
|
||||||
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* ERR_INVALID_PACKAGE_CONFIG // package.json stuff, probably useless
|
||||||
|
*************/
|
||||||
|
|
||||||
import { unreachable } from "../testing/asserts.ts";
|
import { unreachable } from "../testing/asserts.ts";
|
||||||
|
|
||||||
// It will do so until we'll have Node errors completely ported (#5944):
|
/**
|
||||||
|
* All error instances in Node have additional methods and properties
|
||||||
|
* This export class is meant to be extended by these instances abstracting native JS error instances
|
||||||
|
*/
|
||||||
|
export class NodeErrorAbstraction extends Error {
|
||||||
|
code: string;
|
||||||
|
|
||||||
// Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L251
|
constructor(name: string, code: string, message: string) {
|
||||||
// Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L299
|
super(message);
|
||||||
// Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L325
|
this.code = code;
|
||||||
// Ref: https://github.com/nodejs/node/blob/50d28d4b3a616b04537feff014aa70437f064e30/lib/internal/errors.js#L943
|
|
||||||
class ERR_INVALID_ARG_TYPE extends TypeError {
|
|
||||||
code = "ERR_INVALID_ARG_TYPE";
|
|
||||||
|
|
||||||
constructor(a1: string, a2: string, a3: unknown) {
|
|
||||||
super(
|
|
||||||
`The "${a1}" argument must be of type ${a2.toLocaleLowerCase()}. Received ${typeof a3} (${a3})`,
|
|
||||||
);
|
|
||||||
const { name } = this;
|
|
||||||
// Add the error code to the name to include it in the stack trace.
|
|
||||||
this.name = `${name} [${this.code}]`;
|
|
||||||
// Access the stack to generate the error message including the error code from the name.
|
|
||||||
this.stack;
|
|
||||||
// Reset the name to the actual name.
|
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
//This number changes dependending on the name of this class
|
||||||
|
//20 characters as of now
|
||||||
|
this.stack = this.stack && `${name} [${this.code}]${this.stack.slice(20)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.name} [${this.code}]: ${this.message}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ERR_OUT_OF_RANGE extends RangeError {
|
export class NodeError extends NodeErrorAbstraction {
|
||||||
|
constructor(code: string, message: string) {
|
||||||
|
super(Error.prototype.name, code, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeSyntaxError extends NodeErrorAbstraction
|
||||||
|
implements SyntaxError {
|
||||||
|
constructor(code: string, message: string) {
|
||||||
|
super(SyntaxError.prototype.name, code, message);
|
||||||
|
Object.setPrototypeOf(this, SyntaxError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeRangeError extends NodeErrorAbstraction {
|
||||||
|
constructor(code: string, message: string) {
|
||||||
|
super(RangeError.prototype.name, code, message);
|
||||||
|
Object.setPrototypeOf(this, RangeError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeTypeError extends NodeErrorAbstraction implements TypeError {
|
||||||
|
constructor(code: string, message: string) {
|
||||||
|
super(TypeError.prototype.name, code, message);
|
||||||
|
Object.setPrototypeOf(this, TypeError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeURIError extends NodeErrorAbstraction implements URIError {
|
||||||
|
constructor(code: string, message: string) {
|
||||||
|
super(URIError.prototype.name, code, message);
|
||||||
|
Object.setPrototypeOf(this, URIError.prototype);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ERR_INVALID_ARG_TYPE extends NodeTypeError {
|
||||||
|
constructor(a1: string, a2: string | string[], a3: unknown) {
|
||||||
|
super(
|
||||||
|
"ERR_INVALID_ARG_TYPE",
|
||||||
|
`The "${a1}" argument must be of type ${
|
||||||
|
typeof a2 === "string"
|
||||||
|
? a2.toLocaleLowerCase()
|
||||||
|
: a2.map((x) => x.toLocaleLowerCase()).join(", ")
|
||||||
|
}. Received ${typeof a3} (${a3})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ERR_OUT_OF_RANGE extends RangeError {
|
||||||
code = "ERR_OUT_OF_RANGE";
|
code = "ERR_OUT_OF_RANGE";
|
||||||
|
|
||||||
constructor(str: string, range: string, received: unknown) {
|
constructor(str: string, range: string, received: unknown) {
|
||||||
|
@ -64,11 +111,6 @@ class ERR_OUT_OF_RANGE extends RangeError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codes = {
|
|
||||||
ERR_INVALID_ARG_TYPE,
|
|
||||||
ERR_OUT_OF_RANGE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// In Node these values are coming from libuv:
|
// In Node these values are coming from libuv:
|
||||||
// Ref: https://github.com/libuv/libuv/blob/v1.x/include/uv/errno.h
|
// Ref: https://github.com/libuv/libuv/blob/v1.x/include/uv/errno.h
|
||||||
// Ref: https://github.com/nodejs/node/blob/524123fbf064ff64bb6fcd83485cfc27db932f68/lib/internal/errors.js#L383
|
// Ref: https://github.com/nodejs/node/blob/524123fbf064ff64bb6fcd83485cfc27db932f68/lib/internal/errors.js#L383
|
||||||
|
@ -342,3 +384,100 @@ export const errorMap = new Map<number, [string, string]>(
|
||||||
? linux
|
? linux
|
||||||
: unreachable(),
|
: unreachable(),
|
||||||
);
|
);
|
||||||
|
export class ERR_METHOD_NOT_IMPLEMENTED extends NodeError {
|
||||||
|
constructor(x: string) {
|
||||||
|
super(
|
||||||
|
"ERR_METHOD_NOT_IMPLEMENTED",
|
||||||
|
`The ${x} method is not implemented`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_MULTIPLE_CALLBACK extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_MULTIPLE_CALLBACK",
|
||||||
|
`Callback called multiple times`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_ALREADY_FINISHED extends NodeError {
|
||||||
|
constructor(x: string) {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_ALREADY_FINISHED",
|
||||||
|
`Cannot call ${x} after a stream was finished`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_CANNOT_PIPE extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_CANNOT_PIPE",
|
||||||
|
`Cannot pipe, not readable`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_DESTROYED extends NodeError {
|
||||||
|
constructor(x: string) {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_DESTROYED",
|
||||||
|
`Cannot call ${x} after a stream was destroyed`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_NULL_VALUES extends NodeTypeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_NULL_VALUES",
|
||||||
|
`May not write null values to stream`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_PREMATURE_CLOSE extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_PREMATURE_CLOSE",
|
||||||
|
`Premature close`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_PUSH_AFTER_EOF extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_PUSH_AFTER_EOF",
|
||||||
|
`stream.push() after EOF`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_UNSHIFT_AFTER_END_EVENT extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_UNSHIFT_AFTER_END_EVENT",
|
||||||
|
`stream.unshift() after end event`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_STREAM_WRITE_AFTER_END extends NodeError {
|
||||||
|
constructor() {
|
||||||
|
super(
|
||||||
|
"ERR_STREAM_WRITE_AFTER_END",
|
||||||
|
`write after end`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class ERR_UNKNOWN_ENCODING extends NodeTypeError {
|
||||||
|
constructor(x: string) {
|
||||||
|
super(
|
||||||
|
"ERR_UNKNOWN_ENCODING",
|
||||||
|
`Unknown encoding: ${x}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ERR_INVALID_OPT_VALUE extends NodeTypeError {
|
||||||
|
constructor(name: string, value: unknown) {
|
||||||
|
super(
|
||||||
|
"ERR_INVALID_OPT_VALUE",
|
||||||
|
`The value "${value}" is invalid for option "${name}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
260
std/node/_stream/async_iterator.ts
Normal file
260
std/node/_stream/async_iterator.ts
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import type { Buffer } from "../buffer.ts";
|
||||||
|
import finished from "./end-of-stream.ts";
|
||||||
|
import Readable from "./readable.ts";
|
||||||
|
import type Stream from "./stream.ts";
|
||||||
|
|
||||||
|
const kLastResolve = Symbol("lastResolve");
|
||||||
|
const kLastReject = Symbol("lastReject");
|
||||||
|
const kError = Symbol("error");
|
||||||
|
const kEnded = Symbol("ended");
|
||||||
|
const kLastPromise = Symbol("lastPromise");
|
||||||
|
const kHandlePromise = Symbol("handlePromise");
|
||||||
|
const kStream = Symbol("stream");
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Add Duplex streams
|
||||||
|
type IterableStreams = Stream | Readable;
|
||||||
|
|
||||||
|
type IterableItem = Buffer | string | Uint8Array | undefined;
|
||||||
|
type ReadableIteratorResult = IteratorResult<IterableItem>;
|
||||||
|
|
||||||
|
function initIteratorSymbols(
|
||||||
|
o: ReadableStreamAsyncIterator,
|
||||||
|
symbols: symbol[],
|
||||||
|
) {
|
||||||
|
const properties: PropertyDescriptorMap = {};
|
||||||
|
for (const sym in symbols) {
|
||||||
|
properties[sym] = {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Object.defineProperties(o, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// function isRequest(stream: any) {
|
||||||
|
// return stream && stream.setHeader && typeof stream.abort === "function";
|
||||||
|
// }
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Should be any implementation of stream
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
function destroyer(stream: any, err?: Error | null) {
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// if (isRequest(stream)) return stream.abort();
|
||||||
|
// if (isRequest(stream.req)) return stream.req.abort();
|
||||||
|
if (typeof stream.destroy === "function") return stream.destroy(err);
|
||||||
|
if (typeof stream.close === "function") return stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIterResult(
|
||||||
|
value: IterableItem,
|
||||||
|
done: boolean,
|
||||||
|
): ReadableIteratorResult {
|
||||||
|
return { value, done };
|
||||||
|
}
|
||||||
|
|
||||||
|
function readAndResolve(iter: ReadableStreamAsyncIterator) {
|
||||||
|
const resolve = iter[kLastResolve];
|
||||||
|
if (resolve !== null) {
|
||||||
|
const data = iter[kStream].read();
|
||||||
|
if (data !== null) {
|
||||||
|
iter[kLastPromise] = null;
|
||||||
|
iter[kLastResolve] = null;
|
||||||
|
iter[kLastReject] = null;
|
||||||
|
resolve(createIterResult(data, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReadable(iter: ReadableStreamAsyncIterator) {
|
||||||
|
queueMicrotask(() => readAndResolve(iter));
|
||||||
|
}
|
||||||
|
|
||||||
|
function wrapForNext(
|
||||||
|
lastPromise: Promise<ReadableIteratorResult>,
|
||||||
|
iter: ReadableStreamAsyncIterator,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
resolve: (value: ReadableIteratorResult) => void,
|
||||||
|
reject: (error: Error) => void,
|
||||||
|
) => {
|
||||||
|
lastPromise.then(() => {
|
||||||
|
if (iter[kEnded]) {
|
||||||
|
resolve(createIterResult(undefined, true));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter[kHandlePromise](resolve, reject);
|
||||||
|
}, reject);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(self: ReadableStreamAsyncIterator, err?: Error) {
|
||||||
|
return new Promise(
|
||||||
|
(
|
||||||
|
resolve: (result: ReadableIteratorResult) => void,
|
||||||
|
reject: (error: Error) => void,
|
||||||
|
) => {
|
||||||
|
const stream = self[kStream];
|
||||||
|
|
||||||
|
finished(stream, (err) => {
|
||||||
|
if (err && err.code !== "ERR_STREAM_PREMATURE_CLOSE") {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(createIterResult(undefined, true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
destroyer(stream, err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AsyncIteratorPrototype = Object.getPrototypeOf(
|
||||||
|
Object.getPrototypeOf(async function* () {}).prototype,
|
||||||
|
);
|
||||||
|
|
||||||
|
class ReadableStreamAsyncIterator
|
||||||
|
implements AsyncIterableIterator<IterableItem> {
|
||||||
|
[kEnded]: boolean;
|
||||||
|
[kError]: Error | null = null;
|
||||||
|
[kHandlePromise] = (
|
||||||
|
resolve: (value: ReadableIteratorResult) => void,
|
||||||
|
reject: (value: Error) => void,
|
||||||
|
) => {
|
||||||
|
const data = this[kStream].read();
|
||||||
|
if (data) {
|
||||||
|
this[kLastPromise] = null;
|
||||||
|
this[kLastResolve] = null;
|
||||||
|
this[kLastReject] = null;
|
||||||
|
resolve(createIterResult(data, false));
|
||||||
|
} else {
|
||||||
|
this[kLastResolve] = resolve;
|
||||||
|
this[kLastReject] = reject;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
[kLastPromise]: null | Promise<ReadableIteratorResult>;
|
||||||
|
[kLastReject]: null | ((value: Error) => void) = null;
|
||||||
|
[kLastResolve]: null | ((value: ReadableIteratorResult) => void) = null;
|
||||||
|
[kStream]: Readable;
|
||||||
|
[Symbol.asyncIterator] = AsyncIteratorPrototype[Symbol.asyncIterator];
|
||||||
|
|
||||||
|
constructor(stream: Readable) {
|
||||||
|
this[kEnded] = stream.readableEnded || stream._readableState.endEmitted;
|
||||||
|
this[kStream] = stream;
|
||||||
|
initIteratorSymbols(this, [
|
||||||
|
kEnded,
|
||||||
|
kError,
|
||||||
|
kHandlePromise,
|
||||||
|
kLastPromise,
|
||||||
|
kLastReject,
|
||||||
|
kLastResolve,
|
||||||
|
kStream,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
get stream() {
|
||||||
|
return this[kStream];
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): Promise<ReadableIteratorResult> {
|
||||||
|
const error = this[kError];
|
||||||
|
if (error !== null) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this[kEnded]) {
|
||||||
|
return Promise.resolve(createIterResult(undefined, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this[kStream].destroyed) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this[kError]) {
|
||||||
|
reject(this[kError]);
|
||||||
|
} else if (this[kEnded]) {
|
||||||
|
resolve(createIterResult(undefined, true));
|
||||||
|
} else {
|
||||||
|
finished(this[kStream], (err) => {
|
||||||
|
if (err && err.code !== "ERR_STREAM_PREMATURE_CLOSE") {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(createIterResult(undefined, true));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const lastPromise = this[kLastPromise];
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
if (lastPromise) {
|
||||||
|
promise = new Promise(wrapForNext(lastPromise, this));
|
||||||
|
} else {
|
||||||
|
const data = this[kStream].read();
|
||||||
|
if (data !== null) {
|
||||||
|
return Promise.resolve(createIterResult(data, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = new Promise(this[kHandlePromise]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this[kLastPromise] = promise;
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
return(): Promise<ReadableIteratorResult> {
|
||||||
|
return finish(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw(err: Error): Promise<ReadableIteratorResult> {
|
||||||
|
return finish(this, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createReadableStreamAsyncIterator = (stream: IterableStreams) => {
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
if (typeof (stream as any).read !== "function") {
|
||||||
|
const src = stream;
|
||||||
|
stream = new Readable({ objectMode: true }).wrap(src);
|
||||||
|
finished(stream, (err) => destroyer(src, err));
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterator = new ReadableStreamAsyncIterator(stream as Readable);
|
||||||
|
iterator[kLastPromise] = null;
|
||||||
|
|
||||||
|
finished(stream, { writable: false }, (err) => {
|
||||||
|
if (err && err.code !== "ERR_STREAM_PREMATURE_CLOSE") {
|
||||||
|
const reject = iterator[kLastReject];
|
||||||
|
if (reject !== null) {
|
||||||
|
iterator[kLastPromise] = null;
|
||||||
|
iterator[kLastResolve] = null;
|
||||||
|
iterator[kLastReject] = null;
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
iterator[kError] = err;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolve = iterator[kLastResolve];
|
||||||
|
if (resolve !== null) {
|
||||||
|
iterator[kLastPromise] = null;
|
||||||
|
iterator[kLastResolve] = null;
|
||||||
|
iterator[kLastReject] = null;
|
||||||
|
resolve(createIterResult(undefined, true));
|
||||||
|
}
|
||||||
|
iterator[kEnded] = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on("readable", onReadable.bind(null, iterator));
|
||||||
|
|
||||||
|
return iterator;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createReadableStreamAsyncIterator;
|
249
std/node/_stream/async_iterator_test.ts
Normal file
249
std/node/_stream/async_iterator_test.ts
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import Readable from "./readable.ts";
|
||||||
|
import Stream from "./stream.ts";
|
||||||
|
import toReadableAsyncIterator from "./async_iterator.ts";
|
||||||
|
import { deferred } from "../../async/mod.ts";
|
||||||
|
import { assertEquals, assertThrowsAsync } from "../../testing/asserts.ts";
|
||||||
|
|
||||||
|
Deno.test("Stream to async iterator", async () => {
|
||||||
|
let destroyExecuted = 0;
|
||||||
|
const destroyExecutedExpected = 1;
|
||||||
|
const destroyExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
class AsyncIteratorStream extends Stream {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
destroyExecuted++;
|
||||||
|
if (destroyExecuted == destroyExecutedExpected) {
|
||||||
|
destroyExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Symbol.asyncIterator] = Readable.prototype[Symbol.asyncIterator];
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = new AsyncIteratorStream();
|
||||||
|
|
||||||
|
queueMicrotask(() => {
|
||||||
|
stream.emit("data", "hello");
|
||||||
|
stream.emit("data", "world");
|
||||||
|
stream.emit("end");
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = "";
|
||||||
|
|
||||||
|
for await (const d of stream) {
|
||||||
|
res += d;
|
||||||
|
}
|
||||||
|
assertEquals(res, "helloworld");
|
||||||
|
|
||||||
|
const destroyTimeout = setTimeout(
|
||||||
|
() => destroyExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await destroyExpectedExecutions;
|
||||||
|
clearTimeout(destroyTimeout);
|
||||||
|
assertEquals(destroyExecuted, destroyExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Stream to async iterator throws on 'error' emitted", async () => {
|
||||||
|
let closeExecuted = 0;
|
||||||
|
const closeExecutedExpected = 1;
|
||||||
|
const closeExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
let errorExecuted = 0;
|
||||||
|
const errorExecutedExpected = 1;
|
||||||
|
const errorExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
class StreamImplementation extends Stream {
|
||||||
|
close() {
|
||||||
|
closeExecuted++;
|
||||||
|
if (closeExecuted == closeExecutedExpected) {
|
||||||
|
closeExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = new StreamImplementation();
|
||||||
|
queueMicrotask(() => {
|
||||||
|
stream.emit("data", 0);
|
||||||
|
stream.emit("data", 1);
|
||||||
|
stream.emit("error", new Error("asd"));
|
||||||
|
});
|
||||||
|
|
||||||
|
toReadableAsyncIterator(stream)
|
||||||
|
.next()
|
||||||
|
.catch((err) => {
|
||||||
|
errorExecuted++;
|
||||||
|
if (errorExecuted == errorExecutedExpected) {
|
||||||
|
errorExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assertEquals(err.message, "asd");
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeTimeout = setTimeout(
|
||||||
|
() => closeExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
const errorTimeout = setTimeout(
|
||||||
|
() => errorExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await closeExpectedExecutions;
|
||||||
|
await errorExpectedExecutions;
|
||||||
|
clearTimeout(closeTimeout);
|
||||||
|
clearTimeout(errorTimeout);
|
||||||
|
assertEquals(closeExecuted, closeExecutedExpected);
|
||||||
|
assertEquals(errorExecuted, errorExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator matches values of Readable", async () => {
|
||||||
|
const readable = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
readable.push(0);
|
||||||
|
readable.push(1);
|
||||||
|
readable.push(null);
|
||||||
|
|
||||||
|
const iter = readable[Symbol.asyncIterator]();
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
await iter.next().then(({ value }) => value),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
for await (const d of iter) {
|
||||||
|
assertEquals(d, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator throws on Readable destroyed sync", async () => {
|
||||||
|
const message = "kaboom from read";
|
||||||
|
|
||||||
|
const readable = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
this.destroy(new Error(message));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await assertThrowsAsync(
|
||||||
|
async () => {
|
||||||
|
// deno-lint-ignore no-empty
|
||||||
|
for await (const k of readable) {}
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator throws on Readable destroyed async", async () => {
|
||||||
|
const message = "kaboom";
|
||||||
|
const readable = new Readable({
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
const iterator = readable[Symbol.asyncIterator]();
|
||||||
|
|
||||||
|
readable.destroy(new Error(message));
|
||||||
|
|
||||||
|
await assertThrowsAsync(
|
||||||
|
iterator.next.bind(iterator),
|
||||||
|
Error,
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator finishes the iterator when Readable destroyed", async () => {
|
||||||
|
const readable = new Readable({
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
readable.destroy();
|
||||||
|
|
||||||
|
const { done } = await readable[Symbol.asyncIterator]().next();
|
||||||
|
assertEquals(done, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator finishes all item promises when Readable destroyed", async () => {
|
||||||
|
const r = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const b = r[Symbol.asyncIterator]();
|
||||||
|
const c = b.next();
|
||||||
|
const d = b.next();
|
||||||
|
r.destroy();
|
||||||
|
assertEquals(await c, { done: true, value: undefined });
|
||||||
|
assertEquals(await d, { done: true, value: undefined });
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator: 'next' is triggered by Readable push", async () => {
|
||||||
|
const max = 42;
|
||||||
|
let readed = 0;
|
||||||
|
let received = 0;
|
||||||
|
const readable = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
this.push("hello");
|
||||||
|
if (++readed === max) {
|
||||||
|
this.push(null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for await (const k of readable) {
|
||||||
|
received++;
|
||||||
|
assertEquals(k, "hello");
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(readed, received);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Async iterator: 'close' called on forced iteration end", async () => {
|
||||||
|
let closeExecuted = 0;
|
||||||
|
const closeExecutedExpected = 1;
|
||||||
|
const closeExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
class IndestructibleReadable extends Readable {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
autoDestroy: false,
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
closeExecuted++;
|
||||||
|
if (closeExecuted == closeExecutedExpected) {
|
||||||
|
closeExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
readable.emit("close");
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
destroy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const readable = new IndestructibleReadable();
|
||||||
|
readable.push("asd");
|
||||||
|
readable.push("asd");
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
for await (const d of readable) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeTimeout = setTimeout(
|
||||||
|
() => closeExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await closeExpectedExecutions;
|
||||||
|
clearTimeout(closeTimeout);
|
||||||
|
assertEquals(closeExecuted, closeExecutedExpected);
|
||||||
|
});
|
183
std/node/_stream/buffer_list.ts
Normal file
183
std/node/_stream/buffer_list.ts
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
|
||||||
|
type BufferListItem = {
|
||||||
|
data: Buffer | string | Uint8Array;
|
||||||
|
next: BufferListItem | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class BufferList {
|
||||||
|
head: BufferListItem | null = null;
|
||||||
|
tail: BufferListItem | null = null;
|
||||||
|
length: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.head = null;
|
||||||
|
this.tail = null;
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(v: Buffer | string | Uint8Array) {
|
||||||
|
const entry = { data: v, next: null };
|
||||||
|
if (this.length > 0) {
|
||||||
|
(this.tail as BufferListItem).next = entry;
|
||||||
|
} else {
|
||||||
|
this.head = entry;
|
||||||
|
}
|
||||||
|
this.tail = entry;
|
||||||
|
++this.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
unshift(v: Buffer | string | Uint8Array) {
|
||||||
|
const entry = { data: v, next: this.head };
|
||||||
|
if (this.length === 0) {
|
||||||
|
this.tail = entry;
|
||||||
|
}
|
||||||
|
this.head = entry;
|
||||||
|
++this.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
shift() {
|
||||||
|
if (this.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ret = (this.head as BufferListItem).data;
|
||||||
|
if (this.length === 1) {
|
||||||
|
this.head = this.tail = null;
|
||||||
|
} else {
|
||||||
|
this.head = (this.head as BufferListItem).next;
|
||||||
|
}
|
||||||
|
--this.length;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.head = this.tail = null;
|
||||||
|
this.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
join(s: string) {
|
||||||
|
if (this.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
let p: BufferListItem | null = (this.head as BufferListItem);
|
||||||
|
let ret = "" + p.data;
|
||||||
|
p = p.next;
|
||||||
|
while (p) {
|
||||||
|
ret += s + p.data;
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
concat(n: number) {
|
||||||
|
if (this.length === 0) {
|
||||||
|
return Buffer.alloc(0);
|
||||||
|
}
|
||||||
|
const ret = Buffer.allocUnsafe(n >>> 0);
|
||||||
|
let p = this.head;
|
||||||
|
let i = 0;
|
||||||
|
while (p) {
|
||||||
|
ret.set(p.data as Buffer, i);
|
||||||
|
i += p.data.length;
|
||||||
|
p = p.next;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumes a specified amount of bytes or characters from the buffered data.
|
||||||
|
consume(n: number, hasStrings: boolean) {
|
||||||
|
const data = (this.head as BufferListItem).data;
|
||||||
|
if (n < data.length) {
|
||||||
|
// `slice` is the same for buffers and strings.
|
||||||
|
const slice = data.slice(0, n);
|
||||||
|
(this.head as BufferListItem).data = data.slice(n);
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
if (n === data.length) {
|
||||||
|
// First chunk is a perfect match.
|
||||||
|
return this.shift();
|
||||||
|
}
|
||||||
|
// Result spans more than one buffer.
|
||||||
|
return hasStrings ? this._getString(n) : this._getBuffer(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
first() {
|
||||||
|
return (this.head as BufferListItem).data;
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator]() {
|
||||||
|
for (let p = this.head; p; p = p.next) {
|
||||||
|
yield p.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumes a specified amount of characters from the buffered data.
|
||||||
|
_getString(n: number) {
|
||||||
|
let ret = "";
|
||||||
|
let p: BufferListItem | null = (this.head as BufferListItem);
|
||||||
|
let c = 0;
|
||||||
|
p = p.next as BufferListItem;
|
||||||
|
do {
|
||||||
|
const str = p.data;
|
||||||
|
if (n > str.length) {
|
||||||
|
ret += str;
|
||||||
|
n -= str.length;
|
||||||
|
} else {
|
||||||
|
if (n === str.length) {
|
||||||
|
ret += str;
|
||||||
|
++c;
|
||||||
|
if (p.next) {
|
||||||
|
this.head = p.next;
|
||||||
|
} else {
|
||||||
|
this.head = this.tail = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret += str.slice(0, n);
|
||||||
|
this.head = p;
|
||||||
|
p.data = str.slice(n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++c;
|
||||||
|
p = p.next;
|
||||||
|
} while (p);
|
||||||
|
this.length -= c;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumes a specified amount of bytes from the buffered data.
|
||||||
|
_getBuffer(n: number) {
|
||||||
|
const ret = Buffer.allocUnsafe(n);
|
||||||
|
const retLen = n;
|
||||||
|
let p: BufferListItem | null = (this.head as BufferListItem);
|
||||||
|
let c = 0;
|
||||||
|
p = p.next as BufferListItem;
|
||||||
|
do {
|
||||||
|
const buf = p.data as Buffer;
|
||||||
|
if (n > buf.length) {
|
||||||
|
ret.set(buf, retLen - n);
|
||||||
|
n -= buf.length;
|
||||||
|
} else {
|
||||||
|
if (n === buf.length) {
|
||||||
|
ret.set(buf, retLen - n);
|
||||||
|
++c;
|
||||||
|
if (p.next) {
|
||||||
|
this.head = p.next;
|
||||||
|
} else {
|
||||||
|
this.head = this.tail = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.set(new Uint8Array(buf.buffer, buf.byteOffset, n), retLen - n);
|
||||||
|
this.head = p;
|
||||||
|
p.data = buf.slice(n);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++c;
|
||||||
|
p = p.next;
|
||||||
|
} while (p);
|
||||||
|
this.length -= c;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
3
std/node/_stream/duplex.ts
Normal file
3
std/node/_stream/duplex.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
export const errorOrDestroy = (...args: any[]) => {};
|
240
std/node/_stream/end-of-stream.ts
Normal file
240
std/node/_stream/end-of-stream.ts
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { once } from "../_utils.ts";
|
||||||
|
import type Readable from "./readable.ts";
|
||||||
|
import type Stream from "./stream.ts";
|
||||||
|
import type { ReadableState } from "./readable.ts";
|
||||||
|
import type Writable from "./writable.ts";
|
||||||
|
import type { WritableState } from "./writable.ts";
|
||||||
|
import {
|
||||||
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
ERR_STREAM_PREMATURE_CLOSE,
|
||||||
|
NodeErrorAbstraction,
|
||||||
|
} from "../_errors.ts";
|
||||||
|
|
||||||
|
type StreamImplementations = Readable | Stream | Writable;
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// function isRequest(stream: Stream) {
|
||||||
|
// return stream.setHeader && typeof stream.abort === "function";
|
||||||
|
// }
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
function isReadable(stream: any) {
|
||||||
|
return typeof stream.readable === "boolean" ||
|
||||||
|
typeof stream.readableEnded === "boolean" ||
|
||||||
|
!!stream._readableState;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
function isWritable(stream: any) {
|
||||||
|
return typeof stream.writable === "boolean" ||
|
||||||
|
typeof stream.writableEnded === "boolean" ||
|
||||||
|
!!stream._writableState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWritableFinished(stream: Writable) {
|
||||||
|
if (stream.writableFinished) return true;
|
||||||
|
const wState = stream._writableState;
|
||||||
|
if (!wState || wState.errored) return false;
|
||||||
|
return wState.finished || (wState.ended && wState.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nop() {}
|
||||||
|
|
||||||
|
function isReadableEnded(stream: Readable) {
|
||||||
|
if (stream.readableEnded) return true;
|
||||||
|
const rState = stream._readableState;
|
||||||
|
if (!rState || rState.errored) return false;
|
||||||
|
return rState.endEmitted || (rState.ended && rState.length === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FinishedOptions {
|
||||||
|
error?: boolean;
|
||||||
|
readable?: boolean;
|
||||||
|
writable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends an ending callback triggered when a stream is no longer readable,
|
||||||
|
* writable or has experienced an error or a premature close event
|
||||||
|
*/
|
||||||
|
export default function eos(
|
||||||
|
stream: StreamImplementations,
|
||||||
|
options: FinishedOptions | null,
|
||||||
|
callback: (err?: NodeErrorAbstraction | null) => void,
|
||||||
|
): () => void;
|
||||||
|
export default function eos(
|
||||||
|
stream: StreamImplementations,
|
||||||
|
callback: (err?: NodeErrorAbstraction | null) => void,
|
||||||
|
): () => void;
|
||||||
|
export default function eos(
|
||||||
|
stream: StreamImplementations,
|
||||||
|
x: FinishedOptions | ((err?: NodeErrorAbstraction | null) => void) | null,
|
||||||
|
y?: (err?: NodeErrorAbstraction | null) => void,
|
||||||
|
) {
|
||||||
|
let opts: FinishedOptions;
|
||||||
|
let callback: (err?: NodeErrorAbstraction | null) => void;
|
||||||
|
|
||||||
|
if (!y) {
|
||||||
|
if (typeof x !== "function") {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE("callback", "function", x);
|
||||||
|
}
|
||||||
|
opts = {};
|
||||||
|
callback = x;
|
||||||
|
} else {
|
||||||
|
if (!x || Array.isArray(x) || typeof x !== "object") {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE("opts", "object", x);
|
||||||
|
}
|
||||||
|
opts = x;
|
||||||
|
|
||||||
|
if (typeof y !== "function") {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE("callback", "function", y);
|
||||||
|
}
|
||||||
|
callback = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback = once(callback);
|
||||||
|
|
||||||
|
const readable = opts.readable ?? isReadable(stream);
|
||||||
|
const writable = opts.writable ?? isWritable(stream);
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const wState: WritableState | undefined = (stream as any)._writableState;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const rState: ReadableState | undefined = (stream as any)._readableState;
|
||||||
|
const validState = wState || rState;
|
||||||
|
|
||||||
|
const onlegacyfinish = () => {
|
||||||
|
if (!(stream as Writable).writable) {
|
||||||
|
onfinish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let willEmitClose = (
|
||||||
|
validState?.autoDestroy &&
|
||||||
|
validState?.emitClose &&
|
||||||
|
validState?.closed === false &&
|
||||||
|
isReadable(stream) === readable &&
|
||||||
|
isWritable(stream) === writable
|
||||||
|
);
|
||||||
|
|
||||||
|
let writableFinished = (stream as Writable).writableFinished ||
|
||||||
|
wState?.finished;
|
||||||
|
const onfinish = () => {
|
||||||
|
writableFinished = true;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
if ((stream as any).destroyed) {
|
||||||
|
willEmitClose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willEmitClose && (!(stream as Readable).readable || readable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!readable || readableEnded) {
|
||||||
|
callback.call(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let readableEnded = (stream as Readable).readableEnded || rState?.endEmitted;
|
||||||
|
const onend = () => {
|
||||||
|
readableEnded = true;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
if ((stream as any).destroyed) {
|
||||||
|
willEmitClose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (willEmitClose && (!(stream as Writable).writable || writable)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!writable || writableFinished) {
|
||||||
|
callback.call(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onerror = (err: NodeErrorAbstraction) => {
|
||||||
|
callback.call(stream, err);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onclose = () => {
|
||||||
|
if (readable && !readableEnded) {
|
||||||
|
if (!isReadableEnded(stream as Readable)) {
|
||||||
|
return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (writable && !writableFinished) {
|
||||||
|
if (!isWritableFinished(stream as Writable)) {
|
||||||
|
return callback.call(stream, new ERR_STREAM_PREMATURE_CLOSE());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback.call(stream);
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// const onrequest = () => {
|
||||||
|
// stream.req.on("finish", onfinish);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// if (isRequest(stream)) {
|
||||||
|
// stream.on("complete", onfinish);
|
||||||
|
// stream.on("abort", onclose);
|
||||||
|
// if (stream.req) {
|
||||||
|
// onrequest();
|
||||||
|
// } else {
|
||||||
|
// stream.on("request", onrequest);
|
||||||
|
// }
|
||||||
|
// } else
|
||||||
|
if (writable && !wState) {
|
||||||
|
stream.on("end", onlegacyfinish);
|
||||||
|
stream.on("close", onlegacyfinish);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// if (typeof stream.aborted === "boolean") {
|
||||||
|
// stream.on("aborted", onclose);
|
||||||
|
// }
|
||||||
|
|
||||||
|
stream.on("end", onend);
|
||||||
|
stream.on("finish", onfinish);
|
||||||
|
if (opts.error !== false) stream.on("error", onerror);
|
||||||
|
stream.on("close", onclose);
|
||||||
|
|
||||||
|
const closed = (
|
||||||
|
wState?.closed ||
|
||||||
|
rState?.closed ||
|
||||||
|
wState?.errorEmitted ||
|
||||||
|
rState?.errorEmitted ||
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// (rState && stream.req && stream.aborted) ||
|
||||||
|
(
|
||||||
|
(!writable || wState?.finished) &&
|
||||||
|
(!readable || rState?.endEmitted)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (closed) {
|
||||||
|
queueMicrotask(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function () {
|
||||||
|
callback = nop;
|
||||||
|
stream.removeListener("aborted", onclose);
|
||||||
|
stream.removeListener("complete", onfinish);
|
||||||
|
stream.removeListener("abort", onclose);
|
||||||
|
// TODO(Soremwar)
|
||||||
|
// Bring back once requests are implemented
|
||||||
|
// stream.removeListener("request", onrequest);
|
||||||
|
// if (stream.req) stream.req.removeListener("finish", onfinish);
|
||||||
|
stream.removeListener("end", onlegacyfinish);
|
||||||
|
stream.removeListener("close", onlegacyfinish);
|
||||||
|
stream.removeListener("finish", onfinish);
|
||||||
|
stream.removeListener("end", onend);
|
||||||
|
stream.removeListener("error", onerror);
|
||||||
|
stream.removeListener("close", onclose);
|
||||||
|
};
|
||||||
|
}
|
102
std/node/_stream/from.ts
Normal file
102
std/node/_stream/from.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
import Readable from "./readable.ts";
|
||||||
|
import type { ReadableOptions } from "./readable.ts";
|
||||||
|
import { ERR_INVALID_ARG_TYPE, ERR_STREAM_NULL_VALUES } from "../_errors.ts";
|
||||||
|
|
||||||
|
export default function from(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
iterable: Iterable<any> | AsyncIterable<any>,
|
||||||
|
opts?: ReadableOptions,
|
||||||
|
) {
|
||||||
|
let iterator:
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
| Iterator<any, any, undefined>
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
| AsyncIterator<any, any, undefined>;
|
||||||
|
if (typeof iterable === "string" || iterable instanceof Buffer) {
|
||||||
|
return new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
...opts,
|
||||||
|
read() {
|
||||||
|
this.push(iterable);
|
||||||
|
this.push(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Symbol.asyncIterator in iterable) {
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
iterator = (iterable as AsyncIterable<any>)[Symbol.asyncIterator]();
|
||||||
|
} else if (Symbol.iterator in iterable) {
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
iterator = (iterable as Iterable<any>)[Symbol.iterator]();
|
||||||
|
} else {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE("iterable", ["Iterable"], iterable);
|
||||||
|
}
|
||||||
|
|
||||||
|
const readable = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
highWaterMark: 1,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reading boolean to protect against _read
|
||||||
|
// being called before last iteration completion.
|
||||||
|
let reading = false;
|
||||||
|
|
||||||
|
// needToClose boolean if iterator needs to be explicitly closed
|
||||||
|
let needToClose = false;
|
||||||
|
|
||||||
|
readable._read = function () {
|
||||||
|
if (!reading) {
|
||||||
|
reading = true;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
readable._destroy = function (error, cb) {
|
||||||
|
if (needToClose) {
|
||||||
|
needToClose = false;
|
||||||
|
close().then(
|
||||||
|
() => queueMicrotask(() => cb(error)),
|
||||||
|
(e) => queueMicrotask(() => cb(error || e)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
cb(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function close() {
|
||||||
|
if (typeof iterator.return === "function") {
|
||||||
|
const { value } = await iterator.return();
|
||||||
|
await value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function next() {
|
||||||
|
try {
|
||||||
|
needToClose = false;
|
||||||
|
const { value, done } = await iterator.next();
|
||||||
|
needToClose = !done;
|
||||||
|
if (done) {
|
||||||
|
readable.push(null);
|
||||||
|
} else if (readable.destroyed) {
|
||||||
|
await close();
|
||||||
|
} else {
|
||||||
|
const res = await value;
|
||||||
|
if (res === null) {
|
||||||
|
reading = false;
|
||||||
|
throw new ERR_STREAM_NULL_VALUES();
|
||||||
|
} else if (readable.push(res)) {
|
||||||
|
next();
|
||||||
|
} else {
|
||||||
|
reading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
readable.destroy(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return readable;
|
||||||
|
}
|
1248
std/node/_stream/readable.ts
Normal file
1248
std/node/_stream/readable.ts
Normal file
File diff suppressed because it is too large
Load diff
489
std/node/_stream/readable_test.ts
Normal file
489
std/node/_stream/readable_test.ts
Normal file
|
@ -0,0 +1,489 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
import Readable from "../_stream/readable.ts";
|
||||||
|
import { once } from "../events.ts";
|
||||||
|
import { deferred } from "../../async/mod.ts";
|
||||||
|
import {
|
||||||
|
assert,
|
||||||
|
assertEquals,
|
||||||
|
assertStrictEquals,
|
||||||
|
} from "../../testing/asserts.ts";
|
||||||
|
|
||||||
|
Deno.test("Readable stream from iterator", async () => {
|
||||||
|
function* generate() {
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
yield "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate());
|
||||||
|
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals(chunk, expected.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream from async iterator", async () => {
|
||||||
|
async function* generate() {
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
yield "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate());
|
||||||
|
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals(chunk, expected.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream from promise", async () => {
|
||||||
|
const promises = [
|
||||||
|
Promise.resolve("a"),
|
||||||
|
Promise.resolve("b"),
|
||||||
|
Promise.resolve("c"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const stream = Readable.from(promises);
|
||||||
|
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals(chunk, expected.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream from string", async () => {
|
||||||
|
const string = "abc";
|
||||||
|
const stream = Readable.from(string);
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals(chunk, string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream from Buffer", async () => {
|
||||||
|
const string = "abc";
|
||||||
|
const stream = Readable.from(Buffer.from(string));
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals((chunk as Buffer).toString(), string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream gets destroyed on error", async () => {
|
||||||
|
// deno-lint-ignore require-yield
|
||||||
|
async function* generate() {
|
||||||
|
throw new Error("kaboom");
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate());
|
||||||
|
|
||||||
|
stream.read();
|
||||||
|
|
||||||
|
const [err] = await once(stream, "error");
|
||||||
|
assertStrictEquals(err.message, "kaboom");
|
||||||
|
assertStrictEquals(stream.destroyed, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream works as Transform stream", async () => {
|
||||||
|
async function* generate(stream: Readable) {
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
yield (chunk as string).toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = new Readable({
|
||||||
|
objectMode: true,
|
||||||
|
read() {
|
||||||
|
this.push("a");
|
||||||
|
this.push("b");
|
||||||
|
this.push("c");
|
||||||
|
this.push(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const stream = Readable.from(generate(source));
|
||||||
|
|
||||||
|
const expected = ["A", "B", "C"];
|
||||||
|
|
||||||
|
for await (const chunk of stream) {
|
||||||
|
assertStrictEquals(chunk, expected.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream can be paused", () => {
|
||||||
|
const readable = new Readable();
|
||||||
|
|
||||||
|
// _read is a noop, here.
|
||||||
|
readable._read = () => {};
|
||||||
|
|
||||||
|
// Default state of a stream is not "paused"
|
||||||
|
assert(!readable.isPaused());
|
||||||
|
|
||||||
|
// Make the stream start flowing...
|
||||||
|
readable.on("data", () => {});
|
||||||
|
|
||||||
|
// still not paused.
|
||||||
|
assert(!readable.isPaused());
|
||||||
|
|
||||||
|
readable.pause();
|
||||||
|
assert(readable.isPaused());
|
||||||
|
readable.resume();
|
||||||
|
assert(!readable.isPaused());
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream sets enconding correctly", () => {
|
||||||
|
const readable = new Readable({
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
readable.setEncoding("utf8");
|
||||||
|
|
||||||
|
readable.push(new TextEncoder().encode("DEF"));
|
||||||
|
readable.unshift(new TextEncoder().encode("ABC"));
|
||||||
|
|
||||||
|
assertStrictEquals(readable.read(), "ABCDEF");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream sets encoding correctly", () => {
|
||||||
|
const readable = new Readable({
|
||||||
|
read() {},
|
||||||
|
});
|
||||||
|
|
||||||
|
readable.setEncoding("utf8");
|
||||||
|
|
||||||
|
readable.push(new TextEncoder().encode("DEF"));
|
||||||
|
readable.unshift(new TextEncoder().encode("ABC"));
|
||||||
|
|
||||||
|
assertStrictEquals(readable.read(), "ABCDEF");
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream holds up a big push", async () => {
|
||||||
|
let readExecuted = 0;
|
||||||
|
const readExecutedExpected = 3;
|
||||||
|
const readExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
let endExecuted = 0;
|
||||||
|
const endExecutedExpected = 1;
|
||||||
|
const endExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const str = "asdfasdfasdfasdfasdf";
|
||||||
|
|
||||||
|
const r = new Readable({
|
||||||
|
highWaterMark: 5,
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
|
||||||
|
let reads = 0;
|
||||||
|
|
||||||
|
function _read() {
|
||||||
|
if (reads === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
r.push(str);
|
||||||
|
}, 1);
|
||||||
|
reads++;
|
||||||
|
} else if (reads === 1) {
|
||||||
|
const ret = r.push(str);
|
||||||
|
assertEquals(ret, false);
|
||||||
|
reads++;
|
||||||
|
} else {
|
||||||
|
r.push(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r._read = () => {
|
||||||
|
readExecuted++;
|
||||||
|
if (readExecuted == readExecutedExpected) {
|
||||||
|
readExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
_read();
|
||||||
|
};
|
||||||
|
|
||||||
|
r.on("end", () => {
|
||||||
|
endExecuted++;
|
||||||
|
if (endExecuted == endExecutedExpected) {
|
||||||
|
endExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Push some data in to start.
|
||||||
|
// We've never gotten any read event at this point.
|
||||||
|
const ret = r.push(str);
|
||||||
|
assert(!ret);
|
||||||
|
let chunk = r.read();
|
||||||
|
assertEquals(chunk, str);
|
||||||
|
chunk = r.read();
|
||||||
|
assertEquals(chunk, null);
|
||||||
|
|
||||||
|
r.once("readable", () => {
|
||||||
|
// This time, we'll get *all* the remaining data, because
|
||||||
|
// it's been added synchronously, as the read WOULD take
|
||||||
|
// us below the hwm, and so it triggered a _read() again,
|
||||||
|
// which synchronously added more, which we then return.
|
||||||
|
chunk = r.read();
|
||||||
|
assertEquals(chunk, str + str);
|
||||||
|
|
||||||
|
chunk = r.read();
|
||||||
|
assertEquals(chunk, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
const readTimeout = setTimeout(
|
||||||
|
() => readExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
const endTimeout = setTimeout(
|
||||||
|
() => endExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await readExpectedExecutions;
|
||||||
|
await endExpectedExecutions;
|
||||||
|
clearTimeout(readTimeout);
|
||||||
|
clearTimeout(endTimeout);
|
||||||
|
assertEquals(readExecuted, readExecutedExpected);
|
||||||
|
assertEquals(endExecuted, endExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'on' event", async () => {
|
||||||
|
async function* generate() {
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
yield "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate());
|
||||||
|
|
||||||
|
let iterations = 0;
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
stream.on("data", (chunk) => {
|
||||||
|
iterations++;
|
||||||
|
assertStrictEquals(chunk, expected.shift());
|
||||||
|
});
|
||||||
|
|
||||||
|
await once(stream, "end");
|
||||||
|
|
||||||
|
assertStrictEquals(iterations, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'data' event", async () => {
|
||||||
|
async function* generate() {
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
yield "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate(), { objectMode: false });
|
||||||
|
|
||||||
|
let iterations = 0;
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
stream.on("data", (chunk) => {
|
||||||
|
iterations++;
|
||||||
|
assertStrictEquals(chunk instanceof Buffer, true);
|
||||||
|
assertStrictEquals(chunk.toString(), expected.shift());
|
||||||
|
});
|
||||||
|
|
||||||
|
await once(stream, "end");
|
||||||
|
|
||||||
|
assertStrictEquals(iterations, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'data' event on non-object", async () => {
|
||||||
|
async function* generate() {
|
||||||
|
yield "a";
|
||||||
|
yield "b";
|
||||||
|
yield "c";
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = Readable.from(generate(), { objectMode: false });
|
||||||
|
|
||||||
|
let iterations = 0;
|
||||||
|
const expected = ["a", "b", "c"];
|
||||||
|
|
||||||
|
stream.on("data", (chunk) => {
|
||||||
|
iterations++;
|
||||||
|
assertStrictEquals(chunk instanceof Buffer, true);
|
||||||
|
assertStrictEquals(chunk.toString(), expected.shift());
|
||||||
|
});
|
||||||
|
|
||||||
|
await once(stream, "end");
|
||||||
|
|
||||||
|
assertStrictEquals(iterations, 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'readable' event is emitted but 'read' is not on highWaterMark length exceeded", async () => {
|
||||||
|
let readableExecuted = 0;
|
||||||
|
const readableExecutedExpected = 1;
|
||||||
|
const readableExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const r = new Readable({
|
||||||
|
highWaterMark: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
r._read = () => {
|
||||||
|
throw new Error("_read must not be called");
|
||||||
|
};
|
||||||
|
r.push(Buffer.from("blerg"));
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
assert(!r._readableState.reading);
|
||||||
|
r.on("readable", () => {
|
||||||
|
readableExecuted++;
|
||||||
|
if (readableExecuted == readableExecutedExpected) {
|
||||||
|
readableExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
const readableTimeout = setTimeout(
|
||||||
|
() => readableExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await readableExpectedExecutions;
|
||||||
|
clearTimeout(readableTimeout);
|
||||||
|
assertEquals(readableExecuted, readableExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'readable' and 'read' events are emitted on highWaterMark length not reached", async () => {
|
||||||
|
let readableExecuted = 0;
|
||||||
|
const readableExecutedExpected = 1;
|
||||||
|
const readableExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
let readExecuted = 0;
|
||||||
|
const readExecutedExpected = 1;
|
||||||
|
const readExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const r = new Readable({
|
||||||
|
highWaterMark: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
r._read = () => {
|
||||||
|
readExecuted++;
|
||||||
|
if (readExecuted == readExecutedExpected) {
|
||||||
|
readExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
r.push(Buffer.from("bl"));
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
assert(r._readableState.reading);
|
||||||
|
r.on("readable", () => {
|
||||||
|
readableExecuted++;
|
||||||
|
if (readableExecuted == readableExecutedExpected) {
|
||||||
|
readableExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
const readableTimeout = setTimeout(
|
||||||
|
() => readableExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
const readTimeout = setTimeout(
|
||||||
|
() => readExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await readableExpectedExecutions;
|
||||||
|
await readExpectedExecutions;
|
||||||
|
clearTimeout(readableTimeout);
|
||||||
|
clearTimeout(readTimeout);
|
||||||
|
assertEquals(readableExecuted, readableExecutedExpected);
|
||||||
|
assertEquals(readExecuted, readExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'readable' event is emitted but 'read' is not on highWaterMark length not reached and stream ended", async () => {
|
||||||
|
let readableExecuted = 0;
|
||||||
|
const readableExecutedExpected = 1;
|
||||||
|
const readableExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const r = new Readable({
|
||||||
|
highWaterMark: 30,
|
||||||
|
});
|
||||||
|
|
||||||
|
r._read = () => {
|
||||||
|
throw new Error("Must not be executed");
|
||||||
|
};
|
||||||
|
|
||||||
|
r.push(Buffer.from("blerg"));
|
||||||
|
//This ends the stream and triggers end
|
||||||
|
r.push(null);
|
||||||
|
|
||||||
|
setTimeout(function () {
|
||||||
|
// Assert we're testing what we think we are
|
||||||
|
assert(!r._readableState.reading);
|
||||||
|
r.on("readable", () => {
|
||||||
|
readableExecuted++;
|
||||||
|
if (readableExecuted == readableExecutedExpected) {
|
||||||
|
readableExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
const readableTimeout = setTimeout(
|
||||||
|
() => readableExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await readableExpectedExecutions;
|
||||||
|
clearTimeout(readableTimeout);
|
||||||
|
assertEquals(readableExecuted, readableExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: 'read' is emitted on empty string pushed in non-object mode", async () => {
|
||||||
|
let endExecuted = 0;
|
||||||
|
const endExecutedExpected = 1;
|
||||||
|
const endExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const underlyingData = ["", "x", "y", "", "z"];
|
||||||
|
const expected = underlyingData.filter((data) => data);
|
||||||
|
const result: unknown[] = [];
|
||||||
|
|
||||||
|
const r = new Readable({
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
r._read = function () {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (!underlyingData.length) {
|
||||||
|
this.push(null);
|
||||||
|
} else {
|
||||||
|
this.push(underlyingData.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
r.on("readable", () => {
|
||||||
|
const data = r.read();
|
||||||
|
if (data !== null) result.push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
r.on("end", () => {
|
||||||
|
endExecuted++;
|
||||||
|
if (endExecuted == endExecutedExpected) {
|
||||||
|
endExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assertEquals(result, expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
const endTimeout = setTimeout(
|
||||||
|
() => endExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await endExpectedExecutions;
|
||||||
|
clearTimeout(endTimeout);
|
||||||
|
assertEquals(endExecuted, endExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Readable stream: listeners can be removed", () => {
|
||||||
|
const r = new Readable();
|
||||||
|
r._read = () => {};
|
||||||
|
r.on("data", () => {});
|
||||||
|
|
||||||
|
r.removeAllListeners("data");
|
||||||
|
|
||||||
|
assertEquals(r.eventNames().length, 0);
|
||||||
|
});
|
79
std/node/_stream/stream.ts
Normal file
79
std/node/_stream/stream.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
import EventEmitter from "../events.ts";
|
||||||
|
import type Writable from "./writable.ts";
|
||||||
|
import { types } from "../util.ts";
|
||||||
|
|
||||||
|
class Stream extends EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
static _isUint8Array = types.isUint8Array;
|
||||||
|
static _uint8ArrayToBuffer = (chunk: Uint8Array) => Buffer.from(chunk);
|
||||||
|
|
||||||
|
pipe(dest: Writable, options: { end: boolean }) {
|
||||||
|
// deno-lint-ignore no-this-alias
|
||||||
|
const source = this;
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//isStdio exist on stdin || stdout only, which extend from Duplex
|
||||||
|
//if (!dest._isStdio && (options?.end ?? true)) {
|
||||||
|
//Find an alternative to be able to pipe streams to stdin & stdout
|
||||||
|
//Port them as well?
|
||||||
|
if (options?.end ?? true) {
|
||||||
|
source.on("end", onend);
|
||||||
|
source.on("close", onclose);
|
||||||
|
}
|
||||||
|
|
||||||
|
let didOnEnd = false;
|
||||||
|
function onend() {
|
||||||
|
if (didOnEnd) return;
|
||||||
|
didOnEnd = true;
|
||||||
|
|
||||||
|
dest.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onclose() {
|
||||||
|
if (didOnEnd) return;
|
||||||
|
didOnEnd = true;
|
||||||
|
|
||||||
|
if (typeof dest.destroy === "function") dest.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't leave dangling pipes when there are errors.
|
||||||
|
function onerror(this: Stream, er: Error) {
|
||||||
|
cleanup();
|
||||||
|
if (this.listenerCount("error") === 0) {
|
||||||
|
throw er; // Unhandled stream error in pipe.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
source.on("error", onerror);
|
||||||
|
dest.on("error", onerror);
|
||||||
|
|
||||||
|
// Remove all the event listeners that were added.
|
||||||
|
function cleanup() {
|
||||||
|
source.removeListener("end", onend);
|
||||||
|
source.removeListener("close", onclose);
|
||||||
|
|
||||||
|
source.removeListener("error", onerror);
|
||||||
|
dest.removeListener("error", onerror);
|
||||||
|
|
||||||
|
source.removeListener("end", cleanup);
|
||||||
|
source.removeListener("close", cleanup);
|
||||||
|
|
||||||
|
dest.removeListener("close", cleanup);
|
||||||
|
}
|
||||||
|
|
||||||
|
source.on("end", cleanup);
|
||||||
|
source.on("close", cleanup);
|
||||||
|
|
||||||
|
dest.on("close", cleanup);
|
||||||
|
dest.emit("pipe", source);
|
||||||
|
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Stream;
|
4
std/node/_stream/symbols.ts
Normal file
4
std/node/_stream/symbols.ts
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
export const kConstruct = Symbol("kConstruct");
|
||||||
|
export const kDestroy = Symbol("kDestroy");
|
||||||
|
export const kPaused = Symbol("kPaused");
|
952
std/node/_stream/writable.ts
Normal file
952
std/node/_stream/writable.ts
Normal file
|
@ -0,0 +1,952 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
import Stream from "./stream.ts";
|
||||||
|
import { captureRejectionSymbol } from "../events.ts";
|
||||||
|
import { kConstruct, kDestroy } from "./symbols.ts";
|
||||||
|
import {
|
||||||
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
ERR_INVALID_OPT_VALUE,
|
||||||
|
ERR_METHOD_NOT_IMPLEMENTED,
|
||||||
|
ERR_MULTIPLE_CALLBACK,
|
||||||
|
ERR_STREAM_ALREADY_FINISHED,
|
||||||
|
ERR_STREAM_CANNOT_PIPE,
|
||||||
|
ERR_STREAM_DESTROYED,
|
||||||
|
ERR_STREAM_NULL_VALUES,
|
||||||
|
ERR_STREAM_WRITE_AFTER_END,
|
||||||
|
ERR_UNKNOWN_ENCODING,
|
||||||
|
} from "../_errors.ts";
|
||||||
|
|
||||||
|
function nop() {}
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
type write_v = (
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunks: Array<{ chunk: any; encoding: string }>,
|
||||||
|
callback: (error?: Error | null) => void,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
type AfterWriteTick = {
|
||||||
|
cb: (error?: Error) => void;
|
||||||
|
count: number;
|
||||||
|
state: WritableState;
|
||||||
|
stream: Writable;
|
||||||
|
};
|
||||||
|
|
||||||
|
const kOnFinished = Symbol("kOnFinished");
|
||||||
|
|
||||||
|
function destroy(this: Writable, err?: Error, cb?: () => void) {
|
||||||
|
const w = this._writableState;
|
||||||
|
|
||||||
|
if (w.destroyed) {
|
||||||
|
if (typeof cb === "function") {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
|
||||||
|
err.stack;
|
||||||
|
|
||||||
|
if (!w.errored) {
|
||||||
|
w.errored = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.destroyed = true;
|
||||||
|
|
||||||
|
if (!w.constructed) {
|
||||||
|
this.once(kDestroy, (er) => {
|
||||||
|
_destroy(this, err || er, cb);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
_destroy(this, err, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _destroy(
|
||||||
|
self: Writable,
|
||||||
|
err?: Error,
|
||||||
|
cb?: (error?: Error | null) => void,
|
||||||
|
) {
|
||||||
|
self._destroy(err || null, (err) => {
|
||||||
|
const w = self._writableState;
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
|
||||||
|
err.stack;
|
||||||
|
|
||||||
|
if (!w.errored) {
|
||||||
|
w.errored = err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.closed = true;
|
||||||
|
|
||||||
|
if (typeof cb === "function") {
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (!w.errorEmitted) {
|
||||||
|
w.errorEmitted = true;
|
||||||
|
self.emit("error", err);
|
||||||
|
}
|
||||||
|
w.closeEmitted = true;
|
||||||
|
if (w.emitClose) {
|
||||||
|
self.emit("close");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
w.closeEmitted = true;
|
||||||
|
if (w.emitClose) {
|
||||||
|
self.emit("close");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorOrDestroy(stream: Writable, err: Error, sync = false) {
|
||||||
|
const w = stream._writableState;
|
||||||
|
|
||||||
|
if (w.destroyed) {
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w.autoDestroy) {
|
||||||
|
stream.destroy(err);
|
||||||
|
} else if (err) {
|
||||||
|
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
|
||||||
|
err.stack;
|
||||||
|
|
||||||
|
if (!w.errored) {
|
||||||
|
w.errored = err;
|
||||||
|
}
|
||||||
|
if (sync) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (w.errorEmitted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
w.errorEmitted = true;
|
||||||
|
stream.emit("error", err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (w.errorEmitted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
w.errorEmitted = true;
|
||||||
|
stream.emit("error", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function construct(stream: Writable, cb: (error: Error) => void) {
|
||||||
|
if (!stream._construct) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.once(kConstruct, cb);
|
||||||
|
const w = stream._writableState;
|
||||||
|
|
||||||
|
w.constructed = false;
|
||||||
|
|
||||||
|
queueMicrotask(() => {
|
||||||
|
let called = false;
|
||||||
|
stream._construct?.((err) => {
|
||||||
|
w.constructed = true;
|
||||||
|
|
||||||
|
if (called) {
|
||||||
|
err = new ERR_MULTIPLE_CALLBACK();
|
||||||
|
} else {
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w.destroyed) {
|
||||||
|
stream.emit(kDestroy, err);
|
||||||
|
} else if (err) {
|
||||||
|
errorOrDestroy(stream, err, true);
|
||||||
|
} else {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
stream.emit(kConstruct);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring encodings in
|
||||||
|
function writeOrBuffer(
|
||||||
|
stream: Writable,
|
||||||
|
state: WritableState,
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
encoding: string,
|
||||||
|
callback: (error: Error) => void,
|
||||||
|
) {
|
||||||
|
const len = state.objectMode ? 1 : chunk.length;
|
||||||
|
|
||||||
|
state.length += len;
|
||||||
|
|
||||||
|
if (state.writing || state.corked || state.errored || !state.constructed) {
|
||||||
|
state.buffered.push({ chunk, encoding, callback });
|
||||||
|
if (state.allBuffers && encoding !== "buffer") {
|
||||||
|
state.allBuffers = false;
|
||||||
|
}
|
||||||
|
if (state.allNoop && callback !== nop) {
|
||||||
|
state.allNoop = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.writelen = len;
|
||||||
|
state.writecb = callback;
|
||||||
|
state.writing = true;
|
||||||
|
state.sync = true;
|
||||||
|
stream._write(chunk, encoding, state.onwrite);
|
||||||
|
state.sync = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret = state.length < state.highWaterMark;
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
state.needDrain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret && !state.errored && !state.destroyed;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring encodings in
|
||||||
|
function doWrite(
|
||||||
|
stream: Writable,
|
||||||
|
state: WritableState,
|
||||||
|
writev: boolean,
|
||||||
|
len: number,
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
encoding: string,
|
||||||
|
cb: (error: Error) => void,
|
||||||
|
) {
|
||||||
|
state.writelen = len;
|
||||||
|
state.writecb = cb;
|
||||||
|
state.writing = true;
|
||||||
|
state.sync = true;
|
||||||
|
if (state.destroyed) {
|
||||||
|
state.onwrite(new ERR_STREAM_DESTROYED("write"));
|
||||||
|
} else if (writev) {
|
||||||
|
(stream._writev as unknown as write_v)(chunk, state.onwrite);
|
||||||
|
} else {
|
||||||
|
stream._write(chunk, encoding, state.onwrite);
|
||||||
|
}
|
||||||
|
state.sync = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onwriteError(
|
||||||
|
stream: Writable,
|
||||||
|
state: WritableState,
|
||||||
|
er: Error,
|
||||||
|
cb: (error: Error) => void,
|
||||||
|
) {
|
||||||
|
--state.pendingcb;
|
||||||
|
|
||||||
|
cb(er);
|
||||||
|
errorBuffer(state);
|
||||||
|
errorOrDestroy(stream, er);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onwrite(stream: Writable, er?: Error | null) {
|
||||||
|
const state = stream._writableState;
|
||||||
|
const sync = state.sync;
|
||||||
|
const cb = state.writecb;
|
||||||
|
|
||||||
|
if (typeof cb !== "function") {
|
||||||
|
errorOrDestroy(stream, new ERR_MULTIPLE_CALLBACK());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.writing = false;
|
||||||
|
state.writecb = null;
|
||||||
|
state.length -= state.writelen;
|
||||||
|
state.writelen = 0;
|
||||||
|
|
||||||
|
if (er) {
|
||||||
|
// Avoid V8 leak, https://github.com/nodejs/node/pull/34103#issuecomment-652002364
|
||||||
|
er.stack;
|
||||||
|
|
||||||
|
if (!state.errored) {
|
||||||
|
state.errored = er;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
queueMicrotask(() => onwriteError(stream, state, er, cb));
|
||||||
|
} else {
|
||||||
|
onwriteError(stream, state, er, cb);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (state.buffered.length > state.bufferedIndex) {
|
||||||
|
clearBuffer(stream, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
if (
|
||||||
|
state.afterWriteTickInfo !== null &&
|
||||||
|
state.afterWriteTickInfo.cb === cb
|
||||||
|
) {
|
||||||
|
state.afterWriteTickInfo.count++;
|
||||||
|
} else {
|
||||||
|
state.afterWriteTickInfo = {
|
||||||
|
count: 1,
|
||||||
|
cb: (cb as (error?: Error) => void),
|
||||||
|
stream,
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
queueMicrotask(() =>
|
||||||
|
afterWriteTick(state.afterWriteTickInfo as AfterWriteTick)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
afterWrite(stream, state, 1, cb as (error?: Error) => void);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterWriteTick({
|
||||||
|
cb,
|
||||||
|
count,
|
||||||
|
state,
|
||||||
|
stream,
|
||||||
|
}: AfterWriteTick) {
|
||||||
|
state.afterWriteTickInfo = null;
|
||||||
|
return afterWrite(stream, state, count, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterWrite(
|
||||||
|
stream: Writable,
|
||||||
|
state: WritableState,
|
||||||
|
count: number,
|
||||||
|
cb: (error?: Error) => void,
|
||||||
|
) {
|
||||||
|
const needDrain = !state.ending && !stream.destroyed && state.length === 0 &&
|
||||||
|
state.needDrain;
|
||||||
|
if (needDrain) {
|
||||||
|
state.needDrain = false;
|
||||||
|
stream.emit("drain");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (count-- > 0) {
|
||||||
|
state.pendingcb--;
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.destroyed) {
|
||||||
|
errorBuffer(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
finishMaybe(stream, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If there's something in the buffer waiting, then invoke callbacks.*/
|
||||||
|
function errorBuffer(state: WritableState) {
|
||||||
|
if (state.writing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let n = state.bufferedIndex; n < state.buffered.length; ++n) {
|
||||||
|
const { chunk, callback } = state.buffered[n];
|
||||||
|
const len = state.objectMode ? 1 : chunk.length;
|
||||||
|
state.length -= len;
|
||||||
|
callback(new ERR_STREAM_DESTROYED("write"));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const callback of state[kOnFinished].splice(0)) {
|
||||||
|
callback(new ERR_STREAM_DESTROYED("end"));
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBuffer(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** If there's something in the buffer waiting, then process it.*/
|
||||||
|
function clearBuffer(stream: Writable, state: WritableState) {
|
||||||
|
if (
|
||||||
|
state.corked ||
|
||||||
|
state.bufferProcessing ||
|
||||||
|
state.destroyed ||
|
||||||
|
!state.constructed
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { buffered, bufferedIndex, objectMode } = state;
|
||||||
|
const bufferedLength = buffered.length - bufferedIndex;
|
||||||
|
|
||||||
|
if (!bufferedLength) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const i = bufferedIndex;
|
||||||
|
|
||||||
|
state.bufferProcessing = true;
|
||||||
|
if (bufferedLength > 1 && stream._writev) {
|
||||||
|
state.pendingcb -= bufferedLength - 1;
|
||||||
|
|
||||||
|
const callback = state.allNoop ? nop : (err: Error) => {
|
||||||
|
for (let n = i; n < buffered.length; ++n) {
|
||||||
|
buffered[n].callback(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const chunks = state.allNoop && i === 0 ? buffered : buffered.slice(i);
|
||||||
|
|
||||||
|
doWrite(stream, state, true, state.length, chunks, "", callback);
|
||||||
|
|
||||||
|
resetBuffer(state);
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
const { chunk, encoding, callback } = buffered[i];
|
||||||
|
const len = objectMode ? 1 : chunk.length;
|
||||||
|
doWrite(stream, state, false, len, chunk, encoding, callback);
|
||||||
|
} while (i < buffered.length && !state.writing);
|
||||||
|
|
||||||
|
if (i === buffered.length) {
|
||||||
|
resetBuffer(state);
|
||||||
|
} else if (i > 256) {
|
||||||
|
buffered.splice(0, i);
|
||||||
|
state.bufferedIndex = 0;
|
||||||
|
} else {
|
||||||
|
state.bufferedIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.bufferProcessing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function finish(stream: Writable, state: WritableState) {
|
||||||
|
state.pendingcb--;
|
||||||
|
if (state.errorEmitted || state.closeEmitted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.finished = true;
|
||||||
|
|
||||||
|
for (const callback of state[kOnFinished].splice(0)) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.emit("finish");
|
||||||
|
|
||||||
|
if (state.autoDestroy) {
|
||||||
|
stream.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function finishMaybe(stream: Writable, state: WritableState, sync?: boolean) {
|
||||||
|
if (needFinish(state)) {
|
||||||
|
prefinish(stream, state);
|
||||||
|
if (state.pendingcb === 0 && needFinish(state)) {
|
||||||
|
state.pendingcb++;
|
||||||
|
if (sync) {
|
||||||
|
queueMicrotask(() => finish(stream, state));
|
||||||
|
} else {
|
||||||
|
finish(stream, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefinish(stream: Writable, state: WritableState) {
|
||||||
|
if (!state.prefinished && !state.finalCalled) {
|
||||||
|
if (typeof stream._final === "function" && !state.destroyed) {
|
||||||
|
state.finalCalled = true;
|
||||||
|
|
||||||
|
state.sync = true;
|
||||||
|
state.pendingcb++;
|
||||||
|
stream._final((err) => {
|
||||||
|
state.pendingcb--;
|
||||||
|
if (err) {
|
||||||
|
for (const callback of state[kOnFinished].splice(0)) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
errorOrDestroy(stream, err, state.sync);
|
||||||
|
} else if (needFinish(state)) {
|
||||||
|
state.prefinished = true;
|
||||||
|
stream.emit("prefinish");
|
||||||
|
state.pendingcb++;
|
||||||
|
queueMicrotask(() => finish(stream, state));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.sync = false;
|
||||||
|
} else {
|
||||||
|
state.prefinished = true;
|
||||||
|
stream.emit("prefinish");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function needFinish(state: WritableState) {
|
||||||
|
return (state.ending &&
|
||||||
|
state.constructed &&
|
||||||
|
state.length === 0 &&
|
||||||
|
!state.errored &&
|
||||||
|
state.buffered.length === 0 &&
|
||||||
|
!state.finished &&
|
||||||
|
!state.writing);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WritableOptions {
|
||||||
|
autoDestroy?: boolean;
|
||||||
|
decodeStrings?: boolean;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring encodings in
|
||||||
|
defaultEncoding?: string;
|
||||||
|
destroy?(
|
||||||
|
this: Writable,
|
||||||
|
error: Error | null,
|
||||||
|
callback: (error: Error | null) => void,
|
||||||
|
): void;
|
||||||
|
emitClose?: boolean;
|
||||||
|
final?(this: Writable, callback: (error?: Error | null) => void): void;
|
||||||
|
highWaterMark?: number;
|
||||||
|
objectMode?: boolean;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring encodings in
|
||||||
|
write?(
|
||||||
|
this: Writable,
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
encoding: string,
|
||||||
|
callback: (error?: Error | null) => void,
|
||||||
|
): void;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring encodings in
|
||||||
|
writev?(
|
||||||
|
this: Writable,
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunks: Array<{ chunk: any; encoding: string }>,
|
||||||
|
callback: (error?: Error | null) => void,
|
||||||
|
): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WritableState {
|
||||||
|
[kOnFinished]: Array<(error?: Error) => void> = [];
|
||||||
|
afterWriteTickInfo: null | AfterWriteTick = null;
|
||||||
|
allBuffers = true;
|
||||||
|
allNoop = true;
|
||||||
|
autoDestroy: boolean;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
buffered: Array<{
|
||||||
|
allBuffers?: boolean;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any;
|
||||||
|
encoding: string;
|
||||||
|
callback: (error: Error) => void;
|
||||||
|
}> = [];
|
||||||
|
bufferedIndex = 0;
|
||||||
|
bufferProcessing = false;
|
||||||
|
closed = false;
|
||||||
|
closeEmitted = false;
|
||||||
|
constructed: boolean;
|
||||||
|
corked = 0;
|
||||||
|
decodeStrings: boolean;
|
||||||
|
defaultEncoding: string;
|
||||||
|
destroyed = false;
|
||||||
|
emitClose: boolean;
|
||||||
|
ended = false;
|
||||||
|
ending = false;
|
||||||
|
errored: Error | null = null;
|
||||||
|
errorEmitted = false;
|
||||||
|
finalCalled = false;
|
||||||
|
finished = false;
|
||||||
|
highWaterMark: number;
|
||||||
|
length = 0;
|
||||||
|
needDrain = false;
|
||||||
|
objectMode: boolean;
|
||||||
|
onwrite: (error?: Error | null) => void;
|
||||||
|
pendingcb = 0;
|
||||||
|
prefinished = false;
|
||||||
|
sync = true;
|
||||||
|
writecb: null | ((error: Error) => void) = null;
|
||||||
|
writable = true;
|
||||||
|
writelen = 0;
|
||||||
|
writing = false;
|
||||||
|
|
||||||
|
constructor(options: WritableOptions | undefined, stream: Writable) {
|
||||||
|
this.objectMode = !!options?.objectMode;
|
||||||
|
|
||||||
|
this.highWaterMark = options?.highWaterMark ??
|
||||||
|
(this.objectMode ? 16 : 16 * 1024);
|
||||||
|
|
||||||
|
if (Number.isInteger(this.highWaterMark) && this.highWaterMark >= 0) {
|
||||||
|
this.highWaterMark = Math.floor(this.highWaterMark);
|
||||||
|
} else {
|
||||||
|
throw new ERR_INVALID_OPT_VALUE("highWaterMark", this.highWaterMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.decodeStrings = !options?.decodeStrings === false;
|
||||||
|
|
||||||
|
this.defaultEncoding = options?.defaultEncoding || "utf8";
|
||||||
|
|
||||||
|
this.onwrite = onwrite.bind(undefined, stream);
|
||||||
|
|
||||||
|
resetBuffer(this);
|
||||||
|
|
||||||
|
this.emitClose = options?.emitClose ?? true;
|
||||||
|
this.autoDestroy = options?.autoDestroy ?? true;
|
||||||
|
this.constructed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuffer() {
|
||||||
|
return this.buffered.slice(this.bufferedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
get bufferedRequestCount() {
|
||||||
|
return this.buffered.length - this.bufferedIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBuffer(state: WritableState) {
|
||||||
|
state.buffered = [];
|
||||||
|
state.bufferedIndex = 0;
|
||||||
|
state.allBuffers = true;
|
||||||
|
state.allNoop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A bit simpler than readable streams.
|
||||||
|
* Implement an async `._write(chunk, encoding, cb)`, and it'll handle all
|
||||||
|
* the drain event emission and buffering.
|
||||||
|
*/
|
||||||
|
class Writable extends Stream {
|
||||||
|
_construct?: (cb: (error?: Error) => void) => void;
|
||||||
|
_final?: (
|
||||||
|
this: Writable,
|
||||||
|
callback: (error?: Error | null | undefined) => void,
|
||||||
|
) => void;
|
||||||
|
_writableState: WritableState;
|
||||||
|
_writev?: write_v | null = null;
|
||||||
|
|
||||||
|
constructor(options?: WritableOptions) {
|
||||||
|
super();
|
||||||
|
this._writableState = new WritableState(options, this);
|
||||||
|
|
||||||
|
if (options) {
|
||||||
|
if (typeof options.write === "function") {
|
||||||
|
this._write = options.write;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.writev === "function") {
|
||||||
|
this._writev = options.writev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.destroy === "function") {
|
||||||
|
this._destroy = options.destroy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.final === "function") {
|
||||||
|
this._final = options.final;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
construct(this, () => {
|
||||||
|
const state = this._writableState;
|
||||||
|
|
||||||
|
if (!state.writing) {
|
||||||
|
clearBuffer(this, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
finishMaybe(this, state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[captureRejectionSymbol](err?: Error) {
|
||||||
|
this.destroy(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
static WritableState = WritableState;
|
||||||
|
|
||||||
|
get destroyed() {
|
||||||
|
return this._writableState ? this._writableState.destroyed : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
set destroyed(value) {
|
||||||
|
if (this._writableState) {
|
||||||
|
this._writableState.destroyed = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get writable() {
|
||||||
|
const w = this._writableState;
|
||||||
|
return !w.destroyed && !w.errored && !w.ending && !w.ended;
|
||||||
|
}
|
||||||
|
|
||||||
|
set writable(val) {
|
||||||
|
if (this._writableState) {
|
||||||
|
this._writableState.writable = !!val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableFinished() {
|
||||||
|
return this._writableState ? this._writableState.finished : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableObjectMode() {
|
||||||
|
return this._writableState ? this._writableState.objectMode : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableBuffer() {
|
||||||
|
return this._writableState && this._writableState.getBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableEnded() {
|
||||||
|
return this._writableState ? this._writableState.ending : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableHighWaterMark() {
|
||||||
|
return this._writableState && this._writableState.highWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableCorked() {
|
||||||
|
return this._writableState ? this._writableState.corked : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
get writableLength() {
|
||||||
|
return this._writableState && this._writableState.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
_undestroy() {
|
||||||
|
const w = this._writableState;
|
||||||
|
w.constructed = true;
|
||||||
|
w.destroyed = false;
|
||||||
|
w.closed = false;
|
||||||
|
w.closeEmitted = false;
|
||||||
|
w.errored = null;
|
||||||
|
w.errorEmitted = false;
|
||||||
|
w.ended = false;
|
||||||
|
w.ending = false;
|
||||||
|
w.finalCalled = false;
|
||||||
|
w.prefinished = false;
|
||||||
|
w.finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_destroy(err: Error | null, cb: (error?: Error | null) => void) {
|
||||||
|
cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(err?: Error, cb?: () => void) {
|
||||||
|
const state = this._writableState;
|
||||||
|
if (!state.destroyed) {
|
||||||
|
queueMicrotask(() => errorBuffer(state));
|
||||||
|
}
|
||||||
|
destroy.call(this, err, cb);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
end(cb?: () => void): void;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
end(chunk: any, cb?: () => void): void;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
end(chunk: any, encoding: string, cb?: () => void): void;
|
||||||
|
|
||||||
|
end(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
x?: any | (() => void),
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
y?: string | (() => void),
|
||||||
|
z?: () => void,
|
||||||
|
) {
|
||||||
|
const state = this._writableState;
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
let chunk: any | null;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
let encoding: string | null;
|
||||||
|
let cb: undefined | ((error?: Error) => void);
|
||||||
|
|
||||||
|
if (typeof x === "function") {
|
||||||
|
chunk = null;
|
||||||
|
encoding = null;
|
||||||
|
cb = x;
|
||||||
|
} else if (typeof y === "function") {
|
||||||
|
chunk = x;
|
||||||
|
encoding = null;
|
||||||
|
cb = y;
|
||||||
|
} else {
|
||||||
|
chunk = x;
|
||||||
|
encoding = y as string;
|
||||||
|
cb = z;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk !== null && chunk !== undefined) {
|
||||||
|
this.write(chunk, encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.corked) {
|
||||||
|
state.corked = 1;
|
||||||
|
this.uncork();
|
||||||
|
}
|
||||||
|
|
||||||
|
let err: Error | undefined;
|
||||||
|
if (!state.errored && !state.ending) {
|
||||||
|
state.ending = true;
|
||||||
|
finishMaybe(this, state, true);
|
||||||
|
state.ended = true;
|
||||||
|
} else if (state.finished) {
|
||||||
|
err = new ERR_STREAM_ALREADY_FINISHED("end");
|
||||||
|
} else if (state.destroyed) {
|
||||||
|
err = new ERR_STREAM_DESTROYED("end");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof cb === "function") {
|
||||||
|
if (err || state.finished) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
(cb as (error?: Error | undefined) => void)(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
state[kOnFinished].push(cb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
_write(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
encoding: string,
|
||||||
|
cb: (error?: Error | null) => void,
|
||||||
|
): void {
|
||||||
|
if (this._writev) {
|
||||||
|
this._writev([{ chunk, encoding }], cb);
|
||||||
|
} else {
|
||||||
|
throw new ERR_METHOD_NOT_IMPLEMENTED("_write()");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//This signature was changed to keep inheritance coherent
|
||||||
|
pipe(dest: Writable): Writable {
|
||||||
|
errorOrDestroy(this, new ERR_STREAM_CANNOT_PIPE());
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
write(chunk: any, cb?: (error: Error | null | undefined) => void): boolean;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
write(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
encoding: string | null,
|
||||||
|
cb?: (error: Error | null | undefined) => void,
|
||||||
|
): boolean;
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
write(
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
chunk: any,
|
||||||
|
x?: string | null | ((error: Error | null | undefined) => void),
|
||||||
|
y?: ((error: Error | null | undefined) => void),
|
||||||
|
) {
|
||||||
|
const state = this._writableState;
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring in encodings
|
||||||
|
let encoding: string;
|
||||||
|
let cb: (error?: Error | null) => void;
|
||||||
|
|
||||||
|
if (typeof x === "function") {
|
||||||
|
cb = x;
|
||||||
|
encoding = state.defaultEncoding;
|
||||||
|
} else {
|
||||||
|
if (!x) {
|
||||||
|
encoding = state.defaultEncoding;
|
||||||
|
} else if (x !== "buffer" && !Buffer.isEncoding(x)) {
|
||||||
|
throw new ERR_UNKNOWN_ENCODING(x);
|
||||||
|
} else {
|
||||||
|
encoding = x;
|
||||||
|
}
|
||||||
|
if (typeof y !== "function") {
|
||||||
|
cb = nop;
|
||||||
|
} else {
|
||||||
|
cb = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chunk === null) {
|
||||||
|
throw new ERR_STREAM_NULL_VALUES();
|
||||||
|
} else if (!state.objectMode) {
|
||||||
|
if (typeof chunk === "string") {
|
||||||
|
if (state.decodeStrings !== false) {
|
||||||
|
chunk = Buffer.from(chunk, encoding);
|
||||||
|
encoding = "buffer";
|
||||||
|
}
|
||||||
|
} else if (chunk instanceof Buffer) {
|
||||||
|
encoding = "buffer";
|
||||||
|
} else if (Stream._isUint8Array(chunk)) {
|
||||||
|
chunk = Stream._uint8ArrayToBuffer(chunk);
|
||||||
|
encoding = "buffer";
|
||||||
|
} else {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
|
"chunk",
|
||||||
|
["string", "Buffer", "Uint8Array"],
|
||||||
|
chunk,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let err: Error | undefined;
|
||||||
|
if (state.ending) {
|
||||||
|
err = new ERR_STREAM_WRITE_AFTER_END();
|
||||||
|
} else if (state.destroyed) {
|
||||||
|
err = new ERR_STREAM_DESTROYED("write");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
queueMicrotask(() => cb(err));
|
||||||
|
errorOrDestroy(this, err, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.pendingcb++;
|
||||||
|
return writeOrBuffer(this, state, chunk, encoding, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
cork() {
|
||||||
|
this._writableState.corked++;
|
||||||
|
}
|
||||||
|
|
||||||
|
uncork() {
|
||||||
|
const state = this._writableState;
|
||||||
|
|
||||||
|
if (state.corked) {
|
||||||
|
state.corked--;
|
||||||
|
|
||||||
|
if (!state.writing) {
|
||||||
|
clearBuffer(this, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO(Soremwar)
|
||||||
|
//Bring allowed encodings
|
||||||
|
setDefaultEncoding(encoding: string) {
|
||||||
|
// node::ParseEncoding() requires lower case.
|
||||||
|
if (typeof encoding === "string") {
|
||||||
|
encoding = encoding.toLowerCase();
|
||||||
|
}
|
||||||
|
if (!Buffer.isEncoding(encoding)) {
|
||||||
|
throw new ERR_UNKNOWN_ENCODING(encoding);
|
||||||
|
}
|
||||||
|
this._writableState.defaultEncoding = encoding;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Writable;
|
||||||
|
export { WritableState };
|
209
std/node/_stream/writable_test.ts
Normal file
209
std/node/_stream/writable_test.ts
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
// Copyright Node.js contributors. All rights reserved. MIT License.
|
||||||
|
import { Buffer } from "../buffer.ts";
|
||||||
|
import finished from "./end-of-stream.ts";
|
||||||
|
import Writable from "../_stream/writable.ts";
|
||||||
|
import { deferred } from "../../async/mod.ts";
|
||||||
|
import {
|
||||||
|
assert,
|
||||||
|
assertEquals,
|
||||||
|
assertStrictEquals,
|
||||||
|
assertThrows,
|
||||||
|
} from "../../testing/asserts.ts";
|
||||||
|
|
||||||
|
Deno.test("Writable stream writes correctly", async () => {
|
||||||
|
let callback: undefined | ((error?: Error | null | undefined) => void);
|
||||||
|
|
||||||
|
let writeExecuted = 0;
|
||||||
|
const writeExecutedExpected = 1;
|
||||||
|
const writeExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
let writevExecuted = 0;
|
||||||
|
const writevExecutedExpected = 1;
|
||||||
|
const writevExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const writable = new Writable({
|
||||||
|
write: (chunk, encoding, cb) => {
|
||||||
|
writeExecuted++;
|
||||||
|
if (writeExecuted == writeExecutedExpected) {
|
||||||
|
writeExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assert(chunk instanceof Buffer);
|
||||||
|
assertStrictEquals(encoding, "buffer");
|
||||||
|
assertStrictEquals(String(chunk), "ABC");
|
||||||
|
callback = cb;
|
||||||
|
},
|
||||||
|
writev: (chunks) => {
|
||||||
|
writevExecuted++;
|
||||||
|
if (writevExecuted == writevExecutedExpected) {
|
||||||
|
writevExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assertStrictEquals(chunks.length, 2);
|
||||||
|
assertStrictEquals(chunks[0].encoding, "buffer");
|
||||||
|
assertStrictEquals(chunks[1].encoding, "buffer");
|
||||||
|
assertStrictEquals(chunks[0].chunk + chunks[1].chunk, "DEFGHI");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
writable.write(new TextEncoder().encode("ABC"));
|
||||||
|
writable.write(new TextEncoder().encode("DEF"));
|
||||||
|
writable.end(new TextEncoder().encode("GHI"));
|
||||||
|
callback?.();
|
||||||
|
|
||||||
|
const writeTimeout = setTimeout(
|
||||||
|
() => writeExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
const writevTimeout = setTimeout(
|
||||||
|
() => writevExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await writeExpectedExecutions;
|
||||||
|
await writevExpectedExecutions;
|
||||||
|
clearTimeout(writeTimeout);
|
||||||
|
clearTimeout(writevTimeout);
|
||||||
|
assertEquals(writeExecuted, writeExecutedExpected);
|
||||||
|
assertEquals(writevExecuted, writevExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Writable stream writes Uint8Array in object mode", async () => {
|
||||||
|
let writeExecuted = 0;
|
||||||
|
const writeExecutedExpected = 1;
|
||||||
|
const writeExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const ABC = new TextEncoder().encode("ABC");
|
||||||
|
|
||||||
|
const writable = new Writable({
|
||||||
|
objectMode: true,
|
||||||
|
write: (chunk, encoding, cb) => {
|
||||||
|
writeExecuted++;
|
||||||
|
if (writeExecuted == writeExecutedExpected) {
|
||||||
|
writeExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assert(!(chunk instanceof Buffer));
|
||||||
|
assert(chunk instanceof Uint8Array);
|
||||||
|
assertEquals(chunk, ABC);
|
||||||
|
assertEquals(encoding, "utf8");
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
writable.end(ABC);
|
||||||
|
|
||||||
|
const writeTimeout = setTimeout(
|
||||||
|
() => writeExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await writeExpectedExecutions;
|
||||||
|
clearTimeout(writeTimeout);
|
||||||
|
assertEquals(writeExecuted, writeExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Writable stream throws on unexpected close", async () => {
|
||||||
|
let finishedExecuted = 0;
|
||||||
|
const finishedExecutedExpected = 1;
|
||||||
|
const finishedExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const writable = new Writable({
|
||||||
|
write: () => {},
|
||||||
|
});
|
||||||
|
writable.writable = false;
|
||||||
|
writable.destroy();
|
||||||
|
|
||||||
|
finished(writable, (err) => {
|
||||||
|
finishedExecuted++;
|
||||||
|
if (finishedExecuted == finishedExecutedExpected) {
|
||||||
|
finishedExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
assertEquals(err?.code, "ERR_STREAM_PREMATURE_CLOSE");
|
||||||
|
});
|
||||||
|
|
||||||
|
const finishedTimeout = setTimeout(
|
||||||
|
() => finishedExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await finishedExpectedExecutions;
|
||||||
|
clearTimeout(finishedTimeout);
|
||||||
|
assertEquals(finishedExecuted, finishedExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Writable stream finishes correctly", async () => {
|
||||||
|
let finishedExecuted = 0;
|
||||||
|
const finishedExecutedExpected = 1;
|
||||||
|
const finishedExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const w = new Writable({
|
||||||
|
write(_chunk, _encoding, cb) {
|
||||||
|
cb();
|
||||||
|
},
|
||||||
|
autoDestroy: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
w.end("asd");
|
||||||
|
|
||||||
|
queueMicrotask(() => {
|
||||||
|
finished(w, () => {
|
||||||
|
finishedExecuted++;
|
||||||
|
if (finishedExecuted == finishedExecutedExpected) {
|
||||||
|
finishedExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const finishedTimeout = setTimeout(
|
||||||
|
() => finishedExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await finishedExpectedExecutions;
|
||||||
|
clearTimeout(finishedTimeout);
|
||||||
|
assertEquals(finishedExecuted, finishedExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Writable stream finishes correctly after error", async () => {
|
||||||
|
let errorExecuted = 0;
|
||||||
|
const errorExecutedExpected = 1;
|
||||||
|
const errorExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
let finishedExecuted = 0;
|
||||||
|
const finishedExecutedExpected = 1;
|
||||||
|
const finishedExpectedExecutions = deferred();
|
||||||
|
|
||||||
|
const w = new Writable({
|
||||||
|
write(_chunk, _encoding, cb) {
|
||||||
|
cb(new Error());
|
||||||
|
},
|
||||||
|
autoDestroy: false,
|
||||||
|
});
|
||||||
|
w.write("asd");
|
||||||
|
w.on("error", () => {
|
||||||
|
errorExecuted++;
|
||||||
|
if (errorExecuted == errorExecutedExpected) {
|
||||||
|
errorExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
finished(w, () => {
|
||||||
|
finishedExecuted++;
|
||||||
|
if (finishedExecuted == finishedExecutedExpected) {
|
||||||
|
finishedExpectedExecutions.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorTimeout = setTimeout(
|
||||||
|
() => errorExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
const finishedTimeout = setTimeout(
|
||||||
|
() => finishedExpectedExecutions.reject(),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await finishedExpectedExecutions;
|
||||||
|
await errorExpectedExecutions;
|
||||||
|
clearTimeout(finishedTimeout);
|
||||||
|
clearTimeout(errorTimeout);
|
||||||
|
assertEquals(finishedExecuted, finishedExecutedExpected);
|
||||||
|
assertEquals(errorExecuted, errorExecutedExpected);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("Writable stream fails on 'write' null value", () => {
|
||||||
|
const writable = new Writable();
|
||||||
|
assertThrows(() => writable.write(null));
|
||||||
|
});
|
|
@ -1,15 +1,11 @@
|
||||||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||||
export { promisify } from "./_util/_util_promisify.ts";
|
export { promisify } from "./_util/_util_promisify.ts";
|
||||||
export { callbackify } from "./_util/_util_callbackify.ts";
|
export { callbackify } from "./_util/_util_callbackify.ts";
|
||||||
import { codes, errorMap } from "./_errors.ts";
|
import { ERR_INVALID_ARG_TYPE, ERR_OUT_OF_RANGE, errorMap } from "./_errors.ts";
|
||||||
import * as types from "./_util/_util_types.ts";
|
import * as types from "./_util/_util_types.ts";
|
||||||
export { types };
|
export { types };
|
||||||
|
|
||||||
const NumberIsSafeInteger = Number.isSafeInteger;
|
const NumberIsSafeInteger = Number.isSafeInteger;
|
||||||
const {
|
|
||||||
ERR_OUT_OF_RANGE,
|
|
||||||
ERR_INVALID_ARG_TYPE,
|
|
||||||
} = codes;
|
|
||||||
|
|
||||||
const DEFAULT_INSPECT_OPTIONS = {
|
const DEFAULT_INSPECT_OPTIONS = {
|
||||||
showHidden: false,
|
showHidden: false,
|
||||||
|
|
|
@ -131,3 +131,15 @@ export function validateIntegerRange(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OptionalSpread<T> = T extends undefined ? []
|
||||||
|
: [T];
|
||||||
|
|
||||||
|
export function once(callback: (...args: OptionalSpread<undefined>) => void) {
|
||||||
|
let called = false;
|
||||||
|
return function (this: unknown, ...args: OptionalSpread<undefined>) {
|
||||||
|
if (called) return;
|
||||||
|
called = true;
|
||||||
|
callback.apply(this, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -44,8 +44,7 @@ const {
|
||||||
keys: ObjectKeys,
|
keys: ObjectKeys,
|
||||||
} = Object;
|
} = Object;
|
||||||
|
|
||||||
import { codes } from "./_errors.ts";
|
import { ERR_INVALID_ARG_TYPE } from "./_errors.ts";
|
||||||
const { ERR_INVALID_ARG_TYPE } = codes;
|
|
||||||
|
|
||||||
let blue = "";
|
let blue = "";
|
||||||
let green = "";
|
let green = "";
|
||||||
|
|
Loading…
Reference in a new issue