socketduct 0.1.24 → 0.1.26

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 (60) hide show
  1. package/AGENTS.md +2 -2
  2. package/changelog.d/20261010130509-bound-log-frame-compression-memory.md +1 -0
  3. package/docs/runbooks/jsonl-integrity-rollout.md +8 -6
  4. package/package.json +2 -2
  5. package/scripts/test.js +9 -14
  6. package/src/client/multiplexed-transport-set.js +20 -2
  7. package/src/logging/compressed-log-writer.js +1 -1
  8. package/src/logging/zstd-frame-codec.js +15 -1
  9. package/src/reverse/client.js +44 -1
  10. package/src/reverse/hub.js +10 -1
  11. package/src/reverse/runtime.js +11 -2
  12. package/test/e2e/gateway-process.test.js +93 -92
  13. package/test/e2e/logger-shutdown-process.test.js +40 -37
  14. package/test/e2e/reverse-client-recovery-process.test.js +107 -103
  15. package/test/e2e/reverse-gateway-process.test.js +328 -318
  16. package/test/integration/initial-target-recovery.test.js +333 -334
  17. package/test/integration/logging-cli.test.js +121 -119
  18. package/test/integration/logging-compressed-recovery.test.js +96 -95
  19. package/test/integration/multiplexed-compatibility.test.js +178 -177
  20. package/test/integration/multiplexed-corrections.test.js +360 -359
  21. package/test/integration/multiplexed-failures.test.js +253 -251
  22. package/test/integration/multiplexed-transport-set.test.js +334 -322
  23. package/test/integration/multiplexed-transport.test.js +135 -134
  24. package/test/integration/reverse-gateway-stream-id-reset.test.js +90 -89
  25. package/test/integration/reverse-gateway.test.js +2309 -2218
  26. package/test/integration/reverse-resume-continuity.test.js +219 -218
  27. package/test/integration/reverse-route-class.test.js +363 -278
  28. package/test/integration/socketduct-http-agent.test.js +314 -312
  29. package/test/integration/socketduct-https-agent.test.js +516 -485
  30. package/test/integration/stream-compression.test.js +862 -824
  31. package/test/integration/tcp-forwarding.test.js +2550 -2447
  32. package/test/integration/tls-forwarding.test.js +593 -592
  33. package/test/unit/auth/auth.test.js +43 -44
  34. package/test/unit/auth/static-token-store.test.js +9 -8
  35. package/test/unit/client/connect.test.js +41 -40
  36. package/test/unit/client/deferred-virtual-socket.test.js +95 -94
  37. package/test/unit/client/multiplexed-spool-budget.test.js +155 -154
  38. package/test/unit/client/multiplexed-transport-set.test.js +845 -732
  39. package/test/unit/client/socketduct-socket.test.js +768 -771
  40. package/test/unit/client/virtual-socket.test.js +198 -168
  41. package/test/unit/config/docker-gateway-files.test.js +224 -224
  42. package/test/unit/config/environment.test.js +472 -492
  43. package/test/unit/index.test.js +53 -52
  44. package/test/unit/logging/compressed-log-worker.test.js +23 -22
  45. package/test/unit/logging/compressed-log-writer.test.js +155 -154
  46. package/test/unit/logging/log-retention.test.js +52 -47
  47. package/test/unit/logging/log-segment-index.test.js +84 -81
  48. package/test/unit/logging/zstd-frame-codec.test.js +28 -19
  49. package/test/unit/protocol/compression.test.js +254 -298
  50. package/test/unit/protocol/frame.test.js +280 -276
  51. package/test/unit/protocol/v3-compression.test.js +21 -23
  52. package/test/unit/protocol/v3-frame.test.js +42 -41
  53. package/test/unit/release-configuration.test.js +15 -12
  54. package/test/unit/reverse/disconnect-attribution.test.js +65 -64
  55. package/test/unit/reverse-terminal-outcomes.test.js +53 -50
  56. package/test/unit/server/control-handler.test.js +232 -231
  57. package/test/unit/server/primary.test.js +51 -49
  58. package/test/unit/server/relay-runtime.test.js +280 -294
  59. package/test/unit/sessions/session.test.js +48 -62
  60. package/test/unit/spool/spool-file.test.js +401 -385
@@ -1,6 +1,5 @@
1
1
  /* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
2
- import assert from "node:assert/strict"
3
- import test from "node:test"
2
+ import {describe, expect, it} from "@velocious/testing"
4
3
 
5
4
  import {VirtualSocket} from "../../../src/client/virtual-socket.js"
6
5
 
@@ -27,7 +26,7 @@ function fakeConnection() {
27
26
  _resumeReadable: () => { calls.resumed += 1 },
28
27
  _deliverReadable: (/** @type {number} */ _id, /** @type {number} */ bytes) => { calls.delivered += bytes },
29
28
  _acknowledgeReadable: (/** @type {number} */ _id, /** @type {number} */ bytes) => {
30
- assert(bytes <= calls.delivered - calls.acknowledged)
29
+ if (!(bytes <= calls.delivered - calls.acknowledged)) throw new Error("ACK exceeds delivered bytes")
31
30
  calls.acknowledged += bytes
32
31
  return Promise.resolve(calls.acknowledged)
33
32
  },
@@ -42,179 +41,210 @@ function fakeConnection() {
42
41
  }
43
42
  }
44
43
 
45
- test("VirtualSocket exposes normal connect metadata and compatibility methods", async () => {
46
- const fake = fakeConnection()
47
- const socket = new VirtualSocket(fake.connection, 1, {host: "target.internal", port: 443})
48
- /** @type {string[]} */
49
- const events = []
50
- socket.on("connect", () => events.push("connect"))
51
- socket.on("ready", () => events.push("ready"))
52
- socket._markConnected()
53
-
54
- assert.deepEqual(events, ["connect", "ready"])
55
- assert.equal(socket.connecting, false)
56
- assert.equal(socket.pending, false)
57
- assert.equal(socket.remoteAddress, "target.internal")
58
- assert.equal(socket.remotePort, 443)
59
- assert.deepEqual(socket.address(), {address: "127.0.0.1", family: "IPv4", port: 43210})
60
- assert.equal(socket.setNoDelay(), socket)
61
- assert.equal(socket.setKeepAlive(), socket)
62
- assert.equal(socket.ref(), socket)
63
- assert.equal(socket.unref(), socket)
64
- assert.equal(fake.calls.refs, 1)
65
- assert.equal(fake.calls.unrefs, 1)
66
- socket.destroy()
67
- await new Promise((resolve) => socket.once("close", resolve))
68
- })
69
-
70
- test("VirtualSocket write backpressure emits drain and end emits finish", async () => {
71
- const fake = fakeConnection()
72
- const socket = new VirtualSocket(fake.connection, 3, {
73
- host: "127.0.0.1",
74
- port: 80,
75
- writableHighWaterMark: 1,
76
- allowHalfOpen: true
44
+ describe("VirtualSocket", () => {
45
+ it("VirtualSocket exposes normal connect metadata and compatibility methods", async () => {
46
+ const fake = fakeConnection()
47
+ const socket = new VirtualSocket(fake.connection, 1, {host: "target.internal", port: 443})
48
+ /** @type {string[]} */
49
+ const events = []
50
+ socket.on("connect", () => events.push("connect"))
51
+ socket.on("ready", () => events.push("ready"))
52
+ socket._markConnected()
53
+
54
+ expect(events).toEqual(["connect", "ready"])
55
+ expect(socket.connecting).toBe(false)
56
+ expect(socket.pending).toBe(false)
57
+ expect(socket.remoteAddress).toBe("target.internal")
58
+ expect(socket.remotePort).toBe(443)
59
+ expect(socket.address()).toEqual({address: "127.0.0.1", family: "IPv4", port: 43210})
60
+ expect(socket.setNoDelay()).toBe(socket)
61
+ expect(socket.setKeepAlive()).toBe(socket)
62
+ expect(socket.ref()).toBe(socket)
63
+ expect(socket.unref()).toBe(socket)
64
+ expect(fake.calls.refs).toBe(1)
65
+ expect(fake.calls.unrefs).toBe(1)
66
+ socket.destroy()
67
+ await new Promise((resolve) => socket.once("close", resolve))
77
68
  })
78
- socket._markConnected()
79
- const drained = new Promise((resolve) => socket.once("drain", resolve))
80
- assert.equal(socket.write("payload"), false)
81
- await drained
82
- assert.equal(socket.bytesWritten, 7)
83
- const finished = new Promise((resolve) => socket.once("finish", resolve))
84
- socket.end()
85
- await finished
86
- assert.equal(fake.calls.ended, 1)
87
- socket.destroy()
88
- })
89
69
 
90
- test("VirtualSocket pause/resume, timeout, data and half-close remain stream-local", async () => {
91
- const fake = fakeConnection()
92
- const socket = new VirtualSocket(fake.connection, 5, {host: "127.0.0.1", port: 80, allowHalfOpen: true})
93
- socket._markConnected()
94
- socket.pause()
95
- const timeout = new Promise((resolve) => socket.setTimeout(10, () => resolve(undefined)))
96
- await timeout
97
- socket.setTimeout(0)
98
- const data = new Promise((resolve) => socket.once("data", (chunk) => resolve(chunk.toString())))
99
- socket._receive(Buffer.from("hello"))
100
- socket.resume()
101
- assert.equal(await data, "hello")
102
- assert.equal(socket.bytesRead, 5)
103
- const ended = new Promise((resolve) => socket.once("end", resolve))
104
- socket._receiveEnd()
105
- await ended
106
- assert(fake.calls.resumed > 0)
107
- socket.destroy()
108
- })
70
+ it("VirtualSocket write backpressure emits drain and end emits finish", async () => {
71
+ const fake = fakeConnection()
72
+ const socket = new VirtualSocket(fake.connection, 3, {
73
+ host: "127.0.0.1",
74
+ port: 80,
75
+ writableHighWaterMark: 1,
76
+ allowHalfOpen: true
77
+ })
78
+ socket._markConnected()
79
+ const drained = new Promise((resolve) => socket.once("drain", resolve))
80
+ expect(socket.write("payload")).toBe(false)
81
+ await drained
82
+ expect(socket.bytesWritten).toBe(7)
83
+ const finished = new Promise((resolve) => socket.once("finish", resolve))
84
+ socket.end()
85
+ await finished
86
+ expect(fake.calls.ended).toBe(1)
87
+ socket.destroy()
88
+ })
109
89
 
110
- test("VirtualSocket exposes opt-in manual receive ACK and durable write contracts", async () => {
111
- const fake = fakeConnection()
112
- const socket = new VirtualSocket(fake.connection, 7, {
113
- host: "127.0.0.1",
114
- port: 80,
115
- manualReceiveAck: true
90
+ it("VirtualSocket pause/resume, timeout, data and half-close remain stream-local", async () => {
91
+ const fake = fakeConnection()
92
+ const socket = new VirtualSocket(fake.connection, 5, {host: "127.0.0.1", port: 80, allowHalfOpen: true})
93
+ socket._markConnected()
94
+ socket.pause()
95
+ const timeout = new Promise((resolve) => socket.setTimeout(10, () => resolve(undefined)))
96
+ await timeout
97
+ socket.setTimeout(0)
98
+ const data = new Promise((resolve) => socket.once("data", (chunk) => resolve(chunk.toString())))
99
+ socket._receive(Buffer.from("hello"))
100
+ socket.resume()
101
+ expect(await data).toBe("hello")
102
+ expect(socket.bytesRead).toBe(5)
103
+ const ended = new Promise((resolve) => socket.once("end", resolve))
104
+ socket._receiveEnd()
105
+ await ended
106
+ expect(fake.calls.resumed > 0).toBeTruthy()
107
+ socket.destroy()
116
108
  })
117
- socket._markConnected()
118
109
 
119
- const acknowledged = new Promise((resolve, reject) => {
120
- socket.once("data", (chunk) => {
121
- socket.acknowledgeReceive(chunk.length).then(resolve, reject)
110
+ it("VirtualSocket exposes opt-in manual receive ACK and durable write contracts", async () => {
111
+ const fake = fakeConnection()
112
+ const socket = new VirtualSocket(fake.connection, 7, {
113
+ host: "127.0.0.1",
114
+ port: 80,
115
+ manualReceiveAck: true
116
+ })
117
+ socket._markConnected()
118
+
119
+ const acknowledged = new Promise((resolve, reject) => {
120
+ socket.once("data", (chunk) => {
121
+ socket.acknowledgeReceive(chunk.length).then(resolve, reject)
122
+ })
122
123
  })
124
+ socket._receive(Buffer.from("response"))
125
+ expect(await acknowledged).toBe(8)
126
+ expect(fake.calls.delivered).toBe(8)
127
+ expect(fake.calls.acknowledged).toBe(8)
128
+
129
+ await socket.writeDurable(new Uint8Array(Buffer.from("request")))
130
+ expect(fake.calls.durableWrites).toHaveLength(1)
131
+ const durableWrite = fake.calls.durableWrites[0]
132
+ expect(durableWrite).toBeDefined()
133
+ expect(durableWrite?.equals(Buffer.from("request"))).toBe(true)
134
+ expect(socket.bytesWritten).toBe(7)
135
+ // predicate-based check: thrown error must carry SOCKETDUCT_INVALID_RECEIVE_ACK
136
+ let error
137
+ try {
138
+ socket.acknowledgeReceive(1)
139
+ } catch (caught) {
140
+ error = caught
141
+ }
142
+ expect(error).toBeDefined()
143
+ expect(/** @type {{code?: string}} */ (error).code).toBe("SOCKETDUCT_INVALID_RECEIVE_ACK")
144
+ const closed = new Promise((resolve) => socket.once("close", resolve))
145
+ socket.destroy()
146
+ await closed
147
+ // predicate-based rejection: code ERR_STREAM_DESTROYED
148
+ let rejection
149
+ try {
150
+ await socket.acknowledgeReceive(1)
151
+ } catch (caught) {
152
+ rejection = caught
153
+ }
154
+ expect(rejection).toBeDefined()
155
+ expect(/** @type {{code?: string}} */ (rejection).code).toBe("ERR_STREAM_DESTROYED")
123
156
  })
124
- socket._receive(Buffer.from("response"))
125
- assert.equal(await acknowledged, 8)
126
- assert.equal(fake.calls.delivered, 8)
127
- assert.equal(fake.calls.acknowledged, 8)
128
-
129
- await socket.writeDurable(new Uint8Array(Buffer.from("request")))
130
- assert.deepEqual(fake.calls.durableWrites, [Buffer.from("request")])
131
- assert.equal(socket.bytesWritten, 7)
132
- assert.throws(
133
- () => socket.acknowledgeReceive(1),
134
- (error) => /** @type {{code?: string}} */ (error).code === "SOCKETDUCT_INVALID_RECEIVE_ACK"
135
- )
136
- const closed = new Promise((resolve) => socket.once("close", resolve))
137
- socket.destroy()
138
- await closed
139
- await assert.rejects(
140
- socket.acknowledgeReceive(1),
141
- (error) => /** @type {{code?: string}} */ (error).code === "ERR_STREAM_DESTROYED"
142
- )
143
- })
144
157
 
145
- test("VirtualSocket orders durable writes behind queued ordinary writes", async () => {
146
- const fake = fakeConnection()
147
- /** @type {string[]} */
148
- const delivered = []
149
- /** @type {(() => void) | undefined} */
150
- let releaseFirst
151
- const connection = /** @type {{_writeStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void, _writeDurableStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void}} */ (/** @type {unknown} */ (fake.connection))
152
- connection._writeStream = (_id, data, callback) => {
153
- delivered.push(data.toString())
154
- if (data.toString() === "A") releaseFirst = callback
155
- else callback()
156
- }
157
- connection._writeDurableStream = (_id, data, callback) => {
158
- delivered.push(data.toString())
159
- callback()
160
- }
161
- const socket = new VirtualSocket(fake.connection, 9, {host: "127.0.0.1", port: 80})
162
-
163
- const first = new Promise((resolve, reject) => socket.write("A", (error) => error ? reject(error) : resolve(undefined)))
164
- const second = new Promise((resolve, reject) => socket.write("B", (error) => error ? reject(error) : resolve(undefined)))
165
- const durable = socket.writeDurable("C")
166
- assert(releaseFirst !== undefined)
167
- releaseFirst()
168
- await Promise.all([first, second, durable])
169
-
170
- assert.deepEqual(delivered, ["A", "B", "C"])
171
- assert.equal(socket.bytesWritten, 3)
172
- socket.destroy()
173
- })
158
+ it("VirtualSocket orders durable writes behind queued ordinary writes", async () => {
159
+ const fake = fakeConnection()
160
+ /** @type {string[]} */
161
+ const delivered = []
162
+ /** @type {(() => void) | undefined} */
163
+ let releaseFirst
164
+ const connection = /** @type {{_writeStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void, _writeDurableStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void}} */ (/** @type {unknown} */ (fake.connection))
165
+ connection._writeStream = (_id, data, callback) => {
166
+ delivered.push(data.toString())
167
+ if (data.toString() === "A") releaseFirst = callback
168
+ else callback()
169
+ }
170
+ connection._writeDurableStream = (_id, data, callback) => {
171
+ delivered.push(data.toString())
172
+ callback()
173
+ }
174
+ const socket = new VirtualSocket(fake.connection, 9, {host: "127.0.0.1", port: 80})
175
+
176
+ const first = new Promise((resolve, reject) => socket.write("A", (error) => error ? reject(error) : resolve(undefined)))
177
+ const second = new Promise((resolve, reject) => socket.write("B", (error) => error ? reject(error) : resolve(undefined)))
178
+ const durable = socket.writeDurable("C")
179
+ if (releaseFirst === undefined) throw new Error("expected first write callback to be captured")
180
+ releaseFirst()
181
+ await Promise.all([first, second, durable])
182
+
183
+ expect(delivered).toEqual(["A", "B", "C"])
184
+ expect(socket.bytesWritten).toBe(3)
185
+ socket.destroy()
186
+ })
174
187
 
175
- test("VirtualSocket rejects durable writes after end while ordinary writes remain queued", async () => {
176
- const fake = fakeConnection()
177
- /** @type {string[]} */
178
- const delivered = []
179
- /** @type {(() => void) | undefined} */
180
- let releaseFirst
181
- const connection = /** @type {{_writeStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void}} */ (/** @type {unknown} */ (fake.connection))
182
- connection._writeStream = (_id, data, callback) => {
183
- delivered.push(data.toString())
184
- if (data.toString() === "A") releaseFirst = callback
185
- else callback()
186
- }
187
- const socket = new VirtualSocket(fake.connection, 11, {host: "127.0.0.1", port: 80})
188
-
189
- const first = new Promise((resolve, reject) => socket.write("A", (error) => error ? reject(error) : resolve(undefined)))
190
- const second = new Promise((resolve, reject) => socket.write("B", (error) => error ? reject(error) : resolve(undefined)))
191
- const finished = new Promise((resolve) => socket.once("finish", resolve))
192
- socket.end()
193
- const rejected = assert.rejects(
194
- socket.writeDurable("late"),
195
- (error) => /** @type {{code?: string}} */ (error).code === "ERR_STREAM_WRITE_AFTER_END"
196
- )
197
-
198
- assert(releaseFirst !== undefined)
199
- releaseFirst()
200
- await Promise.all([first, second, finished, rejected])
201
- assert.deepEqual(delivered, ["A", "B"])
202
- assert.deepEqual(fake.calls.durableWrites, [])
203
- socket.destroy()
204
- })
188
+ it("VirtualSocket rejects durable writes after end while ordinary writes remain queued", async () => {
189
+ const fake = fakeConnection()
190
+ /** @type {string[]} */
191
+ const delivered = []
192
+ /** @type {(() => void) | undefined} */
193
+ let releaseFirst
194
+ const connection = /** @type {{_writeStream: (id: number, data: Buffer, callback: (error?: Error | null) => void) => void}} */ (/** @type {unknown} */ (fake.connection))
195
+ connection._writeStream = (_id, data, callback) => {
196
+ delivered.push(data.toString())
197
+ if (data.toString() === "A") releaseFirst = callback
198
+ else callback()
199
+ }
200
+ const socket = new VirtualSocket(fake.connection, 11, {host: "127.0.0.1", port: 80})
201
+
202
+ const first = new Promise((resolve, reject) => socket.write("A", (error) => error ? reject(error) : resolve(undefined)))
203
+ const second = new Promise((resolve, reject) => socket.write("B", (error) => error ? reject(error) : resolve(undefined)))
204
+ const finished = new Promise((resolve) => socket.once("finish", resolve))
205
+ socket.end()
206
+ // predicate-based rejection: code ERR_STREAM_WRITE_AFTER_END
207
+ const rejected = (async () => {
208
+ try {
209
+ await socket.writeDurable("late")
210
+ throw new Error("expected writeDurable to reject")
211
+ } catch (caught) {
212
+ const code = /** @type {{code?: string}} */ (caught).code
213
+ if (code !== "ERR_STREAM_WRITE_AFTER_END") throw caught
214
+ }
215
+ })()
216
+
217
+ if (releaseFirst === undefined) throw new Error("expected first write callback to be captured")
218
+ releaseFirst()
219
+ await Promise.all([first, second, finished, rejected])
220
+ expect(delivered).toEqual(["A", "B"])
221
+ expect(fake.calls.durableWrites).toEqual([])
222
+ socket.destroy()
223
+ })
205
224
 
206
- test("VirtualSocket keeps manual ACK opt-in and reports unsupported durable admission", async () => {
207
- const fake = fakeConnection()
208
- delete /** @type {{_writeDurableStream?: unknown}} */ (fake.connection)._writeDurableStream
209
- const socket = new VirtualSocket(fake.connection, 9, {host: "127.0.0.1", port: 80})
210
-
211
- assert.throws(
212
- () => socket.acknowledgeReceive(1),
213
- (error) => /** @type {{code?: string}} */ (error).code === "SOCKETDUCT_MANUAL_ACK_DISABLED"
214
- )
215
- await assert.rejects(
216
- socket.writeDurable("request"),
217
- (error) => /** @type {{code?: string}} */ (error).code === "SOCKETDUCT_DURABLE_WRITE_UNSUPPORTED"
218
- )
219
- socket.destroy()
225
+ it("VirtualSocket keeps manual ACK opt-in and reports unsupported durable admission", async () => {
226
+ const fake = fakeConnection()
227
+ delete /** @type {{_writeDurableStream?: unknown}} */ (fake.connection)._writeDurableStream
228
+ const socket = new VirtualSocket(fake.connection, 9, {host: "127.0.0.1", port: 80})
229
+
230
+ // predicate-based check: thrown error must carry SOCKETDUCT_MANUAL_ACK_DISABLED
231
+ let error
232
+ try {
233
+ socket.acknowledgeReceive(1)
234
+ } catch (caught) {
235
+ error = caught
236
+ }
237
+ expect(error).toBeDefined()
238
+ expect(/** @type {{code?: string}} */ (error).code).toBe("SOCKETDUCT_MANUAL_ACK_DISABLED")
239
+ // predicate-based rejection: code SOCKETDUCT_DURABLE_WRITE_UNSUPPORTED
240
+ let rejection
241
+ try {
242
+ await socket.writeDurable("request")
243
+ } catch (caught) {
244
+ rejection = caught
245
+ }
246
+ expect(rejection).toBeDefined()
247
+ expect(/** @type {{code?: string}} */ (rejection).code).toBe("SOCKETDUCT_DURABLE_WRITE_UNSUPPORTED")
248
+ socket.destroy()
249
+ })
220
250
  })