@moltzap/client 2026.503.4 → 2026.504.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.
- package/dist/channel-core.d.ts +1 -4
- package/dist/channel-core.d.ts.map +1 -1
- package/dist/channel-core.js +0 -9
- package/dist/channel-core.js.map +1 -1
- package/dist/channel-core.test.js +90 -71
- package/dist/channel-core.test.js.map +1 -1
- package/dist/cli/commands/agents.js +2 -2
- package/dist/cli/commands/agents.js.map +1 -1
- package/dist/cli/commands/apps.d.ts +2 -24
- package/dist/cli/commands/apps.d.ts.map +1 -1
- package/dist/cli/commands/apps.js +22 -47
- package/dist/cli/commands/apps.js.map +1 -1
- package/dist/cli/commands/apps.test.js +59 -72
- package/dist/cli/commands/apps.test.js.map +1 -1
- package/dist/cli/commands/contacts.d.ts +1 -5
- package/dist/cli/commands/contacts.d.ts.map +1 -1
- package/dist/cli/commands/contacts.js +11 -26
- package/dist/cli/commands/contacts.js.map +1 -1
- package/dist/cli/commands/conversations-v2.test.js +30 -26
- package/dist/cli/commands/conversations-v2.test.js.map +1 -1
- package/dist/cli/commands/conversations.d.ts.map +1 -1
- package/dist/cli/commands/conversations.js +48 -33
- package/dist/cli/commands/conversations.js.map +1 -1
- package/dist/cli/commands/messages.d.ts.map +1 -1
- package/dist/cli/commands/messages.js +11 -8
- package/dist/cli/commands/messages.js.map +1 -1
- package/dist/cli/commands/messages.test.js +28 -30
- package/dist/cli/commands/messages.test.js.map +1 -1
- package/dist/cli/commands/presence.d.ts.map +1 -1
- package/dist/cli/commands/presence.js +5 -4
- package/dist/cli/commands/presence.js.map +1 -1
- package/dist/cli/commands/send.d.ts.map +1 -1
- package/dist/cli/commands/send.js +20 -11
- package/dist/cli/commands/send.js.map +1 -1
- package/dist/cli/commands/send.test.js +11 -9
- package/dist/cli/commands/send.test.js.map +1 -1
- package/dist/cli/commands/status.js +2 -2
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/cli/commands/test-transport.d.ts +10 -0
- package/dist/cli/commands/test-transport.d.ts.map +1 -0
- package/dist/cli/commands/test-transport.js +29 -0
- package/dist/cli/commands/test-transport.js.map +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +0 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/socket-client.d.ts +9 -4
- package/dist/cli/socket-client.d.ts.map +1 -1
- package/dist/cli/socket-client.js +27 -12
- package/dist/cli/socket-client.js.map +1 -1
- package/dist/cli/transport.d.ts +3 -2
- package/dist/cli/transport.d.ts.map +1 -1
- package/dist/cli/transport.js +6 -6
- package/dist/cli/transport.js.map +1 -1
- package/dist/cli/transport.test.js +4 -4
- package/dist/cli/transport.test.js.map +1 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js +33 -0
- package/dist/internal/__tests__/app-callback-partition-key.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partition-worker.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partition-worker.test.js → app-callback-partition-worker.test.js} +6 -9
- package/dist/internal/__tests__/app-callback-partition-worker.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher-real-ws.test.js → app-callback-partitioned-dispatcher-real-ws.test.js} +69 -94
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher-real-ws.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts +2 -0
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.d.ts.map +1 -0
- package/dist/internal/__tests__/{s2c-partitioned-dispatcher.test.js → app-callback-partitioned-dispatcher.test.js} +35 -69
- package/dist/internal/__tests__/app-callback-partitioned-dispatcher.test.js.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts +58 -0
- package/dist/internal/__tests__/app-callback-test-requests.d.ts.map +1 -0
- package/dist/internal/__tests__/app-callback-test-requests.js +124 -0
- package/dist/internal/__tests__/app-callback-test-requests.js.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.d.ts → app-callback-dispatcher-errors.d.ts} +5 -21
- package/dist/internal/app-callback-dispatcher-errors.d.ts.map +1 -0
- package/dist/internal/{s2c-dispatcher-errors.js → app-callback-dispatcher-errors.js} +3 -13
- package/dist/internal/app-callback-dispatcher-errors.js.map +1 -0
- package/dist/internal/app-callback-partition-key.d.ts +68 -0
- package/dist/internal/app-callback-partition-key.d.ts.map +1 -0
- package/dist/internal/app-callback-partition-key.js +90 -0
- package/dist/internal/app-callback-partition-key.js.map +1 -0
- package/dist/internal/{s2c-partition-worker.d.ts → app-callback-partition-worker.d.ts} +5 -5
- package/dist/internal/app-callback-partition-worker.d.ts.map +1 -0
- package/dist/internal/{s2c-partition-worker.js → app-callback-partition-worker.js} +5 -5
- package/dist/internal/app-callback-partition-worker.js.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.d.ts → app-callback-partitioned-dispatcher.d.ts} +9 -10
- package/dist/internal/app-callback-partitioned-dispatcher.d.ts.map +1 -0
- package/dist/internal/{s2c-partitioned-dispatcher.js → app-callback-partitioned-dispatcher.js} +12 -19
- package/dist/internal/app-callback-partitioned-dispatcher.js.map +1 -0
- package/dist/runtime/errors.d.ts +2 -28
- package/dist/runtime/errors.d.ts.map +1 -1
- package/dist/runtime/errors.js +0 -9
- package/dist/runtime/errors.js.map +1 -1
- package/dist/runtime/errors.test.js +2 -2
- package/dist/runtime/errors.test.js.map +1 -1
- package/dist/runtime/frame.d.ts +17 -18
- package/dist/runtime/frame.d.ts.map +1 -1
- package/dist/runtime/frame.js +86 -31
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/frame.test.js +15 -11
- package/dist/runtime/frame.test.js.map +1 -1
- package/dist/runtime/index.d.ts +1 -1
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/local-service-commands.d.ts +7 -0
- package/dist/runtime/local-service-commands.d.ts.map +1 -0
- package/dist/runtime/local-service-commands.js +6 -0
- package/dist/runtime/local-service-commands.js.map +1 -0
- package/dist/runtime/runtime.test.js +1 -2
- package/dist/runtime/runtime.test.js.map +1 -1
- package/dist/runtime/subscribers.d.ts +34 -29
- package/dist/runtime/subscribers.d.ts.map +1 -1
- package/dist/runtime/subscribers.js +19 -19
- package/dist/runtime/subscribers.js.map +1 -1
- package/dist/runtime/subscribers.test.js +50 -31
- package/dist/runtime/subscribers.test.js.map +1 -1
- package/dist/service.d.ts +24 -39
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +165 -161
- package/dist/service.js.map +1 -1
- package/dist/service.test.js +111 -170
- package/dist/service.test.js.map +1 -1
- package/dist/test-utils/channel-service-fixture.d.ts +1 -2
- package/dist/test-utils/channel-service-fixture.d.ts.map +1 -1
- package/dist/test-utils/channel-service-fixture.js +28 -20
- package/dist/test-utils/channel-service-fixture.js.map +1 -1
- package/dist/test-utils/conformance-adapter.d.ts +2 -2
- package/dist/test-utils/conformance-adapter.d.ts.map +1 -1
- package/dist/test-utils/conformance-adapter.js +33 -26
- package/dist/test-utils/conformance-adapter.js.map +1 -1
- package/dist/test-utils/fake-service.d.ts +14 -39
- package/dist/test-utils/fake-service.d.ts.map +1 -1
- package/dist/test-utils/fake-service.js +33 -39
- package/dist/test-utils/fake-service.js.map +1 -1
- package/dist/test-utils/ids.d.ts +9 -0
- package/dist/test-utils/ids.d.ts.map +1 -0
- package/dist/test-utils/ids.js +52 -0
- package/dist/test-utils/ids.js.map +1 -0
- package/dist/test-utils/index.d.ts +10 -2
- package/dist/test-utils/index.d.ts.map +1 -1
- package/dist/test-utils/index.js +12 -5
- package/dist/test-utils/index.js.map +1 -1
- package/dist/ws-client.d.ts +58 -64
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +151 -133
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-client.test.js +157 -167
- package/dist/ws-client.test.js.map +1 -1
- package/package.json +3 -3
- package/dist/cli/commands/permissions.d.ts +0 -70
- package/dist/cli/commands/permissions.d.ts.map +0 -1
- package/dist/cli/commands/permissions.js +0 -100
- package/dist/cli/commands/permissions.js.map +0 -1
- package/dist/cli/commands/permissions.test.d.ts +0 -2
- package/dist/cli/commands/permissions.test.d.ts.map +0 -1
- package/dist/cli/commands/permissions.test.js +0 -127
- package/dist/cli/commands/permissions.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-key.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-key.test.js +0 -231
- package/dist/internal/__tests__/s2c-partition-key.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partition-worker.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partition-worker.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher-real-ws.test.js.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts +0 -2
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.d.ts.map +0 -1
- package/dist/internal/__tests__/s2c-partitioned-dispatcher.test.js.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.d.ts.map +0 -1
- package/dist/internal/s2c-dispatcher-errors.js.map +0 -1
- package/dist/internal/s2c-partition-key.d.ts +0 -79
- package/dist/internal/s2c-partition-key.d.ts.map +0 -1
- package/dist/internal/s2c-partition-key.js +0 -146
- package/dist/internal/s2c-partition-key.js.map +0 -1
- package/dist/internal/s2c-partition-worker.d.ts.map +0 -1
- package/dist/internal/s2c-partition-worker.js.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.d.ts.map +0 -1
- package/dist/internal/s2c-partitioned-dispatcher.js.map +0 -1
package/dist/ws-client.test.js
CHANGED
|
@@ -20,12 +20,44 @@ import { it as itEffect } from "@effect/vitest";
|
|
|
20
20
|
import { Cause, Duration, Effect, Exit, Fiber, Option, Scope, TestClock, } from "effect";
|
|
21
21
|
import * as NodeSocketServer from "@effect/platform-node/NodeSocketServer";
|
|
22
22
|
import * as Socket from "@effect/platform/Socket";
|
|
23
|
-
import { PROTOCOL_VERSION } from "@moltzap/protocol";
|
|
24
23
|
import { MoltZapWsClient, RPC_TIMEOUT_MS, } from "./ws-client.js";
|
|
25
|
-
import { RpcServerError, RpcTimeoutError } from "
|
|
26
|
-
import {
|
|
24
|
+
import { RpcServerError, RpcTimeoutError } from "@moltzap/protocol";
|
|
25
|
+
import { AppsOnClose, AppsOnJoin, Connect, ConversationsList, JSON_RPC_VERSION, MessagesSend, MessageReceivedNotificationDefinition, PROTOCOL_VERSION, agentId, conversationId, jsonRpcStringId, messageId, notificationFrame, requestFrame, responseFrame, validators, } from "@moltzap/protocol";
|
|
26
|
+
import { onCloseParams, onJoinParams, SESSION_A, SESSION_B, } from "./internal/__tests__/app-callback-test-requests.js";
|
|
27
27
|
// ── Test server helpers ────────────────────────────────────────────────
|
|
28
28
|
const LOCALHOST_HOST = "127.0.0.1";
|
|
29
|
+
const TEST_AGENT_ID = agentId("11111111-1111-4111-8111-111111111111");
|
|
30
|
+
const RECONNECT_AGENT_ID = agentId("22222222-2222-4222-8222-222222222222");
|
|
31
|
+
const TEST_MESSAGE_ID = messageId("44444444-4444-4444-8444-444444444444");
|
|
32
|
+
const TEST_CONVERSATION_ID = conversationId("33333333-3333-4333-8333-333333333333");
|
|
33
|
+
const TEST_POLICY = {
|
|
34
|
+
maxMessageBytes: 1_000_000,
|
|
35
|
+
maxPartsPerMessage: 10,
|
|
36
|
+
maxTextLength: 32_768,
|
|
37
|
+
maxGroupParticipants: 100,
|
|
38
|
+
heartbeatIntervalMs: 30_000,
|
|
39
|
+
rateLimits: {
|
|
40
|
+
messagesPerMinute: 60,
|
|
41
|
+
requestsPerMinute: 120,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
const helloOk = (agentId = TEST_AGENT_ID) => ({
|
|
45
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
46
|
+
agentId,
|
|
47
|
+
conversations: [],
|
|
48
|
+
unreadCounts: {},
|
|
49
|
+
policy: TEST_POLICY,
|
|
50
|
+
});
|
|
51
|
+
const TEST_MESSAGE = {
|
|
52
|
+
id: TEST_MESSAGE_ID,
|
|
53
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
54
|
+
senderId: TEST_AGENT_ID,
|
|
55
|
+
parts: [{ type: "text", text: "hello" }],
|
|
56
|
+
createdAt: "2026-05-03T00:00:00.000Z",
|
|
57
|
+
};
|
|
58
|
+
const messageReceivedFrame = () => notificationFrame(MessageReceivedNotificationDefinition, {
|
|
59
|
+
message: TEST_MESSAGE,
|
|
60
|
+
});
|
|
29
61
|
/**
|
|
30
62
|
* Spin up an in-process `@effect/platform` WS server on `127.0.0.1:0`.
|
|
31
63
|
* Caller owns the provided scope; when it closes, the server shuts down.
|
|
@@ -96,7 +128,7 @@ function makeLogger() {
|
|
|
96
128
|
};
|
|
97
129
|
}
|
|
98
130
|
const connectP = (client) => Effect.runPromise(client.connect().pipe(Effect.catchTag("RpcTimeoutError", (err) => Effect.fail(new Error(`RPC timeout: ${err.method}`))), Effect.catchAll((err) => Effect.fail(new Error(err.message)))));
|
|
99
|
-
const sendRpcP = (client,
|
|
131
|
+
const sendRpcP = (client, definition, params) => Effect.runPromise(client.sendRpc(definition, params).pipe(Effect.catchTag("RpcTimeoutError", (err) => Effect.fail(new Error(`RPC timeout: ${err.method}`))), Effect.catchAll((err) => Effect.fail(new Error(err.message)))));
|
|
100
132
|
const closeClient = (client) => client.close();
|
|
101
133
|
/**
|
|
102
134
|
* Start a server whose handler auto-responds to `auth/connect` and forwards
|
|
@@ -104,15 +136,15 @@ const closeClient = (client) => client.close();
|
|
|
104
136
|
* about post-handshake behaviour.
|
|
105
137
|
*/
|
|
106
138
|
const startHandshakingServer = (handler) => startTestServer((conn, raw) => Effect.gen(function* () {
|
|
107
|
-
const
|
|
139
|
+
const parsed = JSON.parse(raw);
|
|
140
|
+
if (!validators.requestFrame(parsed)) {
|
|
141
|
+
return yield* Effect.die("expected JSON-RPC request frame");
|
|
142
|
+
}
|
|
143
|
+
const frame = parsed;
|
|
108
144
|
if (frame.method === Connect.name) {
|
|
109
|
-
yield* conn.send(JSON.stringify({
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
direction: "c2s",
|
|
113
|
-
id: frame.id,
|
|
114
|
-
result: { agentId: "agent-xyz", protocol: PROTOCOL_VERSION },
|
|
115
|
-
}));
|
|
145
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
146
|
+
result: helloOk(),
|
|
147
|
+
})));
|
|
116
148
|
return;
|
|
117
149
|
}
|
|
118
150
|
yield* handler(conn, raw, frame);
|
|
@@ -147,10 +179,10 @@ async function waitFor(pred, { maxMs = 2000 } = {}) {
|
|
|
147
179
|
}
|
|
148
180
|
}
|
|
149
181
|
/**
|
|
150
|
-
* Build a `MoltZapWsClient`. Spec #222 OQ-4 deletion: `
|
|
182
|
+
* Build a `MoltZapWsClient`. Spec #222 OQ-4 deletion: `onNotification` is no
|
|
151
183
|
* longer a constructor option; tests that previously stashed an
|
|
152
|
-
* `
|
|
153
|
-
* post-construction. The helper accepts the same `
|
|
184
|
+
* `onNotification` callback now register a `subscribe({}, …)` subscription
|
|
185
|
+
* post-construction. The helper accepts the same `onNotification` callback as
|
|
154
186
|
* a convenience and wires it through the new subscription registry,
|
|
155
187
|
* keeping migration noise local to the helper.
|
|
156
188
|
*/
|
|
@@ -162,8 +194,8 @@ function makeClient(url, overrides) {
|
|
|
162
194
|
onReconnect: overrides?.onReconnect ?? (() => { }),
|
|
163
195
|
logger: overrides?.logger ?? makeLogger(),
|
|
164
196
|
});
|
|
165
|
-
if (overrides?.
|
|
166
|
-
const cb = overrides.
|
|
197
|
+
if (overrides?.onNotification !== undefined) {
|
|
198
|
+
const cb = overrides.onNotification;
|
|
167
199
|
Effect.runSync(client.subscribe({}, (frame) => Effect.sync(() => cb(frame))));
|
|
168
200
|
}
|
|
169
201
|
return client;
|
|
@@ -220,7 +252,7 @@ describe("§5.1 connect() does not hang on pre-open failure", () => {
|
|
|
220
252
|
const server = yield* startHandshakingServer(() => Effect.void);
|
|
221
253
|
const client = makeClient(server.url);
|
|
222
254
|
const hello = (yield* Effect.promise(() => connectP(client)));
|
|
223
|
-
expect(hello.agentId).toBe(
|
|
255
|
+
expect(hello.agentId).toBe(TEST_AGENT_ID);
|
|
224
256
|
expect(client.helloOk).toEqual(hello);
|
|
225
257
|
yield* closeClient(client);
|
|
226
258
|
}));
|
|
@@ -237,8 +269,8 @@ describe("§5.2 pending RPCs fail on disconnect", () => {
|
|
|
237
269
|
const server = yield* startHandshakingServer(() => Effect.void);
|
|
238
270
|
const client = makeClient(server.url);
|
|
239
271
|
yield* Effect.promise(() => connectP(client));
|
|
240
|
-
const rpcP = sendRpcP(client, MessagesSend
|
|
241
|
-
conversationId:
|
|
272
|
+
const rpcP = sendRpcP(client, MessagesSend, {
|
|
273
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
242
274
|
parts: [{ type: "text", text: "hi" }],
|
|
243
275
|
});
|
|
244
276
|
// Wait for the RPC frame to land on the server.
|
|
@@ -275,8 +307,8 @@ describe("§5.3 sendRpc does NOT retry on timeout (TestClock)", () => {
|
|
|
275
307
|
yield* Effect.promise(() => connectP(client));
|
|
276
308
|
const serverConn = server.connections[0];
|
|
277
309
|
const beforeCount = serverConn.received.length;
|
|
278
|
-
const rpcFiber = yield* Effect.fork(client.sendRpc(MessagesSend
|
|
279
|
-
conversationId:
|
|
310
|
+
const rpcFiber = yield* Effect.fork(client.sendRpc(MessagesSend, {
|
|
311
|
+
conversationId: TEST_CONVERSATION_ID,
|
|
280
312
|
parts: [{ type: "text", text: "payload" }],
|
|
281
313
|
}));
|
|
282
314
|
// Wait for the frame to land on the server using real-time polling.
|
|
@@ -342,16 +374,16 @@ describe("reconnect backoff", () => {
|
|
|
342
374
|
let authResponsesSent = 0;
|
|
343
375
|
let reconnectHello = null;
|
|
344
376
|
const server = yield* startTestServer((conn, raw) => Effect.gen(function* () {
|
|
345
|
-
const
|
|
377
|
+
const parsed = JSON.parse(raw);
|
|
378
|
+
if (!validators.requestFrame(parsed)) {
|
|
379
|
+
return yield* Effect.die("expected JSON-RPC request frame");
|
|
380
|
+
}
|
|
381
|
+
const frame = parsed;
|
|
346
382
|
if (frame.method === Connect.name) {
|
|
347
383
|
authResponsesSent++;
|
|
348
|
-
yield* conn.send(JSON.stringify({
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
direction: "c2s",
|
|
352
|
-
id: frame.id,
|
|
353
|
-
result: { agentId: "agent-1" },
|
|
354
|
-
}));
|
|
384
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
385
|
+
result: helloOk(RECONNECT_AGENT_ID),
|
|
386
|
+
})));
|
|
355
387
|
}
|
|
356
388
|
}));
|
|
357
389
|
const client = makeClient(server.url, {
|
|
@@ -370,7 +402,7 @@ describe("reconnect backoff", () => {
|
|
|
370
402
|
expect(server.connections.length).toBeGreaterThanOrEqual(2);
|
|
371
403
|
expect(authResponsesSent).toBeGreaterThanOrEqual(2);
|
|
372
404
|
yield* Effect.promise(() => waitFor(() => reconnectHello !== null, { maxMs: 500 }));
|
|
373
|
-
expect(reconnectHello.agentId).toBe(
|
|
405
|
+
expect(reconnectHello.agentId).toBe(RECONNECT_AGENT_ID);
|
|
374
406
|
yield* closeClient(client);
|
|
375
407
|
}));
|
|
376
408
|
});
|
|
@@ -387,101 +419,85 @@ describe("§5.4 malformed frames are logged but do not affect pending RPCs", ()
|
|
|
387
419
|
const server = yield* startHandshakingServer((conn, _raw, frame) => Effect.gen(function* () {
|
|
388
420
|
if (frame.method !== ConversationsList.name)
|
|
389
421
|
return;
|
|
390
|
-
// Inject: non-JSON, then missing-id response, then
|
|
422
|
+
// Inject: non-JSON, then a missing-id response-like frame, then
|
|
423
|
+
// an unknown object shape.
|
|
391
424
|
yield* conn.send("not json at all");
|
|
392
|
-
yield* conn.send(JSON.stringify({
|
|
393
|
-
yield* conn.send(JSON.stringify({
|
|
425
|
+
yield* conn.send(JSON.stringify({ jsonrpc: JSON_RPC_VERSION, result: {} }));
|
|
426
|
+
yield* conn.send(JSON.stringify({ jsonrpc: JSON_RPC_VERSION, id: frame.id }));
|
|
394
427
|
// Real well-formed response.
|
|
395
|
-
yield* conn.send(JSON.stringify({
|
|
396
|
-
jsonrpc: "2.0",
|
|
397
|
-
type: "response",
|
|
398
|
-
direction: "c2s",
|
|
399
|
-
id: frame.id,
|
|
428
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
400
429
|
result: { conversations: [] },
|
|
401
|
-
}));
|
|
430
|
+
})));
|
|
402
431
|
}));
|
|
403
432
|
const client = makeClient(server.url, { logger });
|
|
404
433
|
yield* Effect.promise(() => connectP(client));
|
|
405
|
-
const result = (yield* Effect.promise(() => sendRpcP(client, ConversationsList
|
|
434
|
+
const result = (yield* Effect.promise(() => sendRpcP(client, ConversationsList, {})));
|
|
406
435
|
expect(result.conversations).toEqual([]);
|
|
407
436
|
// Logger saw at least one malformed-frame warning.
|
|
408
437
|
expect(logger.warn).toHaveBeenCalled();
|
|
409
438
|
yield* closeClient(client);
|
|
410
439
|
}));
|
|
411
440
|
});
|
|
412
|
-
it("accepts a padded chunk that contains both
|
|
441
|
+
it("accepts a padded chunk that contains both a notification and the response", async () => {
|
|
413
442
|
await withTestServer(Effect.gen(function* () {
|
|
414
443
|
const logger = makeLogger();
|
|
415
444
|
const events = [];
|
|
416
445
|
const server = yield* startHandshakingServer((conn, _raw, frame) => Effect.gen(function* () {
|
|
417
446
|
if (frame.method !== ConversationsList.name)
|
|
418
447
|
return;
|
|
419
|
-
yield* conn.send(JSON.stringify(
|
|
420
|
-
jsonrpc: "2.0",
|
|
421
|
-
type: "event",
|
|
422
|
-
event: "messages/received",
|
|
423
|
-
data: { message: { id: "m-1", conversationId: "c-1" } },
|
|
424
|
-
}) +
|
|
448
|
+
yield* conn.send(JSON.stringify(messageReceivedFrame()) +
|
|
425
449
|
"\u0000" +
|
|
426
|
-
JSON.stringify({
|
|
427
|
-
jsonrpc: "2.0",
|
|
428
|
-
type: "response",
|
|
429
|
-
direction: "c2s",
|
|
430
|
-
id: frame.id,
|
|
450
|
+
JSON.stringify(responseFrame(frame.id, {
|
|
431
451
|
result: { conversations: [] },
|
|
432
|
-
}));
|
|
452
|
+
})));
|
|
433
453
|
}));
|
|
434
454
|
const client = makeClient(server.url, {
|
|
435
455
|
logger,
|
|
436
|
-
|
|
456
|
+
onNotification: (event) => events.push(event),
|
|
437
457
|
});
|
|
438
458
|
yield* Effect.promise(() => connectP(client));
|
|
439
|
-
const result = (yield* Effect.promise(() => sendRpcP(client, ConversationsList
|
|
459
|
+
const result = (yield* Effect.promise(() => sendRpcP(client, ConversationsList, {})));
|
|
440
460
|
expect(result.conversations).toEqual([]);
|
|
441
461
|
expect(events).toHaveLength(1);
|
|
442
|
-
expect(events[0]).toMatchObject({
|
|
462
|
+
expect(events[0]).toMatchObject({
|
|
463
|
+
method: MessageReceivedNotificationDefinition.name,
|
|
464
|
+
});
|
|
443
465
|
expect(logger.warn).not.toHaveBeenCalled();
|
|
444
466
|
yield* closeClient(client);
|
|
445
467
|
}));
|
|
446
468
|
});
|
|
447
|
-
it("routes a well-formed
|
|
469
|
+
it("routes a well-formed notification frame to onNotification", async () => {
|
|
448
470
|
await withTestServer(Effect.gen(function* () {
|
|
449
471
|
const events = [];
|
|
450
|
-
const server = yield* startHandshakingServer((conn) => conn.send(JSON.stringify(
|
|
451
|
-
jsonrpc: "2.0",
|
|
452
|
-
type: "event",
|
|
453
|
-
event: "message.received",
|
|
454
|
-
data: { message: { id: "m-1", conversationId: "c-1" } },
|
|
455
|
-
})));
|
|
472
|
+
const server = yield* startHandshakingServer((conn) => conn.send(JSON.stringify(messageReceivedFrame())));
|
|
456
473
|
const client = makeClient(server.url, {
|
|
457
|
-
|
|
474
|
+
onNotification: (e) => events.push(e),
|
|
458
475
|
});
|
|
459
476
|
yield* Effect.promise(() => connectP(client));
|
|
460
|
-
// Fire-and-forget: the server responds with an out-of-band
|
|
477
|
+
// Fire-and-forget: the server responds with an out-of-band notification
|
|
461
478
|
// rather than an RPC response, so the noop Deferred never resolves.
|
|
462
479
|
// Awaiting it would wedge the test for the full RPC_TIMEOUT_MS.
|
|
463
|
-
void sendRpcP(client,
|
|
480
|
+
void sendRpcP(client, ConversationsList, {}).catch(() => { });
|
|
464
481
|
yield* Effect.promise(() => waitFor(() => events.length > 0, { maxMs: 2000 }));
|
|
465
482
|
expect(events[0]).toMatchObject({
|
|
466
|
-
|
|
467
|
-
event: "message.received",
|
|
483
|
+
method: MessageReceivedNotificationDefinition.name,
|
|
468
484
|
});
|
|
469
485
|
yield* closeClient(client);
|
|
470
486
|
}));
|
|
471
487
|
});
|
|
472
|
-
it("does NOT route
|
|
488
|
+
it("does NOT route a notification frame missing the method field", async () => {
|
|
473
489
|
await withTestServer(Effect.gen(function* () {
|
|
474
490
|
const events = [];
|
|
475
491
|
const logger = makeLogger();
|
|
476
|
-
// Send a malformed
|
|
477
|
-
const server = yield* startHandshakingServer((conn) => conn.send(JSON.stringify({
|
|
492
|
+
// Send a malformed notification on the first post-handshake frame.
|
|
493
|
+
const server = yield* startHandshakingServer((conn) => conn.send(JSON.stringify({ jsonrpc: JSON_RPC_VERSION, params: {} })));
|
|
478
494
|
const client = makeClient(server.url, {
|
|
479
|
-
|
|
495
|
+
onNotification: (e) => events.push(e),
|
|
480
496
|
logger,
|
|
481
497
|
});
|
|
482
498
|
yield* Effect.promise(() => connectP(client));
|
|
483
499
|
// Fire-and-forget: see the well-formed-event test above for rationale.
|
|
484
|
-
void sendRpcP(client,
|
|
500
|
+
void sendRpcP(client, ConversationsList, {}).catch(() => { });
|
|
485
501
|
yield* Effect.promise(() => new Promise((r) => setTimeout(r, 50)));
|
|
486
502
|
expect(events).toHaveLength(0);
|
|
487
503
|
expect(logger.warn).toHaveBeenCalled();
|
|
@@ -508,7 +524,7 @@ describe("malformed-frame log cadence (MALFORMED_LOG_EVERY)", () => {
|
|
|
508
524
|
logger.warn.mockClear();
|
|
509
525
|
// Fire-and-forget: server responds with 101 malformed frames, no
|
|
510
526
|
// actual RPC response, so awaiting the noop would wedge the test.
|
|
511
|
-
void sendRpcP(client,
|
|
527
|
+
void sendRpcP(client, ConversationsList, {}).catch(() => { });
|
|
512
528
|
// Wait for the malformed frames to flush through the reader fiber.
|
|
513
529
|
yield* Effect.promise(() => new Promise((r) => setTimeout(r, 300)));
|
|
514
530
|
expect(logger.warn).toHaveBeenCalledTimes(3);
|
|
@@ -530,7 +546,7 @@ describe("close() interleaved with a pending RPC", () => {
|
|
|
530
546
|
const server = yield* startHandshakingServer(() => Effect.void);
|
|
531
547
|
const client = makeClient(server.url);
|
|
532
548
|
yield* Effect.promise(() => connectP(client));
|
|
533
|
-
const rpcP = sendRpcP(client, ConversationsList
|
|
549
|
+
const rpcP = sendRpcP(client, ConversationsList, {});
|
|
534
550
|
yield* Effect.promise(() => waitFor(() => server.connections[0].received.length >= 2));
|
|
535
551
|
const beforeMs = Date.now();
|
|
536
552
|
yield* closeClient(client);
|
|
@@ -550,7 +566,7 @@ describe("socket error after connect", () => {
|
|
|
550
566
|
const server = yield* startHandshakingServer((conn) => conn.close(1011));
|
|
551
567
|
const client = makeClient(server.url, { logger });
|
|
552
568
|
yield* Effect.promise(() => connectP(client));
|
|
553
|
-
const rpcP = sendRpcP(client, ConversationsList
|
|
569
|
+
const rpcP = sendRpcP(client, ConversationsList, {});
|
|
554
570
|
yield* Effect.promise(() => expect(rpcP).rejects.toThrow(/WebSocket not connected/));
|
|
555
571
|
// Logger captured the WebSocket error (warn level).
|
|
556
572
|
expect(logger.warn).toHaveBeenCalled();
|
|
@@ -565,40 +581,19 @@ describe("sendRpc(RpcDefinition, params) — typed manifest overload", () => {
|
|
|
565
581
|
it("uses definition.name as the wire-level method string", async () => {
|
|
566
582
|
await withTestServer(Effect.gen(function* () {
|
|
567
583
|
const { AgentsLookupByName } = yield* Effect.promise(() => import("@moltzap/protocol"));
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
result: {
|
|
576
|
-
echoedMethod: frame.method,
|
|
577
|
-
echoedParams: frame.params,
|
|
578
|
-
agents: [],
|
|
579
|
-
},
|
|
580
|
-
})));
|
|
581
|
-
const client = makeClient(server.url);
|
|
582
|
-
yield* Effect.promise(() => connectP(client));
|
|
583
|
-
const result = (yield* Effect.promise(() => Effect.runPromise(client.sendRpc(AgentsLookupByName, { names: ["alice"] }))));
|
|
584
|
-
expect(result.echoedMethod).toBe(AgentsLookupByName.name);
|
|
585
|
-
expect(result.echoedParams).toEqual({ names: ["alice"] });
|
|
586
|
-
yield* closeClient(client);
|
|
587
|
-
}));
|
|
588
|
-
});
|
|
589
|
-
it("preserves the legacy string overload for back-compat", async () => {
|
|
590
|
-
await withTestServer(Effect.gen(function* () {
|
|
591
|
-
const server = yield* startHandshakingServer((conn, _raw, frame) => conn.send(JSON.stringify({
|
|
592
|
-
jsonrpc: "2.0",
|
|
593
|
-
type: "response",
|
|
594
|
-
direction: "c2s",
|
|
595
|
-
id: frame.id,
|
|
596
|
-
result: { echoedMethod: frame.method, agents: [] },
|
|
597
|
-
})));
|
|
584
|
+
const captured = { current: null };
|
|
585
|
+
const server = yield* startHandshakingServer((conn, _raw, frame) => Effect.gen(function* () {
|
|
586
|
+
captured.current = frame;
|
|
587
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
588
|
+
result: { agents: [] },
|
|
589
|
+
})));
|
|
590
|
+
}));
|
|
598
591
|
const client = makeClient(server.url);
|
|
599
592
|
yield* Effect.promise(() => connectP(client));
|
|
600
|
-
const result =
|
|
601
|
-
expect(result.
|
|
593
|
+
const result = yield* Effect.promise(() => Effect.runPromise(client.sendRpc(AgentsLookupByName, { names: ["alice"] })));
|
|
594
|
+
expect(result.agents).toEqual([]);
|
|
595
|
+
expect(captured.current?.method).toBe(AgentsLookupByName.name);
|
|
596
|
+
expect(captured.current?.params).toEqual({ names: ["alice"] });
|
|
602
597
|
yield* closeClient(client);
|
|
603
598
|
}));
|
|
604
599
|
});
|
|
@@ -697,55 +692,49 @@ describe("spec #222 §5.4 — onDisconnect close metadata (V7)", () => {
|
|
|
697
692
|
});
|
|
698
693
|
// ─────────────────────────────────────────────────────────────────────
|
|
699
694
|
// Phase 1.0 (B.1) gating tests — client-side server-initiated RPC
|
|
700
|
-
// (handleServerRpc + dispatcher fiber +
|
|
695
|
+
// (handleServerRpc + dispatcher fiber + appCallback response write-back)
|
|
701
696
|
// ─────────────────────────────────────────────────────────────────────
|
|
702
697
|
describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
703
|
-
it("dispatches an inbound
|
|
698
|
+
it("dispatches an inbound appCallback request to the registered handler and writes the response back", async () => {
|
|
704
699
|
await withTestServer(Effect.gen(function* () {
|
|
705
700
|
// Server: auto-handshake; immediately AFTER replying to
|
|
706
|
-
// auth/connect, send an
|
|
701
|
+
// auth/connect, send an appCallback request to the client; capture every
|
|
707
702
|
// subsequent inbound frame the client writes back. We're testing
|
|
708
703
|
// the client's dispatcher fiber + handler registry + response
|
|
709
704
|
// encoding.
|
|
710
705
|
const server = yield* startTestServer((conn, raw) => Effect.gen(function* () {
|
|
711
|
-
const
|
|
706
|
+
const parsed = JSON.parse(raw);
|
|
707
|
+
if (!validators.requestFrame(parsed)) {
|
|
708
|
+
return yield* Effect.die("expected JSON-RPC request frame");
|
|
709
|
+
}
|
|
710
|
+
const frame = parsed;
|
|
712
711
|
if (frame.method === Connect.name) {
|
|
713
|
-
yield* conn.send(JSON.stringify({
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
id: frame.id,
|
|
718
|
-
result: { agentId: "agent-xyz", protocol: PROTOCOL_VERSION },
|
|
719
|
-
}));
|
|
720
|
-
// Fire the s2c request straight after auth response. The
|
|
712
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
713
|
+
result: helloOk(),
|
|
714
|
+
})));
|
|
715
|
+
// Fire the appCallback request straight after auth response. The
|
|
721
716
|
// client's dispatcher fiber was forked into the connect
|
|
722
717
|
// scope BEFORE auth/connect was sent, so the inbound queue
|
|
723
718
|
// is live the moment we land here.
|
|
724
|
-
yield* conn.send(JSON.stringify(
|
|
725
|
-
jsonrpc: "2.0",
|
|
726
|
-
type: "request",
|
|
727
|
-
direction: "s2c",
|
|
728
|
-
id: "srv-test-1",
|
|
729
|
-
method: AppsOnJoin.name,
|
|
730
|
-
params: { sessionId: "sess-A" },
|
|
731
|
-
}));
|
|
719
|
+
yield* conn.send(JSON.stringify(requestFrame(jsonRpcStringId("srv-test-1"), AppsOnJoin, onJoinParams(SESSION_A))));
|
|
732
720
|
}
|
|
733
721
|
}));
|
|
734
722
|
const client = makeClient(server.url);
|
|
735
723
|
// Register a handler BEFORE connect so the dispatcher fiber sees
|
|
736
|
-
// it on the very first inbound
|
|
737
|
-
yield* client.handleServerRpc(AppsOnJoin
|
|
724
|
+
// it on the very first inbound appCallback request.
|
|
725
|
+
yield* client.handleServerRpc(AppsOnJoin, (params) => Effect.succeed({
|
|
738
726
|
ack: true,
|
|
739
727
|
saw: params.sessionId,
|
|
740
728
|
}));
|
|
741
729
|
yield* Effect.promise(() => connectP(client));
|
|
742
730
|
// Wait for the response frame the dispatcher writes back. The
|
|
743
|
-
// server records every inbound frame in `received` — the
|
|
731
|
+
// server records every inbound frame in `received` — the appCallback
|
|
744
732
|
// response should appear after the client's auth/connect.
|
|
745
733
|
const responseRaw = yield* Effect.promise(() => waitFor(() => server.connections[0].received.some((r) => {
|
|
746
734
|
try {
|
|
747
735
|
const parsed = JSON.parse(r);
|
|
748
|
-
return (parsed
|
|
736
|
+
return (validators.responseFrame(parsed) &&
|
|
737
|
+
parsed.id === "srv-test-1");
|
|
749
738
|
}
|
|
750
739
|
catch {
|
|
751
740
|
return false;
|
|
@@ -753,51 +742,44 @@ describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
|
753
742
|
}), { maxMs: 2000 })).pipe(Effect.flatMap(() => Effect.sync(() => server.connections[0].received.find((r) => {
|
|
754
743
|
try {
|
|
755
744
|
const parsed = JSON.parse(r);
|
|
756
|
-
return parsed
|
|
745
|
+
return (validators.responseFrame(parsed) &&
|
|
746
|
+
parsed.id === "srv-test-1");
|
|
757
747
|
}
|
|
758
748
|
catch {
|
|
759
749
|
return false;
|
|
760
750
|
}
|
|
761
751
|
}))));
|
|
762
752
|
const parsedResponse = JSON.parse(responseRaw);
|
|
763
|
-
expect(parsedResponse
|
|
764
|
-
|
|
753
|
+
expect(validators.responseFrame(parsedResponse)).toBe(true);
|
|
754
|
+
if (!validators.responseFrame(parsedResponse))
|
|
755
|
+
return;
|
|
765
756
|
expect(parsedResponse.id).toBe("srv-test-1");
|
|
766
|
-
expect(parsedResponse
|
|
767
|
-
|
|
757
|
+
expect("result" in parsedResponse).toBe(true);
|
|
758
|
+
if (!("result" in parsedResponse))
|
|
759
|
+
return;
|
|
760
|
+
const result = parsedResponse.result;
|
|
761
|
+
expect(result.ack).toBe(true);
|
|
762
|
+
expect(result.saw).toBe(SESSION_A);
|
|
768
763
|
yield* closeClient(client);
|
|
769
764
|
}));
|
|
770
765
|
});
|
|
771
766
|
it("encodes a typed RpcServerError from the handler as a `response` frame with `error`", async () => {
|
|
772
767
|
await withTestServer(Effect.gen(function* () {
|
|
773
768
|
const server = yield* startTestServer((conn, raw) => Effect.gen(function* () {
|
|
774
|
-
const
|
|
769
|
+
const parsed = JSON.parse(raw);
|
|
770
|
+
if (!validators.requestFrame(parsed)) {
|
|
771
|
+
return yield* Effect.die("expected JSON-RPC request frame");
|
|
772
|
+
}
|
|
773
|
+
const frame = parsed;
|
|
775
774
|
if (frame.method === Connect.name) {
|
|
776
|
-
yield* conn.send(JSON.stringify({
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
id: frame.id,
|
|
781
|
-
result: { agentId: "agent-xyz", protocol: PROTOCOL_VERSION },
|
|
782
|
-
}));
|
|
783
|
-
yield* conn.send(JSON.stringify({
|
|
784
|
-
jsonrpc: "2.0",
|
|
785
|
-
type: "request",
|
|
786
|
-
direction: "s2c",
|
|
787
|
-
id: "srv-err-1",
|
|
788
|
-
method: AppsOnClose.name,
|
|
789
|
-
// Spec #356: the partitioned dispatcher
|
|
790
|
-
// narrow-validates routing fields; sessionId is
|
|
791
|
-
// required for every s2c hook. Synthetic test
|
|
792
|
-
// payloads must include it or the dispatcher
|
|
793
|
-
// rejects the offer with a `-32602` wire error
|
|
794
|
-
// before reaching the handler.
|
|
795
|
-
params: { sessionId: "sess-err-1" },
|
|
796
|
-
}));
|
|
775
|
+
yield* conn.send(JSON.stringify(responseFrame(frame.id, {
|
|
776
|
+
result: helloOk(),
|
|
777
|
+
})));
|
|
778
|
+
yield* conn.send(JSON.stringify(requestFrame(jsonRpcStringId("srv-err-1"), AppsOnClose, onCloseParams(SESSION_B))));
|
|
797
779
|
}
|
|
798
780
|
}));
|
|
799
781
|
const client = makeClient(server.url);
|
|
800
|
-
yield* client.handleServerRpc(AppsOnClose
|
|
782
|
+
yield* client.handleServerRpc(AppsOnClose, () => Effect.fail(new RpcServerError({
|
|
801
783
|
code: -32099,
|
|
802
784
|
message: "domain-rejected",
|
|
803
785
|
data: { reason: "test" },
|
|
@@ -806,7 +788,9 @@ describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
|
806
788
|
yield* Effect.promise(() => waitFor(() => server.connections[0].received.some((r) => {
|
|
807
789
|
try {
|
|
808
790
|
const parsed = JSON.parse(r);
|
|
809
|
-
return (parsed
|
|
791
|
+
return (validators.responseFrame(parsed) &&
|
|
792
|
+
parsed.id === "srv-err-1" &&
|
|
793
|
+
"error" in parsed);
|
|
810
794
|
}
|
|
811
795
|
catch {
|
|
812
796
|
return false;
|
|
@@ -814,14 +798,20 @@ describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
|
814
798
|
}), { maxMs: 2000 }));
|
|
815
799
|
const found = server.connections[0].received.find((r) => {
|
|
816
800
|
try {
|
|
817
|
-
|
|
801
|
+
const parsed = JSON.parse(r);
|
|
802
|
+
return (validators.responseFrame(parsed) && parsed.id === "srv-err-1");
|
|
818
803
|
}
|
|
819
804
|
catch {
|
|
820
805
|
return false;
|
|
821
806
|
}
|
|
822
807
|
});
|
|
823
808
|
const parsed = JSON.parse(found);
|
|
824
|
-
expect(parsed
|
|
809
|
+
expect(validators.responseFrame(parsed)).toBe(true);
|
|
810
|
+
if (!validators.responseFrame(parsed))
|
|
811
|
+
return;
|
|
812
|
+
expect("error" in parsed).toBe(true);
|
|
813
|
+
if (!("error" in parsed))
|
|
814
|
+
return;
|
|
825
815
|
expect(parsed.error.code).toBe(-32099);
|
|
826
816
|
expect(parsed.error.message).toBe("domain-rejected");
|
|
827
817
|
expect(parsed.error.data.reason).toBe("test");
|
|
@@ -833,9 +823,9 @@ describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
|
833
823
|
serverUrl: "http://127.0.0.1:1",
|
|
834
824
|
agentKey: "test",
|
|
835
825
|
});
|
|
836
|
-
const first = await Effect.runPromiseExit(client.handleServerRpc(AppsOnJoin
|
|
826
|
+
const first = await Effect.runPromiseExit(client.handleServerRpc(AppsOnJoin, () => Effect.succeed({})));
|
|
837
827
|
expect(Exit.isSuccess(first)).toBe(true);
|
|
838
|
-
const second = await Effect.runPromiseExit(client.handleServerRpc(AppsOnJoin
|
|
828
|
+
const second = await Effect.runPromiseExit(client.handleServerRpc(AppsOnJoin, () => Effect.succeed({})));
|
|
839
829
|
expect(Exit.isFailure(second)).toBe(true);
|
|
840
830
|
if (Exit.isFailure(second)) {
|
|
841
831
|
const err = Cause.failureOption(second.cause);
|
|
@@ -850,7 +840,7 @@ describe("Phase 1.0 (B.1) — handleServerRpc round-trip", () => {
|
|
|
850
840
|
// ─────────────────────────────────────────────────────────────────────
|
|
851
841
|
// Regression gate (review-295): runSync(client.close()) and
|
|
852
842
|
// runSync(client.disconnect()) must not throw AsyncFiberException after
|
|
853
|
-
// the
|
|
843
|
+
// the appCallback queue + dispatcher were added. The appCallback machinery lives off the
|
|
854
844
|
// per-connect Scope (allocated inline + forked via runtime.runFork) so
|
|
855
845
|
// `Scope.close` stays sync; teardown of the queue and dispatcher fiber
|
|
856
846
|
// is dispatched via `runFork` from close()/disconnectSync().
|