UNPKG

edge-mock

Version:

types for testing an developer edge applications

237 lines (202 loc) 6.33 kB
export class EdgeReadableStream<R = string | Uint8Array | ArrayBuffer> implements ReadableStream { protected readonly _internals: StreamInternals<R> constructor(underlyingSource?: UnderlyingSource<R>, strategy?: QueuingStrategy<R>) { this._internals = new StreamInternals<R>(underlyingSource, strategy || {}) } get locked(): boolean { return this._internals.locked } cancel(reason?: string): Promise<void> { return this._internals.cancel(reason) } getReader({mode}: {mode?: 'byob'} = {}): ReadableStreamDefaultReader<R> { if (mode) { throw new TypeError('ReadableStream modes other than default are not supported') } this._internals.acquireLock() return new EdgeReadableStreamDefaultReader(this._internals) } pipeThrough<T>(_transform: ReadableWritablePair<T, R>, _options?: StreamPipeOptions): ReadableStream<T> { throw new Error('pipeThrough not yet implemented') } pipeTo(_dest: WritableStream<R>, _options?: StreamPipeOptions): Promise<void> { throw new Error('pipeTo not yet implemented') } tee(): [ReadableStream<R>, ReadableStream<R>] { return this._internals.tee() } } class StreamInternals<R> { protected readonly _source?: UnderlyingSource<R> protected readonly _chunks: R[] protected readonly _controller: EdgeReadableStreamDefaultController<R> protected readonly _on_done_resolvers: Set<BasicCallback> = new Set() protected _closed = false protected _done = false protected _error: any = null protected _locked = false protected _start_promise: any = null protected _highWaterMark: number constructor(source?: UnderlyingSource<R>, {highWaterMark, size}: QueuingStrategy<R> = {}) { this._source = source if (source?.type) { throw new Error('UnderlyingSource.type is not yet supported') } this._highWaterMark = highWaterMark || 10 if (size) { throw new Error('TODO call size') } this._chunks = [] this._controller = new EdgeReadableStreamDefaultController<R>(this) if (this._source?.start) { this._start_promise = this._source.start(this._controller) } } cancel(_reason?: string): Promise<void> { this._chunks.length = 0 this._closed = true if (this._source?.cancel) { this._source?.cancel(this._controller) } return new Promise(resolve => { this.addResolver(resolve) }) } get locked(): boolean { return this._locked } acquireLock(): void { if (this._locked) { throw new Error('ReadableStream already locked') } this._locked = true } releaseLock(): void { this._locked = false } close(): void { this._closed = true } enqueue(chunk: R): void { this._chunks.push(chunk) } error(e?: any): void { this._error = e || true } addResolver(resolver: BasicCallback): void { this._on_done_resolvers.add(resolver) } protected done(): ReadableStreamDefaultReadDoneResult { for (const resolve of this._on_done_resolvers) { resolve() } this._done = true return {done: true, value: undefined} } async read(): Promise<ReadableStreamDefaultReadResult<R>> { if (this._done) { return {done: true, value: undefined} } if (this._start_promise) { await this._start_promise this._start_promise = null } if (!this._closed && this._chunks.length < this._highWaterMark && this._source?.pull) { if (this._error) { throw this._error } else { await Promise.resolve(this._source.pull(this._controller)) } } const value = this._chunks.shift() if (value == undefined) { return this.done() } else { return {done: false, value} } } tee(): [ReadableStream<R>, ReadableStream<R>] { this.acquireLock() const chunks1: R[] = [...this._chunks] const chunks2: R[] = [...this._chunks] const start = async () => { const p = this._start_promise if (p) { this._start_promise = null await p } } const pull = async (controller: ReadableStreamController<R>, which: 1 | 2): Promise<void> => { const {value} = await this.read() if (value) { chunks1.push(value) chunks2.push(value) } const chunks = which == 1 ? chunks1 : chunks2 const next = chunks.shift() if (next == undefined) { controller.close() } else { controller.enqueue(next) } } const cancel = async (controller: ReadableStreamController<R>): Promise<void> => { this.cancel() const c = this._source?.cancel if (c) { delete this._source?.cancel await c(controller) } } const source1: UnderlyingSource<R> = { start: () => start(), pull: controller => pull(controller, 1), cancel: controller => cancel(controller), } const source2: UnderlyingSource<R> = { start: () => start(), pull: controller => pull(controller, 2), cancel: controller => cancel(controller), } return [new EdgeReadableStream(source1), new EdgeReadableStream(source2)] } } class EdgeReadableStreamDefaultController<R> implements ReadableStreamDefaultController { readonly desiredSize: number | null = null protected readonly _internals: StreamInternals<R> constructor(internals: StreamInternals<R>) { this._internals = internals } close(): void { this._internals.close() } enqueue(chunk: R): void { this._internals.enqueue(chunk) } error(e?: any): void { this._internals.error(e) } } type BasicCallback = () => void class EdgeReadableStreamDefaultReader<R> implements ReadableStreamDefaultReader { protected readonly _internals: StreamInternals<R> protected readonly _closed_promise: Promise<undefined> constructor(internals: StreamInternals<R>) { this._internals = internals this._closed_promise = new Promise(resolve => { internals.addResolver(() => resolve(undefined)) }) } get closed(): Promise<undefined> { return this._closed_promise } async read(): Promise<ReadableStreamDefaultReadResult<R>> { return this._internals.read() } cancel(reason?: any): Promise<void> { return this._internals.cancel(reason) } releaseLock(): void { this._internals.releaseLock() } }