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.
@@ -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})