@volter/tabnode 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/__sw__.js +259 -5
  2. package/dist/assets/{runtime-worker-LWfW6VWE.js → runtime-worker-DBZ2flHK.js} +4000 -2944
  3. package/dist/fetch-transport.d.ts +20 -0
  4. package/dist/guest-timers.d.ts +15 -0
  5. package/dist/http-client-transport.d.ts +19 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.mjs +5196 -3062
  8. package/dist/native-stream-binding.d.ts +57 -0
  9. package/dist/native-stream-owner.d.ts +121 -0
  10. package/dist/node-lib/binding/duplex_stream.d.ts +26 -0
  11. package/dist/node-lib/binding/fds.d.ts +8 -0
  12. package/dist/node-lib/binding/fs.d.ts +311 -78
  13. package/dist/node-lib/binding/misc.d.ts +1 -0
  14. package/dist/node-lib/binding/pipe_wrap.d.ts +4 -4
  15. package/dist/node-lib/binding/process_wrap.d.ts +12 -1
  16. package/dist/node-lib/binding/stream_wrap.d.ts +27 -18
  17. package/dist/node-lib/binding/tcp_wrap.d.ts +0 -1
  18. package/dist/node-lib/binding/tty_wrap.d.ts +43 -2
  19. package/dist/node-lib/binding/util.d.ts +83 -0
  20. package/dist/node-lib/binding/uv.d.ts +4 -0
  21. package/dist/node-lib/fs-module.d.ts +1 -0
  22. package/dist/node-lib/internals/buffer-and-streams.d.ts +30 -17
  23. package/dist/node-lib/internals/index.d.ts +1 -1
  24. package/dist/node-lib/load.d.ts +17 -3
  25. package/dist/node-lib/module-hooks.d.ts +1 -0
  26. package/dist/node-lib/public-modules.d.ts +1 -1
  27. package/dist/node-lib/timers.d.ts +38 -0
  28. package/dist/node-process-host.d.ts +41 -0
  29. package/dist/process-registry.d.ts +39 -0
  30. package/dist/process-tokens.d.ts +11 -0
  31. package/dist/promise-ownership.d.ts +3 -0
  32. package/dist/runtime.d.ts +10 -9
  33. package/dist/shims/child_process.d.ts +2 -0
  34. package/dist/shims/crypto.d.ts +660 -119
  35. package/dist/shims/dns.d.ts +76 -2
  36. package/dist/shims/process.d.ts +1 -0
  37. package/dist/shims/string_decoder.d.ts +19 -3
  38. package/dist/shims/tls.d.ts +853 -31
  39. package/dist/source-edits.d.ts +7 -0
  40. package/package.json +1 -1
  41. package/src/code-transforms.ts +2 -3
  42. package/src/fetch-transport.ts +98 -0
  43. package/src/guest-timers.ts +89 -0
  44. package/src/http-client-transport.ts +169 -0
  45. package/src/index.ts +18 -0
  46. package/src/native-stream-binding.ts +393 -0
  47. package/src/native-stream-owner.ts +317 -0
  48. package/src/node-lib/binding/duplex_stream.ts +95 -0
  49. package/src/node-lib/binding/fds.ts +18 -1
  50. package/src/node-lib/binding/fs.ts +45 -17
  51. package/src/node-lib/binding/misc.ts +2 -0
  52. package/src/node-lib/binding/pipe_wrap.ts +35 -12
  53. package/src/node-lib/binding/process_wrap.ts +50 -9
  54. package/src/node-lib/binding/stream_wrap.ts +196 -72
  55. package/src/node-lib/binding/tcp_wrap.ts +43 -8
  56. package/src/node-lib/binding/tty_wrap.ts +135 -8
  57. package/src/node-lib/binding/util.ts +24 -15
  58. package/src/node-lib/binding/uv.ts +4 -0
  59. package/src/node-lib/fs-module.ts +2 -18
  60. package/src/node-lib/internal/navigator.js +149 -0
  61. package/src/node-lib/internals/buffer-and-streams.ts +50 -11
  62. package/src/node-lib/internals/events-util.ts +5 -0
  63. package/src/node-lib/internals/index.ts +12 -2
  64. package/src/node-lib/load.ts +170 -62
  65. package/src/node-lib/module-hooks.ts +5 -2
  66. package/src/node-lib/public-modules.ts +12 -48
  67. package/src/node-lib/sources.ts +4 -0
  68. package/src/node-lib/timers.ts +93 -0
  69. package/src/node-process-host.ts +124 -0
  70. package/src/process-registry.ts +144 -0
  71. package/src/process-tokens.ts +55 -6
  72. package/src/promise-ownership.ts +28 -0
  73. package/src/runtime.ts +90 -214
  74. package/src/shims/child_process.ts +310 -80
  75. package/src/shims/crypto.ts +115 -100
  76. package/src/shims/dns.ts +17 -1
  77. package/src/shims/process.ts +33 -11
  78. package/src/shims/string_decoder.ts +10 -6
  79. package/src/shims/tls.ts +49 -19
  80. package/src/source-edits.ts +20 -0
@@ -0,0 +1,393 @@
1
+ /** Worker-side libuv delegation. The capability channel never becomes a handle property. */
2
+ import type { NativeStreamDescriptor, NativeStreamEvent, NativeStreamOperation, NativeStreamReply, NativeStreamTransport } from './native-stream-owner';
3
+ import type { TCP } from './node-lib/binding/tcp_wrap';
4
+ import type { Pipe } from './node-lib/binding/pipe_wrap';
5
+ import { LibuvStreamWrap, type WriteWrap, streamBaseState, kBytesWritten, kLastWriteWasAsync } from './node-lib/binding/stream_wrap';
6
+ import { __adoptHandle, ownerOf, registerHandle, releaseHandle } from './node-lib/binding/handles';
7
+ import { UV_EBADF, UV_ECANCELED, UV_EINVAL, UV_EPIPE, errname } from './node-lib/binding/uv';
8
+
9
+ type Stream = TCP | Pipe;
10
+ const drivers = new WeakMap<LibuvStreamWrap, NativeStreamDriver>();
11
+ const handles = new Map<number, Stream>();
12
+ const constructors: Partial<Record<'tcp' | 'pipe', new (type: number) => Stream>> = {};
13
+ let transport: NativeStreamTransport | undefined;
14
+ let allocated = false;
15
+ let stopped = false;
16
+ let incoming: NativeStreamDescriptor | undefined;
17
+ let nextRequest = 1;
18
+
19
+ // Register after each concrete binding class is defined. Importing TCP/Pipe
20
+ // here at runtime would cycle through StreamWrap before their base class exists.
21
+ export function registerNativeStreamConstructor(kind: 'tcp' | 'pipe', constructor: new (type: number) => Stream): void {
22
+ constructors[kind] = constructor;
23
+ }
24
+
25
+ function requestId(): number {
26
+ if (!Number.isSafeInteger(nextRequest)) throw new Error('Native stream request IDs exhausted.');
27
+ return nextRequest++;
28
+ }
29
+
30
+ /** Host bootstrap only, before any TCP/Pipe exists in this process realm. */
31
+ export function installNativeStreamTransport(next: NativeStreamTransport): void {
32
+ if (transport || allocated) throw new Error('Native stream transport must be installed before stream construction.');
33
+ transport = next;
34
+ next.onEvent(event => {
35
+ if (event.type === 'closed') {
36
+ stopped = true;
37
+ const failures: unknown[] = [];
38
+ for (const handle of [...handles.values()]) {
39
+ try { drivers.get(handle)?.ownerStopped(); } catch (cause) { failures.push(cause); }
40
+ }
41
+ if (failures.length) throw new AggregateError(failures, 'Native stream owner stopped during callbacks.');
42
+ return;
43
+ }
44
+ const handle = handles.get(event.id);
45
+ if (handle) drivers.get(handle)!.receive(event);
46
+ // A read/accept already posted before close still owns its transferred
47
+ // descriptor. Discarding the event must not leak that owner capability.
48
+ else if ('handle' in event && event.handle && !stopped) closeDescriptor(event.handle.id);
49
+ });
50
+ }
51
+
52
+ function closeDescriptor(id: number): void {
53
+ if (!stopped) transport!.call({ operation: 'close', id, request: requestId() });
54
+ }
55
+
56
+ /** uv requests keep their loop alive even when their stream is unreferenced. */
57
+ function holdRequest(handle: Stream): () => void {
58
+ const request = { close: (): void => releaseHandle(request) };
59
+ registerHandle(request);
60
+ __adoptHandle(request, ownerOf(handle));
61
+ return request.close;
62
+ }
63
+
64
+ function adopt(descriptor: NativeStreamDescriptor, receiver: Stream): Stream {
65
+ if (handles.has(descriptor.id)) throw new Error('Native descriptor was delivered twice.');
66
+ const previous = incoming;
67
+ incoming = descriptor;
68
+ try {
69
+ const Constructor = constructors[descriptor.kind];
70
+ if (!Constructor) throw new Error('Native stream constructor is unavailable.');
71
+ const handle = new Constructor(descriptor.type);
72
+ __adoptHandle(handle, ownerOf(receiver));
73
+ return handle;
74
+ } finally { incoming = previous; }
75
+ }
76
+
77
+ export function nativeStreamFor(handle: LibuvStreamWrap): NativeStreamDriver | undefined { return drivers.get(handle); }
78
+
79
+ /** Inherited descriptors live with the owner, not in this realm's JS fd table. */
80
+ export function nativeInheritedFdType(fd: number): 'TCP' | 'PIPE' | undefined {
81
+ if (!transport || stopped || !Number.isInteger(fd) || fd < 0) return undefined;
82
+ const result = transport.call({ operation: 'fdType', fd });
83
+ if (result.status === UV_EBADF) return undefined;
84
+ if (result.status !== 0 || (result.handleType !== 'TCP' && result.handleType !== 'PIPE')) {
85
+ throw new Error(`Native descriptor lookup failed: ${errname(result.status)}`);
86
+ }
87
+ return result.handleType;
88
+ }
89
+
90
+ /** Trusted process admission uses this ID for explicit descriptor inheritance. */
91
+ export function nativeStreamDescriptor(handle: LibuvStreamWrap): NativeStreamDescriptor | undefined {
92
+ const driver = drivers.get(handle);
93
+ return driver && !handle.closed ? { ...driver.descriptor } : undefined;
94
+ }
95
+
96
+ export function attachNativeStream(
97
+ handle: Stream, kind: 'tcp' | 'pipe', type: number,
98
+ closeLocal: (callback?: () => void) => void, flushLocal: () => void,
99
+ ): void {
100
+ allocated = true;
101
+ if (!transport) return;
102
+ try {
103
+ if (stopped) throw new Error('Native stream owner stopped.');
104
+ const result = incoming ? { status: 0, handle: incoming } : transport.call({ operation: 'create', kind, type });
105
+ if (result.status !== 0 || !result.handle) {
106
+ throw Object.assign(new Error(`Native stream creation failed: ${errname(result.status)}`), { code: errname(result.status), errno: result.status });
107
+ }
108
+ const descriptor = result.handle;
109
+ if (descriptor.kind !== kind || descriptor.type !== type || handles.has(descriptor.id)) throw new Error('Invalid native stream descriptor.');
110
+ incoming = undefined;
111
+ drivers.set(handle, new NativeStreamDriver(handle, descriptor, transport, closeLocal, flushLocal));
112
+ handles.set(descriptor.id, handle);
113
+ } catch (cause) { releaseHandle(handle); throw cause; }
114
+ }
115
+
116
+ interface PendingWrite {
117
+ request: WriteWrap;
118
+ parts: Uint8Array[];
119
+ bytes: number;
120
+ sent?: number;
121
+ ended: boolean;
122
+ release(): void;
123
+ }
124
+ interface LocalEndpoint { handles: Set<Stream>; peer?: LocalEndpoint }
125
+
126
+ export class NativeStreamDriver {
127
+ private readingCredit = false;
128
+ private buffered = false;
129
+ private readEnded = false;
130
+ private closing = false;
131
+ private readonly completions = new Map<number, (status: number) => void>();
132
+ private readonly writes: PendingWrite[] = [];
133
+ private readonly closeCallbacks: Array<() => void> = [];
134
+ private endpoint: LocalEndpoint;
135
+ private pumping = false;
136
+ private releaseClose: (() => void) | undefined;
137
+ private writeEnded = false;
138
+ private pendingShutdown: (() => void) | undefined;
139
+
140
+ constructor(
141
+ private readonly handle: Stream,
142
+ readonly descriptor: NativeStreamDescriptor,
143
+ private readonly channel: NativeStreamTransport,
144
+ private readonly closeLocal: (callback?: () => void) => void,
145
+ private readonly flushLocal: () => void,
146
+ ) { this.endpoint = { handles: new Set([handle]) }; }
147
+
148
+ get peer(): LibuvStreamWrap | null {
149
+ for (const handle of this.endpoint.peer?.handles ?? []) if (!handle.closed) return handle;
150
+ return null;
151
+ }
152
+
153
+ pair(other: NativeStreamDriver): void {
154
+ const { status } = this.call({ operation: 'pair', id: this.descriptor.id, peer: other.descriptor.id });
155
+ if (status !== 0) throw Object.assign(new Error('Native stream pairing failed.'), { errno: status });
156
+ this.endpoint.peer = other.endpoint;
157
+ other.endpoint.peer = this.endpoint;
158
+ }
159
+
160
+ call(operation: NativeStreamOperation): NativeStreamReply {
161
+ return this.closing || stopped ? { status: UV_EBADF } : this.channel.call(operation);
162
+ }
163
+
164
+ operation(operation: 'readStop' | 'ref' | 'unref'): number {
165
+ return this.call({ operation, id: this.descriptor.id }).status;
166
+ }
167
+
168
+ beginRead(): number {
169
+ if (this.closing || stopped) return UV_EBADF;
170
+ if (this.readingCredit || this.buffered || this.readEnded) return 0;
171
+ this.readingCredit = true;
172
+ const status = this.call({ operation: 'readStart', id: this.descriptor.id }).status;
173
+ if (status !== 0) this.readingCredit = false;
174
+ return status;
175
+ }
176
+
177
+ readFlushed(empty: boolean, ended: boolean): void {
178
+ this.buffered = !empty;
179
+ this.readEnded ||= ended;
180
+ if (this.handle.reading && empty && !this.readEnded && !this.closing && !stopped) {
181
+ const status = this.beginRead();
182
+ if (status !== 0) this.handle.receiveError(status);
183
+ }
184
+ }
185
+
186
+ complete(operation: NativeStreamOperation, callback: (status: number) => void): number {
187
+ if (!('request' in operation)) throw new Error('Native completion requires a request ID.');
188
+ const release = holdRequest(this.handle);
189
+ this.completions.set(operation.request, status => { try { callback(status); } finally { release(); } });
190
+ try {
191
+ const status = this.call(operation).status;
192
+ if (status !== 0) { this.completions.delete(operation.request); release(); }
193
+ return status;
194
+ } catch (cause) { this.completions.delete(operation.request); release(); throw cause; }
195
+ }
196
+
197
+ connect(address: string, port: number | undefined, ipv6: boolean, callback: (status: number) => void): number {
198
+ return this.complete({ operation: 'connect', id: this.descriptor.id, request: requestId(), address, port, ipv6 }, callback);
199
+ }
200
+
201
+ shutdown(callback: (status: number) => void): number {
202
+ if (this.closing || stopped) return UV_EBADF;
203
+ if (this.writeEnded) return UV_EPIPE;
204
+ this.writeEnded = true;
205
+ const operation = { operation: 'shutdown' as const, id: this.descriptor.id, request: requestId() };
206
+ if (!this.writes.length) return this.complete(operation, callback);
207
+ // uv_shutdown follows all accepted writes, including writes still split
208
+ // into transport chunks. EOF must not overtake those bytes.
209
+ const release = holdRequest(this.handle);
210
+ let completed = false;
211
+ const complete = (status: number): void => {
212
+ if (completed) return;
213
+ completed = true;
214
+ try { callback(status); } finally { release(); }
215
+ };
216
+ this.pendingShutdown = () => {
217
+ if (this.closing || stopped) { queueMicrotask(() => complete(UV_ECANCELED)); return; }
218
+ try {
219
+ const status = this.complete(operation, complete);
220
+ if (status !== 0) queueMicrotask(() => complete(status));
221
+ } catch { queueMicrotask(() => complete(UV_ECANCELED)); }
222
+ };
223
+ return 0;
224
+ }
225
+
226
+ duplicate(): Stream {
227
+ const result = this.call({ operation: 'duplicate', id: this.descriptor.id });
228
+ if (result.status !== 0 || !result.handle) throw Object.assign(new Error('Native descriptor duplication failed.'), { code: errname(result.status), errno: result.status });
229
+ const copy = adopt(result.handle, this.handle);
230
+ const other = drivers.get(copy)!;
231
+ other.endpoint.handles.delete(copy);
232
+ other.endpoint = this.endpoint;
233
+ this.endpoint.handles.add(copy);
234
+ return copy;
235
+ }
236
+
237
+ write(request: WriteWrap, parts: Uint8Array[], sent?: unknown): number {
238
+ streamBaseState[kLastWriteWasAsync] = 0;
239
+ const bytes = parts.reduce((total, part) => total + part.byteLength, 0);
240
+ streamBaseState[kBytesWritten] = bytes;
241
+ if (this.closing || stopped) return UV_EBADF;
242
+ if (this.writeEnded) return UV_EPIPE;
243
+ if (!Number.isSafeInteger(bytes)) return UV_EINVAL;
244
+ if (bytes === 0) return sent == null ? 0 : UV_EINVAL;
245
+ let retained: number | undefined;
246
+ if (sent != null) {
247
+ const driver = sent instanceof LibuvStreamWrap ? drivers.get(sent) : undefined;
248
+ if (!driver || this.descriptor.kind !== 'pipe') return UV_EINVAL;
249
+ const result = driver.call({ operation: 'duplicate', id: driver.descriptor.id });
250
+ if (result.status !== 0 || !result.handle) return result.status || UV_EBADF;
251
+ retained = result.handle.id;
252
+ }
253
+ const write: PendingWrite = { request, parts, bytes, sent: retained, ended: false, release: holdRequest(this.handle) };
254
+ // Node retains the caller's buffers until completion, even for a write
255
+ // larger than the transport budget. The channel bounds copied chunks and
256
+ // waits for capacity; rejecting this whole write breaks normal streaming.
257
+ this.handle.writeQueueSize += bytes;
258
+ this.writes.push(write);
259
+ request.async = true;
260
+ streamBaseState[kLastWriteWasAsync] = 1;
261
+ if (!this.pumping) void this.pumpWrites();
262
+ return 0;
263
+ }
264
+
265
+ private async pumpWrites(): Promise<void> {
266
+ this.pumping = true;
267
+ try { while (this.writes.length) await this.writeNext(); }
268
+ finally { this.pumping = false; this.flushShutdown(); }
269
+ }
270
+
271
+ private flushShutdown(): void {
272
+ if (this.writes.length || !this.pendingShutdown) return;
273
+ const dispatch = this.pendingShutdown;
274
+ this.pendingShutdown = undefined;
275
+ dispatch();
276
+ }
277
+
278
+ private async writeNext(): Promise<void> {
279
+ const write = this.writes[0];
280
+ if (!write || write.ended) return;
281
+ let status = 0;
282
+ try {
283
+ outer: for (const part of write.parts) {
284
+ for (let offset = 0; offset < part.byteLength; offset += this.channel.limits.maxReadChunkBytes) {
285
+ if (this.closing || stopped) { status = UV_ECANCELED; break outer; }
286
+ const chunk = part.subarray(offset, offset + this.channel.limits.maxReadChunkBytes);
287
+ status = await this.channel.write(this.descriptor.id, chunk, write.sent);
288
+ if (write.sent !== undefined) { closeDescriptor(write.sent); write.sent = undefined; }
289
+ if (status !== 0) break outer;
290
+ this.handle.bytesWritten += chunk.byteLength;
291
+ }
292
+ }
293
+ } catch { status = stopped ? UV_ECANCELED : UV_EPIPE; }
294
+ this.finishWrite(write, status);
295
+ }
296
+
297
+ private finishWrite(write: PendingWrite, status: number): void {
298
+ if (write.ended) return;
299
+ write.ended = true;
300
+ const index = this.writes.indexOf(write);
301
+ if (index >= 0) this.writes.splice(index, 1);
302
+ this.handle.writeQueueSize -= write.bytes;
303
+ write.parts = [];
304
+ try { if (write.sent !== undefined) closeDescriptor(write.sent); }
305
+ catch (cause) {
306
+ status = UV_ECANCELED;
307
+ write.request.error = cause instanceof Error ? cause.message : String(cause);
308
+ } finally { write.sent = undefined; }
309
+ // A Node callback throws as a native callback, not as a rejection of this
310
+ // bridge's private async function.
311
+ queueMicrotask(() => { try { write.request.oncomplete?.(status); } finally { write.release(); } });
312
+ }
313
+
314
+ close(callback?: () => void, reset = false): number {
315
+ if (callback) this.closeCallbacks.push(callback);
316
+ if (this.closing) return 0;
317
+ this.closing = true;
318
+ this.releaseClose = holdRequest(this.handle);
319
+ this.handle.closed = true;
320
+ this.handle.reading = false;
321
+ // Keep the local handle registered until the owner's close completes.
322
+ // Otherwise the process could be reaped before its descriptors are closed.
323
+ const request = requestId();
324
+ this.completions.set(request, () => this.finishClose());
325
+ if (stopped) { this.finishClose(); return 0; }
326
+ try {
327
+ const status = this.channel.call({ operation: reset ? 'reset' : 'close', id: this.descriptor.id, request }).status;
328
+ if (status !== 0) { this.completions.delete(request); this.finishClose(); }
329
+ return status;
330
+ } catch (cause) {
331
+ this.completions.delete(request);
332
+ this.finishClose();
333
+ throw cause;
334
+ }
335
+ }
336
+
337
+ private finishClose(): void {
338
+ if (!handles.has(this.descriptor.id)) return;
339
+ handles.delete(this.descriptor.id);
340
+ drivers.delete(this.handle);
341
+ this.endpoint.handles.delete(this.handle);
342
+ if (this.endpoint.handles.size === 0) {
343
+ if (this.endpoint.peer) this.endpoint.peer.peer = undefined;
344
+ this.endpoint.peer = undefined;
345
+ }
346
+ // closeLocal owns the existing local queue/fd/ref cleanup; reset its guard
347
+ // only for this synchronous handoff after the native descriptor is gone.
348
+ this.handle.closed = false;
349
+ const callbacks = this.closeCallbacks.splice(0);
350
+ this.closeLocal(() => {
351
+ const failures: unknown[] = [];
352
+ try {
353
+ for (const callback of callbacks) { try { callback(); } catch (cause) { failures.push(cause); } }
354
+ } finally { this.releaseClose?.(); this.releaseClose = undefined; }
355
+ if (failures.length === 1) throw failures[0];
356
+ if (failures.length > 1) throw new AggregateError(failures, 'Native close callbacks failed.');
357
+ });
358
+ }
359
+
360
+ receive(event: Exclude<NativeStreamEvent, { type: 'closed' }>): void {
361
+ if (event.type === 'read') {
362
+ this.readingCredit = false;
363
+ if (this.closing) { if (event.handle) closeDescriptor(event.handle.id); return; }
364
+ this.buffered = true;
365
+ if (event.status > 0 && event.bytes) this.handle.receive(event.bytes, event.handle ? adopt(event.handle, this.handle) : undefined);
366
+ else { this.readEnded = true; this.handle.receiveError(event.status); }
367
+ } else if (event.type === 'connection') {
368
+ if (this.closing || !this.handle.onconnection) { if (event.handle) closeDescriptor(event.handle.id); return; }
369
+ const accepted = event.handle ? adopt(event.handle, this.handle) : undefined;
370
+ (this.handle.onconnection as (status: number, handle?: Stream) => void)(event.status, accepted);
371
+ } else {
372
+ const complete = this.completions.get(event.request);
373
+ this.completions.delete(event.request);
374
+ complete?.(event.status);
375
+ }
376
+ }
377
+
378
+ ownerStopped(): void {
379
+ const failures: unknown[] = [];
380
+ const attempt = (action: () => void): void => { try { action(); } catch (cause) { failures.push(cause); } };
381
+ this.closing = true;
382
+ for (const write of [...this.writes]) attempt(() => this.finishWrite(write, UV_ECANCELED));
383
+ attempt(() => this.flushShutdown());
384
+ const completions = [...this.completions.values()];
385
+ this.completions.clear();
386
+ for (const complete of completions) attempt(() => complete(UV_ECANCELED));
387
+ if (handles.has(this.descriptor.id)) {
388
+ attempt(() => { this.handle.receiveError(UV_ECANCELED); this.flushLocal(); });
389
+ attempt(() => this.finishClose());
390
+ }
391
+ if (failures.length) throw new AggregateError(failures, 'Native stream shutdown callbacks failed.');
392
+ }
393
+ }