summaryrefslogtreecommitdiff
path: root/cli/js/streams/shared-internals.ts
diff options
context:
space:
mode:
authorNick Stott <nick@nickstott.com>2019-10-28 12:41:36 -0400
committerRy Dahl <ry@tinyclouds.org>2019-10-28 12:41:36 -0400
commit65d9286203cf239f68c6015818e82e8521e600a1 (patch)
tree0af1a7be449036f2f4ae9d3ecf06b7d645c8bddc /cli/js/streams/shared-internals.ts
parent967c236fa5fb1e87e1b5ee788fe77d3a07361da1 (diff)
Re-enable basic stream support for fetch bodies (#3192)
* Add sd-streams from https://github.com/stardazed/sd-streams/blob/master/packages/streams/src/ * change the interfaces in dom_types to match what sd-streams expects
Diffstat (limited to 'cli/js/streams/shared-internals.ts')
-rw-r--r--cli/js/streams/shared-internals.ts310
1 files changed, 310 insertions, 0 deletions
diff --git a/cli/js/streams/shared-internals.ts b/cli/js/streams/shared-internals.ts
new file mode 100644
index 000000000..90e66e591
--- /dev/null
+++ b/cli/js/streams/shared-internals.ts
@@ -0,0 +1,310 @@
+// Forked from https://github.com/stardazed/sd-streams/tree/8928cf04b035fd02fb1340b7eb541c76be37e546
+// Copyright (c) 2018-Present by Arthur Langereis - @zenmumbler MIT
+
+/**
+ * streams/shared-internals - common types and methods for streams
+ * Part of Stardazed
+ * (c) 2018-Present by Arthur Langereis - @zenmumbler
+ * https://github.com/stardazed/sd-streams
+ */
+
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// TODO don't disable this warning
+
+import { AbortSignal, QueuingStrategySizeCallback } from "../dom_types.ts";
+import { DenoError, ErrorKind } from "../errors.ts";
+
+// common stream fields
+
+export const state_ = Symbol("state_");
+export const storedError_ = Symbol("storedError_");
+
+// ---------
+
+/** An error reason / result can be anything */
+export type ErrorResult = any;
+
+// ---------
+
+export function isInteger(value: number): boolean {
+ if (!isFinite(value)) {
+ // covers NaN, +Infinity and -Infinity
+ return false;
+ }
+ const absValue = Math.abs(value);
+ return Math.floor(absValue) === absValue;
+}
+
+export function isFiniteNonNegativeNumber(value: unknown): boolean {
+ if (!(typeof value === "number" && isFinite(value))) {
+ // covers NaN, +Infinity and -Infinity
+ return false;
+ }
+ return value >= 0;
+}
+
+export function isAbortSignal(signal: any): signal is AbortSignal {
+ if (typeof signal !== "object" || signal === null) {
+ return false;
+ }
+ try {
+ // TODO
+ // calling signal.aborted() probably isn't the right way to perform this test
+ // https://github.com/stardazed/sd-streams/blob/master/packages/streams/src/shared-internals.ts#L41
+ signal.aborted();
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+
+export function invokeOrNoop<O extends object, P extends keyof O>(
+ o: O,
+ p: P,
+ args: any[]
+): any {
+ // Assert: O is not undefined.
+ // Assert: IsPropertyKey(P) is true.
+ // Assert: args is a List.
+ const method: Function | undefined = (o as any)[p]; // tslint:disable-line:ban-types
+ if (method === undefined) {
+ return undefined;
+ }
+ return Function.prototype.apply.call(method, o, args);
+}
+
+export function cloneArrayBuffer(
+ srcBuffer: ArrayBufferLike,
+ srcByteOffset: number,
+ srcLength: number,
+ cloneConstructor: ArrayBufferConstructor | SharedArrayBufferConstructor
+): InstanceType<typeof cloneConstructor> {
+ // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
+ return srcBuffer.slice(
+ srcByteOffset,
+ srcByteOffset + srcLength
+ ) as InstanceType<typeof cloneConstructor>;
+}
+
+export function transferArrayBuffer(buffer: ArrayBufferLike): ArrayBuffer {
+ // This would in a JS engine context detach the buffer's backing store and return
+ // a new ArrayBuffer with the same backing store, invalidating `buffer`,
+ // i.e. a move operation in C++ parlance.
+ // Sadly ArrayBuffer.transfer is yet to be implemented by a single browser vendor.
+ return buffer.slice(0); // copies instead of moves
+}
+
+export function copyDataBlockBytes(
+ toBlock: ArrayBufferLike,
+ toIndex: number,
+ fromBlock: ArrayBufferLike,
+ fromIndex: number,
+ count: number
+): void {
+ new Uint8Array(toBlock, toIndex, count).set(
+ new Uint8Array(fromBlock, fromIndex, count)
+ );
+}
+
+// helper memoisation map for object values
+// weak so it doesn't keep memoized versions of old objects indefinitely.
+const objectCloneMemo = new WeakMap<object, object>();
+
+let sharedArrayBufferSupported_: boolean | undefined;
+function supportsSharedArrayBuffer(): boolean {
+ if (sharedArrayBufferSupported_ === undefined) {
+ try {
+ new SharedArrayBuffer(16);
+ sharedArrayBufferSupported_ = true;
+ } catch (e) {
+ sharedArrayBufferSupported_ = false;
+ }
+ }
+ return sharedArrayBufferSupported_;
+}
+
+/**
+ * Implement a method of value cloning that is reasonably close to performing `StructuredSerialize(StructuredDeserialize(value))`
+ * from the HTML standard. Used by the internal `readableStreamTee` method to clone values for connected implementations.
+ * @see https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
+ */
+export function cloneValue(value: any): any {
+ const valueType = typeof value;
+ switch (valueType) {
+ case "number":
+ case "string":
+ case "boolean":
+ case "undefined":
+ // @ts-ignore
+ case "bigint":
+ return value;
+ case "object": {
+ if (objectCloneMemo.has(value)) {
+ return objectCloneMemo.get(value);
+ }
+ if (value === null) {
+ return value;
+ }
+ if (value instanceof Date) {
+ return new Date(value.valueOf());
+ }
+ if (value instanceof RegExp) {
+ return new RegExp(value);
+ }
+ if (supportsSharedArrayBuffer() && value instanceof SharedArrayBuffer) {
+ return value;
+ }
+ if (value instanceof ArrayBuffer) {
+ const cloned = cloneArrayBuffer(
+ value,
+ 0,
+ value.byteLength,
+ ArrayBuffer
+ );
+ objectCloneMemo.set(value, cloned);
+ return cloned;
+ }
+ if (ArrayBuffer.isView(value)) {
+ const clonedBuffer = cloneValue(value.buffer) as ArrayBufferLike;
+ // Use DataViewConstructor type purely for type-checking, can be a DataView or TypedArray.
+ // They use the same constructor signature, only DataView has a length in bytes and TypedArrays
+ // use a length in terms of elements, so we adjust for that.
+ let length: number;
+ if (value instanceof DataView) {
+ length = value.byteLength;
+ } else {
+ length = (value as Uint8Array).length;
+ }
+ return new (value.constructor as DataViewConstructor)(
+ clonedBuffer,
+ value.byteOffset,
+ length
+ );
+ }
+ if (value instanceof Map) {
+ const clonedMap = new Map();
+ objectCloneMemo.set(value, clonedMap);
+ value.forEach((v, k) => clonedMap.set(k, cloneValue(v)));
+ return clonedMap;
+ }
+ if (value instanceof Set) {
+ const clonedSet = new Map();
+ objectCloneMemo.set(value, clonedSet);
+ value.forEach((v, k) => clonedSet.set(k, cloneValue(v)));
+ return clonedSet;
+ }
+
+ // generic object
+ const clonedObj = {} as any;
+ objectCloneMemo.set(value, clonedObj);
+ const sourceKeys = Object.getOwnPropertyNames(value);
+ for (const key of sourceKeys) {
+ clonedObj[key] = cloneValue(value[key]);
+ }
+ return clonedObj;
+ }
+ case "symbol":
+ case "function":
+ default:
+ // TODO this should be a DOMException,
+ // https://github.com/stardazed/sd-streams/blob/master/packages/streams/src/shared-internals.ts#L171
+ throw new DenoError(
+ ErrorKind.DataCloneError,
+ "Uncloneable value in stream"
+ );
+ }
+}
+
+export function promiseCall<F extends Function>(
+ f: F,
+ v: object | undefined,
+ args: any[]
+): Promise<any> {
+ // tslint:disable-line:ban-types
+ try {
+ const result = Function.prototype.apply.call(f, v, args);
+ return Promise.resolve(result);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+}
+
+export function createAlgorithmFromUnderlyingMethod<
+ O extends object,
+ K extends keyof O
+>(obj: O, methodName: K, extraArgs: any[]): any {
+ const method = obj[methodName];
+ if (method === undefined) {
+ return (): any => Promise.resolve(undefined);
+ }
+ if (typeof method !== "function") {
+ throw new TypeError(`Field "${methodName}" is not a function.`);
+ }
+ return function(...fnArgs: any[]): any {
+ return promiseCall(method, obj, fnArgs.concat(extraArgs));
+ };
+}
+
+/*
+Deprecated for now, all usages replaced by readableStreamCreateReadResult
+
+function createIterResultObject<T>(value: T, done: boolean): IteratorResult<T> {
+ return { value, done };
+}
+*/
+
+export function validateAndNormalizeHighWaterMark(hwm: unknown): number {
+ const highWaterMark = Number(hwm);
+ if (isNaN(highWaterMark) || highWaterMark < 0) {
+ throw new RangeError(
+ "highWaterMark must be a valid, non-negative integer."
+ );
+ }
+ return highWaterMark;
+}
+
+export function makeSizeAlgorithmFromSizeFunction<T>(
+ sizeFn: undefined | ((chunk: T) => number)
+): QueuingStrategySizeCallback<T> {
+ if (typeof sizeFn !== "function" && typeof sizeFn !== "undefined") {
+ throw new TypeError("size function must be undefined or a function");
+ }
+ return function(chunk: T): number {
+ if (typeof sizeFn === "function") {
+ return sizeFn(chunk);
+ }
+ return 1;
+ };
+}
+
+// ----
+
+export const enum ControlledPromiseState {
+ Pending,
+ Resolved,
+ Rejected
+}
+
+export interface ControlledPromise<V> {
+ resolve(value?: V): void;
+ reject(error: ErrorResult): void;
+ promise: Promise<V>;
+ state: ControlledPromiseState;
+}
+
+export function createControlledPromise<V>(): ControlledPromise<V> {
+ const conProm = {
+ state: ControlledPromiseState.Pending
+ } as ControlledPromise<V>;
+ conProm.promise = new Promise<V>(function(resolve, reject) {
+ conProm.resolve = function(v?: V): void {
+ conProm.state = ControlledPromiseState.Resolved;
+ resolve(v);
+ };
+ conProm.reject = function(e?: ErrorResult): void {
+ conProm.state = ControlledPromiseState.Rejected;
+ reject(e);
+ };
+ });
+ return conProm;
+}