char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport webstreams extension to typescripte9b1b43

main
8.2 KiB260 linesraw
1const Readable = globalThis.ReadableStream;
2const Writable = globalThis.WritableStream;
3const Transform = globalThis.TransformStream;
4const ReadableDefaultReader = globalThis.ReadableStreamDefaultReader;
5const WritableDefaultWriter = globalThis.WritableStreamDefaultWriter;
6const getReader = Readable.prototype.getReader;
7const read = ReadableDefaultReader.prototype.read;
8const cancelReader = ReadableDefaultReader.prototype.cancel;
9const getWriter = Writable.prototype.getWriter;
10const write = WritableDefaultWriter.prototype.write;
11const closeWriter = WritableDefaultWriter.prototype.close;
12const closeStream = Writable.prototype.close;
13const abortWriter = WritableDefaultWriter.prototype.abort;
14const writerClosed = Object.getOwnPropertyDescriptor(
15  WritableDefaultWriter.prototype,
16  "closed",
17)!
18  .get as () => Promise<void>;
19const isExtensible = Object.isExtensible;
20const transferStates = new WeakMap<object, StreamRecord>();
21function invoke<F extends (...args: never[]) => unknown>(
22  fn: F,
23  receiver: unknown,
24  args: unknown[] = [],
25): ReturnType<F> {
26  return apply(fn, receiver, args) as ReturnType<F>;
27}
28function cloneRejection<T>(promise: Promise<T>): Promise<T> {
29  return then(promise, undefined, (error) => {
30    throw cloneError(error);
31  });
32}
33function cloneError(error: unknown): unknown {
34  try {
35    return cloneValue(error);
36  } catch (cloneError) {
37    return cloneError;
38  }
39}
40function cancelAfterFailure(
41  record: StreamRecord,
42  error: unknown,
43): Promise<never> {
44  let cancellation;
45  try {
46    cancellation = invoke(cancelReader, record.reader, [error]);
47  } catch {
48    throw error;
49  }
50  return then(cancellation, () => {
51    throw error;
52  }, () => {
53    throw error;
54  });
55}
56function makeReadable(record: StreamRecord): ReadableResource {
57  const source = {
58    pull(controller: ReadController) {
59      const reading = then(invoke(read, record.reader), (result) => {
60        if (result.done) controller.close();
61        else controller.enqueue(cloneValue(result.value));
62      });
63      return then(
64        reading,
65        undefined,
66        (error) => cancelAfterFailure(record, cloneError(error)),
67      );
68    },
69    cancel(reason: unknown) {
70      let copied;
71      try {
72        copied = cloneValue(reason);
73      } catch (error) {
74        return cancelAfterFailure(record, error);
75      }
76      return cloneRejection(invoke(cancelReader, record.reader, [copied]));
77    },
78  };
79  return new Readable(source, { highWaterMark: 0 });
80}
81function abortAfterFailure(
82  record: StreamRecord,
83  error: unknown,
84): Promise<never> {
85  let abort;
86  try {
87    abort = invoke(abortWriter, record.writer, [error]);
88  } catch {
89    throw error;
90  }
91  return then(abort, () => {
92    throw error;
93  }, () => {
94    throw error;
95  });
96}
97function makeWritable(record: StreamRecord): WritableResource {
98  return new Writable({
99    start(controller) {
100      record.controller = controller;
101    },
102    write(chunk) {
103      let copied;
104      try {
105        copied = cloneValue(chunk);
106      } catch (error) {
107        return abortAfterFailure(record, error);
108      }
109      return cloneRejection(invoke(write, record.writer, [copied]));
110    },
111    close() {
112      if (record.writableState === "closed") {
113        return invoke(writerClosed, record.writer);
114      }
115      return cloneRejection(invoke(closeWriter, record.writer));
116    },
117    abort(reason) {
118      let copied;
119      try {
120        copied = cloneValue(reason);
121      } catch (error) {
122        return abortAfterFailure(record, error);
123      }
124      return cloneRejection(invoke(abortWriter, record.writer, [copied]));
125    },
126  }, { highWaterMark: 1 });
127}
128function prepareMarked(_state: unknown, source: object): StreamResource {
129  const record: StreamRecord = {
130    source,
131    readable: null,
132    writable: null,
133    reader: null,
134    writer: null,
135  };
136  if (descriptor(source, "_readableStreamController")) {
137    record.readable = source as ReadableResource;
138    record.kind = "readable";
139  } else if (descriptor(source, "_writableStreamController")) {
140    record.writable = source as WritableResource;
141    record.kind = "writable";
142  } else if (descriptor(source, "_transformStreamController")) {
143    record.readable = ownValue(source, "_readable") as ReadableResource;
144    record.writable = ownValue(source, "_writable") as WritableResource;
145    record.kind = "transform";
146  } else {
147    throw dataCloneError("This object is not a transferable stream");
148  }
149  validateRecord(record);
150  if (record.readable) record.readableClone = makeReadable(record);
151  if (record.writable) record.writableClone = makeWritable(record);
152  let destination: StreamResource | undefined = record.readableClone;
153  if (record.kind === "transform") {
154    destination = new Transform();
155    define(destination, "_readable", { value: record.readableClone });
156    define(destination, "_writable", { value: record.writableClone });
157  } else if (record.kind === "writable") destination = record.writableClone;
158  record.destination = destination;
159  set(transferStates, destination!, record);
160  return destination!;
161}
162function validateMarked(
163  _state: unknown,
164  _source: object,
165  destination: StreamResource,
166): void {
167  validateRecord(get(transferStates, destination)!);
168}
169function validateRecord(record: StreamRecord): void {
170  if (record.readable) {
171    if (!descriptor(record.readable, "_readableStreamController")) {
172      throw dataCloneError("Invalid readable stream state");
173    }
174    validateSlot(record.readable, "_reader");
175  }
176  if (record.writable) {
177    if (!descriptor(record.writable, "_writableStreamController")) {
178      throw dataCloneError("Invalid writable stream state");
179    }
180    validateSlot(record.writable, "_writer");
181  }
182  if (record.writable) {
183    record.writableState = ownValue(record.writable, "_state");
184  }
185  if (
186    (record.readable && getPrototype(record.readable) !== Readable.prototype &&
187      !isExtensible(record.readable)) ||
188    (record.writable && getPrototype(record.writable) !== Writable.prototype &&
189      !isExtensible(record.writable))
190  ) {
191    throw dataCloneError(
192      "not yet implemented: transferring this non-extensible stream",
193    );
194  }
195}
196function commitMarked(
197  _state: unknown,
198  _source: object,
199  destination: StreamResource,
200): void {
201  const record = get(transferStates, destination)!;
202  if (record.readable) record.reader = acquireReader(record.readable);
203  if (record.writable) record.writer = acquireWriter(record.writable);
204  if (record.writable) {
205    if (record.writableState === "closed") {
206      const closing = invoke(closeStream, record.writableClone);
207      markPromiseHandled(closing);
208      then(closing, undefined, (error) => {
209        try {
210          record.controller!.error(cloneError(error));
211        } catch (cloneError) {
212          record.controller!.error(cloneError);
213        }
214      });
215    }
216    then(invoke(writerClosed, record.writer), undefined, (error) => {
217      try {
218        record.controller!.error(cloneValue(error));
219      } catch (cloneError) {
220        record.controller!.error(cloneError);
221      }
222    });
223  }
224}
225function validateSlot(stream: object, key: PropertyKey): void {
226  const property = descriptor(stream, key);
227  if (property?.value !== undefined) {
228    throw dataCloneError("Cannot transfer a locked stream");
229  }
230  if (!property || !("value" in property) || !property.writable) {
231    throw dataCloneError(
232      "not yet implemented: transferring a stream with immutable internal state",
233    );
234  }
235}
236function ownValue(object: object, key: PropertyKey): unknown {
237  const property = descriptor(object, key);
238  return property && "value" in property ? property.value : undefined;
239}
240function acquireReader(stream: ReadableResource): Reader {
241  const prototype = getPrototype(stream);
242  if (prototype === Readable.prototype) return invoke(getReader, stream);
243  setPrototype(stream, Readable.prototype);
244  try {
245    return invoke(getReader, stream);
246  } finally {
247    setPrototype(stream, prototype);
248  }
249}
250function acquireWriter(stream: WritableResource): Writer {
251  const prototype = getPrototype(stream);
252  if (prototype === Writable.prototype) return invoke(getWriter, stream);
253  setPrototype(stream, Writable.prototype);
254  try {
255    return invoke(getWriter, stream);
256  } finally {
257    setPrototype(stream, prototype);
258  }
259}
260registerMarkedTransferable(prepareMarked, validateMarked, commitMarked);