trellis
Version:
Agentic State Engine — event-sourced causal graph with branching, decision traces, and realtime sync for AI-native applications
89 lines • 2.87 kB
TypeScript
/**
* Trellis Server — Realtime Subscription Engine
*
* InstantDB-style reactive queries over WebSocket.
*
* Clients subscribe to an EQL-S query string. When any op lands that touches
* an entity type in that query's result set, the server re-runs the query
* and pushes the full updated result (plus a diff) to subscribed clients.
*
* Protocol (JSON over WebSocket):
*
* Client → Server:
* { type: "subscribe", id: "sub_1", query: "find Post where ...",
* entityType?: "Post", resolve?: { author: true }, tenantId?: "t1" }
* { type: "unsubscribe", id: "sub_1" }
* { type: "ping" }
*
* Server → Client:
* { type: "subscribed", id: "sub_1" }
* { type: "data", id: "sub_1", result: [...], diff: { added, updated, removed },
* resolved?: true }
* { type: "error", id: "sub_1", message: "..." }
* { type: "pong" }
*
* @module trellis/server
*/
import type { ResolveSpec } from '../schema/resolve.js';
import type { TenantPool } from './tenancy.js';
import type { UsageMeter } from './usage-meter.js';
import type { AuthContext } from './auth.js';
import type { PermissionRegistry } from './permissions.js';
export interface Subscription {
id: string;
query: string;
tenantId: string | null;
auth: AuthContext;
lastResult: Record<string, unknown>[];
/** Entity type name — enables server-side `resolve` expansion. */
entityType?: string;
resolve?: ResolveSpec;
}
export interface WsClient {
id: string;
ws: {
send(data: string): void;
readyState: number;
};
subscriptions: Map<string, Subscription>;
auth: AuthContext;
tenantId: string | null;
}
export type RealtimeMessage = {
type: 'subscribe';
id: string;
query: string;
tenantId?: string;
entityType?: string;
resolve?: ResolveSpec;
} | {
type: 'unsubscribe';
id: string;
} | {
type: 'ping';
};
/**
* Manages WebSocket clients and their query subscriptions.
* Call `notify(tenantId)` after any mutation to fan out updates.
*/
export declare class SubscriptionManager {
private clients;
private pool;
private permissions;
private meter;
constructor(pool: TenantPool, permissions?: PermissionRegistry | null, meter?: UsageMeter | null);
addClient(clientId: string, ws: WsClient['ws'], auth: AuthContext, tenantId: string | null): void;
removeClient(clientId: string): void;
handleMessage(clientId: string, raw: string): Promise<void>;
/**
* Re-evaluate all subscriptions for a given tenant and push diffs.
* Called after every write op lands.
*/
notify(tenantId: string | null): Promise<void>;
get clientCount(): number;
private _handleSubscribe;
private _pushUpdate;
private _recordGraphIo;
private _send;
}
//# sourceMappingURL=realtime.d.ts.map