socketduct 0.1.20 → 0.1.21
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.
- package/changelog.d/20260925180000-manual-ack-durable-admission.md +1 -0
- package/docs/protocol.md +21 -3
- package/docs/reverse-gateway.md +49 -8
- package/docs/running.md +3 -2
- package/docs/session-lifecycle.md +22 -0
- package/docs/socket-api.md +22 -17
- package/package.json +1 -1
- package/src/client/deferred-virtual-socket.js +42 -1
- package/src/client/multiplexed-connection.js +17 -0
- package/src/client/multiplexed-transport-set.js +43 -8
- package/src/client/multiplexed.js +1 -1
- package/src/client/virtual-socket.js +109 -4
- package/src/index.js +1 -1
- package/src/reverse/client.js +174 -23
- package/src/reverse/gateway.js +10 -1
- package/src/spool/spool-file.js +92 -6
- package/tensorbuzz.yml +1 -1
- package/test/e2e/reverse-client-recovery-process.test.js +3 -0
- package/test/fixtures/reverse-client-recovery.mjs +14 -2
- package/test/integration/multiplexed-transport-set.test.js +1 -5
- package/test/integration/reverse-gateway.test.js +158 -1
- package/test/unit/client/multiplexed-transport-set.test.js +50 -3
- package/test/unit/client/virtual-socket.test.js +123 -1
- package/test/unit/index.test.js +2 -0
- package/test/unit/spool/spool-file.test.js +58 -0
package/test/unit/index.test.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
MultiplexedSocketductTransportSet,
|
|
14
14
|
OutboundReverseGateway,
|
|
15
15
|
ReverseGatewayConnection,
|
|
16
|
+
SocketductRecoveryLookupError,
|
|
16
17
|
SocketductHttpAgent,
|
|
17
18
|
SocketductSocket
|
|
18
19
|
} from "../../src/index.js"
|
|
@@ -57,6 +58,7 @@ test("package exports the protocol-v3 pooled connection and relay entrypoints",
|
|
|
57
58
|
|
|
58
59
|
assert.equal(multiplexed.MultiplexedSocketductConnection, MultiplexedSocketductConnection)
|
|
59
60
|
assert.equal(multiplexed.MultiplexedSocketductTransportSet, MultiplexedSocketductTransportSet)
|
|
61
|
+
assert.equal(multiplexed.SocketductRecoveryLookupError, SocketductRecoveryLookupError)
|
|
60
62
|
assert.equal(createMultiplexedSocketductConnection({
|
|
61
63
|
relay: {host: "127.0.0.1", port: 3100, token: "test-token"}
|
|
62
64
|
}) instanceof MultiplexedSocketductConnection, true)
|
|
@@ -63,6 +63,64 @@ test("SessionSpool truncates acknowledged chunks", async () => {
|
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
test("SessionSpool truncates an acknowledgement inside a chunk in application bytes", async () => {
|
|
67
|
+
await withTempDir(async (root) => {
|
|
68
|
+
const spool = new SessionSpool({rootDir: root})
|
|
69
|
+
const sessionId = "session_partial_ack"
|
|
70
|
+
await spool.create({sessionId, ownerId: "user-1"})
|
|
71
|
+
await spool.appendChunk({sessionId, sequence: 8, data: Buffer.from("abcdefgh")})
|
|
72
|
+
|
|
73
|
+
await spool.truncateAcked(sessionId, 3)
|
|
74
|
+
|
|
75
|
+
const chunks = await spool.readChunksAfter(sessionId, 3)
|
|
76
|
+
assert.deepEqual(chunks, [{sequence: 8, data: Buffer.from("defgh")}])
|
|
77
|
+
assert.equal(spool.getUsedBytes(sessionId), 5)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("SessionSpool atomically admits a complete chunk batch before recovery commit", async () => {
|
|
82
|
+
await withTempDir(async (root) => {
|
|
83
|
+
const spool = new SessionSpool({rootDir: root, maxBytes: 32})
|
|
84
|
+
const sessionId = "session_durable_batch"
|
|
85
|
+
await spool.createNew({sessionId, ownerId: "reverse-client-v3"})
|
|
86
|
+
await spool.appendChunk({sessionId, ownerId: "reverse-client-v3", sequence: 4, data: Buffer.from("base")})
|
|
87
|
+
await spool.writeRecoveryState(sessionId, {version: 3, sendSequence: 4})
|
|
88
|
+
|
|
89
|
+
await spool.appendChunksAndRecoveryState({
|
|
90
|
+
sessionId,
|
|
91
|
+
ownerId: "reverse-client-v3",
|
|
92
|
+
previousSequence: 4,
|
|
93
|
+
chunks: [
|
|
94
|
+
{sequence: 7, data: Buffer.from("one")},
|
|
95
|
+
{sequence: 10, data: Buffer.from("two")}
|
|
96
|
+
],
|
|
97
|
+
recoveryState: {version: 3, sendSequence: 10}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
assert.deepEqual((await spool.readChunksAfter(sessionId, 0)).map(({sequence, data}) => [sequence, data.toString()]), [
|
|
101
|
+
[4, "base"],
|
|
102
|
+
[7, "one"],
|
|
103
|
+
[10, "two"]
|
|
104
|
+
])
|
|
105
|
+
assert.deepEqual(spool.readRecoveryStateSync(sessionId, "reverse-client-v3"), {version: 3, sendSequence: 10})
|
|
106
|
+
assert.equal(spool.getUsedBytes(sessionId), 10)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("SessionSpool recovery lookup distinguishes missing state from invalid state", async () => {
|
|
111
|
+
await withTempDir(async (root) => {
|
|
112
|
+
const spool = new SessionSpool({rootDir: root})
|
|
113
|
+
assert.equal(spool.findRecoveryStateSync("missing_session", "edge-v3"), undefined)
|
|
114
|
+
|
|
115
|
+
await spool.createNew({sessionId: "present_session", ownerId: "edge-v3"})
|
|
116
|
+
await spool.writeRecoveryState("present_session", {version: 3})
|
|
117
|
+
assert.deepEqual(spool.findRecoveryStateSync("present_session", "edge-v3"), {version: 3})
|
|
118
|
+
|
|
119
|
+
await writeFile(join(root, "present_session", "recovery.json"), "{invalid\n")
|
|
120
|
+
assert.throws(() => spool.findRecoveryStateSync("present_session", "edge-v3"), /Invalid Socketduct recovery state/)
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
66
124
|
test("SessionSpool rejects path traversal and mismatched metadata", async () => {
|
|
67
125
|
await withTempDir(async (root) => {
|
|
68
126
|
const spool = new SessionSpool({rootDir: root})
|