mirror of
https://github.com/denoland/deno.git
synced 2024-11-30 16:40:57 -05:00
306 lines
8.9 KiB
TypeScript
306 lines
8.9 KiB
TypeScript
// 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";
|
|
|
|
// 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 Error("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;
|
|
}
|