mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 01:19:38 +02:00
Use Predicate and FileSystem in final Effect cleanup
This commit is contained in:
parent
c40bd406f8
commit
976e7ecfc5
6 changed files with 164 additions and 32 deletions
|
|
@ -2378,6 +2378,49 @@ Notes:
|
|||
- `cd ts && bun run lint`
|
||||
- `git diff --check`
|
||||
|
||||
### 2026-06-04: Predicate Error Message Cleanup Slice
|
||||
|
||||
- Status: migrated and package-verified.
|
||||
- Completed:
|
||||
- `ts/packages/base/src/errors.ts` now narrows unknown message-bearing
|
||||
failures through `effect/Predicate` instead of a structural type
|
||||
assertion.
|
||||
- `ts/packages/base/src/processor/flow-processor.ts` now logs config
|
||||
consumer failures through the shared `errorMessage` helper instead of an
|
||||
`instanceof Error` branch.
|
||||
- `ts/packages/client/src/socket/trustgraph-socket.ts` now uses
|
||||
`effect/Predicate` for legacy callback error-message extraction instead of
|
||||
native `Error` checks or structural assertions.
|
||||
- `ts/packages/workbench/src/atoms/workbench.ts` now uses
|
||||
`effect/Predicate` for workbench promise-boundary error-message
|
||||
extraction.
|
||||
- Verification:
|
||||
- `cd ts && bun run --cwd packages/base test`
|
||||
- `cd ts && bun run --cwd packages/base build`
|
||||
- `cd ts && bun run --cwd packages/client test`
|
||||
- `cd ts && bun run --cwd packages/client build`
|
||||
- `cd ts && bun run --cwd packages/workbench build`
|
||||
- `cd ts && bun run check:tsgo`
|
||||
- `rg -n "instanceof Error|\\bas \\{ message\\?: unknown \\}|\\bas \\{ message\\?:" ts/packages --glob '*.ts' --glob '*.tsx'`
|
||||
|
||||
### 2026-06-04: Flow Persistence FileSystem Slice
|
||||
|
||||
- Status: migrated and package-verified.
|
||||
- Completed:
|
||||
- `ts/packages/flow/src/runtime/effect-files.ts` now provides
|
||||
Effect-native `ensureDirectoryEffect`, `readTextFileEffect`,
|
||||
`readBinaryFileEffect`, `writeTextFileEffect`, `writeBinaryFileEffect`,
|
||||
and `removePathEffect` helpers backed by `effect/FileSystem`.
|
||||
- The existing Promise helpers remain compatibility wrappers over a
|
||||
`ManagedRuntime` provided with `@effect/platform-bun/BunFileSystem.layer`,
|
||||
preserving current config/core/librarian service call sites while removing
|
||||
direct `Bun.$`, `Bun.file`, and `Bun.write` production IO from the helper.
|
||||
- File operation error channels are typed as `effect/PlatformError`.
|
||||
- Verification:
|
||||
- `cd ts && bun run --cwd packages/flow build`
|
||||
- `cd ts && bunx --bun vitest run packages/flow/src/__tests__/config-service.test.ts packages/flow/src/__tests__/knowledge-core-service.test.ts packages/flow/src/__tests__/librarian-service.test.ts`
|
||||
- `rg -n "Bun\\.\\$|Bun\\.file|Bun\\.write" ts/packages/flow/src/runtime/effect-files.ts ts/packages/flow/src --glob '*.ts'`
|
||||
|
||||
## Subagent Findings To Preserve
|
||||
|
||||
- MCP/workbench:
|
||||
|
|
@ -2555,14 +2598,20 @@ Notes:
|
|||
design, not listener bookkeeping.
|
||||
- Long-lived `Map` / `Set` state in ref-backed services is complete for the
|
||||
current scratch inventory; local pure traversal maps/sets remain no-ops.
|
||||
- Fresh strict signal sweep after the 2026-06-04 helper and collection
|
||||
slices found no production normal `Error`, raw `try`/`catch`, native
|
||||
`switch`, or Effect-focused type assertions under `ts/packages`.
|
||||
- Remaining real helper-normalization targets from the fresh sweep are
|
||||
separately scoped inline callback/program factories in messaging
|
||||
compatibility facades, gateway/librarian helpers, and CLI command actions.
|
||||
The workbench random id helper is complete; the remaining workbench
|
||||
`Effect.gen` match is a local one-shot command effect value.
|
||||
- Fresh strict signal sweep after the 2026-06-04 Predicate cleanup found no
|
||||
committed production native `Error` construction/inheritance, raw
|
||||
`try`/`catch`, native `switch`, message-shape assertions, or
|
||||
Effect-focused type assertions under `ts/packages`. Two `instanceof Error`
|
||||
hits remain only in the unrelated local dirty
|
||||
`ts/packages/flow/src/extract/knowledge-extract.ts` file and were left
|
||||
untouched.
|
||||
- Remaining production `Effect.gen` matches are one-shot program values,
|
||||
scoped runtime factories, local command effect values, or public
|
||||
compatibility callbacks; reusable service helpers from the scratch
|
||||
inventory are normalized to `Effect.fn` / `Effect.fnUntraced`.
|
||||
- Flow service persistence helpers now use `effect/FileSystem` with the Bun
|
||||
FileSystem layer. Remaining direct `Bun.file` / `Bun.write` matches in the
|
||||
Flow persistence lane are test fixture setup/assertions, not production IO.
|
||||
- Fresh long-lived native collection targets from the scratch inventory are
|
||||
complete: Librarian service state, base processor registries, the
|
||||
standalone Librarian collection manager, prompt template cache, and
|
||||
|
|
@ -2621,6 +2670,49 @@ Notes:
|
|||
- Treat the legacy consumer facade as a completed compatibility wrapper over
|
||||
`makeEffectConsumerFromPubSub`; do not flag blocking `start()` semantics.
|
||||
|
||||
### No-op: Legacy Promise Facades And One-Shot Program Effects
|
||||
|
||||
- Status:
|
||||
- Closed as active P0/P1/P2 migration evidence after the final 2026-06-04
|
||||
signal pass.
|
||||
- TrustGraph evidence:
|
||||
- `ts/packages/base/src/messaging/consumer.ts`
|
||||
- `ts/packages/base/src/messaging/producer.ts`
|
||||
- `ts/packages/base/src/messaging/request-response.ts`
|
||||
- `ts/packages/base/src/processor/flow-processor.ts`
|
||||
- `ts/packages/flow/src/config/service.ts`
|
||||
- `ts/packages/flow/src/cores/service.ts`
|
||||
- `ts/packages/flow/src/flow-manager/service.ts`
|
||||
- `ts/packages/flow/src/librarian/service.ts`
|
||||
- `ts/packages/flow/src/gateway/server.ts`
|
||||
- `ts/packages/mcp/src/server.ts`
|
||||
- `ts/packages/cli/src/commands/*.ts`
|
||||
- Effect primitives:
|
||||
- `Effect.fn`, `ManagedRuntime`, `NodeRuntime.runMain`, `Scope`,
|
||||
`Layer`, `Effect.tryPromise`, `effect/FileSystem`, Bun/Node FileSystem
|
||||
provider layers, and tagged error constructors.
|
||||
- Evidence:
|
||||
- Internal service handlers and reusable helpers now expose Effect-returning
|
||||
functions or `Effect.fn` helpers; legacy object methods call
|
||||
`Effect.runPromise` only where the existing public interface still
|
||||
requires `Promise` callbacks.
|
||||
- CLI command actions, Fastify route handlers, MCP SDK callbacks, and client
|
||||
callback APIs are host/library boundaries that require Promise-returning
|
||||
callbacks. These boundaries now delegate to Effect programs and map
|
||||
failures into tagged errors or wire-contract errors.
|
||||
- Remaining `Effect.gen` matches are one-shot program/runtime values such as
|
||||
MCP stdio startup, socket RPC protocol construction, agent runtime
|
||||
construction, or local command effect values; they are not reusable helper
|
||||
functions that would benefit from `Effect.fn`.
|
||||
- Flow service file persistence now goes through `effect/FileSystem`; direct
|
||||
Bun file helpers remain only in tests and fixture setup.
|
||||
- Rule:
|
||||
- Do not reopen `Effect.runPromise` matches unless the surrounding public API
|
||||
is being intentionally redesigned to an Effect-first contract.
|
||||
- New internal helpers should continue to be written as `Effect.fn` or
|
||||
direct Effect-returning functions, with Promise compatibility isolated at
|
||||
the host boundary.
|
||||
|
||||
### No-op: Remaining Effect AI Provider Swaps
|
||||
|
||||
- TrustGraph evidence:
|
||||
|
|
@ -2756,11 +2848,18 @@ Notes:
|
|||
|
||||
## Recommended PR Order
|
||||
|
||||
1. MCP tool-call parity tests and legacy stdio flip/removal decision.
|
||||
2. Flow/client RPC stream API design beyond callback/Promise compatibility.
|
||||
3. Long-lived ref-backed `HashMap` state cleanup where clone helpers remain.
|
||||
4. Sibling service `Effect.fn` normalization where arrow-returned generators
|
||||
still appear.
|
||||
No active PR-sized Effect-native migration remains from the current playbook
|
||||
signal set. Future work is intentionally API-design or compatibility-policy
|
||||
work, not a current P0/P1/P2 rewrite blocker:
|
||||
|
||||
1. MCP `tools/call` parity tests before deleting the legacy SDK/Zod stdio
|
||||
compatibility entrypoint.
|
||||
2. Flow/client RPC stream API redesign beyond callback/Promise public
|
||||
compatibility.
|
||||
3. CLI migration from Commander to `effect/unstable/cli` if the public CLI
|
||||
surface is intentionally redesigned.
|
||||
4. Provider-specific Effect AI swaps when installed provider semantics match
|
||||
TrustGraph's existing Chat Completions/local-server behavior.
|
||||
|
||||
## No-Op Rules
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
* Python reference: trustgraph-base/trustgraph/exceptions.py
|
||||
*/
|
||||
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import * as S from "effect/Schema";
|
||||
import type { TgError } from "./schema/index.ts";
|
||||
|
||||
|
|
@ -315,8 +316,8 @@ export function flowParameterDecodeError(
|
|||
}
|
||||
|
||||
export function errorMessage(error: unknown): string {
|
||||
if (typeof error === "object" && error !== null && "message" in error) {
|
||||
const message = (error as { message?: unknown }).message;
|
||||
if (Predicate.isObject(error) && Predicate.hasProperty(error, "message")) {
|
||||
const message = error.message;
|
||||
if (typeof message === "string") return message;
|
||||
}
|
||||
return String(error);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import type { BackendConsumer, PubSubBackend } from "../backend/types.js";
|
|||
import { Flow, type FlowDefinition } from "./flow.js";
|
||||
import { topics } from "../schema/topics.js";
|
||||
import {
|
||||
errorMessage,
|
||||
pubSubError,
|
||||
type FlowRuntimeError,
|
||||
type ProcessorLifecycleError,
|
||||
|
|
@ -282,7 +283,7 @@ export function runFlowProcessorDefinitionScoped<
|
|||
return Effect.void;
|
||||
}
|
||||
return Effect.logError(`[${options.id}] Config consumer error`, {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
error: errorMessage(error),
|
||||
}).pipe(
|
||||
Effect.flatMap(() => Effect.sleep(Duration.millis(1000))),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -113,15 +113,12 @@ function withDefault(value: string | undefined, fallback: string): string {
|
|||
}
|
||||
|
||||
function toErrorMessage(value: unknown, fallback: string): string {
|
||||
if (value instanceof Error) {
|
||||
return value.message;
|
||||
}
|
||||
if (typeof value === "string" && value.length > 0) {
|
||||
if (Predicate.isString(value) && value.length > 0) {
|
||||
return value;
|
||||
}
|
||||
if (value !== null && typeof value === "object" && "message" in value) {
|
||||
const message = (value as { message?: unknown }).message;
|
||||
if (typeof message === "string" && message.length > 0) {
|
||||
if (Predicate.isObject(value) && Predicate.hasProperty(value, "message")) {
|
||||
const message = value.message;
|
||||
if (Predicate.isString(message) && message.length > 0) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
import * as BunFileSystem from "@effect/platform-bun/BunFileSystem";
|
||||
import { Effect, ManagedRuntime } from "effect";
|
||||
import * as FileSystem from "effect/FileSystem";
|
||||
import type { PlatformError } from "effect/PlatformError";
|
||||
|
||||
const fileSystemRuntime = ManagedRuntime.make(BunFileSystem.layer);
|
||||
|
||||
export function joinPath(...segments: string[]): string {
|
||||
const joined = segments
|
||||
.filter((segment) => segment.length > 0)
|
||||
|
|
@ -15,26 +22,52 @@ export function dirnamePath(path: string): string {
|
|||
return normalized.slice(0, index);
|
||||
}
|
||||
|
||||
export const ensureDirectoryEffect = (path: string): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) =>
|
||||
fs.makeDirectory(path, { recursive: true })
|
||||
);
|
||||
|
||||
export function ensureDirectory(path: string): Promise<void> {
|
||||
return Bun.$`mkdir -p ${path}`.quiet().then(() => undefined);
|
||||
return fileSystemRuntime.runPromise(ensureDirectoryEffect(path));
|
||||
}
|
||||
|
||||
export const readTextFileEffect = (path: string): Effect.Effect<string, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) => fs.readFileString(path));
|
||||
|
||||
export function readTextFile(path: string): Promise<string> {
|
||||
return Bun.file(path).text();
|
||||
return fileSystemRuntime.runPromise(readTextFileEffect(path));
|
||||
}
|
||||
|
||||
export const readBinaryFileEffect = (path: string): Effect.Effect<Uint8Array, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) => fs.readFile(path));
|
||||
|
||||
export function readBinaryFile(path: string): Promise<Uint8Array> {
|
||||
return Bun.file(path).arrayBuffer().then((buffer) => new Uint8Array(buffer));
|
||||
return fileSystemRuntime.runPromise(readBinaryFileEffect(path));
|
||||
}
|
||||
|
||||
export const writeTextFileEffect = (
|
||||
path: string,
|
||||
data: string,
|
||||
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) => fs.writeFileString(path, data));
|
||||
|
||||
export function writeTextFile(path: string, data: string): Promise<void> {
|
||||
return Bun.write(path, data).then(() => undefined);
|
||||
return fileSystemRuntime.runPromise(writeTextFileEffect(path, data));
|
||||
}
|
||||
|
||||
export const writeBinaryFileEffect = (
|
||||
path: string,
|
||||
data: Uint8Array,
|
||||
): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) => fs.writeFile(path, data));
|
||||
|
||||
export function writeBinaryFile(path: string, data: Uint8Array): Promise<void> {
|
||||
return Bun.write(path, data).then(() => undefined);
|
||||
return fileSystemRuntime.runPromise(writeBinaryFileEffect(path, data));
|
||||
}
|
||||
|
||||
export const removePathEffect = (path: string): Effect.Effect<void, PlatformError, FileSystem.FileSystem> =>
|
||||
Effect.flatMap(FileSystem.FileSystem, (fs) => fs.remove(path));
|
||||
|
||||
export function removePath(path: string): Promise<void> {
|
||||
return Bun.file(path).delete();
|
||||
return fileSystemRuntime.runPromise(removePathEffect(path));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import * as BrowserKeyValueStore from "@effect/platform-browser/BrowserKeyValueS
|
|||
import { BaseApi, type ConnectionState, type DocumentMetadata, type ExplainEvent, type StreamingMetadata, type Term, type Triple } from "@trustgraph/client";
|
||||
import { Cause, Clock, Context, Effect, Layer, Match, Metric, Option, Random, Schema as S } from "effect";
|
||||
import * as MutableHashMap from "effect/MutableHashMap";
|
||||
import * as Predicate from "effect/Predicate";
|
||||
import * as Otlp from "effect/unstable/observability/Otlp";
|
||||
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
||||
import * as Atom from "effect/unstable/reactivity/Atom";
|
||||
|
|
@ -44,9 +45,9 @@ const isWorkbenchPromiseError = S.is(WorkbenchPromiseError);
|
|||
|
||||
function errorMessage(error: unknown): string {
|
||||
if (isWorkbenchPromiseError(error)) return error.message;
|
||||
if (typeof error === "object" && error !== null && "message" in error) {
|
||||
const message = (error as { message?: unknown }).message;
|
||||
if (typeof message === "string") return message;
|
||||
if (Predicate.isObject(error) && Predicate.hasProperty(error, "message")) {
|
||||
const message = error.message;
|
||||
if (Predicate.isString(message)) return message;
|
||||
}
|
||||
return String(error);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue