mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-02 02:58:10 +02:00
saving
This commit is contained in:
parent
e8c7a4f6e0
commit
ffd97375a8
160 changed files with 6704 additions and 1895 deletions
|
|
@ -94,7 +94,7 @@ export class DispatcherManager {
|
|||
key: string,
|
||||
): Promise<RequestResponse<unknown, unknown>> {
|
||||
let pending = this.requestors.get(key);
|
||||
if (!pending) {
|
||||
if (pending === undefined) {
|
||||
pending = (async () => {
|
||||
const rr = new RequestResponse({
|
||||
pubsub: this.pubsub,
|
||||
|
|
@ -114,7 +114,7 @@ export class DispatcherManager {
|
|||
kind: string,
|
||||
): { requestTopic: string; responseTopic: string } {
|
||||
const entry = GLOBAL_SERVICES.get(kind);
|
||||
if (entry) {
|
||||
if (entry !== undefined) {
|
||||
return {
|
||||
requestTopic: topicName(entry.request),
|
||||
responseTopic: topicName(entry.response),
|
||||
|
|
@ -131,7 +131,7 @@ export class DispatcherManager {
|
|||
kind: string,
|
||||
): { requestTopic: string; responseTopic: string } {
|
||||
const entry = FLOW_SERVICES.get(kind);
|
||||
if (entry) {
|
||||
if (entry !== undefined) {
|
||||
return {
|
||||
requestTopic: topicName(entry.request),
|
||||
responseTopic: topicName(entry.response),
|
||||
|
|
@ -152,15 +152,15 @@ export class DispatcherManager {
|
|||
if (typeof response !== "object" || response === null) return true;
|
||||
const res = response as Record<string, unknown>;
|
||||
return (
|
||||
!!res.complete ||
|
||||
!!res.endOfStream ||
|
||||
!!res.endOfSession ||
|
||||
!!res.end_of_stream ||
|
||||
!!res.end_of_session ||
|
||||
!!res.end_of_dialog ||
|
||||
!!res.eos ||
|
||||
res.complete === true ||
|
||||
res.endOfStream === true ||
|
||||
res.endOfSession === true ||
|
||||
res.end_of_stream === true ||
|
||||
res.end_of_session === true ||
|
||||
res.end_of_dialog === true ||
|
||||
res.eos === true ||
|
||||
// error responses are always final
|
||||
!!res.error
|
||||
(res.error !== undefined && res.error !== null)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,11 @@ export class Mux {
|
|||
private queue = new AsyncQueue<MuxRequest>();
|
||||
private outstanding = 0;
|
||||
private running = true;
|
||||
private readonly handler: MuxHandler;
|
||||
|
||||
constructor(private readonly handler: MuxHandler) {}
|
||||
constructor(handler: MuxHandler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
receive(request: MuxRequest): void {
|
||||
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
||||
|
|
|
|||
|
|
@ -65,14 +65,18 @@ export function clientTermToInternal(wire: ClientTerm): Term {
|
|||
return {
|
||||
type: "LITERAL",
|
||||
value: wire.v,
|
||||
datatype: wire.dt,
|
||||
language: wire.ln,
|
||||
...(wire.dt !== undefined ? { datatype: wire.dt } : {}),
|
||||
...(wire.ln !== undefined ? { language: wire.ln } : {}),
|
||||
};
|
||||
case "t":
|
||||
case "t": {
|
||||
if (wire.tr === undefined) {
|
||||
throw new Error("Client triple term is missing tr");
|
||||
}
|
||||
return {
|
||||
type: "TRIPLE",
|
||||
triple: wire.tr ? clientTripleToInternal(wire.tr) : undefined!,
|
||||
triple: clientTripleToInternal(wire.tr),
|
||||
};
|
||||
}
|
||||
default:
|
||||
// Defensive: pass through unknown term types
|
||||
return wire as unknown as Term;
|
||||
|
|
@ -105,14 +109,14 @@ export function internalTermToClient(term: Term): ClientTerm {
|
|||
return { t: "b", d: term.id };
|
||||
case "LITERAL": {
|
||||
const lit: ClientLiteralTerm = { t: "l", v: term.value };
|
||||
if (term.datatype) lit.dt = term.datatype;
|
||||
if (term.language) lit.ln = term.language;
|
||||
if (term.datatype !== undefined) lit.dt = term.datatype;
|
||||
if (term.language !== undefined) lit.ln = term.language;
|
||||
return lit;
|
||||
}
|
||||
case "TRIPLE":
|
||||
return {
|
||||
t: "t",
|
||||
tr: term.triple ? internalTripleToClient(term.triple) : undefined,
|
||||
tr: internalTripleToClient(term.triple),
|
||||
};
|
||||
default:
|
||||
return term as unknown as ClientTerm;
|
||||
|
|
@ -131,7 +135,10 @@ export function internalTripleToClient(triple: Triple): ClientTriple {
|
|||
result.g = g;
|
||||
} else {
|
||||
// If g is a Term, convert it back to client wire format
|
||||
result.g = (g as Record<string, unknown>).iri as string | undefined;
|
||||
const iri = (g as Record<string, unknown>).iri;
|
||||
if (typeof iri === "string") {
|
||||
result.g = iri;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@
|
|||
|
||||
import Fastify from "fastify";
|
||||
import websocketPlugin from "@fastify/websocket";
|
||||
import { registry } from "@trustgraph/base";
|
||||
import { Config, Effect } from "effect";
|
||||
import * as O from "effect/Option";
|
||||
import { errorMessage, optionalStringConfig, registry, toTgError } from "@trustgraph/base";
|
||||
import { DispatcherManager } from "./dispatch/manager.js";
|
||||
import { Mux, type MuxRequest, type MuxHandler } from "./dispatch/mux.js";
|
||||
|
||||
|
|
@ -33,9 +35,9 @@ export async function createGateway(config: GatewayConfig) {
|
|||
if (request.url === "/api/v1/metrics") return;
|
||||
if (request.url.startsWith("/api/v1/socket")) return; // Socket auth via query param
|
||||
|
||||
if (config.secret) {
|
||||
if (config.secret !== undefined && config.secret.length > 0) {
|
||||
const auth = request.headers.authorization;
|
||||
if (!auth || auth !== `Bearer ${config.secret}`) {
|
||||
if (auth === undefined || auth !== `Bearer ${config.secret}`) {
|
||||
reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
}
|
||||
|
|
@ -49,13 +51,13 @@ export async function createGateway(config: GatewayConfig) {
|
|||
try {
|
||||
const result = await dispatcher.dispatchGlobalService(kind, body) as Record<string, unknown>;
|
||||
const err = result?.error as { type?: string; message?: string } | undefined;
|
||||
if (err) {
|
||||
if (err !== undefined) {
|
||||
const statusCode = err.type === "not-found" ? 404 : 400;
|
||||
return reply.code(statusCode).send(result);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
reply.code(500).send({ error: { type: "internal", message: String(err) } });
|
||||
reply.code(500).send({ error: toTgError(err) });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -69,13 +71,13 @@ export async function createGateway(config: GatewayConfig) {
|
|||
try {
|
||||
const result = await dispatcher.dispatchFlowService(flow, kind, body) as Record<string, unknown>;
|
||||
const err = result?.error as { type?: string; message?: string } | undefined;
|
||||
if (err) {
|
||||
if (err !== undefined) {
|
||||
const statusCode = err.type === "not-found" ? 404 : 400;
|
||||
return reply.code(statusCode).send(result);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
reply.code(500).send({ error: { type: "internal", message: String(err) } });
|
||||
reply.code(500).send({ error: toTgError(err) });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
@ -91,7 +93,7 @@ export async function createGateway(config: GatewayConfig) {
|
|||
collection?: string;
|
||||
};
|
||||
|
||||
if (!body.documentId) {
|
||||
if (body.documentId === undefined || body.documentId.length === 0) {
|
||||
return reply.code(400).send({
|
||||
error: { type: "bad-request", message: "documentId is required" },
|
||||
});
|
||||
|
|
@ -116,7 +118,7 @@ export async function createGateway(config: GatewayConfig) {
|
|||
return { status: "processing", documentId, flow };
|
||||
} catch (err) {
|
||||
reply.code(500).send({
|
||||
error: { type: "internal", message: String(err) },
|
||||
error: toTgError(err),
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -128,14 +130,14 @@ export async function createGateway(config: GatewayConfig) {
|
|||
// Auth via query param
|
||||
const url = new URL(request.url, `http://${request.headers.host}`);
|
||||
const token = url.searchParams.get("token");
|
||||
if (config.secret && token !== config.secret) {
|
||||
if (config.secret !== undefined && config.secret.length > 0 && token !== config.secret) {
|
||||
socket.close(4001, "Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the MuxHandler that dispatches to the DispatcherManager
|
||||
const handler: MuxHandler = async (muxReq, respond) => {
|
||||
if (muxReq.flow) {
|
||||
if (muxReq.flow !== undefined && muxReq.flow.length > 0) {
|
||||
await dispatcher.dispatchFlowServiceStreaming(
|
||||
muxReq.flow,
|
||||
muxReq.service,
|
||||
|
|
@ -171,7 +173,13 @@ export async function createGateway(config: GatewayConfig) {
|
|||
request?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
if (!msg.id || !msg.service || !msg.request) {
|
||||
if (
|
||||
msg.id === undefined ||
|
||||
msg.id.length === 0 ||
|
||||
msg.service === undefined ||
|
||||
msg.service.length === 0 ||
|
||||
msg.request === undefined
|
||||
) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
id: msg.id ?? null,
|
||||
|
|
@ -185,15 +193,15 @@ export async function createGateway(config: GatewayConfig) {
|
|||
const muxReq: MuxRequest = {
|
||||
id: msg.id,
|
||||
service: msg.service,
|
||||
flow: msg.flow,
|
||||
request: msg.request,
|
||||
...(msg.flow !== undefined ? { flow: msg.flow } : {}),
|
||||
};
|
||||
|
||||
mux.receive(muxReq);
|
||||
} catch (err) {
|
||||
socket.send(
|
||||
JSON.stringify({
|
||||
error: { type: "parse-error", message: String(err) },
|
||||
error: { type: "parse-error", message: errorMessage(err) },
|
||||
complete: true,
|
||||
}),
|
||||
);
|
||||
|
|
@ -234,14 +242,36 @@ export async function createGateway(config: GatewayConfig) {
|
|||
}
|
||||
|
||||
export async function run(): Promise<void> {
|
||||
const config: GatewayConfig = {
|
||||
port: parseInt(process.env.GATEWAY_PORT ?? "8088", 10),
|
||||
metricsPort: parseInt(process.env.METRICS_PORT ?? "8000", 10),
|
||||
secret: process.env.GATEWAY_SECRET,
|
||||
natsUrl: process.env.NATS_URL,
|
||||
};
|
||||
|
||||
const gateway = await createGateway(config);
|
||||
await gateway.start();
|
||||
console.log(`[Gateway] Listening on port ${config.port}`);
|
||||
await Effect.runPromise(program);
|
||||
}
|
||||
|
||||
export const loadGatewayConfig = Effect.fn("loadGatewayConfig")(function* () {
|
||||
const secret = O.getOrUndefined(yield* Config.string("GATEWAY_SECRET").pipe(Config.option));
|
||||
const natsUrl = yield* optionalStringConfig("NATS_URL");
|
||||
const port = yield* Config.number("GATEWAY_PORT").pipe(Config.withDefault(8088));
|
||||
const metricsPort = yield* Config.number("METRICS_PORT").pipe(Config.withDefault(8000));
|
||||
return {
|
||||
port,
|
||||
metricsPort,
|
||||
...(secret !== undefined ? { secret } : {}),
|
||||
...(natsUrl !== undefined ? { natsUrl } : {}),
|
||||
} satisfies GatewayConfig;
|
||||
});
|
||||
|
||||
export const program = Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const config = yield* loadGatewayConfig();
|
||||
const gateway = yield* Effect.promise(() => createGateway(config)).pipe(Effect.orDie);
|
||||
yield* Effect.addFinalizer(() => Effect.promise(() => gateway.stop()).pipe(Effect.orDie));
|
||||
yield* Effect.promise(() => gateway.start()).pipe(
|
||||
Effect.orDie,
|
||||
Effect.withSpan("trustgraph.gateway.start", {
|
||||
attributes: {
|
||||
"trustgraph.gateway.port": config.port,
|
||||
},
|
||||
}),
|
||||
);
|
||||
yield* Effect.log(`[Gateway] Listening on port ${config.port}`);
|
||||
return yield* Effect.never;
|
||||
}),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue