mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29: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`
|
||||||
- `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
|
### 2026-06-02: RAG And Agent Requestor Bridge Slice
|
||||||
|
|
||||||
- Status: migrated, root-verified, committed, and pushed.
|
- Status: migrated, root-verified, committed, and pushed.
|
||||||
|
|
@ -1792,8 +1811,8 @@ Notes:
|
||||||
- FlowManager `() => Effect.gen(...)` factories are normalized to
|
- FlowManager `() => Effect.gen(...)` factories are normalized to
|
||||||
`Effect.fn` / `Effect.fnUntraced`. Sibling service factories still need a
|
`Effect.fn` / `Effect.fnUntraced`. Sibling service factories still need a
|
||||||
focused scan before treating them as valid migration targets.
|
focused scan before treating them as valid migration targets.
|
||||||
- KnowledgeCore operation dispatch now uses `effect/Match` with
|
- ConfigService and KnowledgeCore operation dispatch now use `effect/Match`
|
||||||
`Match.exhaustive`; remaining service operation switches are in config and
|
with `Match.exhaustive`; remaining service operation switches are in
|
||||||
librarian surfaces.
|
librarian surfaces.
|
||||||
- Long-lived `Map` / `Set` state in ref-backed services can move toward
|
- Long-lived `Map` / `Set` state in ref-backed services can move toward
|
||||||
Effect collections later; local pure traversal maps/sets remain no-ops.
|
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 () => {
|
it("pushes config from the stored producer handle", async () => {
|
||||||
const backend = new NoopPubSub();
|
const backend = new NoopPubSub();
|
||||||
const service = makeConfigService({
|
const service = makeConfigService({
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {NodeRuntime} from "@effect/platform-node";
|
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 Predicate from "effect/Predicate";
|
||||||
import * as S from "effect/Schema";
|
import * as S from "effect/Schema";
|
||||||
import {
|
import {
|
||||||
|
|
@ -109,9 +109,9 @@ export interface ConfigService extends AsyncProcessorRuntime<ConfigServiceError>
|
||||||
readonly pushConfig: () => Promise<void>;
|
readonly pushConfig: () => Promise<void>;
|
||||||
readonly pushConfigEffect: Effect.Effect<void, ConfigServiceError>;
|
readonly pushConfigEffect: Effect.Effect<void, ConfigServiceError>;
|
||||||
readonly persist: () => Promise<void>;
|
readonly persist: () => Promise<void>;
|
||||||
readonly persistEffect: Effect.Effect<void, never>;
|
readonly persistEffect: Effect.Effect<void>;
|
||||||
readonly loadFromDisk: () => Promise<void>;
|
readonly loadFromDisk: () => Promise<void>;
|
||||||
readonly loadFromDiskEffect: Effect.Effect<void, never>;
|
readonly loadFromDiskEffect: Effect.Effect<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState = (): ConfigServiceState => ({
|
const initialState = (): ConfigServiceState => ({
|
||||||
|
|
@ -340,7 +340,7 @@ const updateHandles = (
|
||||||
const persistStateEffect = (
|
const persistStateEffect = (
|
||||||
persistPath: string | null,
|
persistPath: string | null,
|
||||||
state: ConfigServiceState,
|
state: ConfigServiceState,
|
||||||
): Effect.Effect<void, never> =>
|
): Effect.Effect<void> =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
if (persistPath === null) return;
|
if (persistPath === null) return;
|
||||||
const payload = {
|
const payload = {
|
||||||
|
|
@ -383,7 +383,7 @@ const pushConfigWithStateEffect = (
|
||||||
|
|
||||||
const readPersistedConfigEffect = (
|
const readPersistedConfigEffect = (
|
||||||
persistPath: string,
|
persistPath: string,
|
||||||
): Effect.Effect<PersistedConfig | null, never> =>
|
): Effect.Effect<PersistedConfig | null> =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const raw = yield* Effect.tryPromise({
|
const raw = yield* Effect.tryPromise({
|
||||||
try: () => readTextFile(persistPath),
|
try: () => readTextFile(persistPath),
|
||||||
|
|
@ -778,26 +778,24 @@ export function makeConfigService(config: ConfigServiceConfig): ConfigService {
|
||||||
const baseStop = base.stop;
|
const baseStop = base.stop;
|
||||||
const persistPath = config.persistPath ?? null;
|
const persistPath = config.persistPath ?? null;
|
||||||
|
|
||||||
const handleOperationEffect = (request: ConfigRequest) => {
|
const handleOperationEffect = Effect.fn("ConfigService.handleOperation")(function* (
|
||||||
|
request: ConfigRequest,
|
||||||
|
) {
|
||||||
const op: ConfigOperation = request.operation;
|
const op: ConfigOperation = request.operation;
|
||||||
|
|
||||||
switch (op) {
|
return yield* Match.value(op).pipe(
|
||||||
case "get":
|
Match.when("get", () => Effect.succeed(handleGetWithState(stateSnapshot(state), request))),
|
||||||
return Effect.succeed(handleGetWithState(stateSnapshot(state), request));
|
Match.when("put", () => handlePutEffect(state, persistPath, request)),
|
||||||
case "put":
|
Match.when("delete", () => handleDeleteEffect(state, persistPath, request)),
|
||||||
return handlePutEffect(state, persistPath, request);
|
Match.when("list", () => Effect.succeed(handleListWithState(stateSnapshot(state), request))),
|
||||||
case "delete":
|
Match.when("config", () => Effect.succeed(handleConfigDumpWithState(stateSnapshot(state), request))),
|
||||||
return handleDeleteEffect(state, persistPath, request);
|
Match.when("getvalues", () => Effect.succeed(handleGetValuesWithState(stateSnapshot(state), request))),
|
||||||
case "list":
|
Match.when("getvalues-all-ws", () =>
|
||||||
return Effect.succeed(handleListWithState(stateSnapshot(state), request));
|
Effect.succeed(handleGetValuesAllWorkspacesWithState(stateSnapshot(state), request))
|
||||||
case "config":
|
),
|
||||||
return Effect.succeed(handleConfigDumpWithState(stateSnapshot(state), request));
|
Match.exhaustive,
|
||||||
case "getvalues":
|
);
|
||||||
return Effect.succeed(handleGetValuesWithState(stateSnapshot(state), request));
|
});
|
||||||
case "getvalues-all-ws":
|
|
||||||
return Effect.succeed(handleGetValuesAllWorkspacesWithState(stateSnapshot(state), request));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMessageEffect = Effect.fn("handleMessageEffect")(function* (msg: Message<ConfigRequest>) {
|
const handleMessageEffect = Effect.fn("handleMessageEffect")(function* (msg: Message<ConfigRequest>) {
|
||||||
const request = yield* S.decodeUnknownEffect(ConfigRequestSchema)(msg.value()).pipe(
|
const request = yield* S.decodeUnknownEffect(ConfigRequestSchema)(msg.value()).pipe(
|
||||||
|
|
@ -810,17 +808,16 @@ export function makeConfigService(config: ConfigServiceConfig): ConfigService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const sendResponse = (response: ConfigResponse): Effect.Effect<void, ConfigServiceError> =>
|
const sendResponse = Effect.fnUntraced(function* (response: ConfigResponse) {
|
||||||
Effect.gen(function* () {
|
const responseProducer = (yield* SynchronizedRef.get(state)).responseProducer;
|
||||||
const responseProducer = (yield* SynchronizedRef.get(state)).responseProducer;
|
if (responseProducer === null) {
|
||||||
if (responseProducer === null) {
|
return yield* configServiceError("respond", "Config response producer not started");
|
||||||
return yield* configServiceError("respond", "Config response producer not started");
|
}
|
||||||
}
|
yield* Effect.tryPromise({
|
||||||
yield* Effect.tryPromise({
|
try: () => responseProducer.send(response, {id: requestId}),
|
||||||
try: () => responseProducer.send(response, {id: requestId}),
|
catch: (cause) => configServiceError("respond", cause),
|
||||||
catch: (cause) => configServiceError("respond", cause),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
yield* handleOperationEffect(request).pipe(
|
yield* handleOperationEffect(request).pipe(
|
||||||
Effect.flatMap(sendResponse),
|
Effect.flatMap(sendResponse),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue