@volter/tabnode 0.4.0 → 0.5.0

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-DoV8lqBn.js → runtime-worker-B9buriZs.js} +4021 -2961
  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 +5204 -3057
  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 +13 -2
  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 +16 -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 +12 -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 +72 -18
  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 +60 -6
  72. package/src/promise-ownership.ts +28 -0
  73. package/src/runtime.ts +90 -214
  74. package/src/shims/child_process.ts +313 -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
@@ -13,7 +13,10 @@
13
13
  * `net.js` is vendored.
14
14
  */
15
15
  import { bytesOfString } from './buffer';
16
- import { UV_EOF, UV_EBADF, UV_ECONNRESET } from './uv';
16
+ import { UV_EOF, UV_EBADF, UV_ECONNRESET, UV_EPIPE } from './uv';
17
+ import { attachNativeStream, nativeStreamFor } from '../../native-stream-binding';
18
+ import type { TCP } from './tcp_wrap';
19
+ import type { Pipe } from './pipe_wrap';
17
20
 
18
21
  /**
19
22
  * The bytes this view names. Node's `Buffer.from` of a short string is a
@@ -70,15 +73,33 @@ interface Inbound {
70
73
  handle?: unknown;
71
74
  }
72
75
 
76
+ /** The connection end shared by duplicated native descriptors, not a JS socket. */
77
+ class StreamEndpoint {
78
+ readonly handles = new Set<LibuvStreamWrap>();
79
+ peer: StreamEndpoint | null = null;
80
+ inbound: Inbound[] = [];
81
+ eof = false;
82
+ error: number | null = null;
83
+ writeEnded = false;
84
+ flushScheduled = false;
85
+ readonly finalizers: Array<() => void> = [];
86
+ readonly capacityListeners = new Set<() => void>();
87
+
88
+ capacityChanged(): void {
89
+ for (const listener of [...this.capacityListeners]) listener();
90
+ }
91
+
92
+ constructor(handle: LibuvStreamWrap) { this.handles.add(handle); }
93
+ }
94
+
73
95
  /**
74
96
  * The base every `TCP` and `Pipe` extends, as `LibuvStreamWrap` is the base of
75
97
  * libuv's `uv_stream_t` wrappers.
76
98
  *
77
- * A pairing is two of these with `peer` pointing at each other. A write copies
78
- * its bytes and hands them to the peer on a microtask, because libuv never
79
- * delivers a read inside the write that caused it; the peer delivers them to
80
- * `onread` while it is reading and holds them while it is not, which is what
81
- * `readStart` and `readStop` mean.
99
+ * A pairing joins two connection endpoints. Duplicated wrappers reference the
100
+ * same endpoint and consume its bytes once. A write queues a copy at the peer;
101
+ * reads are delivered on its own turn while a wrapper is reading, which is
102
+ * what `readStart` and `readStop` mean.
82
103
  */
83
104
  export class LibuvStreamWrap implements OwnedHandle {
84
105
  /** Set by `net.js` to `onStreamRead`; the only way bytes reach a stream. */
@@ -92,8 +113,13 @@ export class LibuvStreamWrap implements OwnedHandle {
92
113
  /** The engine has no descriptors for a paired handle, as libuv reports -1 for one it has no fd for. */
93
114
  fd = -1;
94
115
 
95
- /** The other end of the pairing, once there is one. */
96
- peer: LibuvStreamWrap | null = null;
116
+ private streamEndpoint = new StreamEndpoint(this);
117
+ /** A live wrapper on the other end, including after the original was closed. */
118
+ get peer(): LibuvStreamWrap | null {
119
+ const native = nativeStreamFor(this);
120
+ if (native) return native.peer;
121
+ return this.streamEndpoint.peer?.handles.values().next().value ?? null;
122
+ }
97
123
  /** True once `close()` has been called; a closed handle carries nothing. */
98
124
  closed = false;
99
125
  /**
@@ -103,9 +129,9 @@ export class LibuvStreamWrap implements OwnedHandle {
103
129
  */
104
130
  pendingHandle: unknown = null;
105
131
 
106
- protected inbound: Inbound[] = [];
107
- protected inboundEof = false;
108
- protected inboundError: number | null = null;
132
+ protected get inbound(): Inbound[] { return this.streamEndpoint.inbound; }
133
+ protected get inboundEof(): boolean { return this.streamEndpoint.eof; }
134
+ protected get inboundError(): number | null { return this.streamEndpoint.error; }
109
135
  protected eofDelivered = false;
110
136
  protected userBuffer: Uint8Array | null = null;
111
137
  protected readonly asyncId = nextAsyncId++;
@@ -114,15 +140,22 @@ export class LibuvStreamWrap implements OwnedHandle {
114
140
  registerHandle(this);
115
141
  }
116
142
 
143
+ protected attachNative(kind: 'tcp' | 'pipe', type: number): void {
144
+ attachNativeStream(this as unknown as TCP | Pipe, kind, type,
145
+ callback => this.closeLocal(callback), () => this.flushInbound());
146
+ }
147
+
117
148
  getAsyncId(): number {
118
149
  return this.asyncId;
119
150
  }
120
151
 
121
152
  ref(): void {
153
+ nativeStreamFor(this)?.operation('ref');
122
154
  refHandle(this);
123
155
  }
124
156
 
125
157
  unref(): void {
158
+ nativeStreamFor(this)?.operation('unref');
126
159
  unrefHandle(this);
127
160
  }
128
161
 
@@ -132,12 +165,16 @@ export class LibuvStreamWrap implements OwnedHandle {
132
165
 
133
166
  readStart(): number {
134
167
  if (this.closed) return UV_EBADF;
168
+ const status = nativeStreamFor(this)?.beginRead() ?? 0;
169
+ if (status !== 0) return status;
135
170
  this.reading = true;
136
171
  this.flushInbound();
137
172
  return 0;
138
173
  }
139
174
 
140
175
  readStop(): number {
176
+ // The remote owner may already have posted its one permitted read. Keep
177
+ // that credit until arrival; pause guest delivery without granting another.
141
178
  this.reading = false;
142
179
  return 0;
143
180
  }
@@ -152,13 +189,16 @@ export class LibuvStreamWrap implements OwnedHandle {
152
189
  * reads EOF. The request completes on its own turn, as libuv's does.
153
190
  */
154
191
  shutdown(req: ShutdownWrap): number {
192
+ if (this.closed) return UV_EBADF;
193
+ const native = nativeStreamFor(this);
194
+ if (native) return native.shutdown(status => req.oncomplete?.(status));
155
195
  this.sendEof();
156
196
  queueMicrotask(() => { req.oncomplete?.(0); });
157
197
  return 0;
158
198
  }
159
199
 
160
- writeBuffer(req: WriteWrap, buffer: Uint8Array): number {
161
- return this.dispatchWrite(req, buffer);
200
+ writeBuffer(req: WriteWrap, buffer: Uint8Array, handle?: unknown): number {
201
+ return this.dispatchWrite(req, buffer, handle);
162
202
  }
163
203
 
164
204
  writev(req: WriteWrap, chunks: unknown[], allBuffers: boolean): number {
@@ -172,6 +212,8 @@ export class LibuvStreamWrap implements OwnedHandle {
172
212
  parts.push(typeof chunk === 'string' ? bytesOfString(chunk, encoding) : (chunk as Uint8Array));
173
213
  }
174
214
  }
215
+ const native = nativeStreamFor(this);
216
+ if (native) return native.write(req, parts);
175
217
  let total = 0;
176
218
  for (const part of parts) total += part.byteLength;
177
219
  const joined = new Uint8Array(total);
@@ -200,25 +242,58 @@ export class LibuvStreamWrap implements OwnedHandle {
200
242
  }
201
243
 
202
244
  /**
203
- * libuv's `uv_close`. The peer reads EOF, as it does when the other end of a
204
- * socket goes; the callback runs on its own turn, because `net.js` emits
245
+ * libuv's `uv_close`. The peer reads EOF when the last descriptor closes;
246
+ * the callback runs on its own turn, because `net.js` emits
205
247
  * `close` from it and Node emits that after the turn that destroyed.
206
248
  */
207
249
  close(callback?: () => void): void {
250
+ const native = nativeStreamFor(this);
251
+ if (native) { native.close(callback); return; }
252
+ this.closeLocal(callback);
253
+ }
254
+
255
+ private closeLocal(callback?: () => void): void {
256
+ const failures: unknown[] = [];
257
+ const release = (action: () => void): void => { try { action(); } catch (cause) { failures.push(cause); } };
208
258
  if (!this.closed) {
209
259
  this.closed = true;
210
260
  releaseHandle(this);
211
- const peer = this.peer;
212
- this.peer = null;
261
+ const endpoint = this.streamEndpoint;
262
+ endpoint.handles.delete(this);
213
263
  // Writes land in the peer's inbound queue at once; flushing here
214
264
  // delivers those bytes and then EOF, in that order, on this turn. A
215
265
  // child's setupChannel then disconnects (`connected` false) before the
216
266
  // next `process.send`, so that send is ERR_IPC_CHANNEL_CLOSED rather
217
267
  // than a write on a handle a later release would close (`write EBADF`).
218
- if (peer) { peer.peer = null; peer.receiveEof(); peer.flushInbound(); }
219
- this.onCloseHandle();
268
+ // Closing one descriptor must not end a connection another process
269
+ // still holds (`send(socket, { keepOpen: true })` is one such case).
270
+ if (endpoint.handles.size === 0) {
271
+ const peer = endpoint.peer;
272
+ endpoint.peer = null;
273
+ if (peer) {
274
+ peer.peer = null;
275
+ peer.eof = true;
276
+ for (const handle of peer.handles) release(() => handle.flushInbound());
277
+ }
278
+ for (const finalize of endpoint.finalizers.splice(0)) release(finalize);
279
+ // SCM_RIGHTS copies queued on an unread IPC channel have no receiving
280
+ // process yet. Closing that channel must release them too.
281
+ for (const pending of endpoint.inbound) {
282
+ if (pending.handle instanceof LibuvStreamWrap) release(() => (pending.handle as LibuvStreamWrap).close());
283
+ }
284
+ endpoint.inbound.length = 0;
285
+ release(() => endpoint.capacityChanged());
286
+ endpoint.capacityListeners.clear();
287
+ }
288
+ release(() => this.onCloseHandle());
220
289
  }
221
- if (callback) queueMicrotask(callback);
290
+ // Node schedules a destroy(error)'s error on nextTick after close() returns.
291
+ // The native close completion comes afterward. One microtask here ran the
292
+ // close listener first, so ClientRequest replaced the actual policy denial
293
+ // with ECONNRESET ("socket hang up"). Leave that tick ahead of completion.
294
+ if (callback) queueMicrotask(() => queueMicrotask(callback));
295
+ if (failures.length === 1) throw failures[0];
296
+ if (failures.length > 1) throw new AggregateError(failures, 'Native stream cleanup failed.');
222
297
  }
223
298
 
224
299
  /** What a `TCP` or a `Pipe` gives up beyond the stream: a port, a path. */
@@ -234,21 +309,61 @@ export class LibuvStreamWrap implements OwnedHandle {
234
309
  * for both, that close ended the connection the child had just been given
235
310
  * and its first write was `EBADF`.
236
311
  *
237
- * The duplicate takes over this end of the pairing and this end is left
238
- * peerless, so closing it announces nothing -- the sender has already given
239
- * the socket up (`handleConversion`'s `send` sets `socket._handle = null`
240
- * and silences its reads before the write).
312
+ * Both descriptors share the connection and its unread bytes. Node may
313
+ * close the sender on acknowledgement or keep it open; the binding must
314
+ * support either, without knowing the child_process option that chose it.
241
315
  */
242
316
  duplicate(): LibuvStreamWrap {
243
317
  const copy = new LibuvStreamWrap();
244
- copy.takeOverFrom(this);
318
+ copy.shareConnectionFrom(this);
245
319
  return copy;
246
320
  }
247
321
 
248
322
  /** Two handles become each other's peer; what one writes the other reads. */
249
323
  static pair(a: LibuvStreamWrap, b: LibuvStreamWrap): void {
250
- a.peer = b;
251
- b.peer = a;
324
+ const left = nativeStreamFor(a), right = nativeStreamFor(b);
325
+ if (left || right) {
326
+ if (!left || !right) throw new Error('Cannot pair native and realm-local stream handles.');
327
+ left.pair(right);
328
+ return;
329
+ }
330
+ a.streamEndpoint.peer = b.streamEndpoint;
331
+ b.streamEndpoint.peer = a.streamEndpoint;
332
+ }
333
+
334
+ /** A fresh wrapper acquires a descriptor reference on this connection. */
335
+ protected shareConnectionFrom(other: LibuvStreamWrap): void {
336
+ this.streamEndpoint.handles.delete(this);
337
+ this.streamEndpoint = other.streamEndpoint;
338
+ this.streamEndpoint.handles.add(this);
339
+ }
340
+
341
+ /** Native connection resources outlive individual duplicated wrappers. */
342
+ protected onLastReferenceClose(callback: () => void): void {
343
+ this.streamEndpoint.finalizers.push(callback);
344
+ }
345
+
346
+ /** Host transports bound copies waiting in the native receiving queue. */
347
+ get peerQueuedBytes(): number {
348
+ let bytes = 0;
349
+ for (const chunk of this.streamEndpoint.peer?.inbound ?? []) bytes += chunk.bytes.byteLength;
350
+ return bytes;
351
+ }
352
+
353
+ get peerQueuedHandles(): number {
354
+ let count = 0;
355
+ for (const chunk of this.streamEndpoint.peer?.inbound ?? []) {
356
+ if (chunk.handle !== undefined && chunk.handle !== null) count++;
357
+ }
358
+ return count;
359
+ }
360
+
361
+ /** Changes to the same connection end, even after its wrapper is replaced. */
362
+ onPeerReadCapacity(listener: () => void): () => void {
363
+ const endpoint = this.streamEndpoint.peer;
364
+ if (!endpoint) return () => {};
365
+ endpoint.capacityListeners.add(listener);
366
+ return () => { endpoint.capacityListeners.delete(listener); };
252
367
  }
253
368
 
254
369
  /**
@@ -261,15 +376,11 @@ export class LibuvStreamWrap implements OwnedHandle {
261
376
  * waiting for a message its parent had already sent.
262
377
  */
263
378
  takeOverFrom(other: LibuvStreamWrap): void {
264
- const peer = other.peer;
265
- other.peer = null;
266
- if (peer) LibuvStreamWrap.pair(this, peer);
267
- if (other.inbound.length > 0) {
268
- this.inbound = [...this.inbound, ...other.inbound];
269
- other.inbound = [];
270
- }
271
- if (other.inboundEof) this.inboundEof = true;
272
- if (other.inboundError !== null && this.inboundError === null) this.inboundError = other.inboundError;
379
+ if (other === this) return;
380
+ if (nativeStreamFor(this) || nativeStreamFor(other)) throw new Error('Remote descriptors must be inherited and opened through their owner.');
381
+ this.shareConnectionFrom(other);
382
+ this.streamEndpoint.handles.delete(other);
383
+ other.streamEndpoint = new StreamEndpoint(other);
273
384
  this.flushInbound();
274
385
  }
275
386
 
@@ -289,11 +400,14 @@ export class LibuvStreamWrap implements OwnedHandle {
289
400
  * produced nothing.
290
401
  */
291
402
  protected dispatchWrite(req: WriteWrap, buffer: Uint8Array, handle?: unknown): number {
403
+ const native = nativeStreamFor(this);
404
+ if (native) return native.write(req, [buffer], handle);
292
405
  void req;
293
406
  const length = buffer.byteLength;
294
407
  streamBaseState[kBytesWritten] = length;
295
408
  streamBaseState[kLastWriteWasAsync] = 0;
296
409
  if (this.closed) return UV_EBADF;
410
+ if (this.streamEndpoint.writeEnded) return UV_EPIPE;
297
411
  this.bytesWritten += length;
298
412
  const peer = this.peer;
299
413
  if (!peer || peer.closed) return 0;
@@ -310,6 +424,7 @@ export class LibuvStreamWrap implements OwnedHandle {
310
424
  * reader first and the bytes never reached it at all.
311
425
  */
312
426
  protected sendEof(): void {
427
+ this.streamEndpoint.writeEnded = true;
313
428
  const peer = this.peer;
314
429
  if (peer) peer.receiveEof();
315
430
  }
@@ -319,6 +434,7 @@ export class LibuvStreamWrap implements OwnedHandle {
319
434
  * which is how `socket.resetAndDestroy()` differs from `socket.destroy()`.
320
435
  */
321
436
  protected sendReset(): void {
437
+ this.streamEndpoint.writeEnded = true;
322
438
  const peer = this.peer;
323
439
  if (peer) peer.receiveError(UV_ECONNRESET);
324
440
  }
@@ -326,7 +442,7 @@ export class LibuvStreamWrap implements OwnedHandle {
326
442
  /** An error arriving on the read side, delivered as libuv delivers one. */
327
443
  receiveError(code: number): void {
328
444
  if (this.closed) return;
329
- this.inboundError = code;
445
+ this.streamEndpoint.error = code;
330
446
  this.scheduleFlush();
331
447
  }
332
448
 
@@ -340,7 +456,7 @@ export class LibuvStreamWrap implements OwnedHandle {
340
456
  /** The peer is done writing. */
341
457
  receiveEof(): void {
342
458
  if (this.closed) return;
343
- this.inboundEof = true;
459
+ this.streamEndpoint.eof = true;
344
460
  this.scheduleFlush();
345
461
  }
346
462
 
@@ -350,11 +466,14 @@ export class LibuvStreamWrap implements OwnedHandle {
350
466
  * libuv's loop reads in: everything a writer did in one turn is read in the
351
467
  * order it did it, and never from inside the call that did it.
352
468
  */
353
- private flushScheduled = false;
354
469
  protected scheduleFlush(): void {
355
- if (this.flushScheduled) return;
356
- this.flushScheduled = true;
357
- queueMicrotask(() => { this.flushScheduled = false; this.flushInbound(); });
470
+ const endpoint = this.streamEndpoint;
471
+ if (endpoint.flushScheduled) return;
472
+ endpoint.flushScheduled = true;
473
+ queueMicrotask(() => {
474
+ endpoint.flushScheduled = false;
475
+ for (const handle of endpoint.handles) handle.flushInbound();
476
+ });
358
477
  }
359
478
 
360
479
  /**
@@ -364,39 +483,44 @@ export class LibuvStreamWrap implements OwnedHandle {
364
483
  * from inside `onread`, so the loop re-reads `reading` every time.
365
484
  */
366
485
  protected flushInbound(): void {
367
- while (this.reading && !this.closed && this.inbound.length > 0) {
368
- const next = this.inbound.shift() as Inbound;
369
- const bytes = next.bytes;
370
- // The handle is on the channel before the bytes it belongs to reach the
371
- // reader, which is where `setupChannel` takes it off.
372
- if (next.handle !== undefined) this.pendingHandle = next.handle;
373
- this.bytesRead += bytes.byteLength;
374
- const target = this.userBuffer;
375
- if (target) {
376
- // `net.Socket({ onread })`: the bytes land in the program's buffer and
377
- // `onStreamRead` hands the count to its callback.
378
- const taken = Math.min(target.byteLength, bytes.byteLength);
379
- target.set(bytes.subarray(0, taken));
380
- if (taken < bytes.byteLength) this.inbound.unshift({ bytes: bytes.subarray(taken) });
381
- streamBaseState[kReadBytesOrError] = taken;
486
+ try {
487
+ while (this.reading && !this.closed && this.inbound.length > 0) {
488
+ const next = this.inbound.shift() as Inbound;
489
+ const bytes = next.bytes;
490
+ // The handle is on the channel before the bytes it belongs to reach the
491
+ // reader, which is where `setupChannel` takes it off.
492
+ if (next.handle !== undefined) this.pendingHandle = next.handle;
493
+ const target = this.userBuffer;
494
+ if (target) {
495
+ // `net.Socket({ onread })`: the bytes land in the program's buffer and
496
+ // `onStreamRead` hands the count to its callback.
497
+ const taken = Math.min(target.byteLength, bytes.byteLength);
498
+ this.bytesRead += taken;
499
+ target.set(bytes.subarray(0, taken));
500
+ if (taken < bytes.byteLength) this.inbound.unshift({ bytes: bytes.subarray(taken) });
501
+ streamBaseState[kReadBytesOrError] = taken;
502
+ streamBaseState[kArrayBufferOffset] = 0;
503
+ try { this.onread?.(null); } finally { this.streamEndpoint.capacityChanged(); }
504
+ continue;
505
+ }
506
+ streamBaseState[kReadBytesOrError] = bytes.byteLength;
507
+ this.bytesRead += bytes.byteLength;
508
+ streamBaseState[kArrayBufferOffset] = bytes.byteOffset;
509
+ try { this.onread?.(bytes.buffer as ArrayBuffer); } finally { this.streamEndpoint.capacityChanged(); }
510
+ }
511
+ if (this.reading && !this.closed && this.inbound.length === 0 && !this.eofDelivered &&
512
+ (this.inboundError !== null || this.inboundEof)) {
513
+ this.eofDelivered = true;
514
+ // libuv stops a stream when it reads EOF, and a stopped handle does not
515
+ // hold the loop: a socket whose peer has gone is not work, however much
516
+ // unread data is still in its buffer.
517
+ stopHandle(this);
518
+ streamBaseState[kReadBytesOrError] = this.inboundError ?? UV_EOF;
382
519
  streamBaseState[kArrayBufferOffset] = 0;
383
520
  this.onread?.(null);
384
- continue;
385
521
  }
386
- streamBaseState[kReadBytesOrError] = bytes.byteLength;
387
- streamBaseState[kArrayBufferOffset] = bytes.byteOffset;
388
- this.onread?.(bytes.buffer as ArrayBuffer);
389
- }
390
- if (this.reading && !this.closed && this.inbound.length === 0 && !this.eofDelivered &&
391
- (this.inboundError !== null || this.inboundEof)) {
392
- this.eofDelivered = true;
393
- // libuv stops a stream when it reads EOF, and a stopped handle does not
394
- // hold the loop: a socket whose peer has gone is not work, however much
395
- // unread data is still in its buffer.
396
- stopHandle(this);
397
- streamBaseState[kReadBytesOrError] = this.inboundError ?? UV_EOF;
398
- streamBaseState[kArrayBufferOffset] = 0;
399
- this.onread?.(null);
522
+ } finally {
523
+ nativeStreamFor(this)?.readFlushed(this.inbound.length === 0, this.eofDelivered);
400
524
  }
401
525
  }
402
526
  }
@@ -14,7 +14,8 @@
14
14
  */
15
15
  import { LibuvStreamWrap } from './stream_wrap';
16
16
  import { __adoptHandle, ownerOf } from './handles';
17
- import { UV_EADDRINUSE, UV_ECONNREFUSED, UV_EINVAL, UV_ENOTSUP } from './uv';
17
+ import { nativeStreamFor, registerNativeStreamConstructor } from '../../native-stream-binding';
18
+ import { UV_EADDRINUSE, UV_ECONNREFUSED, UV_EINVAL, UV_ENOTSUP, UV_EBADF } from './uv';
18
19
 
19
20
  /** libuv's `uv_tcp_t` flavours, and the two flags `getFlags` builds. */
20
21
  export const constants = {
@@ -100,18 +101,22 @@ export class TCP extends LibuvStreamWrap {
100
101
 
101
102
  private local: SockName | null = null;
102
103
  private remote: SockName | null = null;
103
- private ephemeral: number | null = null;
104
104
 
105
105
  constructor(type: number = constants.SOCKET) {
106
106
  super();
107
107
  this.type = type;
108
+ this.attachNative('tcp', type);
108
109
  }
109
110
 
110
111
  bind(address: string, port: number, _flags?: number): number {
112
+ const native = nativeStreamFor(this);
113
+ if (native) return native.call({ operation: 'bind', id: native.descriptor.id, address, port, flags: _flags }).status;
111
114
  return this.bindTo(address || '0.0.0.0', port, 'IPv4');
112
115
  }
113
116
 
114
117
  bind6(address: string, port: number, _flags?: number): number {
118
+ const native = nativeStreamFor(this);
119
+ if (native) return native.call({ operation: 'bind', id: native.descriptor.id, address, port, flags: _flags, ipv6: true }).status;
115
120
  return this.bindTo(address || '::', port, 'IPv6');
116
121
  }
117
122
 
@@ -124,10 +129,17 @@ export class TCP extends LibuvStreamWrap {
124
129
  if (this.local && boundPorts.get(this.local.port) === this) boundPorts.delete(this.local.port);
125
130
  this.local = { address, family, port: bound };
126
131
  boundPorts.set(bound, this);
132
+ this.onLastReferenceClose(() => { if (boundPorts.get(bound) === this) boundPorts.delete(bound); });
127
133
  return 0;
128
134
  }
129
135
 
130
136
  listen(_backlog: number): number {
137
+ const native = nativeStreamFor(this);
138
+ if (native) {
139
+ const status = native.call({ operation: 'listen', id: native.descriptor.id, backlog: _backlog }).status;
140
+ if (status === 0) this.listening = true;
141
+ return status;
142
+ }
131
143
  if (!this.local) {
132
144
  const err = this.bindTo('0.0.0.0', 0, 'IPv4');
133
145
  if (err) return err;
@@ -146,12 +158,14 @@ export class TCP extends LibuvStreamWrap {
146
158
  }
147
159
 
148
160
  private connectTo(req: TCPConnectWrap, address: string, port: number, family: string): number {
161
+ const native = nativeStreamFor(this);
162
+ if (native) return native.connect(address, port, family === 'IPv6', status => req.oncomplete?.(status, this, req, true, true));
149
163
  const target = Number(port);
150
164
  const reachable = isThisHost(address);
151
165
  if (!this.local) {
152
166
  const mine = allocatePort();
153
167
  ephemeralPorts.add(mine);
154
- this.ephemeral = mine;
168
+ this.onLastReferenceClose(() => { ephemeralPorts.delete(mine); });
155
169
  this.local = { address: family === 'IPv6' ? '::1' : '127.0.0.1', family, port: mine };
156
170
  }
157
171
  this.remote = { address, family, port: target };
@@ -165,7 +179,7 @@ export class TCP extends LibuvStreamWrap {
165
179
  req.oncomplete?.(UV_ECONNREFUSED, this, req, true, true);
166
180
  return;
167
181
  }
168
- const accepted = new TCP(constants.SOCKET);
182
+ const accepted = new (server.constructor as typeof TCP)(constants.SOCKET);
169
183
  accepted.local = { ...(server.local as SockName), port: target };
170
184
  accepted.remote = { ...(this.local as SockName) };
171
185
  // An accepted connection is a handle of the loop that is serving, not of
@@ -193,13 +207,20 @@ export class TCP extends LibuvStreamWrap {
193
207
 
194
208
  /** libuv's `dup()`: the same connection, under both its names. */
195
209
  override duplicate(): TCP {
196
- const copy = new TCP(constants.SOCKET);
210
+ const native = nativeStreamFor(this);
211
+ const copy = native ? native.duplicate() as TCP : new (this.constructor as typeof TCP)(constants.SOCKET);
197
212
  if (this.local && this.remote) copy.adoptNames(this.local, this.remote);
198
- copy.takeOverFrom(this);
213
+ if (!native) copy.shareConnectionFrom(this);
199
214
  return copy;
200
215
  }
201
216
 
202
217
  getsockname(out: Partial<SockName>): number {
218
+ const native = nativeStreamFor(this);
219
+ if (native) {
220
+ const result = native.call({ operation: 'getsockname', id: native.descriptor.id });
221
+ if (result.address) Object.assign(out, result.address);
222
+ return result.status;
223
+ }
203
224
  if (!this.local) return UV_EINVAL;
204
225
  out.address = this.local.address;
205
226
  out.family = this.local.family;
@@ -208,6 +229,12 @@ export class TCP extends LibuvStreamWrap {
208
229
  }
209
230
 
210
231
  getpeername(out: Partial<SockName>): number {
232
+ const native = nativeStreamFor(this);
233
+ if (native) {
234
+ const result = native.call({ operation: 'getpeername', id: native.descriptor.id });
235
+ if (result.address) Object.assign(out, result.address);
236
+ return result.status;
237
+ }
211
238
  if (!this.remote) return UV_EINVAL;
212
239
  out.address = this.remote.address;
213
240
  out.family = this.remote.family;
@@ -231,11 +258,20 @@ export class TCP extends LibuvStreamWrap {
231
258
 
232
259
  /** An engine handle carries no descriptor a program can hand it. */
233
260
  open(_fd: number): number {
261
+ const native = nativeStreamFor(this);
262
+ if (native) {
263
+ const status = native.call({ operation: 'open', id: native.descriptor.id, fd: _fd }).status;
264
+ if (status === 0) this.fd = _fd;
265
+ return status;
266
+ }
234
267
  return UV_ENOTSUP;
235
268
  }
236
269
 
237
270
  /** libuv's `uv_tcp_close_reset`: the peer reads ECONNRESET rather than EOF. */
238
271
  reset(callback?: () => void): number {
272
+ if (this.closed) return UV_EBADF;
273
+ const native = nativeStreamFor(this);
274
+ if (native) return native.close(callback, true);
239
275
  this.sendReset();
240
276
  this.close(callback);
241
277
  return 0;
@@ -246,9 +282,8 @@ export class TCP extends LibuvStreamWrap {
246
282
  this.listening = false;
247
283
  this.onconnection = null;
248
284
  if (wasListening && this.local) { try { onCloseWatcher?.(this.local.port); } catch { /* as above */ } }
249
- if (this.local && boundPorts.get(this.local.port) === this) boundPorts.delete(this.local.port);
250
- if (this.ephemeral !== null) { ephemeralPorts.delete(this.ephemeral); this.ephemeral = null; }
251
285
  }
252
286
  }
253
287
 
288
+ registerNativeStreamConstructor('tcp', TCP);
254
289
  export default { TCP, TCPConnectWrap, constants };