mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-04 10:52:27 +02:00
saving
This commit is contained in:
parent
9e9307a2aa
commit
e26caa0b12
123 changed files with 3478 additions and 10078 deletions
110
ts/packages/flow/src/gateway/dispatch/manager.ts
Normal file
110
ts/packages/flow/src/gateway/dispatch/manager.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Dispatcher manager — routes requests to backend services via pub/sub.
|
||||
*
|
||||
* Python reference: trustgraph-flow/trustgraph/gateway/dispatch/manager.py
|
||||
*/
|
||||
|
||||
import { NatsBackend, RequestResponse, type PubSubBackend } from "@trustgraph/base";
|
||||
import type { GatewayConfig } from "../server.js";
|
||||
|
||||
export type Responder = (response: unknown, complete: boolean) => Promise<void>;
|
||||
|
||||
export class DispatcherManager {
|
||||
private pubsub: PubSubBackend;
|
||||
private requestors = new Map<string, RequestResponse<unknown, unknown>>();
|
||||
|
||||
constructor(private readonly config: GatewayConfig) {
|
||||
this.pubsub = new NatsBackend(config.natsUrl ?? "nats://localhost:4222");
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
// Pre-create requestors for known global services
|
||||
// Flow-specific requestors are created on demand
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
for (const rr of this.requestors.values()) {
|
||||
await rr.stop();
|
||||
}
|
||||
await this.pubsub.close();
|
||||
}
|
||||
|
||||
private async getRequestor(
|
||||
requestTopic: string,
|
||||
responseTopic: string,
|
||||
key: string,
|
||||
): Promise<RequestResponse<unknown, unknown>> {
|
||||
let rr = this.requestors.get(key);
|
||||
if (!rr) {
|
||||
rr = new RequestResponse({
|
||||
pubsub: this.pubsub,
|
||||
requestTopic,
|
||||
responseTopic,
|
||||
subscription: `gateway-${key}`,
|
||||
});
|
||||
await rr.start();
|
||||
this.requestors.set(key, rr);
|
||||
}
|
||||
return rr;
|
||||
}
|
||||
|
||||
async dispatchGlobalService(
|
||||
kind: string,
|
||||
request: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const requestTopic = `tg.flow.${kind}-request`;
|
||||
const responseTopic = `tg.flow.${kind}-response`;
|
||||
const rr = await this.getRequestor(requestTopic, responseTopic, `global:${kind}`);
|
||||
return rr.request(request);
|
||||
}
|
||||
|
||||
async dispatchFlowService(
|
||||
flow: string,
|
||||
kind: string,
|
||||
request: Record<string, unknown>,
|
||||
): Promise<unknown> {
|
||||
const requestTopic = `tg.flow.${kind}-request`;
|
||||
const responseTopic = `tg.flow.${kind}-response`;
|
||||
const rr = await this.getRequestor(requestTopic, responseTopic, `flow:${flow}:${kind}`);
|
||||
return rr.request(request);
|
||||
}
|
||||
|
||||
async dispatchGlobalServiceStreaming(
|
||||
kind: string,
|
||||
request: Record<string, unknown>,
|
||||
responder: Responder,
|
||||
): Promise<void> {
|
||||
const requestTopic = `tg.flow.${kind}-request`;
|
||||
const responseTopic = `tg.flow.${kind}-response`;
|
||||
const rr = await this.getRequestor(requestTopic, responseTopic, `global:${kind}`);
|
||||
|
||||
await rr.request(request, {
|
||||
recipient: async (response) => {
|
||||
const res = response as Record<string, unknown>;
|
||||
const complete = !!res.complete || !!res.endOfStream || !!res.endOfSession;
|
||||
await responder(res, complete);
|
||||
return complete;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async dispatchFlowServiceStreaming(
|
||||
flow: string,
|
||||
kind: string,
|
||||
request: Record<string, unknown>,
|
||||
responder: Responder,
|
||||
): Promise<void> {
|
||||
const requestTopic = `tg.flow.${kind}-request`;
|
||||
const responseTopic = `tg.flow.${kind}-response`;
|
||||
const rr = await this.getRequestor(requestTopic, responseTopic, `flow:${flow}:${kind}`);
|
||||
|
||||
await rr.request(request, {
|
||||
recipient: async (response) => {
|
||||
const res = response as Record<string, unknown>;
|
||||
const complete = !!res.complete || !!res.endOfStream || !!res.endOfSession;
|
||||
await responder(res, complete);
|
||||
return complete;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
86
ts/packages/flow/src/gateway/dispatch/mux.ts
Normal file
86
ts/packages/flow/src/gateway/dispatch/mux.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* WebSocket multiplexer — handles concurrent requests over a single connection.
|
||||
*
|
||||
* Python reference: trustgraph-flow/trustgraph/gateway/dispatch/mux.py
|
||||
*/
|
||||
|
||||
import { AsyncQueue } from "@trustgraph/base";
|
||||
|
||||
const MAX_OUTSTANDING = 15;
|
||||
const MAX_QUEUE_SIZE = 10;
|
||||
|
||||
export interface MuxRequest {
|
||||
id: string;
|
||||
service: string;
|
||||
flow?: string;
|
||||
request: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type MuxHandler = (
|
||||
request: MuxRequest,
|
||||
respond: (response: unknown, complete: boolean) => Promise<void>,
|
||||
) => Promise<void>;
|
||||
|
||||
export class Mux {
|
||||
private queue = new AsyncQueue<MuxRequest>();
|
||||
private outstanding = 0;
|
||||
private running = true;
|
||||
|
||||
constructor(private readonly handler: MuxHandler) {}
|
||||
|
||||
receive(request: MuxRequest): void {
|
||||
if (this.queue.length >= MAX_QUEUE_SIZE) {
|
||||
console.warn("[Mux] Queue full, dropping request:", request.id);
|
||||
return;
|
||||
}
|
||||
this.queue.push(request);
|
||||
}
|
||||
|
||||
async run(send: (data: string) => void): Promise<void> {
|
||||
while (this.running) {
|
||||
if (this.outstanding >= MAX_OUTSTANDING) {
|
||||
await sleep(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = await this.queue.pop(1000);
|
||||
this.outstanding++;
|
||||
|
||||
// Fire and forget — error handling inside
|
||||
this.processRequest(request, send).finally(() => {
|
||||
this.outstanding--;
|
||||
});
|
||||
} catch {
|
||||
// Timeout on queue pop — just loop
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
private async processRequest(
|
||||
request: MuxRequest,
|
||||
send: (data: string) => void,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.handler(request, async (response, complete) => {
|
||||
send(JSON.stringify({ id: request.id, response, complete }));
|
||||
});
|
||||
} catch (err) {
|
||||
send(
|
||||
JSON.stringify({
|
||||
id: request.id,
|
||||
error: { type: "internal", message: String(err) },
|
||||
complete: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue