mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-06-30 17:09:38 +02:00
Use Match for config operations
This commit is contained in:
parent
66e1009671
commit
8d5edfae9a
3 changed files with 103 additions and 35 deletions
|
|
@ -346,6 +346,25 @@ Notes:
|
|||
- `git diff --check`
|
||||
- `git diff --check`
|
||||
|
||||
### 2026-06-02: ConfigService Operation Match Slice
|
||||
|
||||
- Status: migrated and package-verified.
|
||||
- Completed:
|
||||
- `ts/packages/flow/src/config/service.ts` now dispatches
|
||||
`ConfigOperation` with `effect/Match` instead of a native `switch`.
|
||||
- The dispatcher is a named `Effect.fn` and uses `Match.exhaustive` against
|
||||
the schema-derived `ConfigOperation` union.
|
||||
- The per-message response sender now uses `Effect.fnUntraced` instead of an
|
||||
arrow function returning `Effect.gen(...)`.
|
||||
- Config-service tests now cover all seven operations through
|
||||
`handleOperation`, including tagged invalid mutation failures.
|
||||
- Existing explicit `never` annotations on `persistEffect`,
|
||||
`loadFromDiskEffect`, `persistStateEffect`, and
|
||||
`readPersistedConfigEffect` were removed so Effect can infer the channel.
|
||||
- Verification:
|
||||
- `bun run --cwd ts/packages/flow test -- src/__tests__/config-service.test.ts`
|
||||
- `cd ts && bun run check:tsgo`
|
||||
|
||||
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
||||
|
||||
- Status: migrated, root-verified, committed, and pushed.
|
||||
|
|
@ -1792,8 +1811,8 @@ Notes:
|
|||
- FlowManager `() => Effect.gen(...)` factories are normalized to
|
||||
`Effect.fn` / `Effect.fnUntraced`. Sibling service factories still need a
|
||||
focused scan before treating them as valid migration targets.
|
||||
- KnowledgeCore operation dispatch now uses `effect/Match` with
|
||||
`Match.exhaustive`; remaining service operation switches are in config and
|
||||
- ConfigService and KnowledgeCore operation dispatch now use `effect/Match`
|
||||
with `Match.exhaustive`; remaining service operation switches are in
|
||||
librarian surfaces.
|
||||
- Long-lived `Map` / `Set` state in ref-backed services can move toward
|
||||
Effect collections later; local pure traversal maps/sets remain no-ops.
|
||||
|
|
|
|||
|
|
@ -147,6 +147,58 @@ describe("ConfigService operations", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it("dispatches all config operations through the Match-backed handler", async () => {
|
||||
const service = makeService();
|
||||
|
||||
await expect(service.handleOperation({ operation: "put" })).rejects.toMatchObject({
|
||||
_tag: "ConfigServiceError",
|
||||
operation: "put",
|
||||
});
|
||||
await expect(service.handleOperation({ operation: "delete" })).rejects.toMatchObject({
|
||||
_tag: "ConfigServiceError",
|
||||
operation: "delete",
|
||||
});
|
||||
|
||||
await expect(service.handleOperation({
|
||||
operation: "put",
|
||||
values: [{ type: "prompt", key: "system", value: "hello" }],
|
||||
})).resolves.toEqual({ version: 1 });
|
||||
|
||||
await expect(service.handleOperation({
|
||||
operation: "get",
|
||||
keys: ["prompt", "system"],
|
||||
})).resolves.toEqual({
|
||||
version: 1,
|
||||
values: { system: "hello" },
|
||||
});
|
||||
await expect(service.handleOperation({ operation: "list" })).resolves.toEqual({
|
||||
version: 1,
|
||||
directory: ["prompt"],
|
||||
});
|
||||
await expect(service.handleOperation({ operation: "config" })).resolves.toEqual({
|
||||
version: 1,
|
||||
config: { prompt: { system: "hello" } },
|
||||
});
|
||||
await expect(service.handleOperation({
|
||||
operation: "getvalues",
|
||||
type: "prompt",
|
||||
})).resolves.toEqual({
|
||||
version: 1,
|
||||
values: [{ type: "prompt", key: "system", value: "hello" }],
|
||||
});
|
||||
await expect(service.handleOperation({
|
||||
operation: "getvalues-all-ws",
|
||||
type: "prompt",
|
||||
})).resolves.toEqual({
|
||||
version: 1,
|
||||
values: [{ workspace: "default", type: "prompt", key: "system", value: "hello" }],
|
||||
});
|
||||
await expect(service.handleOperation({
|
||||
operation: "delete",
|
||||
keys: ["prompt", "system"],
|
||||
})).resolves.toEqual({ version: 2 });
|
||||
});
|
||||
|
||||
it("pushes config from the stored producer handle", async () => {
|
||||
const backend = new NoopPubSub();
|
||||
const service = makeConfigService({
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import {NodeRuntime} from "@effect/platform-node";
|
||||
import {Duration, Effect, Layer, ManagedRuntime, SynchronizedRef} from "effect";
|
||||
import {Duration, Effect, Layer, ManagedRuntime, Match, SynchronizedRef} from "effect";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import * as S from "effect/Schema";
|
||||
import {
|
||||
|
|
@ -109,9 +109,9 @@ export interface ConfigService extends AsyncProcessorRuntime<ConfigServiceError>
|
|||
readonly pushConfig: () => Promise<void>;
|
||||
readonly pushConfigEffect: Effect.Effect<void, ConfigServiceError>;
|
||||
readonly persist: () => Promise<void>;
|
||||
readonly persistEffect: Effect.Effect<void, never>;
|
||||
readonly persistEffect: Effect.Effect<void>;
|
||||
readonly loadFromDisk: () => Promise<void>;
|
||||
readonly loadFromDiskEffect: Effect.Effect<void, never>;
|
||||
readonly loadFromDiskEffect: Effect.Effect<void>;
|
||||
}
|
||||
|
||||
const initialState = (): ConfigServiceState => ({
|
||||
|
|
@ -340,7 +340,7 @@ const updateHandles = (
|
|||
const persistStateEffect = (
|
||||
persistPath: string | null,
|
||||
state: ConfigServiceState,
|
||||
): Effect.Effect<void, never> =>
|
||||
): Effect.Effect<void> =>
|
||||
Effect.gen(function* () {
|
||||
if (persistPath === null) return;
|
||||
const payload = {
|
||||
|
|
@ -383,7 +383,7 @@ const pushConfigWithStateEffect = (
|
|||
|
||||
const readPersistedConfigEffect = (
|
||||
persistPath: string,
|
||||
): Effect.Effect<PersistedConfig | null, never> =>
|
||||
): Effect.Effect<PersistedConfig | null> =>
|
||||
Effect.gen(function* () {
|
||||
const raw = yield* Effect.tryPromise({
|
||||
try: () => readTextFile(persistPath),
|
||||
|
|
@ -778,26 +778,24 @@ export function makeConfigService(config: ConfigServiceConfig): ConfigService {
|
|||
const baseStop = base.stop;
|
||||
const persistPath = config.persistPath ?? null;
|
||||
|
||||
const handleOperationEffect = (request: ConfigRequest) => {
|
||||
const handleOperationEffect = Effect.fn("ConfigService.handleOperation")(function* (
|
||||
request: ConfigRequest,
|
||||
) {
|
||||
const op: ConfigOperation = request.operation;
|
||||
|
||||
switch (op) {
|
||||
case "get":
|
||||
return Effect.succeed(handleGetWithState(stateSnapshot(state), request));
|
||||
case "put":
|
||||
return handlePutEffect(state, persistPath, request);
|
||||
case "delete":
|
||||
return handleDeleteEffect(state, persistPath, request);
|
||||
case "list":
|
||||
return Effect.succeed(handleListWithState(stateSnapshot(state), request));
|
||||
case "config":
|
||||
return Effect.succeed(handleConfigDumpWithState(stateSnapshot(state), request));
|
||||
case "getvalues":
|
||||
return Effect.succeed(handleGetValuesWithState(stateSnapshot(state), request));
|
||||
case "getvalues-all-ws":
|
||||
return Effect.succeed(handleGetValuesAllWorkspacesWithState(stateSnapshot(state), request));
|
||||
}
|
||||
};
|
||||
return yield* Match.value(op).pipe(
|
||||
Match.when("get", () => Effect.succeed(handleGetWithState(stateSnapshot(state), request))),
|
||||
Match.when("put", () => handlePutEffect(state, persistPath, request)),
|
||||
Match.when("delete", () => handleDeleteEffect(state, persistPath, request)),
|
||||
Match.when("list", () => Effect.succeed(handleListWithState(stateSnapshot(state), request))),
|
||||
Match.when("config", () => Effect.succeed(handleConfigDumpWithState(stateSnapshot(state), request))),
|
||||
Match.when("getvalues", () => Effect.succeed(handleGetValuesWithState(stateSnapshot(state), request))),
|
||||
Match.when("getvalues-all-ws", () =>
|
||||
Effect.succeed(handleGetValuesAllWorkspacesWithState(stateSnapshot(state), request))
|
||||
),
|
||||
Match.exhaustive,
|
||||
);
|
||||
});
|
||||
|
||||
const handleMessageEffect = Effect.fn("handleMessageEffect")(function* (msg: Message<ConfigRequest>) {
|
||||
const request = yield* S.decodeUnknownEffect(ConfigRequestSchema)(msg.value()).pipe(
|
||||
|
|
@ -810,17 +808,16 @@ export function makeConfigService(config: ConfigServiceConfig): ConfigService {
|
|||
return;
|
||||
}
|
||||
|
||||
const sendResponse = (response: ConfigResponse): Effect.Effect<void, ConfigServiceError> =>
|
||||
Effect.gen(function* () {
|
||||
const responseProducer = (yield* SynchronizedRef.get(state)).responseProducer;
|
||||
if (responseProducer === null) {
|
||||
return yield* configServiceError("respond", "Config response producer not started");
|
||||
}
|
||||
yield* Effect.tryPromise({
|
||||
try: () => responseProducer.send(response, {id: requestId}),
|
||||
catch: (cause) => configServiceError("respond", cause),
|
||||
});
|
||||
const sendResponse = Effect.fnUntraced(function* (response: ConfigResponse) {
|
||||
const responseProducer = (yield* SynchronizedRef.get(state)).responseProducer;
|
||||
if (responseProducer === null) {
|
||||
return yield* configServiceError("respond", "Config response producer not started");
|
||||
}
|
||||
yield* Effect.tryPromise({
|
||||
try: () => responseProducer.send(response, {id: requestId}),
|
||||
catch: (cause) => configServiceError("respond", cause),
|
||||
});
|
||||
});
|
||||
|
||||
yield* handleOperationEffect(request).pipe(
|
||||
Effect.flatMap(sendResponse),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue