Started to implement server

This commit is contained in:
didier amyot committed 2023-06-11 15:43:45 -04:00
1 parent 1f2ea8b7e5
commit 17a1b4bed3
177 files changed
+29894 -74

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* All internal non-test code, that is files that do not have `test` or `bench` in the name, must use the assertion functions within `_utils/asserts.ts` and not `testing/asserts.ts`. This is to create a separation of concerns between internal and testing assertions.
*/
export class DenoStdInternalError extends Error {
constructor(message: string) {
super(message);
this.name = "DenoStdInternalError";
}
}
/** Make an assertion, if not `true`, then throw. */
export function assert(expr: unknown, msg = ""): asserts expr {
if (!expr) {
throw new DenoStdInternalError(msg);
}
}
/** Use this to assert unreachable code. */
export function unreachable(): never {
throw new DenoStdInternalError("unreachable");
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
// TODO(ry) It'd be better to make Deferred a class that inherits from
// Promise, rather than an interface. This is possible in ES2016, however
// typescript produces broken code when targeting ES5 code.
// See https://github.com/Microsoft/TypeScript/issues/15202
// At the time of writing, the github issue is closed but the problem remains.
export interface Deferred<T> extends Promise<T> {
readonly state: "pending" | "fulfilled" | "rejected";
resolve(value?: T | PromiseLike<T>): void;
// deno-lint-ignore no-explicit-any
reject(reason?: any): void;
}
/**
* Creates a Promise with the `reject` and `resolve` functions placed as methods
* on the promise object itself.
*
* @example
* ```typescript
* import { deferred } from "https://deno.land/std@$STD_VERSION/async/deferred.ts";
*
* const p = deferred<number>();
* // ...
* p.resolve(42);
* ```
*/
export function deferred<T>(): Deferred<T> {
let methods;
let state = "pending";
const promise = new Promise<T>((resolve, reject) => {
methods = {
async resolve(value: T | PromiseLike<T>) {
await value;
state = "fulfilled";
resolve(value);
},
// deno-lint-ignore no-explicit-any
reject(reason?: any) {
state = "rejected";
reject(reason);
},
};
});
Object.defineProperty(promise, "state", { get: () => state });
return Object.assign(promise, methods) as Deferred<T>;
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* An abstraction of multiple Uint8Arrays
*/
export class BytesList {
#len = 0;
#chunks: {
value: Uint8Array;
start: number; // start offset from head of chunk
end: number; // end offset from head of chunk
offset: number; // offset of head in all bytes
}[] = [];
constructor() {}
/**
* Total size of bytes
*/
size() {
return this.#len;
}
/**
* Push bytes with given offset infos
*/
add(value: Uint8Array, start = 0, end = value.byteLength) {
if (value.byteLength === 0 || end - start === 0) {
return;
}
checkRange(start, end, value.byteLength);
this.#chunks.push({
value,
end,
start,
offset: this.#len,
});
this.#len += end - start;
}
/**
* Drop head `n` bytes.
*/
shift(n: number) {
if (n === 0) {
return;
}
if (this.#len <= n) {
this.#chunks = [];
this.#len = 0;
return;
}
const idx = this.getChunkIndex(n);
this.#chunks.splice(0, idx);
const [chunk] = this.#chunks;
if (chunk) {
const diff = n - chunk.offset;
chunk.start += diff;
}
let offset = 0;
for (const chunk of this.#chunks) {
chunk.offset = offset;
offset += chunk.end - chunk.start;
}
this.#len = offset;
}
/**
* Find chunk index in which `pos` locates by binary-search
* returns -1 if out of range
*/
getChunkIndex(pos: number): number {
let max = this.#chunks.length;
let min = 0;
while (true) {
const i = min + Math.floor((max - min) / 2);
if (i < 0 || this.#chunks.length <= i) {
return -1;
}
const { offset, start, end } = this.#chunks[i];
const len = end - start;
if (offset <= pos && pos < offset + len) {
return i;
} else if (offset + len <= pos) {
min = i + 1;
} else {
max = i - 1;
}
}
}
/**
* Get indexed byte from chunks
*/
get(i: number): number {
if (i < 0 || this.#len <= i) {
throw new Error("out of range");
}
const idx = this.getChunkIndex(i);
const { value, offset, start } = this.#chunks[idx];
return value[start + i - offset];
}
/**
* Iterator of bytes from given position
*/
*iterator(start = 0): IterableIterator<number> {
const startIdx = this.getChunkIndex(start);
if (startIdx < 0) return;
const first = this.#chunks[startIdx];
let firstOffset = start - first.offset;
for (let i = startIdx; i < this.#chunks.length; i++) {
const chunk = this.#chunks[i];
for (let j = chunk.start + firstOffset; j < chunk.end; j++) {
yield chunk.value[j];
}
firstOffset = 0;
}
}
/**
* Returns subset of bytes copied
*/
slice(start: number, end: number = this.#len): Uint8Array {
if (end === start) {
return new Uint8Array();
}
checkRange(start, end, this.#len);
const result = new Uint8Array(end - start);
const startIdx = this.getChunkIndex(start);
const endIdx = this.getChunkIndex(end - 1);
let written = 0;
for (let i = startIdx; i <= endIdx; i++) {
const {
value: chunkValue,
start: chunkStart,
end: chunkEnd,
offset: chunkOffset,
} = this.#chunks[i];
const readStart = chunkStart + (i === startIdx ? start - chunkOffset : 0);
const readEnd = i === endIdx ? end - chunkOffset + chunkStart : chunkEnd;
const len = readEnd - readStart;
result.set(chunkValue.subarray(readStart, readEnd), written);
written += len;
}
return result;
}
/**
* Concatenate chunks into single Uint8Array copied.
*/
concat(): Uint8Array {
const result = new Uint8Array(this.#len);
let sum = 0;
for (const { value, start, end } of this.#chunks) {
result.set(value.subarray(start, end), sum);
sum += end - start;
}
return result;
}
}
function checkRange(start: number, end: number, len: number) {
if (start < 0 || len < start || end < 0 || len < end || end < start) {
throw new Error("invalid range");
}
}
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** Copy bytes from the `src` array to the `dst` array. Returns the number of
* bytes copied.
*
* If the `src` array is larger than what the `dst` array can hold, only the
* amount of bytes that fit in the `dst` array are copied.
*
* An offset can be specified as the third argument that begins the copy at
* that given index in the `dst` array. The offset defaults to the beginning of
* the array.
*
* ```ts
* import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts";
* const src = new Uint8Array([9, 8, 7]);
* const dst = new Uint8Array([0, 1, 2, 3, 4, 5]);
* console.log(copy(src, dst)); // 3
* console.log(dst); // [9, 8, 7, 3, 4, 5]
* ```
*
* ```ts
* import { copy } from "https://deno.land/std@$STD_VERSION/bytes/copy.ts";
* const src = new Uint8Array([1, 1, 1, 1]);
* const dst = new Uint8Array([0, 0, 0, 0]);
* console.log(copy(src, dst, 1)); // 3
* console.log(dst); // [0, 1, 1, 1]
* ```
*/
export function copy(src: Uint8Array, dst: Uint8Array, off = 0): number {
off = Math.max(0, Math.min(off, dst.byteLength));
const dstBytesAvailable = dst.byteLength - off;
if (src.byteLength > dstBytesAvailable) {
src = src.subarray(0, dstBytesAvailable);
}
dst.set(src, off);
return src.byteLength;
}
+249
View File
@@ -0,0 +1,249 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { assert } from "../_util/asserts.ts";
import { copy } from "../bytes/copy.ts";
import type { Reader, ReaderSync } from "../types.d.ts";
// MIN_READ is the minimum ArrayBuffer size passed to a read call by
// buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond
// what is required to hold the contents of r, readFrom() will not grow the
// underlying buffer.
const MIN_READ = 32 * 1024;
const MAX_SIZE = 2 ** 32 - 2;
/** A variable-sized buffer of bytes with `read()` and `write()` methods.
*
* Buffer is almost always used with some I/O like files and sockets. It allows
* one to buffer up a download from a socket. Buffer grows and shrinks as
* necessary.
*
* Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in
* 2009 before JavaScript had the concept of ArrayBuffers. It's simply a
* non-standard ArrayBuffer.
*
* ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of
* ArrayBuffer.
*
* Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */
export class Buffer {
#buf: Uint8Array; // contents are the bytes buf[off : len(buf)]
#off = 0; // read at buf[off], write at buf[buf.byteLength]
constructor(ab?: ArrayBufferLike | ArrayLike<number>) {
this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab);
}
/** Returns a slice holding the unread portion of the buffer.
*
* The slice is valid for use only until the next buffer modification (that
* is, only until the next call to a method like `read()`, `write()`,
* `reset()`, or `truncate()`). If `options.copy` is false the slice aliases the buffer content at
* least until the next buffer modification, so immediate changes to the
* slice will affect the result of future reads.
* @param [options={ copy: true }]
*/
bytes(options = { copy: true }): Uint8Array {
if (options.copy === false) return this.#buf.subarray(this.#off);
return this.#buf.slice(this.#off);
}
/** Returns whether the unread portion of the buffer is empty. */
empty(): boolean {
return this.#buf.byteLength <= this.#off;
}
/** A read only number of bytes of the unread portion of the buffer. */
get length(): number {
return this.#buf.byteLength - this.#off;
}
/** The read only capacity of the buffer's underlying byte slice, that is,
* the total space allocated for the buffer's data. */
get capacity(): number {
return this.#buf.buffer.byteLength;
}
/** Discards all but the first `n` unread bytes from the buffer but
* continues to use the same allocated storage. It throws if `n` is
* negative or greater than the length of the buffer. */
truncate(n: number) {
if (n === 0) {
this.reset();
return;
}
if (n < 0 || n > this.length) {
throw Error("bytes.Buffer: truncation out of range");
}
this.#reslice(this.#off + n);
}
reset() {
this.#reslice(0);
this.#off = 0;
}
#tryGrowByReslice(n: number) {
const l = this.#buf.byteLength;
if (n <= this.capacity - l) {
this.#reslice(l + n);
return l;
}
return -1;
}
#reslice(len: number) {
assert(len <= this.#buf.buffer.byteLength);
this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
}
/** Reads the next `p.length` bytes from the buffer or until the buffer is
* drained. Returns the number of bytes read. If the buffer has no data to
* return, the return is EOF (`null`). */
readSync(p: Uint8Array): number | null {
if (this.empty()) {
// Buffer is empty, reset to recover space.
this.reset();
if (p.byteLength === 0) {
// this edge case is tested in 'bufferReadEmptyAtEOF' test
return 0;
}
return null;
}
const nread = copy(this.#buf.subarray(this.#off), p);
this.#off += nread;
return nread;
}
/** Reads the next `p.length` bytes from the buffer or until the buffer is
* drained. Resolves to the number of bytes read. If the buffer has no
* data to return, resolves to EOF (`null`).
*
* NOTE: This methods reads bytes synchronously; it's provided for
* compatibility with `Reader` interfaces.
*/
read(p: Uint8Array): Promise<number | null> {
const rr = this.readSync(p);
return Promise.resolve(rr);
}
writeSync(p: Uint8Array): number {
const m = this.#grow(p.byteLength);
return copy(p, this.#buf, m);
}
/** NOTE: This methods writes bytes synchronously; it's provided for
* compatibility with `Writer` interface. */
write(p: Uint8Array): Promise<number> {
const n = this.writeSync(p);
return Promise.resolve(n);
}
#grow(n: number) {
const m = this.length;
// If buffer is empty, reset to recover space.
if (m === 0 && this.#off !== 0) {
this.reset();
}
// Fast: Try to grow by means of a reslice.
const i = this.#tryGrowByReslice(n);
if (i >= 0) {
return i;
}
const c = this.capacity;
if (n <= Math.floor(c / 2) - m) {
// We can slide things down instead of allocating a new
// ArrayBuffer. We only need m+n <= c to slide, but
// we instead let capacity get twice as large so we
// don't spend all our time copying.
copy(this.#buf.subarray(this.#off), this.#buf);
} else if (c + n > MAX_SIZE) {
throw new Error("The buffer cannot be grown beyond the maximum size.");
} else {
// Not enough space anywhere, we need to allocate.
const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE));
copy(this.#buf.subarray(this.#off), buf);
this.#buf = buf;
}
// Restore this.#off and len(this.#buf).
this.#off = 0;
this.#reslice(Math.min(m + n, MAX_SIZE));
return m;
}
/** Grows the buffer's capacity, if necessary, to guarantee space for
* another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to
* the buffer without another allocation. If `n` is negative, `.grow()` will
* throw. If the buffer can't grow it will throw an error.
*
* Based on Go Lang's
* [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */
grow(n: number) {
if (n < 0) {
throw Error("Buffer.grow: negative count");
}
const m = this.#grow(n);
this.#reslice(m);
}
/** Reads data from `r` until EOF (`null`) and appends it to the buffer,
* growing the buffer as needed. It resolves to the number of bytes read.
* If the buffer becomes too large, `.readFrom()` will reject with an error.
*
* Based on Go Lang's
* [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */
async readFrom(r: Reader): Promise<number> {
let n = 0;
const tmp = new Uint8Array(MIN_READ);
while (true) {
const shouldGrow = this.capacity - this.length < MIN_READ;
// read into tmp buffer if there's not enough room
// otherwise read directly into the internal buffer
const buf = shouldGrow
? tmp
: new Uint8Array(this.#buf.buffer, this.length);
const nread = await r.read(buf);
if (nread === null) {
return n;
}
// write will grow if needed
if (shouldGrow) this.writeSync(buf.subarray(0, nread));
else this.#reslice(this.length + nread);
n += nread;
}
}
/** Reads data from `r` until EOF (`null`) and appends it to the buffer,
* growing the buffer as needed. It returns the number of bytes read. If the
* buffer becomes too large, `.readFromSync()` will throw an error.
*
* Based on Go Lang's
* [Buffer.ReadFrom](https://golang.org/pkg/bytes/#Buffer.ReadFrom). */
readFromSync(r: ReaderSync): number {
let n = 0;
const tmp = new Uint8Array(MIN_READ);
while (true) {
const shouldGrow = this.capacity - this.length < MIN_READ;
// read into tmp buffer if there's not enough room
// otherwise read directly into the internal buffer
const buf = shouldGrow
? tmp
: new Uint8Array(this.#buf.buffer, this.length);
const nread = r.readSync(buf);
if (nread === null) {
return n;
}
// write will grow if needed
if (shouldGrow) this.writeSync(buf.subarray(0, nread));
else this.#reslice(this.length + nread);
n += nread;
}
}
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
export const DEFAULT_CHUNK_SIZE = 16_640;
export const DEFAULT_BUFFER_SIZE = 32 * 1024;
/** Generate longest proper prefix which is also suffix array. */
export function createLPS(pat: Uint8Array): Uint8Array {
const lps = new Uint8Array(pat.length);
lps[0] = 0;
let prefixEnd = 0;
let i = 1;
while (i < lps.length) {
if (pat[i] == pat[prefixEnd]) {
prefixEnd++;
lps[i] = prefixEnd;
i++;
} else if (prefixEnd === 0) {
lps[i] = 0;
i++;
} else {
prefixEnd = lps[prefixEnd - 1];
}
}
return lps;
}
+169
View File
@@ -0,0 +1,169 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { assert } from "../_util/asserts.ts";
import { copy } from "../bytes/copy.ts";
const MAX_SIZE = 2 ** 32 - 2;
const DEFAULT_CHUNK_SIZE = 16_640;
/** A variable-sized buffer of bytes with `read()` and `write()` methods.
*
* Buffer is almost always used with some I/O like files and sockets. It allows
* one to buffer up a download from a socket. Buffer grows and shrinks as
* necessary.
*
* Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in
* 2009 before JavaScript had the concept of ArrayBuffers. It's simply a
* non-standard ArrayBuffer.
*
* ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of
* ArrayBuffer.
*
* Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */
export class Buffer {
#buf: Uint8Array; // contents are the bytes buf[off : len(buf)]
#off = 0; // read at buf[off], write at buf[buf.byteLength]
#readable: ReadableStream<Uint8Array> = new ReadableStream({
type: "bytes",
pull: (controller) => {
const view = new Uint8Array(controller.byobRequest!.view!.buffer);
if (this.empty()) {
// Buffer is empty, reset to recover space.
this.reset();
controller.close();
controller.byobRequest!.respond(0);
return;
}
const nread = copy(this.#buf.subarray(this.#off), view);
this.#off += nread;
controller.byobRequest!.respond(nread);
},
autoAllocateChunkSize: DEFAULT_CHUNK_SIZE,
});
get readable() {
return this.#readable;
}
#writable = new WritableStream<Uint8Array>({
write: (chunk) => {
const m = this.#grow(chunk.byteLength);
copy(chunk, this.#buf, m);
},
});
get writable() {
return this.#writable;
}
constructor(ab?: ArrayBufferLike | ArrayLike<number>) {
this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab);
}
/** Returns a slice holding the unread portion of the buffer.
*
* The slice is valid for use only until the next buffer modification (that
* is, only until the next call to a method like `read()`, `write()`,
* `reset()`, or `truncate()`). If `options.copy` is false the slice aliases
* the buffer content at least until the next buffer modification, so
* immediate changes to the slice will affect the result of future reads.
*/
bytes(options = { copy: true }): Uint8Array {
if (options.copy === false) return this.#buf.subarray(this.#off);
return this.#buf.slice(this.#off);
}
/** Returns whether the unread portion of the buffer is empty. */
empty(): boolean {
return this.#buf.byteLength <= this.#off;
}
/** A read only number of bytes of the unread portion of the buffer. */
get length(): number {
return this.#buf.byteLength - this.#off;
}
/** The read only capacity of the buffer's underlying byte slice, that is,
* the total space allocated for the buffer's data. */
get capacity(): number {
return this.#buf.buffer.byteLength;
}
/** Discards all but the first `n` unread bytes from the buffer but
* continues to use the same allocated storage. It throws if `n` is
* negative or greater than the length of the buffer. */
truncate(n: number) {
if (n === 0) {
this.reset();
return;
}
if (n < 0 || n > this.length) {
throw Error("bytes.Buffer: truncation out of range");
}
this.#reslice(this.#off + n);
}
reset() {
this.#reslice(0);
this.#off = 0;
}
#tryGrowByReslice(n: number) {
const l = this.#buf.byteLength;
if (n <= this.capacity - l) {
this.#reslice(l + n);
return l;
}
return -1;
}
#reslice(len: number) {
assert(len <= this.#buf.buffer.byteLength);
this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
}
#grow(n: number) {
const m = this.length;
// If buffer is empty, reset to recover space.
if (m === 0 && this.#off !== 0) {
this.reset();
}
// Fast: Try to grow by means of a reslice.
const i = this.#tryGrowByReslice(n);
if (i >= 0) {
return i;
}
const c = this.capacity;
if (n <= Math.floor(c / 2) - m) {
// We can slide things down instead of allocating a new
// ArrayBuffer. We only need m+n <= c to slide, but
// we instead let capacity get twice as large so we
// don't spend all our time copying.
copy(this.#buf.subarray(this.#off), this.#buf);
} else if (c + n > MAX_SIZE) {
throw new Error("The buffer cannot be grown beyond the maximum size.");
} else {
// Not enough space anywhere, we need to allocate.
const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE));
copy(this.#buf.subarray(this.#off), buf);
this.#buf = buf;
}
// Restore this.#off and len(this.#buf).
this.#off = 0;
this.#reslice(Math.min(m + n, MAX_SIZE));
return m;
}
/** Grows the buffer's capacity, if necessary, to guarantee space for
* another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to
* the buffer without another allocation. If `n` is negative, `.grow()` will
* throw. If the buffer can't grow it will throw an error.
*
* Based on Go Lang's
* [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */
grow(n: number) {
if (n < 0) {
throw Error("Buffer.grow: negative count");
}
const m = this.#grow(n);
this.#reslice(m);
}
}
@@ -0,0 +1,45 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { assert } from "../_util/asserts.ts";
/**
* A transform stream that only transforms from the zero-indexed `start` and `end` bytes (both inclusive).
*
* @example
* ```ts
* import { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/byte_slice_stream.ts";
* const response = await fetch("https://example.com");
* const rangedStream = response.body!
* .pipeThrough(new ByteSliceStream(3, 8));
* ```
*/
export class ByteSliceStream extends TransformStream<Uint8Array, Uint8Array> {
#offsetStart = 0;
#offsetEnd = 0;
constructor(start = 0, end = Infinity) {
super({
start: () => {
assert(start >= 0, "`start` must be greater than 0");
end += 1;
},
transform: (chunk, controller) => {
this.#offsetStart = this.#offsetEnd;
this.#offsetEnd += chunk.byteLength;
if (this.#offsetEnd > start) {
if (this.#offsetStart < start) {
chunk = chunk.slice(start - this.#offsetStart);
}
if (this.#offsetEnd >= end) {
chunk = chunk.slice(0, chunk.byteLength - this.#offsetEnd + end);
controller.enqueue(chunk);
controller.terminate();
} else {
controller.enqueue(chunk);
}
}
},
});
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { DEFAULT_BUFFER_SIZE } from "./_common.ts";
import type { Reader, Writer } from "../types.d.ts";
/** Copies from `src` to `dst` until either EOF (`null`) is read from `src` or
* an error occurs. It resolves to the number of bytes copied or rejects with
* the first error encountered while copying.
*
* ```ts
* import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts";
*
* const source = await Deno.open("my_file.txt");
* const bytesCopied1 = await copy(source, Deno.stdout);
* const destination = await Deno.create("my_file_2.txt");
* const bytesCopied2 = await copy(source, destination);
* ```
*
* @param src The source to copy from
* @param dst The destination to copy to
* @param options Can be used to tune size of the buffer. Default size is 32kB
*/
export async function copy(
src: Reader,
dst: Writer,
options?: {
bufSize?: number;
},
): Promise<number> {
let n = 0;
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
let gotEOF = false;
while (gotEOF === false) {
const result = await src.read(b);
if (result === null) {
gotEOF = true;
} else {
let nwritten = 0;
while (nwritten < result) {
nwritten += await dst.write(b.subarray(nwritten, result));
}
n += nwritten;
}
}
return n;
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { BytesList } from "../bytes/bytes_list.ts";
import { createLPS } from "./_common.ts";
/** Disposition of the delimiter. */
export type DelimiterDisposition =
/** Include delimiter in the found chunk. */
| "suffix"
/** Include delimiter in the subsequent chunk. */
| "prefix"
/** Discard the delimiter. */
| "discard" // delimiter discarded
;
export interface DelimiterStreamOptions {
/** Disposition of the delimiter. */
disposition?: DelimiterDisposition;
}
/**
* Divide a stream into chunks delimited by a given byte sequence.
*
* @example
* Divide a CSV stream by commas, discarding the commas:
* ```ts
* import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts";
* const res = await fetch("https://example.com/data.csv");
* const parts = res.body!
* .pipeThrough(new DelimiterStream(new TextEncoder().encode(",")))
* .pipeThrough(new TextDecoderStream());
* ```
*
* @example
* Divide a stream after semi-colons, keeping the semi-colons in the output:
* ```ts
* import { DelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/delimiter_stream.ts";
* const res = await fetch("https://example.com/file.js");
* const parts = res.body!
* .pipeThrough(
* new DelimiterStream(
* new TextEncoder().encode(";"),
* { disposition: "suffix" },
* )
* )
* .pipeThrough(new TextDecoderStream());
* ```
*
* @param delimiter Delimiter byte sequence
* @param options Options for the transform stream
* @returns Transform stream
*/
export class DelimiterStream extends TransformStream<Uint8Array, Uint8Array> {
#bufs = new BytesList();
#delimiter: Uint8Array;
#inspectIndex = 0;
#matchIndex = 0;
#delimLen: number;
#delimLPS: Uint8Array;
#disp?: DelimiterDisposition;
constructor(
delimiter: Uint8Array,
options?: DelimiterStreamOptions,
) {
super({
transform: (chunk, controller) => {
this.#handle(chunk, controller);
},
flush: (controller) => {
controller.enqueue(this.#bufs.concat());
},
});
this.#delimiter = delimiter;
this.#delimLen = delimiter.length;
this.#delimLPS = createLPS(delimiter);
this.#disp = options?.disposition ?? "discard";
}
#handle(
chunk: Uint8Array,
controller: TransformStreamDefaultController<Uint8Array>,
) {
this.#bufs.add(chunk);
let localIndex = 0;
while (this.#inspectIndex < this.#bufs.size()) {
if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) {
this.#inspectIndex++;
localIndex++;
this.#matchIndex++;
if (this.#matchIndex === this.#delimLen) {
// Full match
const start = this.#inspectIndex - this.#delimLen;
const end = this.#disp == "suffix" ? this.#inspectIndex : start;
const copy = this.#bufs.slice(0, end);
controller.enqueue(copy);
const shift = this.#disp == "prefix" ? start : this.#inspectIndex;
this.#bufs.shift(shift);
this.#inspectIndex = this.#disp == "prefix" ? this.#delimLen : 0;
this.#matchIndex = 0;
}
} else {
if (this.#matchIndex === 0) {
this.#inspectIndex++;
localIndex++;
} else {
this.#matchIndex = this.#delimLPS[this.#matchIndex - 1];
}
}
}
}
}
@@ -0,0 +1,34 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* Merge multiple streams into a single one, taking order into account, and each stream
* will wait for a chunk to enqueue before the next stream can append another chunk.
* If a stream ends before other ones, the others will be cancelled.
*/
export function earlyZipReadableStreams<T>(
...streams: ReadableStream<T>[]
): ReadableStream<T> {
const readers = streams.map((s) => s.getReader());
return new ReadableStream<T>({
async start(controller) {
try {
loop:
while (true) {
for (const reader of readers) {
const { value, done } = await reader.read();
if (!done) {
controller.enqueue(value!);
} else {
await Promise.all(readers.map((reader) => reader.cancel()));
break loop;
}
}
}
controller.close();
} catch (e) {
controller.error(e);
}
},
});
}
+102
View File
@@ -0,0 +1,102 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { DEFAULT_BUFFER_SIZE } from "./_common.ts";
import type { Reader, ReaderSync } from "../types.d.ts";
/** Turns a Reader, `r`, into an async iterator.
*
* ```ts
* import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = await Deno.open("/etc/passwd");
* for await (const chunk of iterateReader(f)) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Second argument can be used to tune size of a buffer.
* Default size of the buffer is 32kB.
*
* ```ts
* import { iterateReader } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = await Deno.open("/etc/passwd");
* const it = iterateReader(f, {
* bufSize: 1024 * 1024
* });
* for await (const chunk of it) {
* console.log(chunk);
* }
* f.close();
* ```
*/
export async function* iterateReader(
r: Reader,
options?: {
bufSize?: number;
},
): AsyncIterableIterator<Uint8Array> {
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
while (true) {
const result = await r.read(b);
if (result === null) {
break;
}
yield b.slice(0, result);
}
}
/** Turns a ReaderSync, `r`, into an iterator.
*
* ```ts
* import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
*
* let f = Deno.openSync("/etc/passwd");
* for (const chunk of iterateReaderSync(f)) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Second argument can be used to tune size of a buffer.
* Default size of the buffer is 32kB.
*
* ```ts
* import { iterateReaderSync } from "https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts";
* let f = await Deno.open("/etc/passwd");
* const iter = iterateReaderSync(f, {
* bufSize: 1024 * 1024
* });
* for (const chunk of iter) {
* console.log(chunk);
* }
* f.close();
* ```
*
* Iterator uses an internal buffer of fixed size for efficiency; it returns
* a view on that buffer on each iteration. It is therefore caller's
* responsibility to copy contents of the buffer if needed; otherwise the
* next iteration will overwrite contents of previously returned chunk.
*/
export function* iterateReaderSync(
r: ReaderSync,
options?: {
bufSize?: number;
},
): IterableIterator<Uint8Array> {
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
const b = new Uint8Array(bufSize);
while (true) {
const result = r.readSync(b);
if (result === null) {
break;
}
yield b.slice(0, result);
}
}
@@ -0,0 +1,37 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** A TransformStream that will only read & enqueue `size` amount of bytes.
* This operation is chunk based and not BYOB based,
* and as such will read more than needed.
*
* if options.error is set, then instead of terminating the stream,
* an error will be thrown.
*
* ```ts
* import { LimitedBytesTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_bytes_transform_stream.ts";
* const res = await fetch("https://example.com");
* const parts = res.body!
* .pipeThrough(new LimitedBytesTransformStream(512 * 1024));
* ```
*/
export class LimitedBytesTransformStream
extends TransformStream<Uint8Array, Uint8Array> {
#read = 0;
constructor(size: number, options: { error?: boolean } = {}) {
super({
transform: (chunk, controller) => {
if ((this.#read + chunk.byteLength) > size) {
if (options.error) {
throw new RangeError(`Exceeded byte size limit of '${size}'`);
} else {
controller.terminate();
}
} else {
this.#read += chunk.byteLength;
controller.enqueue(chunk);
}
},
});
}
}
@@ -0,0 +1,33 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** A TransformStream that will only read & enqueue `size` amount of chunks.
*
* if options.error is set, then instead of terminating the stream,
* an error will be thrown.
*
* ```ts
* import { LimitedTransformStream } from "https://deno.land/std@$STD_VERSION/streams/limited_transform_stream.ts";
* const res = await fetch("https://example.com");
* const parts = res.body!.pipeThrough(new LimitedTransformStream(50));
* ```
*/
export class LimitedTransformStream<T> extends TransformStream<T, T> {
#read = 0;
constructor(size: number, options: { error?: boolean } = {}) {
super({
transform: (chunk, controller) => {
if ((this.#read + 1) > size) {
if (options.error) {
throw new RangeError(`Exceeded chunk limit of '${size}'`);
} else {
controller.terminate();
}
} else {
this.#read++;
controller.enqueue(chunk);
}
},
});
}
}
@@ -0,0 +1,33 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { deferred } from "../async/deferred.ts";
/**
* Merge multiple streams into a single one, not taking order into account.
* If a stream ends before other ones, the other will continue adding data,
* and the finished one will not add any more data.
*/
export function mergeReadableStreams<T>(
...streams: ReadableStream<T>[]
): ReadableStream<T> {
const resolvePromises = streams.map(() => deferred<void>());
return new ReadableStream<T>({
start(controller) {
Promise.all(resolvePromises).then(() => {
controller.close();
});
try {
for (const [key, stream] of Object.entries(streams)) {
(async () => {
for await (const data of stream) {
controller.enqueue(data);
}
resolvePromises[+key].resolve();
})();
}
} catch (e) {
controller.error(e);
}
},
});
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
/**
* Utilities for working with the
* [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API).
*
* Includes buffering and conversion.
*
* @module
*/
export * from "./buffer.ts";
export * from "./byte_slice_stream.ts";
export * from "./copy.ts";
export * from "./delimiter_stream.ts";
export * from "./early_zip_readable_streams.ts";
export * from "./iterate_reader.ts";
export * from "./limited_bytes_transform_stream.ts";
export * from "./limited_transform_stream.ts";
export * from "./merge_readable_streams.ts";
export * from "./read_all.ts";
export * from "./readable_stream_from_iterable.ts";
export * from "./readable_stream_from_reader.ts";
export * from "./reader_from_iterable.ts";
export * from "./reader_from_stream_reader.ts";
export * from "./text_delimiter_stream.ts";
export * from "./text_line_stream.ts";
export * from "./to_transform_stream.ts";
export * from "./writable_stream_from_writer.ts";
export * from "./write_all.ts";
export * from "./writer_from_stream_writer.ts";
export * from "./zip_readable_streams.ts";
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { Buffer } from "../io/buffer.ts";
import type { Reader, ReaderSync } from "../types.d.ts";
/** Read Reader `r` until EOF (`null`) and resolve to the content as
* Uint8Array`.
*
* ```ts
* import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts";
* import { readAll } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts";
*
* // Example from stdin
* const stdinContent = await readAll(Deno.stdin);
*
* // Example from file
* const file = await Deno.open("my_file.txt", {read: true});
* const myFileContent = await readAll(file);
* file.close();
*
* // Example from buffer
* const myData = new Uint8Array(100);
* // ... fill myData array with data
* const reader = new Buffer(myData.buffer);
* const bufferContent = await readAll(reader);
* ```
*/
export async function readAll(r: Reader): Promise<Uint8Array> {
const buf = new Buffer();
await buf.readFrom(r);
return buf.bytes();
}
/** Synchronously reads Reader `r` until EOF (`null`) and returns the content
* as `Uint8Array`.
*
* ```ts
* import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts";
* import { readAllSync } from "https://deno.land/std@$STD_VERSION/streams/read_all.ts";
*
* // Example from stdin
* const stdinContent = readAllSync(Deno.stdin);
*
* // Example from file
* const file = Deno.openSync("my_file.txt", {read: true});
* const myFileContent = readAllSync(file);
* file.close();
*
* // Example from buffer
* const myData = new Uint8Array(100);
* // ... fill myData array with data
* const reader = new Buffer(myData.buffer);
* const bufferContent = readAllSync(reader);
* ```
*/
export function readAllSync(r: ReaderSync): Uint8Array {
const buf = new Buffer();
buf.readFromSync(r);
return buf.bytes();
}
@@ -0,0 +1,63 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** Create a `ReadableStream` from any kind of iterable.
*
* ```ts
* import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts";
*
* const r1 = readableStreamFromIterable(["foo, bar, baz"]);
* const r2 = readableStreamFromIterable(async function* () {
* await new Promise(((r) => setTimeout(r, 1000)));
* yield "foo";
* await new Promise(((r) => setTimeout(r, 1000)));
* yield "bar";
* await new Promise(((r) => setTimeout(r, 1000)));
* yield "baz";
* }());
* ```
*
* If the produced iterator (`iterable[Symbol.asyncIterator]()` or
* `iterable[Symbol.iterator]()`) is a generator, or more specifically is found
* to have a `.throw()` method on it, that will be called upon
* `readableStream.cancel()`. This is the case for the second input type above:
*
* ```ts
* import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts";
*
* const r3 = readableStreamFromIterable(async function* () {
* try {
* yield "foo";
* } catch (error) {
* console.log(error); // Error: Cancelled by consumer.
* }
* }());
* const reader = r3.getReader();
* console.log(await reader.read()); // { value: "foo", done: false }
* await reader.cancel(new Error("Cancelled by consumer."));
* ```
*/
export function readableStreamFromIterable<T>(
iterable: Iterable<T> | AsyncIterable<T>,
): ReadableStream<T> {
const iterator: Iterator<T> | AsyncIterator<T> =
(iterable as AsyncIterable<T>)[Symbol.asyncIterator]?.() ??
(iterable as Iterable<T>)[Symbol.iterator]?.();
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (done) {
controller.close();
} else {
controller.enqueue(value);
}
},
async cancel(reason) {
if (typeof iterator.throw == "function") {
try {
await iterator.throw(reason);
} catch { /* `iterator.throw()` always throws on site. We catch it. */ }
}
},
});
}
@@ -0,0 +1,81 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { DEFAULT_CHUNK_SIZE } from "./_common.ts";
import type { Closer, Reader } from "../types.d.ts";
function isCloser(value: unknown): value is Closer {
return typeof value === "object" && value != null && "close" in value &&
// deno-lint-ignore no-explicit-any
typeof (value as Record<string, any>)["close"] === "function";
}
export interface ReadableStreamFromReaderOptions {
/** If the `reader` is also a `Closer`, automatically close the `reader`
* when `EOF` is encountered, or a read error occurs.
*
* @default {true}
*/
autoClose?: boolean;
/** The size of chunks to allocate to read, the default is ~16KiB, which is
* the maximum size that Deno operations can currently support. */
chunkSize?: number;
/** The queuing strategy to create the `ReadableStream` with. */
strategy?: { highWaterMark?: number | undefined; size?: undefined };
}
/**
* Create a `ReadableStream<Uint8Array>` from a `Reader`.
*
* When the pull algorithm is called on the stream, a chunk from the reader
* will be read. When `null` is returned from the reader, the stream will be
* closed along with the reader (if it is also a `Closer`).
*
* An example converting a `Deno.FsFile` into a readable stream:
*
* ```ts
* import { readableStreamFromReader } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_reader.ts";
*
* const file = await Deno.open("./file.txt", { read: true });
* const fileStream = readableStreamFromReader(file);
* ```
*/
export function readableStreamFromReader(
reader: Reader | (Reader & Closer),
options: ReadableStreamFromReaderOptions = {},
): ReadableStream<Uint8Array> {
const {
autoClose = true,
chunkSize = DEFAULT_CHUNK_SIZE,
strategy,
} = options;
return new ReadableStream({
async pull(controller) {
const chunk = new Uint8Array(chunkSize);
try {
const read = await reader.read(chunk);
if (read === null) {
if (isCloser(reader) && autoClose) {
reader.close();
}
controller.close();
return;
}
controller.enqueue(chunk.subarray(0, read));
} catch (e) {
controller.error(e);
if (isCloser(reader)) {
reader.close();
}
}
},
cancel() {
if (isCloser(reader) && autoClose) {
reader.close();
}
},
}, strategy);
}
@@ -0,0 +1,56 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { Buffer } from "../io/buffer.ts";
import { writeAll } from "./write_all.ts";
import { Reader } from "../types.d.ts";
/** Create a `Reader` from an iterable of `Uint8Array`s.
*
* ```ts
* import { readerFromIterable } from "https://deno.land/std@$STD_VERSION/streams/reader_from_iterable.ts";
* import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts";
*
* const file = await Deno.open("metrics.txt", { write: true });
* const reader = readerFromIterable((async function* () {
* while (true) {
* await new Promise((r) => setTimeout(r, 1000));
* const message = `data: ${JSON.stringify(Deno.metrics())}\n\n`;
* yield new TextEncoder().encode(message);
* }
* })());
* await copy(reader, file);
* ```
*/
export function readerFromIterable(
iterable: Iterable<Uint8Array> | AsyncIterable<Uint8Array>,
): Reader {
const iterator: Iterator<Uint8Array> | AsyncIterator<Uint8Array> =
(iterable as AsyncIterable<Uint8Array>)[Symbol.asyncIterator]?.() ??
(iterable as Iterable<Uint8Array>)[Symbol.iterator]?.();
const buffer = new Buffer();
return {
async read(p: Uint8Array): Promise<number | null> {
if (buffer.length == 0) {
const result = await iterator.next();
if (result.done) {
return null;
} else {
if (result.value.byteLength <= p.byteLength) {
p.set(result.value);
return result.value.byteLength;
}
p.set(result.value.subarray(0, p.byteLength));
await writeAll(buffer, result.value.subarray(p.byteLength));
return p.byteLength;
}
} else {
const n = await buffer.read(p);
if (n == null) {
return this.read(p);
}
return n;
}
},
};
}
@@ -0,0 +1,43 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { Buffer } from "../io/buffer.ts";
import { writeAll } from "./write_all.ts";
import type { Reader } from "../types.d.ts";
/**
* Create a `Reader` from a `ReadableStreamDefaultReader`.
*
* @example
* ```ts
* import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts";
* import { readerFromStreamReader } from "https://deno.land/std@$STD_VERSION/streams/reader_from_stream_reader.ts";
*
* const res = await fetch("https://deno.land");
* const file = await Deno.open("./deno.land.html", { create: true, write: true });
*
* const reader = readerFromStreamReader(res.body!.getReader());
* await copy(reader, file);
* file.close();
* ```
*/
export function readerFromStreamReader(
streamReader: ReadableStreamDefaultReader<Uint8Array>,
): Reader {
const buffer = new Buffer();
return {
async read(p: Uint8Array): Promise<number | null> {
if (buffer.empty()) {
const res = await streamReader.read();
if (res.done) {
return null; // EOF
}
await writeAll(buffer, res.value);
}
return buffer.read(p);
},
};
}
@@ -0,0 +1,78 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { createLPS } from "./_common.ts";
import type {
DelimiterDisposition,
DelimiterStreamOptions,
} from "./delimiter_stream.ts";
/** Transform a stream into a stream where each chunk is divided by a given delimiter.
*
* ```ts
* import { TextDelimiterStream } from "https://deno.land/std@$STD_VERSION/streams/text_delimiter_stream.ts";
* const res = await fetch("https://example.com");
* const parts = res.body!
* .pipeThrough(new TextDecoderStream())
* .pipeThrough(new TextDelimiterStream("foo"));
* ```
*/
export class TextDelimiterStream extends TransformStream<string, string> {
#buf = "";
#delimiter: string;
#inspectIndex = 0;
#matchIndex = 0;
#delimLPS: Uint8Array;
#disp: DelimiterDisposition;
constructor(delimiter: string, options?: DelimiterStreamOptions) {
super({
transform: (chunk, controller) => {
this.#handle(chunk, controller);
},
flush: (controller) => {
controller.enqueue(this.#buf);
},
});
this.#delimiter = delimiter;
this.#delimLPS = createLPS(new TextEncoder().encode(delimiter));
this.#disp = options?.disposition ?? "discard";
}
#handle(
chunk: string,
controller: TransformStreamDefaultController<string>,
) {
this.#buf += chunk;
let localIndex = 0;
while (this.#inspectIndex < this.#buf.length) {
if (chunk[localIndex] === this.#delimiter[this.#matchIndex]) {
this.#inspectIndex++;
localIndex++;
this.#matchIndex++;
if (this.#matchIndex === this.#delimiter.length) {
// Full match
const start = this.#inspectIndex - this.#delimiter.length;
const end = this.#disp === "suffix" ? this.#inspectIndex : start;
const copy = this.#buf.slice(0, end);
controller.enqueue(copy);
const shift = this.#disp == "prefix" ? start : this.#inspectIndex;
this.#buf = this.#buf.slice(shift);
this.#inspectIndex = this.#disp == "prefix"
? this.#delimiter.length
: 0;
this.#matchIndex = 0;
}
} else {
if (this.#matchIndex === 0) {
this.#inspectIndex++;
localIndex++;
} else {
this.#matchIndex = this.#delimLPS[this.#matchIndex - 1];
}
}
}
}
}
@@ -0,0 +1,74 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
interface TextLineStreamOptions {
/** Allow splitting by solo \r */
allowCR: boolean;
}
/** Transform a stream into a stream where each chunk is divided by a newline,
* be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option.
*
* ```ts
* import { TextLineStream } from "https://deno.land/std@$STD_VERSION/streams/text_line_stream.ts";
* const res = await fetch("https://example.com");
* const lines = res.body!
* .pipeThrough(new TextDecoderStream())
* .pipeThrough(new TextLineStream());
* ```
*/
export class TextLineStream extends TransformStream<string, string> {
readonly #allowCR: boolean;
#buf = "";
constructor(options?: TextLineStreamOptions) {
super({
transform: (chunk, controller) => this.#handle(chunk, controller),
flush: (controller) => {
if (this.#buf.length > 0) {
if (
this.#allowCR &&
this.#buf[this.#buf.length - 1] === "\r"
) controller.enqueue(this.#buf.slice(0, -1));
else controller.enqueue(this.#buf);
}
},
});
this.#allowCR = options?.allowCR ?? false;
}
#handle(chunk: string, controller: TransformStreamDefaultController<string>) {
chunk = this.#buf + chunk;
for (;;) {
const lfIndex = chunk.indexOf("\n");
if (this.#allowCR) {
const crIndex = chunk.indexOf("\r");
if (
crIndex !== -1 && crIndex !== (chunk.length - 1) &&
(lfIndex === -1 || (lfIndex - 1) > crIndex)
) {
controller.enqueue(chunk.slice(0, crIndex));
chunk = chunk.slice(crIndex + 1);
continue;
}
}
if (lfIndex !== -1) {
let crOrLfIndex = lfIndex;
if (chunk[lfIndex - 1] === "\r") {
crOrLfIndex--;
}
controller.enqueue(chunk.slice(0, crOrLfIndex));
chunk = chunk.slice(lfIndex + 1);
continue;
}
break;
}
this.#buf = chunk;
}
}
@@ -0,0 +1,75 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* Convert the generator function into a TransformStream.
*
* ```ts
* import { readableStreamFromIterable } from "https://deno.land/std@$STD_VERSION/streams/readable_stream_from_iterable.ts";
* import { toTransformStream } from "https://deno.land/std@$STD_VERSION/streams/to_transform_stream.ts";
*
* const readable = readableStreamFromIterable([0, 1, 2])
* .pipeThrough(toTransformStream(async function* (src) {
* for await (const chunk of src) {
* yield chunk * 100;
* }
* }));
*
* for await (const chunk of readable) {
* console.log(chunk);
* }
* // output: 0, 100, 200
* ```
*
* @param transformer A function to transform.
* @param writableStrategy An object that optionally defines a queuing strategy for the stream.
* @param readableStrategy An object that optionally defines a queuing strategy for the stream.
*/
export function toTransformStream<I, O>(
transformer: (src: ReadableStream<I>) => Iterable<O> | AsyncIterable<O>,
writableStrategy?: QueuingStrategy<I>,
readableStrategy?: QueuingStrategy<O>,
): TransformStream<I, O> {
const {
writable,
readable,
} = new TransformStream<I, I>(undefined, writableStrategy);
const iterable = transformer(readable);
const iterator: Iterator<O> | AsyncIterator<O> =
(iterable as AsyncIterable<O>)[Symbol.asyncIterator]?.() ??
(iterable as Iterable<O>)[Symbol.iterator]?.();
return {
writable,
readable: new ReadableStream<O>({
async pull(controller) {
let result: IteratorResult<O>;
try {
result = await iterator.next();
} catch (error) {
// Propagate error to stream from iterator
// If the stream status is "errored", it will be thrown, but ignore.
await readable.cancel(error).catch(() => {});
controller.error(error);
return;
}
if (result.done) {
controller.close();
return;
}
controller.enqueue(result.value);
},
async cancel(reason) {
// Propagate cancellation to readable and iterator
if (typeof iterator.throw == "function") {
try {
await iterator.throw(reason);
} catch {
/* `iterator.throw()` always throws on site. We catch it. */
}
}
await readable.cancel(reason);
},
}, readableStrategy),
};
}
@@ -0,0 +1,52 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { writeAll } from "./write_all.ts";
import type { Closer, Writer } from "../types.d.ts";
function isCloser(value: unknown): value is Closer {
return typeof value === "object" && value != null && "close" in value &&
// deno-lint-ignore no-explicit-any
typeof (value as Record<string, any>)["close"] === "function";
}
export interface WritableStreamFromWriterOptions {
/**
* If the `writer` is also a `Closer`, automatically close the `writer`
* when the stream is closed, aborted, or a write error occurs.
*
* @default {true}
*/
autoClose?: boolean;
}
/** Create a `WritableStream` from a `Writer`. */
export function writableStreamFromWriter(
writer: Writer,
options: WritableStreamFromWriterOptions = {},
): WritableStream<Uint8Array> {
const { autoClose = true } = options;
return new WritableStream({
async write(chunk, controller) {
try {
await writeAll(writer, chunk);
} catch (e) {
controller.error(e);
if (isCloser(writer) && autoClose) {
writer.close();
}
}
},
close() {
if (isCloser(writer) && autoClose) {
writer.close();
}
},
abort() {
if (isCloser(writer) && autoClose) {
writer.close();
}
},
});
}
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import type { Writer, WriterSync } from "../types.d.ts";
/** Write all the content of the array buffer (`arr`) to the writer (`w`).
*
* ```ts
* import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts";
* import { writeAll } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts";
* // Example writing to stdout
* let contentBytes = new TextEncoder().encode("Hello World");
* await writeAll(Deno.stdout, contentBytes);
*
* // Example writing to file
* contentBytes = new TextEncoder().encode("Hello World");
* const file = await Deno.open('test.file', {write: true});
* await writeAll(file, contentBytes);
* file.close();
*
* // Example writing to buffer
* contentBytes = new TextEncoder().encode("Hello World");
* const writer = new Buffer();
* await writeAll(writer, contentBytes);
* console.log(writer.bytes().length); // 11
* ```
*/
export async function writeAll(w: Writer, arr: Uint8Array) {
let nwritten = 0;
while (nwritten < arr.length) {
nwritten += await w.write(arr.subarray(nwritten));
}
}
/** Synchronously write all the content of the array buffer (`arr`) to the
* writer (`w`).
*
* ```ts
* import { Buffer } from "https://deno.land/std@$STD_VERSION/io/buffer.ts";
* import { writeAllSync } from "https://deno.land/std@$STD_VERSION/streams/write_all.ts";
*
* // Example writing to stdout
* let contentBytes = new TextEncoder().encode("Hello World");
* writeAllSync(Deno.stdout, contentBytes);
*
* // Example writing to file
* contentBytes = new TextEncoder().encode("Hello World");
* const file = Deno.openSync('test.file', {write: true});
* writeAllSync(file, contentBytes);
* file.close();
*
* // Example writing to buffer
* contentBytes = new TextEncoder().encode("Hello World");
* const writer = new Buffer();
* writeAllSync(writer, contentBytes);
* console.log(writer.bytes().length); // 11
* ```
*/
export function writeAllSync(w: WriterSync, arr: Uint8Array) {
let nwritten = 0;
while (nwritten < arr.length) {
nwritten += w.writeSync(arr.subarray(nwritten));
}
}
@@ -0,0 +1,35 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import type { Writer } from "../types.d.ts";
/** Create a `Writer` from a `WritableStreamDefaultWriter`.
*
* @example
* ```ts
* import { copy } from "https://deno.land/std@$STD_VERSION/streams/copy.ts";
* import { writerFromStreamWriter } from "https://deno.land/std@$STD_VERSION/streams/writer_from_stream_writer.ts";
*
* const file = await Deno.open("./deno.land.html", { read: true });
*
* const writableStream = new WritableStream({
* write(chunk): void {
* console.log(chunk);
* },
* });
* const writer = writerFromStreamWriter(writableStream.getWriter());
* await copy(file, writer);
* file.close();
* ```
*/
export function writerFromStreamWriter(
streamWriter: WritableStreamDefaultWriter<Uint8Array>,
): Writer {
return {
async write(p: Uint8Array): Promise<number> {
await streamWriter.ready;
await streamWriter.write(p);
return p.length;
},
};
}
@@ -0,0 +1,35 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/**
* Merge multiple streams into a single one, taking order into account, and each stream
* will wait for a chunk to enqueue before the next stream can append another chunk.
* If a stream ends before other ones, the others will continue adding data in order,
* and the finished one will not add any more data.
*/
export function zipReadableStreams<T>(
...streams: ReadableStream<T>[]
): ReadableStream<T> {
const readers = streams.map((s) => s.getReader());
return new ReadableStream<T>({
async start(controller) {
try {
let resolved = 0;
while (resolved != streams.length) {
for (const [key, reader] of Object.entries(readers)) {
const { value, done } = await reader.read();
if (!done) {
controller.enqueue(value!);
} else {
resolved++;
readers.splice(+key, 1);
}
}
}
controller.close();
} catch (e) {
controller.error(e);
}
},
});
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
/** See the Contributing > Types section in the README for an explanation of this file. */
/** An abstract interface which when implemented provides an interface to read bytes into an array buffer asynchronously. */
export interface Reader {
/** Reads up to `p.byteLength` bytes into `p`. It resolves to the number of
* bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error
* encountered. Even if `read()` resolves to `n` < `p.byteLength`, it may
* use all of `p` as scratch space during the call. If some data is
* available but not `p.byteLength` bytes, `read()` conventionally resolves
* to what is available instead of waiting for more.
*
* When `read()` encounters end-of-file condition, it resolves to EOF
* (`null`).
*
* When `read()` encounters an error, it rejects with an error.
*
* Callers should always process the `n` > `0` bytes returned before
* considering the EOF (`null`). Doing so correctly handles I/O errors that
* happen after reading some bytes and also both of the allowed EOF
* behaviors.
*
* Implementations should not retain a reference to `p`.
*
* Use iterateReader() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a Reader into an
* AsyncIterator.
*/
read(p: Uint8Array): Promise<number | null>;
}
/** An abstract interface which when implemented provides an interface to read bytes into an array buffer synchronously. */
export interface ReaderSync {
/** Reads up to `p.byteLength` bytes into `p`. It resolves to the number
* of bytes read (`0` < `n` <= `p.byteLength`) and rejects if any error
* encountered. Even if `read()` returns `n` < `p.byteLength`, it may use
* all of `p` as scratch space during the call. If some data is available
* but not `p.byteLength` bytes, `read()` conventionally returns what is
* available instead of waiting for more.
*
* When `readSync()` encounters end-of-file condition, it returns EOF
* (`null`).
*
* When `readSync()` encounters an error, it throws with an error.
*
* Callers should always process the `n` > `0` bytes returned before
* considering the EOF (`null`). Doing so correctly handles I/O errors that happen
* after reading some bytes and also both of the allowed EOF behaviors.
*
* Implementations should not retain a reference to `p`.
*
* Use iterateReaderSync() from https://deno.land/std@$STD_VERSION/streams/iterate_reader.ts to turn a ReaderSync
* into an Iterator.
*/
readSync(p: Uint8Array): number | null;
}
/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource asynchronously. */
export interface Writer {
/** Writes `p.byteLength` bytes from `p` to the underlying data stream. It
* resolves to the number of bytes written from `p` (`0` <= `n` <=
* `p.byteLength`) or reject with the error encountered that caused the
* write to stop early. `write()` must reject with a non-null error if
* would resolve to `n` < `p.byteLength`. `write()` must not modify the
* slice data, even temporarily.
*
* Implementations should not retain a reference to `p`.
*/
write(p: Uint8Array): Promise<number>;
}
/** An abstract interface which when implemented provides an interface to write bytes from an array buffer to a file/resource synchronously. */
export interface WriterSync {
/** Writes `p.byteLength` bytes from `p` to the underlying data
* stream. It returns the number of bytes written from `p` (`0` <= `n`
* <= `p.byteLength`) and any error encountered that caused the write to
* stop early. `writeSync()` must throw a non-null error if it returns `n` <
* `p.byteLength`. `writeSync()` must not modify the slice data, even
* temporarily.
*
* Implementations should not retain a reference to `p`.
*/
writeSync(p: Uint8Array): number;
}
/** An abstract interface which when implemented provides an interface to close files/resources that were previously opened. */
export interface Closer {
/** Closes the resource, "freeing" the backing file/resource. */
close(): void;
}