mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +02:00
Use Effect collections for RPC protocol clients
This commit is contained in:
parent
5a945af345
commit
935ded616c
3 changed files with 61 additions and 16 deletions
|
|
@ -2099,6 +2099,29 @@ Notes:
|
||||||
- `cd ts && bun run lint`
|
- `cd ts && bun run lint`
|
||||||
- `git diff --check`
|
- `git diff --check`
|
||||||
|
|
||||||
|
### 2026-06-04: Gateway RPC Protocol Mutable Collections Slice
|
||||||
|
|
||||||
|
- Status: migrated and package-verified.
|
||||||
|
- Completed:
|
||||||
|
- `ts/packages/flow/src/gateway/rpc-protocol.ts` now tracks active socket
|
||||||
|
clients with `MutableHashMap` and `MutableHashSet` instead of native
|
||||||
|
`Map` / `Set` closure state.
|
||||||
|
- The protocol still returns a native `ReadonlySet<number>` snapshot at the
|
||||||
|
`RpcServer.Protocol.clientIds` API boundary because that is the Effect RPC
|
||||||
|
protocol contract.
|
||||||
|
- `ts/packages/flow/src/__tests__/gateway-rpc-protocol.test.ts` now covers
|
||||||
|
server response sends through the registered client and the public
|
||||||
|
`clientIds` snapshot.
|
||||||
|
- The focused scan for native map/set state in `gateway/rpc-protocol.ts` is
|
||||||
|
clean.
|
||||||
|
- Verification:
|
||||||
|
- `cd ts/packages/flow && bunx --bun vitest run src/__tests__/gateway-rpc-protocol.test.ts`
|
||||||
|
- `cd ts && bun run check:tsgo`
|
||||||
|
- `cd ts && bun run build`
|
||||||
|
- `cd ts && bun run test`
|
||||||
|
- `cd ts && bun run lint`
|
||||||
|
- `git diff --check`
|
||||||
|
|
||||||
## Subagent Findings To Preserve
|
## Subagent Findings To Preserve
|
||||||
|
|
||||||
- MCP/workbench:
|
- MCP/workbench:
|
||||||
|
|
@ -2260,10 +2283,10 @@ Notes:
|
||||||
- Remaining real helper-normalization targets from the fresh sweep are
|
- Remaining real helper-normalization targets from the fresh sweep are
|
||||||
retrieval/document-rag, retrieval/graph-rag, embeddings/ollama, base
|
retrieval/document-rag, retrieval/graph-rag, embeddings/ollama, base
|
||||||
processor flow helpers, and one workbench atom helper.
|
processor flow helpers, and one workbench atom helper.
|
||||||
- Remaining real long-lived native collection targets include
|
- Remaining real long-lived native collection targets include base processor
|
||||||
`gateway/rpc-protocol.ts`, base processor registries, Librarian service /
|
registries, Librarian service / collection manager state, prompt template
|
||||||
collection manager state, prompt template cache, and a workbench module
|
cache, and a workbench module cache. Local traversal sets and test fakes
|
||||||
cache. Local traversal sets and test fakes remain no-op boundaries.
|
remain no-op boundaries.
|
||||||
|
|
||||||
## Ranked Findings
|
## Ranked Findings
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ interface ReceivedMessage {
|
||||||
interface ProtocolRunResult {
|
interface ProtocolRunResult {
|
||||||
readonly messages: ReadonlyArray<ReceivedMessage>;
|
readonly messages: ReadonlyArray<ReceivedMessage>;
|
||||||
readonly writes: ReadonlyArray<string | Uint8Array | CloseEvent>;
|
readonly writes: ReadonlyArray<string | Uint8Array | CloseEvent>;
|
||||||
|
readonly clientIds: ReadonlyArray<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonFrame = (value: unknown): string => `${JSON.stringify(value)}\n`;
|
const jsonFrame = (value: unknown): string => `${JSON.stringify(value)}\n`;
|
||||||
|
|
@ -27,6 +28,7 @@ const optionToArray = <A>(value: O.Option<A>): Array<A> =>
|
||||||
const runProtocolFrames = (
|
const runProtocolFrames = (
|
||||||
frames: ReadonlyArray<string | Uint8Array>,
|
frames: ReadonlyArray<string | Uint8Array>,
|
||||||
headers?: ReadonlyArray<[string, string]>,
|
headers?: ReadonlyArray<[string, string]>,
|
||||||
|
sendResponse?: RpcMessage.FromServerEncoded,
|
||||||
): Promise<ProtocolRunResult> =>
|
): Promise<ProtocolRunResult> =>
|
||||||
Effect.runPromise(
|
Effect.runPromise(
|
||||||
Effect.scoped(
|
Effect.scoped(
|
||||||
|
|
@ -56,6 +58,10 @@ const runProtocolFrames = (
|
||||||
|
|
||||||
yield* onSocket(socket, headers);
|
yield* onSocket(socket, headers);
|
||||||
yield* Effect.yieldNow;
|
yield* Effect.yieldNow;
|
||||||
|
const clientIds = yield* protocol.clientIds;
|
||||||
|
if (sendResponse !== undefined) {
|
||||||
|
yield* protocol.send(0, sendResponse);
|
||||||
|
}
|
||||||
|
|
||||||
const first = yield* Queue.poll(received);
|
const first = yield* Queue.poll(received);
|
||||||
const second = yield* Queue.poll(received);
|
const second = yield* Queue.poll(received);
|
||||||
|
|
@ -68,6 +74,7 @@ const runProtocolFrames = (
|
||||||
...optionToArray(third),
|
...optionToArray(third),
|
||||||
],
|
],
|
||||||
writes,
|
writes,
|
||||||
|
clientIds: Array.from(clientIds),
|
||||||
};
|
};
|
||||||
}).pipe(
|
}).pipe(
|
||||||
Effect.provideService(RpcSerialization.RpcSerialization, RpcSerialization.ndjson),
|
Effect.provideService(RpcSerialization.RpcSerialization, RpcSerialization.ndjson),
|
||||||
|
|
@ -145,4 +152,16 @@ describe("gateway RPC socket protocol", () => {
|
||||||
expect(result.writes).toHaveLength(1);
|
expect(result.writes).toHaveLength(1);
|
||||||
expect(String(result.writes[0])).toContain("\"_tag\":\"Defect\"");
|
expect(String(result.writes[0])).toContain("\"_tag\":\"Defect\"");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("sends server responses through the registered client", async () => {
|
||||||
|
const result = await runProtocolFrames(
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
RpcMessage.ResponseDefectEncoded("server-boom"),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.clientIds).toEqual([0]);
|
||||||
|
expect(result.writes).toHaveLength(1);
|
||||||
|
expect(String(result.writes[0])).toContain("server-boom");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
import { Effect, Queue, Scope } from "effect";
|
import { Effect, Queue, Scope } from "effect";
|
||||||
|
import * as MutableHashMap from "effect/MutableHashMap";
|
||||||
|
import * as MutableHashSet from "effect/MutableHashSet";
|
||||||
|
import * as O from "effect/Option";
|
||||||
import * as S from "effect/Schema";
|
import * as S from "effect/Schema";
|
||||||
import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
import * as RpcMessage from "effect/unstable/rpc/RpcMessage";
|
||||||
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
import * as RpcSerialization from "effect/unstable/rpc/RpcSerialization";
|
||||||
|
|
@ -41,10 +44,10 @@ export const makeSocketRpcProtocol = Effect.gen(function* () {
|
||||||
const disconnects = yield* Queue.make<number>();
|
const disconnects = yield* Queue.make<number>();
|
||||||
|
|
||||||
let nextClientId = 0;
|
let nextClientId = 0;
|
||||||
const clients = new Map<number, {
|
const clients = MutableHashMap.empty<number, {
|
||||||
readonly write: (response: RpcMessage.FromServerEncoded) => Effect.Effect<void>;
|
readonly write: (response: RpcMessage.FromServerEncoded) => Effect.Effect<void>;
|
||||||
}>();
|
}>();
|
||||||
const clientIds = new Set<number>();
|
const clientIds = MutableHashSet.empty<number>();
|
||||||
|
|
||||||
let writeRequest!: (
|
let writeRequest!: (
|
||||||
clientId: number,
|
clientId: number,
|
||||||
|
|
@ -60,8 +63,8 @@ export const makeSocketRpcProtocol = Effect.gen(function* () {
|
||||||
const clientId = nextClientId++;
|
const clientId = nextClientId++;
|
||||||
|
|
||||||
yield* Scope.addFinalizerExit(scope, () => {
|
yield* Scope.addFinalizerExit(scope, () => {
|
||||||
clients.delete(clientId);
|
MutableHashMap.remove(clients, clientId);
|
||||||
clientIds.delete(clientId);
|
MutableHashSet.remove(clientIds, clientId);
|
||||||
return Queue.offer(disconnects, clientId);
|
return Queue.offer(disconnects, clientId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -80,8 +83,8 @@ export const makeSocketRpcProtocol = Effect.gen(function* () {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
clients.set(clientId, { write });
|
MutableHashMap.set(clients, clientId, { write });
|
||||||
clientIds.add(clientId);
|
MutableHashSet.add(clientIds, clientId);
|
||||||
|
|
||||||
yield* socket.runRaw((data) =>
|
yield* socket.runRaw((data) =>
|
||||||
Effect.try({
|
Effect.try({
|
||||||
|
|
@ -126,13 +129,13 @@ export const makeSocketRpcProtocol = Effect.gen(function* () {
|
||||||
writeRequest = writeRequest_;
|
writeRequest = writeRequest_;
|
||||||
return Effect.succeed({
|
return Effect.succeed({
|
||||||
disconnects,
|
disconnects,
|
||||||
send: (clientId, response) => {
|
send: (clientId, response) =>
|
||||||
const client = clients.get(clientId);
|
O.match(MutableHashMap.get(clients, clientId), {
|
||||||
if (client === undefined) return Effect.void;
|
onNone: () => Effect.void,
|
||||||
return Effect.orDie(client.write(response));
|
onSome: (client) => Effect.orDie(client.write(response)),
|
||||||
},
|
}),
|
||||||
end: () => Effect.void,
|
end: () => Effect.void,
|
||||||
clientIds: Effect.sync(() => clientIds),
|
clientIds: Effect.sync(() => new Set(clientIds)),
|
||||||
initialMessage: Effect.succeedNone,
|
initialMessage: Effect.succeedNone,
|
||||||
supportsAck: true,
|
supportsAck: true,
|
||||||
supportsTransferables: false,
|
supportsTransferables: false,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue