mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 17:39:39 +02:00
Advance TS port Effect workbench
This commit is contained in:
parent
92dae8c374
commit
3515106670
116 changed files with 12286 additions and 9584 deletions
|
|
@ -13,79 +13,108 @@ import {
|
|||
ProducerSpec,
|
||||
type ProcessorConfig,
|
||||
type FlowContext,
|
||||
type FlowResourceNotFoundError,
|
||||
type MessagingDeliveryError,
|
||||
type DocumentEmbeddingsRequest,
|
||||
type DocumentEmbeddingsResponse,
|
||||
type Spec,
|
||||
} from "@trustgraph/base";
|
||||
import { makeProcessorProgram } from "@trustgraph/base";
|
||||
import { QdrantDocEmbeddingsQuery } from "./qdrant-doc.js";
|
||||
import { makeFlowProcessorProgram } from "@trustgraph/base";
|
||||
import { Effect } from "effect";
|
||||
import {
|
||||
QdrantDocEmbeddingsQueryLive,
|
||||
QdrantDocEmbeddingsQueryService,
|
||||
makeQdrantDocEmbeddingsQueryService,
|
||||
type QdrantDocQueryConfig,
|
||||
} from "./qdrant-doc.js";
|
||||
|
||||
export class DocEmbeddingsQueryService extends FlowProcessor {
|
||||
private query: QdrantDocEmbeddingsQuery;
|
||||
const onDocEmbeddingsQueryMessage = Effect.fn("DocEmbeddingsQueryService.onMessage")(function* (
|
||||
msg: DocumentEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext<QdrantDocEmbeddingsQueryService>,
|
||||
) {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<DocumentEmbeddingsResponse>("document-embeddings-response");
|
||||
const query = yield* QdrantDocEmbeddingsQueryService;
|
||||
const collection = msg.collection ?? "default";
|
||||
const allChunks: DocumentEmbeddingsResponse["chunks"] = [];
|
||||
|
||||
for (const vector of msg.vectors ?? []) {
|
||||
const matches = yield* query.query({
|
||||
vector,
|
||||
user: msg.user ?? "default",
|
||||
collection,
|
||||
limit: msg.limit ?? 10,
|
||||
}).pipe(
|
||||
Effect.catch((error) =>
|
||||
Effect.logError("[DocEmbeddingsQuery] Query failed", {
|
||||
error: error.message,
|
||||
operation: error.operation,
|
||||
}).pipe(
|
||||
Effect.flatMap(() =>
|
||||
producer.send(requestId, {
|
||||
chunks: [],
|
||||
error: { type: "query-error", message: error.message },
|
||||
})
|
||||
),
|
||||
Effect.as(null),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (matches === null) return;
|
||||
|
||||
for (const match of matches) {
|
||||
allChunks.push({
|
||||
chunkId: match.chunkId,
|
||||
score: match.score,
|
||||
...(match.content !== undefined ? { content: match.content } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
yield* producer.send(requestId, { chunks: allChunks });
|
||||
});
|
||||
|
||||
export const makeDocEmbeddingsQuerySpecs = (): ReadonlyArray<Spec<QdrantDocEmbeddingsQueryService>> => [
|
||||
new ConsumerSpec<
|
||||
DocumentEmbeddingsRequest,
|
||||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
QdrantDocEmbeddingsQueryService
|
||||
>("document-embeddings-request", onDocEmbeddingsQueryMessage),
|
||||
new ProducerSpec<DocumentEmbeddingsResponse>("document-embeddings-response"),
|
||||
];
|
||||
|
||||
export class DocEmbeddingsQueryService extends FlowProcessor<QdrantDocEmbeddingsQueryService> {
|
||||
private readonly query = makeQdrantDocEmbeddingsQueryService();
|
||||
|
||||
constructor(config: ProcessorConfig) {
|
||||
super(config);
|
||||
this.query = new QdrantDocEmbeddingsQuery();
|
||||
|
||||
this.registerSpecification(
|
||||
ConsumerSpec.fromPromise<DocumentEmbeddingsRequest>(
|
||||
"document-embeddings-request",
|
||||
this.onMessage.bind(this),
|
||||
),
|
||||
);
|
||||
this.registerSpecification(
|
||||
new ProducerSpec<DocumentEmbeddingsResponse>("document-embeddings-response"),
|
||||
);
|
||||
for (const spec of makeDocEmbeddingsQuerySpecs()) {
|
||||
this.registerSpecification(spec);
|
||||
}
|
||||
|
||||
console.log("[DocEmbeddingsQuery] Service initialized");
|
||||
}
|
||||
|
||||
private async onMessage(
|
||||
msg: DocumentEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext,
|
||||
): Promise<void> {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = flowCtx.flow.producer<DocumentEmbeddingsResponse>("document-embeddings-response");
|
||||
const collection = msg.collection ?? "default";
|
||||
|
||||
try {
|
||||
const allChunks: DocumentEmbeddingsResponse["chunks"] = [];
|
||||
|
||||
for (const vector of msg.vectors ?? []) {
|
||||
const matches = await this.query.query({
|
||||
vector,
|
||||
user: msg.user ?? "default",
|
||||
collection,
|
||||
limit: msg.limit ?? 10,
|
||||
});
|
||||
|
||||
for (const match of matches) {
|
||||
allChunks.push({
|
||||
chunkId: match.chunkId,
|
||||
score: match.score,
|
||||
...(match.content !== undefined ? { content: match.content } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await producer.send(requestId, { chunks: allChunks });
|
||||
} catch (err) {
|
||||
console.error("[DocEmbeddingsQuery] Query failed:", err);
|
||||
await producer.send(requestId, {
|
||||
chunks: [],
|
||||
error: { type: "query-error", message: String(err) },
|
||||
});
|
||||
}
|
||||
override startEffect() {
|
||||
return super.startEffect().pipe(
|
||||
Effect.provideService(
|
||||
QdrantDocEmbeddingsQueryService,
|
||||
QdrantDocEmbeddingsQueryService.of(this.query),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const program = makeProcessorProgram({
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig & QdrantDocQueryConfig, never, QdrantDocEmbeddingsQueryService>({
|
||||
id: "doc-embeddings-query",
|
||||
make: (config) => new DocEmbeddingsQueryService(config),
|
||||
specs: () => makeDocEmbeddingsQuerySpecs(),
|
||||
layer: (config) => QdrantDocEmbeddingsQueryLive(config),
|
||||
});
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
await DocEmbeddingsQueryService.launch("doc-embeddings-query");
|
||||
await Effect.runPromise(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@
|
|||
*/
|
||||
|
||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||
import { errorMessage } from "@trustgraph/base";
|
||||
import { Context, Effect, Layer } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface QdrantDocQueryConfig {
|
||||
url?: string;
|
||||
|
|
@ -83,3 +86,54 @@ export class QdrantDocEmbeddingsQuery {
|
|||
return chunks;
|
||||
}
|
||||
}
|
||||
|
||||
export class QdrantDocEmbeddingsQueryError extends S.TaggedErrorClass<QdrantDocEmbeddingsQueryError>()(
|
||||
"QdrantDocEmbeddingsQueryError",
|
||||
{
|
||||
message: S.String,
|
||||
operation: S.String,
|
||||
cause: S.DefectWithStack,
|
||||
},
|
||||
) {}
|
||||
|
||||
export interface QdrantDocEmbeddingsQueryServiceShape {
|
||||
readonly query: (
|
||||
request: DocEmbeddingsQueryRequest,
|
||||
) => Effect.Effect<ReadonlyArray<ChunkMatch>, QdrantDocEmbeddingsQueryError>;
|
||||
}
|
||||
|
||||
export class QdrantDocEmbeddingsQueryService extends Context.Service<
|
||||
QdrantDocEmbeddingsQueryService,
|
||||
QdrantDocEmbeddingsQueryServiceShape
|
||||
>()(
|
||||
"@trustgraph/flow/query/embeddings/qdrant-doc/QdrantDocEmbeddingsQueryService",
|
||||
) {}
|
||||
|
||||
const qdrantDocEmbeddingsQueryError = (operation: string, cause: unknown) =>
|
||||
new QdrantDocEmbeddingsQueryError({
|
||||
operation,
|
||||
message: errorMessage(cause),
|
||||
cause,
|
||||
});
|
||||
|
||||
export const makeQdrantDocEmbeddingsQueryService = (
|
||||
config: QdrantDocQueryConfig = {},
|
||||
): QdrantDocEmbeddingsQueryServiceShape => {
|
||||
const query = new QdrantDocEmbeddingsQuery(config);
|
||||
return {
|
||||
query: Effect.fn("QdrantDocEmbeddingsQuery.query")(function* (request) {
|
||||
return yield* Effect.tryPromise({
|
||||
try: () => query.query(request),
|
||||
catch: (cause) => qdrantDocEmbeddingsQueryError("query", cause),
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const QdrantDocEmbeddingsQueryLive = (
|
||||
config: QdrantDocQueryConfig = {},
|
||||
): Layer.Layer<QdrantDocEmbeddingsQueryService> =>
|
||||
Layer.succeed(
|
||||
QdrantDocEmbeddingsQueryService,
|
||||
QdrantDocEmbeddingsQueryService.of(makeQdrantDocEmbeddingsQueryService(config)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,78 +13,109 @@ import {
|
|||
ProducerSpec,
|
||||
type ProcessorConfig,
|
||||
type FlowContext,
|
||||
type FlowResourceNotFoundError,
|
||||
type MessagingDeliveryError,
|
||||
type GraphEmbeddingsRequest,
|
||||
type GraphEmbeddingsResponse,
|
||||
type Spec,
|
||||
} from "@trustgraph/base";
|
||||
import { makeProcessorProgram } from "@trustgraph/base";
|
||||
import { QdrantGraphEmbeddingsQuery } from "./qdrant-graph.js";
|
||||
import { makeFlowProcessorProgram } from "@trustgraph/base";
|
||||
import { Effect } from "effect";
|
||||
import {
|
||||
QdrantGraphEmbeddingsQueryLive,
|
||||
QdrantGraphEmbeddingsQueryService,
|
||||
makeQdrantGraphEmbeddingsQueryService,
|
||||
type QdrantGraphQueryConfig,
|
||||
} from "./qdrant-graph.js";
|
||||
|
||||
export class GraphEmbeddingsQueryService extends FlowProcessor {
|
||||
private query: QdrantGraphEmbeddingsQuery;
|
||||
const onGraphEmbeddingsQueryMessage = Effect.fn("GraphEmbeddingsQueryService.onMessage")(function* (
|
||||
msg: GraphEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext<QdrantGraphEmbeddingsQueryService>,
|
||||
) {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<GraphEmbeddingsResponse>("graph-embeddings-response");
|
||||
const query = yield* QdrantGraphEmbeddingsQueryService;
|
||||
const user = msg.user ?? "default";
|
||||
const collection = msg.collection ?? "default";
|
||||
yield* Effect.log(
|
||||
`[GraphEmbeddingsQuery] Request: user=${user}, collection=${collection}, vectors=${msg.vectors?.length ?? 0}, limit=${msg.limit}`,
|
||||
);
|
||||
|
||||
const allEntities: GraphEmbeddingsResponse["entities"] = [];
|
||||
|
||||
for (const vector of msg.vectors ?? []) {
|
||||
const matches = yield* query.query({
|
||||
vector,
|
||||
user,
|
||||
collection,
|
||||
limit: msg.limit ?? 50,
|
||||
}).pipe(
|
||||
Effect.catch((error) =>
|
||||
Effect.logError("[GraphEmbeddingsQuery] Query failed", {
|
||||
error: error.message,
|
||||
operation: error.operation,
|
||||
}).pipe(
|
||||
Effect.flatMap(() =>
|
||||
producer.send(requestId, {
|
||||
entities: [],
|
||||
error: { type: "query-error", message: error.message },
|
||||
})
|
||||
),
|
||||
Effect.as(null),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (matches === null) return;
|
||||
|
||||
for (const match of matches) {
|
||||
allEntities.push(match.entity);
|
||||
}
|
||||
}
|
||||
|
||||
yield* producer.send(requestId, { entities: allEntities });
|
||||
});
|
||||
|
||||
export const makeGraphEmbeddingsQuerySpecs = (): ReadonlyArray<Spec<QdrantGraphEmbeddingsQueryService>> => [
|
||||
new ConsumerSpec<
|
||||
GraphEmbeddingsRequest,
|
||||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
QdrantGraphEmbeddingsQueryService
|
||||
>("graph-embeddings-request", onGraphEmbeddingsQueryMessage),
|
||||
new ProducerSpec<GraphEmbeddingsResponse>("graph-embeddings-response"),
|
||||
];
|
||||
|
||||
export class GraphEmbeddingsQueryService extends FlowProcessor<QdrantGraphEmbeddingsQueryService> {
|
||||
private readonly query = makeQdrantGraphEmbeddingsQueryService();
|
||||
|
||||
constructor(config: ProcessorConfig) {
|
||||
super(config);
|
||||
this.query = new QdrantGraphEmbeddingsQuery();
|
||||
|
||||
this.registerSpecification(
|
||||
ConsumerSpec.fromPromise<GraphEmbeddingsRequest>(
|
||||
"graph-embeddings-request",
|
||||
this.onMessage.bind(this),
|
||||
),
|
||||
);
|
||||
this.registerSpecification(
|
||||
new ProducerSpec<GraphEmbeddingsResponse>("graph-embeddings-response"),
|
||||
);
|
||||
for (const spec of makeGraphEmbeddingsQuerySpecs()) {
|
||||
this.registerSpecification(spec);
|
||||
}
|
||||
|
||||
console.log("[GraphEmbeddingsQuery] Service initialized");
|
||||
}
|
||||
|
||||
private async onMessage(
|
||||
msg: GraphEmbeddingsRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext,
|
||||
): Promise<void> {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = flowCtx.flow.producer<GraphEmbeddingsResponse>("graph-embeddings-response");
|
||||
const user = msg.user ?? "default";
|
||||
const collection = msg.collection ?? "default";
|
||||
console.log(`[GraphEmbeddingsQuery] Request: user=${user}, collection=${collection}, vectors=${msg.vectors?.length ?? 0}, limit=${msg.limit}`);
|
||||
|
||||
try {
|
||||
// Query for each vector and aggregate results
|
||||
const allEntities: GraphEmbeddingsResponse["entities"] = [];
|
||||
|
||||
for (const vector of msg.vectors ?? []) {
|
||||
const matches = await this.query.query({
|
||||
vector,
|
||||
user,
|
||||
collection,
|
||||
limit: msg.limit ?? 50,
|
||||
});
|
||||
|
||||
for (const match of matches) {
|
||||
allEntities.push(match.entity);
|
||||
}
|
||||
}
|
||||
|
||||
await producer.send(requestId, { entities: allEntities });
|
||||
} catch (err) {
|
||||
console.error("[GraphEmbeddingsQuery] Query failed:", err);
|
||||
await producer.send(requestId, {
|
||||
entities: [],
|
||||
error: { type: "query-error", message: String(err) },
|
||||
});
|
||||
}
|
||||
override startEffect() {
|
||||
return super.startEffect().pipe(
|
||||
Effect.provideService(
|
||||
QdrantGraphEmbeddingsQueryService,
|
||||
QdrantGraphEmbeddingsQueryService.of(this.query),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const program = makeProcessorProgram({
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig & QdrantGraphQueryConfig, never, QdrantGraphEmbeddingsQueryService>({
|
||||
id: "graph-embeddings-query",
|
||||
make: (config) => new GraphEmbeddingsQueryService(config),
|
||||
specs: () => makeGraphEmbeddingsQuerySpecs(),
|
||||
layer: (config) => QdrantGraphEmbeddingsQueryLive(config),
|
||||
});
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
await GraphEmbeddingsQueryService.launch("graph-embeddings-query");
|
||||
await Effect.runPromise(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@
|
|||
*/
|
||||
|
||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||
import type { Term } from "@trustgraph/base";
|
||||
import { errorMessage, type Term } from "@trustgraph/base";
|
||||
import { Context, Effect, Layer } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface QdrantGraphQueryConfig {
|
||||
url?: string;
|
||||
|
|
@ -104,3 +106,54 @@ export class QdrantGraphEmbeddingsQuery {
|
|||
return entities;
|
||||
}
|
||||
}
|
||||
|
||||
export class QdrantGraphEmbeddingsQueryError extends S.TaggedErrorClass<QdrantGraphEmbeddingsQueryError>()(
|
||||
"QdrantGraphEmbeddingsQueryError",
|
||||
{
|
||||
message: S.String,
|
||||
operation: S.String,
|
||||
cause: S.DefectWithStack,
|
||||
},
|
||||
) {}
|
||||
|
||||
export interface QdrantGraphEmbeddingsQueryServiceShape {
|
||||
readonly query: (
|
||||
request: GraphEmbeddingsQueryRequest,
|
||||
) => Effect.Effect<ReadonlyArray<EntityMatch>, QdrantGraphEmbeddingsQueryError>;
|
||||
}
|
||||
|
||||
export class QdrantGraphEmbeddingsQueryService extends Context.Service<
|
||||
QdrantGraphEmbeddingsQueryService,
|
||||
QdrantGraphEmbeddingsQueryServiceShape
|
||||
>()(
|
||||
"@trustgraph/flow/query/embeddings/qdrant-graph/QdrantGraphEmbeddingsQueryService",
|
||||
) {}
|
||||
|
||||
const qdrantGraphEmbeddingsQueryError = (operation: string, cause: unknown) =>
|
||||
new QdrantGraphEmbeddingsQueryError({
|
||||
operation,
|
||||
message: errorMessage(cause),
|
||||
cause,
|
||||
});
|
||||
|
||||
export const makeQdrantGraphEmbeddingsQueryService = (
|
||||
config: QdrantGraphQueryConfig = {},
|
||||
): QdrantGraphEmbeddingsQueryServiceShape => {
|
||||
const query = new QdrantGraphEmbeddingsQuery(config);
|
||||
return {
|
||||
query: Effect.fn("QdrantGraphEmbeddingsQuery.query")(function* (request) {
|
||||
return yield* Effect.tryPromise({
|
||||
try: () => query.query(request),
|
||||
catch: (cause) => qdrantGraphEmbeddingsQueryError("query", cause),
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export const QdrantGraphEmbeddingsQueryLive = (
|
||||
config: QdrantGraphQueryConfig = {},
|
||||
): Layer.Layer<QdrantGraphEmbeddingsQueryService> =>
|
||||
Layer.succeed(
|
||||
QdrantGraphEmbeddingsQueryService,
|
||||
QdrantGraphEmbeddingsQueryService.of(makeQdrantGraphEmbeddingsQueryService(config)),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,61 +13,95 @@ import {
|
|||
ProducerSpec,
|
||||
type ProcessorConfig,
|
||||
type FlowContext,
|
||||
type FlowResourceNotFoundError,
|
||||
type MessagingDeliveryError,
|
||||
type TriplesQueryRequest,
|
||||
type TriplesQueryResponse,
|
||||
type Spec,
|
||||
} from "@trustgraph/base";
|
||||
import { makeProcessorProgram } from "@trustgraph/base";
|
||||
import { FalkorDBTriplesQuery } from "./falkordb.js";
|
||||
import { makeFlowProcessorProgram } from "@trustgraph/base";
|
||||
import { Effect } from "effect";
|
||||
import {
|
||||
FalkorDBTriplesQueryLive,
|
||||
FalkorDBTriplesQueryService,
|
||||
makeFalkorDBTriplesQueryService,
|
||||
type FalkorDBQueryConfig,
|
||||
} from "./falkordb.js";
|
||||
|
||||
export class TriplesQueryService extends FlowProcessor {
|
||||
private query: FalkorDBTriplesQuery;
|
||||
const onTriplesQueryMessage = Effect.fn("TriplesQueryService.onMessage")(function* (
|
||||
msg: TriplesQueryRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext<FalkorDBTriplesQueryService>,
|
||||
) {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = yield* flowCtx.flow.producerEffect<TriplesQueryResponse>("triples-response");
|
||||
const query = yield* FalkorDBTriplesQueryService;
|
||||
const triples = yield* query.queryTriples(
|
||||
msg.s,
|
||||
msg.p,
|
||||
msg.o,
|
||||
msg.limit ?? 100,
|
||||
).pipe(
|
||||
Effect.catch((error) =>
|
||||
Effect.logError("[TriplesQuery] Query failed", {
|
||||
error: error.message,
|
||||
operation: error.operation,
|
||||
}).pipe(
|
||||
Effect.flatMap(() =>
|
||||
producer.send(requestId, {
|
||||
triples: [],
|
||||
error: { type: "query-error", message: error.message },
|
||||
})
|
||||
),
|
||||
Effect.as(null),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (triples === null) return;
|
||||
|
||||
yield* producer.send(requestId, { triples: Array.from(triples) });
|
||||
});
|
||||
|
||||
export const makeTriplesQuerySpecs = (): ReadonlyArray<Spec<FalkorDBTriplesQueryService>> => [
|
||||
new ConsumerSpec<
|
||||
TriplesQueryRequest,
|
||||
FlowResourceNotFoundError | MessagingDeliveryError,
|
||||
FalkorDBTriplesQueryService
|
||||
>("triples-request", onTriplesQueryMessage),
|
||||
new ProducerSpec<TriplesQueryResponse>("triples-response"),
|
||||
];
|
||||
|
||||
export class TriplesQueryService extends FlowProcessor<FalkorDBTriplesQueryService> {
|
||||
private readonly query = makeFalkorDBTriplesQueryService();
|
||||
|
||||
constructor(config: ProcessorConfig) {
|
||||
super(config);
|
||||
this.query = new FalkorDBTriplesQuery();
|
||||
|
||||
this.registerSpecification(
|
||||
ConsumerSpec.fromPromise<TriplesQueryRequest>("triples-request", this.onMessage.bind(this)),
|
||||
);
|
||||
this.registerSpecification(new ProducerSpec<TriplesQueryResponse>("triples-response"));
|
||||
for (const spec of makeTriplesQuerySpecs()) {
|
||||
this.registerSpecification(spec);
|
||||
}
|
||||
|
||||
console.log("[TriplesQuery] Service initialized");
|
||||
}
|
||||
|
||||
private async onMessage(
|
||||
msg: TriplesQueryRequest,
|
||||
properties: Record<string, string>,
|
||||
flowCtx: FlowContext,
|
||||
): Promise<void> {
|
||||
const requestId = properties.id;
|
||||
if (requestId === undefined || requestId.length === 0) return;
|
||||
|
||||
const producer = flowCtx.flow.producer<TriplesQueryResponse>("triples-response");
|
||||
|
||||
try {
|
||||
const triples = await this.query.queryTriples(
|
||||
msg.s,
|
||||
msg.p,
|
||||
msg.o,
|
||||
msg.limit ?? 100,
|
||||
);
|
||||
|
||||
await producer.send(requestId, { triples });
|
||||
} catch (err) {
|
||||
console.error("[TriplesQuery] Query failed:", err);
|
||||
await producer.send(requestId, {
|
||||
triples: [],
|
||||
error: { type: "query-error", message: String(err) },
|
||||
});
|
||||
}
|
||||
override startEffect() {
|
||||
return super.startEffect().pipe(
|
||||
Effect.provideService(
|
||||
FalkorDBTriplesQueryService,
|
||||
FalkorDBTriplesQueryService.of(this.query),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const program = makeProcessorProgram({
|
||||
export const program = makeFlowProcessorProgram<ProcessorConfig & FalkorDBQueryConfig, never, FalkorDBTriplesQueryService>({
|
||||
id: "triples-query",
|
||||
make: (config) => new TriplesQueryService(config),
|
||||
specs: () => makeTriplesQuerySpecs(),
|
||||
layer: (config) => FalkorDBTriplesQueryLive(config),
|
||||
});
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
await TriplesQueryService.launch("triples-query");
|
||||
await Effect.runPromise(program);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@
|
|||
*/
|
||||
|
||||
import { createClient, Graph } from "falkordb";
|
||||
import type { Term, Triple } from "@trustgraph/base";
|
||||
import { errorMessage, type Term, type Triple } from "@trustgraph/base";
|
||||
import { Context, Effect, Layer } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface FalkorDBQueryConfig {
|
||||
url?: string;
|
||||
|
|
@ -264,3 +266,61 @@ export class FalkorDBTriplesQuery {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class FalkorDBTriplesQueryError extends S.TaggedErrorClass<FalkorDBTriplesQueryError>()(
|
||||
"FalkorDBTriplesQueryError",
|
||||
{
|
||||
message: S.String,
|
||||
operation: S.String,
|
||||
cause: S.DefectWithStack,
|
||||
},
|
||||
) {}
|
||||
|
||||
export interface FalkorDBTriplesQueryServiceShape {
|
||||
readonly queryTriples: (
|
||||
s: Term | undefined,
|
||||
p: Term | undefined,
|
||||
o: Term | undefined,
|
||||
limit: number,
|
||||
) => Effect.Effect<ReadonlyArray<Triple>, FalkorDBTriplesQueryError>;
|
||||
}
|
||||
|
||||
export class FalkorDBTriplesQueryService extends Context.Service<
|
||||
FalkorDBTriplesQueryService,
|
||||
FalkorDBTriplesQueryServiceShape
|
||||
>()(
|
||||
"@trustgraph/flow/query/triples/falkordb/FalkorDBTriplesQueryService",
|
||||
) {}
|
||||
|
||||
const falkorDBTriplesQueryError = (operation: string, cause: unknown) =>
|
||||
new FalkorDBTriplesQueryError({
|
||||
operation,
|
||||
message: errorMessage(cause),
|
||||
cause,
|
||||
});
|
||||
|
||||
export const makeFalkorDBTriplesQueryService = (
|
||||
config: FalkorDBQueryConfig = {},
|
||||
): FalkorDBTriplesQueryServiceShape => {
|
||||
const query = new FalkorDBTriplesQuery(config);
|
||||
return {
|
||||
queryTriples: Effect.fn("FalkorDBTriplesQuery.queryTriples")((
|
||||
s: Term | undefined,
|
||||
p: Term | undefined,
|
||||
o: Term | undefined,
|
||||
limit: number,
|
||||
) =>
|
||||
Effect.tryPromise({
|
||||
try: () => query.queryTriples(s, p, o, limit),
|
||||
catch: (cause) => falkorDBTriplesQueryError("query-triples", cause),
|
||||
})),
|
||||
};
|
||||
};
|
||||
|
||||
export const FalkorDBTriplesQueryLive = (
|
||||
config: FalkorDBQueryConfig = {},
|
||||
): Layer.Layer<FalkorDBTriplesQueryService> =>
|
||||
Layer.succeed(
|
||||
FalkorDBTriplesQueryService,
|
||||
FalkorDBTriplesQueryService.of(makeFalkorDBTriplesQueryService(config)),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue