Enforce strict Effect tsgo migrations

This commit is contained in:
elpresidank 2026-06-01 23:19:54 -05:00
parent 64fb23e7d0
commit f6878d4dd7
49 changed files with 5547 additions and 3250 deletions

View file

@ -66,7 +66,7 @@ export function makeTriplesStoreService(config: ProcessorConfig): TriplesStoreSe
),
),
});
console.log("[TriplesStore] Service initialized");
void Effect.runPromise(Effect.log("[TriplesStore] Service initialized"));
return service;
}
@ -78,6 +78,6 @@ export const program = makeFlowProcessorProgram<ProcessorConfig & FalkorDBConfig
layer: (config) => FalkorDBTriplesStoreLive(config),
});
export async function run(): Promise<void> {
await Effect.runPromise(program);
export function run(): Promise<void> {
return Effect.runPromise(program);
}

View file

@ -1,5 +1,5 @@
/**
* FalkorDB triples store writes RDF triples to a FalkorDB graph.
* FalkorDB triples store - writes RDF triples to a FalkorDB graph.
*
* FalkorDB is Redis-based and uses Cypher queries, same as the Python impl.
* Pairs well with Graphiti which also uses FalkorDB as its backend.
@ -9,7 +9,7 @@
import { createClient, Graph } from "falkordb";
import { errorMessage, type Term, type Triple } from "@trustgraph/base";
import { Context, Effect, Layer } from "effect";
import { Config, Context, Effect, Layer } from "effect";
import * as S from "effect/Schema";
export interface FalkorDBConfig {
@ -26,7 +26,7 @@ function getTermValue(term: Term): string {
case "BLANK":
return term.id;
case "TRIPLE":
return getTermValue(term.triple.s); // fallback
return getTermValue(term.triple.s);
}
}
@ -55,113 +55,6 @@ export interface FalkorDBTriplesStore {
readonly deleteCollection: (user: string, collection: string) => Promise<void>;
}
export function makeFalkorDBTriplesStore(config: FalkorDBConfig = {}): FalkorDBTriplesStore {
const url = config.url ?? process.env.FALKORDB_URL ?? "redis://localhost:6379";
const database = config.database ?? "falkordb";
const client = createClient({ url });
const graph = new Graph(client, database);
const connectPromise = client.connect().then(() => {
console.log(`[FalkorDBTriplesStore] Connected to ${url}, graph: ${database}`);
}).catch((err) => {
console.error(`[FalkorDBTriplesStore] Connection failed:`, err);
throw err;
});
const ensureConnected = async (): Promise<void> => {
await connectPromise;
};
const createNode = async (uri: string, user: string, collection: string): Promise<void> => {
await ensureConnected();
await graph.query(
"MERGE (n:Node {uri: $uri, user: $user, collection: $collection})",
{ params: { uri, user, collection } },
);
};
const createLiteral = async (value: string, user: string, collection: string): Promise<void> => {
await ensureConnected();
await graph.query(
"MERGE (n:Literal {value: $value, user: $user, collection: $collection})",
{ params: { value, user, collection } },
);
};
const relateNode = async (
src: string, uri: string, dest: string,
user: string, collection: string,
): Promise<void> => {
await ensureConnected();
await graph.query(
"MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " +
"MATCH (dest:Node {uri: $dest, user: $user, collection: $collection}) " +
"MERGE (src)-[:Rel {uri: $uri, user: $user, collection: $collection}]->(dest)",
{ params: { src, dest, uri, user, collection } },
);
};
const relateLiteral = async (
src: string, uri: string, dest: string,
user: string, collection: string,
): Promise<void> => {
await ensureConnected();
await graph.query(
"MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " +
"MATCH (dest:Literal {value: $dest, user: $user, collection: $collection}) " +
"MERGE (src)-[:Rel {uri: $uri, user: $user, collection: $collection}]->(dest)",
{ params: { src, dest, uri, user, collection } },
);
};
const storeTriples = async (
triples: Triple[],
user = "default",
collection = "default",
): Promise<void> => {
for (const t of triples) {
const s = getTermValue(t.s);
const p = getTermValue(t.p);
const o = getTermValue(t.o);
await createNode(s, user, collection);
if (t.o.type === "IRI") {
await createNode(o, user, collection);
await relateNode(s, p, o, user, collection);
} else {
await createLiteral(o, user, collection);
await relateLiteral(s, p, o, user, collection);
}
}
};
const deleteCollection = async (user: string, collection: string): Promise<void> => {
await ensureConnected();
await graph.query(
"MATCH (n:Node {user: $user, collection: $collection}) DETACH DELETE n",
{ params: { user, collection } },
);
await graph.query(
"MATCH (n:Literal {user: $user, collection: $collection}) DETACH DELETE n",
{ params: { user, collection } },
);
await graph.query(
"MATCH (c:CollectionMetadata {user: $user, collection: $collection}) DELETE c",
{ params: { user, collection } },
);
};
return {
createNode,
createLiteral,
relateNode,
relateLiteral,
storeTriples,
deleteCollection,
};
}
export class FalkorDBTriplesStoreError extends S.TaggedErrorClass<FalkorDBTriplesStoreError>()(
"FalkorDBTriplesStoreError",
{
@ -190,35 +83,268 @@ export class FalkorDBTriplesStoreService extends Context.Service<
"@trustgraph/flow/storage/triples/falkordb/FalkorDBTriplesStoreService",
) {}
const falkorDBTriplesStoreError = (operation: string, cause: unknown) =>
new FalkorDBTriplesStoreError({
const falkorDBTriplesStoreError = (operation: string, cause: unknown): FalkorDBTriplesStoreError =>
FalkorDBTriplesStoreError.make({
operation,
message: errorMessage(cause),
cause,
});
interface FalkorDBStoreConnection {
readonly graph: Graph;
}
type FalkorDBQueryOptions = Parameters<Graph["query"]>[1];
interface FalkorDBTriplesStoreEffectShape {
readonly createNode: (
uri: string,
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
readonly createLiteral: (
value: string,
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
readonly relateNode: (
src: string,
uri: string,
dest: string,
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
readonly relateLiteral: (
src: string,
uri: string,
dest: string,
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
readonly storeTriples: (
triples: ReadonlyArray<Triple>,
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
readonly deleteCollection: (
user: string,
collection: string,
) => Effect.Effect<void, FalkorDBTriplesStoreError>;
}
const resolveFalkorDBStoreConfig = Effect.fn("FalkorDBTriplesStore.resolveConfig")(function* (
config: FalkorDBConfig,
) {
const url = config.url ?? (yield* Config.string("FALKORDB_URL").pipe(
Config.withDefault("redis://localhost:6379"),
Effect.mapError((cause) => falkorDBTriplesStoreError("config", cause)),
));
return {
url,
database: config.database ?? "falkordb",
};
});
const connectFalkorDBTriplesStore = (
config: FalkorDBConfig,
): Effect.Effect<FalkorDBStoreConnection, FalkorDBTriplesStoreError> =>
Effect.gen(function* () {
const { url, database } = yield* resolveFalkorDBStoreConfig(config);
const { client, graph } = yield* Effect.try({
try: () => {
const client = createClient({ url });
return { client, graph: new Graph(client, database) };
},
catch: (cause) => falkorDBTriplesStoreError("create-client", cause),
});
yield* Effect.tryPromise({
try: () => client.connect(),
catch: (cause) => falkorDBTriplesStoreError("connect", cause),
}).pipe(
Effect.tapError((error) =>
Effect.logError("[FalkorDBTriplesStore] Connection failed", {
error: error.message,
operation: error.operation,
})
),
);
yield* Effect.log(`[FalkorDBTriplesStore] Connected to ${url}, graph: ${database}`);
return { graph };
});
const runGraphQuery = (
graph: Graph,
operation: string,
query: string,
options?: FalkorDBQueryOptions,
): Effect.Effect<void, FalkorDBTriplesStoreError> =>
Effect.tryPromise({
try: () => graph.query(query, options),
catch: (cause) => falkorDBTriplesStoreError(operation, cause),
}).pipe(
Effect.asVoid,
);
const makeFalkorDBTriplesStoreEffect = (
config: FalkorDBConfig = {},
): FalkorDBTriplesStoreEffectShape => {
let cachedConnection: Effect.Effect<FalkorDBStoreConnection, FalkorDBTriplesStoreError> | undefined;
const getConnection = Effect.fn("FalkorDBTriplesStore.connection")(function* () {
if (cachedConnection === undefined) {
cachedConnection = yield* Effect.cached(connectFalkorDBTriplesStore(config));
}
return yield* cachedConnection;
});
const createNode = Effect.fn("FalkorDBTriplesStore.createNode")(function* (
uri: string,
user: string,
collection: string,
) {
const { graph } = yield* getConnection();
yield* runGraphQuery(
graph,
"create-node",
"MERGE (n:Node {uri: $uri, user: $user, collection: $collection})",
{ params: { uri, user, collection } },
);
});
const createLiteral = Effect.fn("FalkorDBTriplesStore.createLiteral")(function* (
value: string,
user: string,
collection: string,
) {
const { graph } = yield* getConnection();
yield* runGraphQuery(
graph,
"create-literal",
"MERGE (n:Literal {value: $value, user: $user, collection: $collection})",
{ params: { value, user, collection } },
);
});
const relateNode = Effect.fn("FalkorDBTriplesStore.relateNode")(function* (
src: string,
uri: string,
dest: string,
user: string,
collection: string,
) {
const { graph } = yield* getConnection();
yield* runGraphQuery(
graph,
"relate-node",
"MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " +
"MATCH (dest:Node {uri: $dest, user: $user, collection: $collection}) " +
"MERGE (src)-[:Rel {uri: $uri, user: $user, collection: $collection}]->(dest)",
{ params: { src, dest, uri, user, collection } },
);
});
const relateLiteral = Effect.fn("FalkorDBTriplesStore.relateLiteral")(function* (
src: string,
uri: string,
dest: string,
user: string,
collection: string,
) {
const { graph } = yield* getConnection();
yield* runGraphQuery(
graph,
"relate-literal",
"MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " +
"MATCH (dest:Literal {value: $dest, user: $user, collection: $collection}) " +
"MERGE (src)-[:Rel {uri: $uri, user: $user, collection: $collection}]->(dest)",
{ params: { src, dest, uri, user, collection } },
);
});
const storeTriples = Effect.fn("FalkorDBTriplesStore.storeTriples")(function* (
triples: ReadonlyArray<Triple>,
user: string,
collection: string,
) {
for (const triple of triples) {
const s = getTermValue(triple.s);
const p = getTermValue(triple.p);
const o = getTermValue(triple.o);
yield* createNode(s, user, collection);
if (triple.o.type === "IRI") {
yield* createNode(o, user, collection);
yield* relateNode(s, p, o, user, collection);
} else {
yield* createLiteral(o, user, collection);
yield* relateLiteral(s, p, o, user, collection);
}
}
});
const deleteCollection = Effect.fn("FalkorDBTriplesStore.deleteCollection")(function* (
user: string,
collection: string,
) {
const { graph } = yield* getConnection();
yield* runGraphQuery(
graph,
"delete-collection-nodes",
"MATCH (n:Node {user: $user, collection: $collection}) DETACH DELETE n",
{ params: { user, collection } },
);
yield* runGraphQuery(
graph,
"delete-collection-literals",
"MATCH (n:Literal {user: $user, collection: $collection}) DETACH DELETE n",
{ params: { user, collection } },
);
yield* runGraphQuery(
graph,
"delete-collection-metadata",
"MATCH (c:CollectionMetadata {user: $user, collection: $collection}) DELETE c",
{ params: { user, collection } },
);
});
return {
createNode,
createLiteral,
relateNode,
relateLiteral,
storeTriples,
deleteCollection,
};
};
export function makeFalkorDBTriplesStore(config: FalkorDBConfig = {}): FalkorDBTriplesStore {
const store = makeFalkorDBTriplesStoreEffect(config);
return {
createNode: (uri, user, collection) =>
Effect.runPromise(store.createNode(uri, user, collection)),
createLiteral: (value, user, collection) =>
Effect.runPromise(store.createLiteral(value, user, collection)),
relateNode: (src, uri, dest, user, collection) =>
Effect.runPromise(store.relateNode(src, uri, dest, user, collection)),
relateLiteral: (src, uri, dest, user, collection) =>
Effect.runPromise(store.relateLiteral(src, uri, dest, user, collection)),
storeTriples: (triples, user = "default", collection = "default") =>
Effect.runPromise(store.storeTriples(triples, user, collection)),
deleteCollection: (user, collection) =>
Effect.runPromise(store.deleteCollection(user, collection)),
};
}
export const makeFalkorDBTriplesStoreService = (
config: FalkorDBConfig = {},
): FalkorDBTriplesStoreServiceShape => {
const store = makeFalkorDBTriplesStore(config);
const store = makeFalkorDBTriplesStoreEffect(config);
return {
storeTriples: Effect.fn("FalkorDBTriplesStore.storeTriples")((
triples: ReadonlyArray<Triple>,
user: string,
collection: string,
) =>
Effect.tryPromise({
try: () => store.storeTriples(Array.from(triples), user, collection),
catch: (cause) => falkorDBTriplesStoreError("store-triples", cause),
})),
deleteCollection: Effect.fn("FalkorDBTriplesStore.deleteCollection")((
user: string,
collection: string,
) =>
Effect.tryPromise({
try: () => store.deleteCollection(user, collection),
catch: (cause) => falkorDBTriplesStoreError("delete-collection", cause),
})),
storeTriples: store.storeTriples,
deleteCollection: store.deleteCollection,
};
};