mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-01 09:29:38 +02:00
Migrate strict Effect runtime surfaces
This commit is contained in:
parent
f6878d4dd7
commit
b4ee2b691f
35 changed files with 1717 additions and 1410 deletions
|
|
@ -5,21 +5,20 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket } from "./util.js";
|
||||
|
||||
export function registerAgentCommands(program: Command): void {
|
||||
program
|
||||
.command("agent")
|
||||
.description("Ask the TrustGraph agent a question")
|
||||
.argument("<question>", "Question to ask")
|
||||
.action(async (question: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((question: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket, opts) =>
|
||||
Effect.gen(function* () {
|
||||
const flow = socket.flow(opts.flow);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
yield* Effect.callback<void, ReturnType<typeof cliCommandError>>((resume) => {
|
||||
flow.agent(
|
||||
question,
|
||||
(chunk) => {
|
||||
|
|
@ -35,14 +34,13 @@ export function registerAgentCommands(program: Command): void {
|
|||
if (chunk.length > 0) process.stdout.write(chunk);
|
||||
if (complete) {
|
||||
process.stdout.write("\n");
|
||||
resolve();
|
||||
resume(Effect.void);
|
||||
}
|
||||
},
|
||||
(err) => reject(new Error(err)),
|
||||
(err) => resume(Effect.fail(cliCommandError("agent", err))),
|
||||
);
|
||||
});
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket, writeJson } from "./util.js";
|
||||
|
||||
export function registerConfigCommands(program: Command): void {
|
||||
const config = program
|
||||
|
|
@ -15,28 +16,26 @@ export function registerConfigCommands(program: Command): void {
|
|||
config
|
||||
.command("show")
|
||||
.description("Show current configuration")
|
||||
.action(async (_opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((_opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = socket.config();
|
||||
const resp = await cfg.getConfigAll();
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => cfg.getConfigAll(),
|
||||
catch: (error) => cliCommandError("config.show", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
config
|
||||
.command("get")
|
||||
.description("Get a configuration value")
|
||||
.argument("<key>", "Config key (format: type/key)")
|
||||
.action(async (key: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((key: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = socket.config();
|
||||
// Support "type/key" format; fall back to using the whole string as key
|
||||
const parts = key.split("/");
|
||||
|
|
@ -44,72 +43,74 @@ export function registerConfigCommands(program: Command): void {
|
|||
parts.length >= 2
|
||||
? { type: parts[0], key: parts.slice(1).join("/") }
|
||||
: { type: "config", key };
|
||||
const resp = await cfg.getConfig([configKey]);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => cfg.getConfig([configKey]),
|
||||
catch: (error) => cliCommandError("config.get", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
config
|
||||
.command("set")
|
||||
.description("Set a configuration value")
|
||||
.argument("<key>", "Config key (format: type/key)")
|
||||
.argument("<value>", "Config value (JSON)")
|
||||
.action(async (key: string, value: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((key: string, value: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = socket.config();
|
||||
const parts = key.split("/");
|
||||
const configEntry =
|
||||
parts.length >= 2
|
||||
? { type: parts[0], key: parts.slice(1).join("/"), value }
|
||||
: { type: "config", key, value };
|
||||
const resp = await cfg.putConfig([configEntry]);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => cfg.putConfig([configEntry]),
|
||||
catch: (error) => cliCommandError("config.set", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
config
|
||||
.command("list")
|
||||
.description("List configuration keys for a type")
|
||||
.argument("[type]", "Config type to list", "config")
|
||||
.action(async (type: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((type: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = socket.config();
|
||||
const resp = await cfg.list(type);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => cfg.list(type),
|
||||
catch: (error) => cliCommandError("config.list", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
config
|
||||
.command("delete")
|
||||
.description("Delete a configuration entry")
|
||||
.argument("<key>", "Config key (format: type/key)")
|
||||
.action(async (key: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((key: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const cfg = socket.config();
|
||||
const parts = key.split("/");
|
||||
const configKey =
|
||||
parts.length >= 2
|
||||
? { type: parts[0], key: parts.slice(1).join("/") }
|
||||
: { type: "config", key };
|
||||
const resp = await cfg.deleteConfig(configKey);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => cfg.deleteConfig(configKey),
|
||||
catch: (error) => cliCommandError("config.delete", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,23 +5,24 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket, writeJson } from "./util.js";
|
||||
|
||||
export function registerEmbeddingsCommands(program: Command): void {
|
||||
program
|
||||
.command("embeddings")
|
||||
.description("Generate text embeddings")
|
||||
.argument("<text...>", "Text(s) to embed")
|
||||
.action(async (texts: string[], _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((texts: string[], _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket, opts) =>
|
||||
Effect.gen(function* () {
|
||||
const flow = socket.flow(opts.flow);
|
||||
const vectors = await flow.embeddings(texts);
|
||||
console.log(JSON.stringify(vectors, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const vectors = yield* Effect.tryPromise({
|
||||
try: () => flow.embeddings(texts),
|
||||
catch: (error) => cliCommandError("embeddings", error),
|
||||
});
|
||||
yield* writeJson(vectors);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,9 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
import { cliCommandError, withSocket, writeJson } from "./util.js";
|
||||
|
||||
export function registerFlowCommands(program: Command): void {
|
||||
const flow = program
|
||||
|
|
@ -15,35 +17,35 @@ export function registerFlowCommands(program: Command): void {
|
|||
flow
|
||||
.command("list")
|
||||
.description("List active flows")
|
||||
.action(async (_opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((_opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const flows = socket.flows();
|
||||
const ids = await flows.getFlows();
|
||||
console.log(JSON.stringify(ids, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const ids = yield* Effect.tryPromise({
|
||||
try: () => flows.getFlows(),
|
||||
catch: (error) => cliCommandError("flow.list", error),
|
||||
});
|
||||
yield* writeJson(ids);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
flow
|
||||
.command("get")
|
||||
.description("Get a flow definition")
|
||||
.argument("<id>", "Flow ID")
|
||||
.action(async (id: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((id: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const flows = socket.flows();
|
||||
const def = await flows.getFlow(id);
|
||||
console.log(JSON.stringify(def, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const def = yield* Effect.tryPromise({
|
||||
try: () => flows.getFlow(id),
|
||||
catch: (error) => cliCommandError("flow.get", error),
|
||||
});
|
||||
yield* writeJson(def);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
flow
|
||||
.command("start")
|
||||
|
|
@ -52,42 +54,46 @@ export function registerFlowCommands(program: Command): void {
|
|||
.requiredOption("-b, --blueprint <name>", "Blueprint name")
|
||||
.option("-d, --description <text>", "Flow description", "")
|
||||
.option("-p, --parameters <json>", "Parameters as JSON")
|
||||
.action(async (id: string, cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((id: string, cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const flows = socket.flows();
|
||||
const rawParameters = cmdOpts.parameters as string | undefined;
|
||||
const params = rawParameters !== undefined && rawParameters.length > 0
|
||||
? JSON.parse(rawParameters)
|
||||
? yield* S.decodeUnknownEffect(S.UnknownFromJsonString)(rawParameters).pipe(
|
||||
Effect.flatMap(S.decodeUnknownEffect(S.Record(S.String, S.Unknown))),
|
||||
Effect.mapError((error) => cliCommandError("flow.start.parameters", error)),
|
||||
)
|
||||
: undefined;
|
||||
const resp = await flows.startFlow(
|
||||
id,
|
||||
cmdOpts.blueprint as string,
|
||||
cmdOpts.description as string,
|
||||
params as Record<string, unknown> | undefined,
|
||||
);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
flows.startFlow(
|
||||
id,
|
||||
cmdOpts.blueprint as string,
|
||||
cmdOpts.description as string,
|
||||
params,
|
||||
),
|
||||
catch: (error) => cliCommandError("flow.start", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
flow
|
||||
.command("stop")
|
||||
.description("Stop a flow")
|
||||
.argument("<id>", "Flow ID")
|
||||
.action(async (id: string, _opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((id: string, _opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const flows = socket.flows();
|
||||
const resp = await flows.stopFlow(id);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => flows.stopFlow(id),
|
||||
catch: (error) => cliCommandError("flow.stop", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket, writeLine } from "./util.js";
|
||||
|
||||
export function registerGraphRagCommands(program: Command): void {
|
||||
program
|
||||
|
|
@ -15,26 +16,27 @@ export function registerGraphRagCommands(program: Command): void {
|
|||
.option("--entity-limit <n>", "Max entities", "50")
|
||||
.option("--triple-limit <n>", "Max triples per entity", "30")
|
||||
.option("--collection <name>", "Collection name")
|
||||
.action(async (query: string, cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((query: string, cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket, opts) =>
|
||||
Effect.gen(function* () {
|
||||
const flow = socket.flow(opts.flow);
|
||||
const collection = cmdOpts.collection as string | undefined;
|
||||
const response = await flow.graphRag(
|
||||
query,
|
||||
{
|
||||
entityLimit: parseInt(cmdOpts.entityLimit, 10),
|
||||
tripleLimit: parseInt(cmdOpts.tripleLimit, 10),
|
||||
},
|
||||
collection,
|
||||
);
|
||||
console.log(response);
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const response = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
flow.graphRag(
|
||||
query,
|
||||
{
|
||||
entityLimit: parseInt(cmdOpts.entityLimit, 10),
|
||||
tripleLimit: parseInt(cmdOpts.tripleLimit, 10),
|
||||
},
|
||||
collection,
|
||||
),
|
||||
catch: (error) => cliCommandError("graph-rag", error),
|
||||
});
|
||||
yield* writeLine(response);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
program
|
||||
.command("document-rag")
|
||||
|
|
@ -42,24 +44,25 @@ export function registerGraphRagCommands(program: Command): void {
|
|||
.argument("<query>", "Natural language query")
|
||||
.option("--doc-limit <n>", "Max documents", "20")
|
||||
.option("--collection <name>", "Collection name")
|
||||
.action(async (query: string, cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((query: string, cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket, opts) =>
|
||||
Effect.gen(function* () {
|
||||
const flow = socket.flow(opts.flow);
|
||||
const docLimit = cmdOpts.docLimit as string | undefined;
|
||||
const collection = cmdOpts.collection as string | undefined;
|
||||
const response = await flow.documentRag(
|
||||
query,
|
||||
docLimit !== undefined && docLimit.length > 0
|
||||
? parseInt(docLimit, 10)
|
||||
: undefined,
|
||||
collection,
|
||||
);
|
||||
console.log(response);
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const response = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
flow.documentRag(
|
||||
query,
|
||||
docLimit !== undefined && docLimit.length > 0
|
||||
? parseInt(docLimit, 10)
|
||||
: undefined,
|
||||
collection,
|
||||
),
|
||||
catch: (error) => cliCommandError("document-rag", error),
|
||||
});
|
||||
yield* writeLine(response);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
*/
|
||||
|
||||
import type { Command } from "commander";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket, writeJson } from "./util.js";
|
||||
|
||||
function basenamePath(filepath: string): string {
|
||||
const normalized = filepath.replace(/\/+$/, "");
|
||||
|
|
@ -45,18 +46,18 @@ export function registerLibraryCommands(program: Command): void {
|
|||
library
|
||||
.command("list")
|
||||
.description("List documents in the library")
|
||||
.action(async (_opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((_opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const lib = socket.librarian();
|
||||
const docs = await lib.getDocuments();
|
||||
console.log(JSON.stringify(docs, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const docs = yield* Effect.tryPromise({
|
||||
try: () => lib.getDocuments(),
|
||||
catch: (error) => cliCommandError("library.list", error),
|
||||
});
|
||||
yield* writeJson(docs);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
library
|
||||
.command("load")
|
||||
|
|
@ -67,64 +68,68 @@ export function registerLibraryCommands(program: Command): void {
|
|||
.option("-c, --comments <text>", "Comments", "")
|
||||
.option("--tags <tags...>", "Document tags")
|
||||
.option("--id <id>", "Optional document ID")
|
||||
.action(async (file: string, cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((file: string, cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const lib = socket.librarian();
|
||||
const data = new Uint8Array(await Bun.file(file).arrayBuffer());
|
||||
const data = new Uint8Array(yield* Effect.tryPromise({
|
||||
try: () => Bun.file(file).arrayBuffer(),
|
||||
catch: (error) => cliCommandError("library.load.read-file", error),
|
||||
}));
|
||||
const b64 = Buffer.from(data).toString("base64");
|
||||
const mimeType = (cmdOpts.mimeType as string | undefined) ?? guessMimeType(file);
|
||||
const title = (cmdOpts.title as string | undefined) ?? basenamePath(file);
|
||||
const comments = cmdOpts.comments as string;
|
||||
const tags: string[] = (cmdOpts.tags as string[] | undefined) ?? [];
|
||||
|
||||
const resp = await lib.loadDocument(
|
||||
b64,
|
||||
mimeType,
|
||||
title,
|
||||
comments,
|
||||
tags,
|
||||
cmdOpts.id as string | undefined,
|
||||
);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
lib.loadDocument(
|
||||
b64,
|
||||
mimeType,
|
||||
title,
|
||||
comments,
|
||||
tags,
|
||||
cmdOpts.id as string | undefined,
|
||||
),
|
||||
catch: (error) => cliCommandError("library.load", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
library
|
||||
.command("remove")
|
||||
.description("Remove a document from the library")
|
||||
.argument("<id>", "Document ID to remove")
|
||||
.option("--collection <name>", "Collection name")
|
||||
.action(async (id: string, cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((id: string, cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const lib = socket.librarian();
|
||||
const resp = await lib.removeDocument(id, cmdOpts.collection as string | undefined);
|
||||
console.log(JSON.stringify(resp, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const resp = yield* Effect.tryPromise({
|
||||
try: () => lib.removeDocument(id, cmdOpts.collection as string | undefined),
|
||||
catch: (error) => cliCommandError("library.remove", error),
|
||||
});
|
||||
yield* writeJson(resp);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
|
||||
library
|
||||
.command("processing")
|
||||
.description("List documents currently being processed")
|
||||
.action(async (_opts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((_opts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket) =>
|
||||
Effect.gen(function* () {
|
||||
const lib = socket.librarian();
|
||||
const items = await lib.getProcessing();
|
||||
console.log(JSON.stringify(items, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const items = yield* Effect.tryPromise({
|
||||
try: () => lib.getProcessing(),
|
||||
catch: (error) => cliCommandError("library.processing", error),
|
||||
});
|
||||
yield* writeJson(items);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
|
||||
import type { Command } from "commander";
|
||||
import type { Term } from "@trustgraph/client";
|
||||
import { createSocket, getOpts } from "./util.js";
|
||||
import { Effect } from "effect";
|
||||
import { cliCommandError, withSocket, writeJson } from "./util.js";
|
||||
|
||||
export function registerTriplesCommands(program: Command): void {
|
||||
program
|
||||
|
|
@ -17,11 +18,9 @@ export function registerTriplesCommands(program: Command): void {
|
|||
.option("-o, --object <iri>", "Object IRI or literal")
|
||||
.option("-l, --limit <n>", "Max results", "20")
|
||||
.option("--collection <name>", "Collection name")
|
||||
.action(async (cmdOpts, cmd) => {
|
||||
const opts = getOpts(cmd);
|
||||
const socket = await createSocket(opts);
|
||||
|
||||
try {
|
||||
.action((cmdOpts, cmd) =>
|
||||
Effect.runPromise(withSocket(cmd, (socket, opts) =>
|
||||
Effect.gen(function* () {
|
||||
const flow = socket.flow(opts.flow);
|
||||
const subject = cmdOpts.subject as string | undefined;
|
||||
const predicate = cmdOpts.predicate as string | undefined;
|
||||
|
|
@ -36,16 +35,19 @@ export function registerTriplesCommands(program: Command): void {
|
|||
? { t: "i", i: object }
|
||||
: undefined;
|
||||
|
||||
const triples = await flow.triplesQuery(
|
||||
s,
|
||||
p,
|
||||
o,
|
||||
parseInt(cmdOpts.limit as string, 10),
|
||||
cmdOpts.collection as string | undefined,
|
||||
);
|
||||
console.log(JSON.stringify(triples, null, 2));
|
||||
} finally {
|
||||
socket.close();
|
||||
}
|
||||
});
|
||||
const triples = yield* Effect.tryPromise({
|
||||
try: () =>
|
||||
flow.triplesQuery(
|
||||
s,
|
||||
p,
|
||||
o,
|
||||
parseInt(cmdOpts.limit as string, 10),
|
||||
cmdOpts.collection as string | undefined,
|
||||
),
|
||||
catch: (error) => cliCommandError("triples", error),
|
||||
});
|
||||
yield* writeJson(triples);
|
||||
}),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
import type { Command } from "commander";
|
||||
import { createTrustGraphSocket, type BaseApi } from "@trustgraph/client";
|
||||
import { Duration, Effect } from "effect";
|
||||
import * as S from "effect/Schema";
|
||||
|
||||
export interface CliOpts {
|
||||
gateway: string;
|
||||
|
|
@ -19,36 +21,76 @@ export function getOpts(cmd: Command): CliOpts {
|
|||
return root.opts() as CliOpts;
|
||||
}
|
||||
|
||||
export class CliCommandError extends S.TaggedErrorClass<CliCommandError>()(
|
||||
"CliCommandError",
|
||||
{
|
||||
message: S.String,
|
||||
operation: S.String,
|
||||
},
|
||||
) {}
|
||||
|
||||
export function cliCommandError(operation: string, error: unknown): CliCommandError {
|
||||
const message = typeof error === "object" && error !== null && "message" in error
|
||||
? String(error.message)
|
||||
: String(error);
|
||||
return CliCommandError.make({ operation, message });
|
||||
}
|
||||
|
||||
export const writeLine = (line: string) =>
|
||||
Effect.sync(() => {
|
||||
process.stdout.write(`${line}\n`);
|
||||
});
|
||||
|
||||
export const writeJson = (value: unknown) =>
|
||||
S.encodeUnknownEffect(S.UnknownFromJsonString)(value).pipe(
|
||||
Effect.mapError((error) => cliCommandError("write-json", error)),
|
||||
Effect.flatMap(writeLine),
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a BaseApi socket client and wait for the connection to be established.
|
||||
* The client auto-connects; we listen for the first "connected/authenticated"
|
||||
* state before handing it back to the caller.
|
||||
*/
|
||||
export async function createSocket(opts: CliOpts): Promise<BaseApi> {
|
||||
export function createSocketEffect(opts: CliOpts): Effect.Effect<BaseApi, CliCommandError> {
|
||||
const socket = createTrustGraphSocket(opts.user, opts.token, opts.gateway);
|
||||
|
||||
// Wait for the socket to reach an open state
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
unsub();
|
||||
reject(new Error("Timed out waiting for WebSocket connection"));
|
||||
}, 15_000);
|
||||
|
||||
return Effect.callback<void, CliCommandError>((resume) => {
|
||||
const unsub = socket.onConnectionStateChange((state) => {
|
||||
if (
|
||||
state.status === "authenticated" ||
|
||||
state.status === "unauthenticated"
|
||||
) {
|
||||
clearTimeout(timeout);
|
||||
if (state.status === "authenticated" || state.status === "unauthenticated") {
|
||||
unsub();
|
||||
resolve();
|
||||
resume(Effect.void);
|
||||
} else if (state.status === "failed") {
|
||||
clearTimeout(timeout);
|
||||
unsub();
|
||||
reject(new Error(state.lastError ?? "WebSocket connection failed"));
|
||||
resume(Effect.fail(cliCommandError("connect", state.lastError ?? "WebSocket connection failed")));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return socket;
|
||||
return Effect.sync(() => {
|
||||
unsub();
|
||||
});
|
||||
}).pipe(
|
||||
Effect.timeout(Duration.seconds(15)),
|
||||
Effect.catchTag("TimeoutError", () =>
|
||||
Effect.fail(cliCommandError("connect", "Timed out waiting for WebSocket connection")),
|
||||
),
|
||||
Effect.as(socket),
|
||||
);
|
||||
}
|
||||
|
||||
export function createSocket(opts: CliOpts): Promise<BaseApi> {
|
||||
return Effect.runPromise(createSocketEffect(opts));
|
||||
}
|
||||
|
||||
export const withSocket = <A, E, R>(
|
||||
cmd: Command,
|
||||
use: (socket: BaseApi, opts: CliOpts) => Effect.Effect<A, E, R>,
|
||||
) =>
|
||||
Effect.acquireUseRelease(
|
||||
createSocketEffect(getOpts(cmd)),
|
||||
(socket) => use(socket, getOpts(cmd)),
|
||||
(socket) =>
|
||||
Effect.sync(() => {
|
||||
socket.close();
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue