Wire the CoreDeno sidecar into a fully bidirectional bridge: - Deno→Go (gRPC): Deno connects as CoreService client via polyfilled @grpc/grpc-js over Unix socket. Polyfill patches Deno 2.x http2 gaps (getDefaultSettings, pre-connected socket handling, remoteSettings). - Go→Deno (JSON-RPC): Go connects to Deno's newline-delimited JSON-RPC server for module lifecycle (LoadModule, UnloadModule, ModuleStatus). gRPC server direction avoided due to Deno http2.createServer limitations. - ProcessStart/ProcessStop: gRPC handlers delegate to process.Service with manifest permission gating (run permissions). - Deno runtime: main.ts boots DenoService server, connects CoreService client with retry + health-check round-trip, handles SIGTERM shutdown. 40 unit tests + 2 integration tests (Tier 1 boot + Tier 2 bidirectional). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
3.1 KiB
TypeScript
95 lines
3.1 KiB
TypeScript
// CoreService gRPC client — Deno calls Go for I/O operations.
|
|
// All filesystem, store, and process operations route through this client.
|
|
|
|
import * as grpc from "@grpc/grpc-js";
|
|
import * as protoLoader from "@grpc/proto-loader";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const PROTO_PATH = join(__dirname, "..", "proto", "coredeno.proto");
|
|
|
|
let packageDef: protoLoader.PackageDefinition | null = null;
|
|
|
|
function getProto(): any {
|
|
if (!packageDef) {
|
|
packageDef = protoLoader.loadSync(PROTO_PATH, {
|
|
keepCase: true,
|
|
longs: String,
|
|
enums: String,
|
|
defaults: true,
|
|
oneofs: true,
|
|
});
|
|
}
|
|
return grpc.loadPackageDefinition(packageDef).coredeno as any;
|
|
}
|
|
|
|
export interface CoreClient {
|
|
raw: any;
|
|
storeGet(group: string, key: string): Promise<{ value: string; found: boolean }>;
|
|
storeSet(group: string, key: string, value: string): Promise<{ ok: boolean }>;
|
|
fileRead(path: string, moduleCode: string): Promise<{ content: string }>;
|
|
fileWrite(path: string, content: string, moduleCode: string): Promise<{ ok: boolean }>;
|
|
fileList(path: string, moduleCode: string): Promise<{ entries: Array<{ name: string; is_dir: boolean; size: number }> }>;
|
|
fileDelete(path: string, moduleCode: string): Promise<{ ok: boolean }>;
|
|
processStart(command: string, args: string[], moduleCode: string): Promise<{ process_id: string }>;
|
|
processStop(processId: string): Promise<{ ok: boolean }>;
|
|
close(): void;
|
|
}
|
|
|
|
function promisify<T>(client: any, method: string, request: any): Promise<T> {
|
|
return new Promise((resolve, reject) => {
|
|
client[method](request, (err: Error | null, response: T) => {
|
|
if (err) reject(err);
|
|
else resolve(response);
|
|
});
|
|
});
|
|
}
|
|
|
|
export function createCoreClient(socketPath: string): CoreClient {
|
|
const proto = getProto();
|
|
const client = new proto.CoreService(
|
|
`unix:${socketPath}`,
|
|
grpc.credentials.createInsecure(),
|
|
);
|
|
|
|
return {
|
|
raw: client,
|
|
|
|
storeGet(group: string, key: string) {
|
|
return promisify(client, "StoreGet", { group, key });
|
|
},
|
|
|
|
storeSet(group: string, key: string, value: string) {
|
|
return promisify(client, "StoreSet", { group, key, value });
|
|
},
|
|
|
|
fileRead(path: string, moduleCode: string) {
|
|
return promisify(client, "FileRead", { path, module_code: moduleCode });
|
|
},
|
|
|
|
fileWrite(path: string, content: string, moduleCode: string) {
|
|
return promisify(client, "FileWrite", { path, content, module_code: moduleCode });
|
|
},
|
|
|
|
fileList(path: string, moduleCode: string) {
|
|
return promisify(client, "FileList", { path, module_code: moduleCode });
|
|
},
|
|
|
|
fileDelete(path: string, moduleCode: string) {
|
|
return promisify(client, "FileDelete", { path, module_code: moduleCode });
|
|
},
|
|
|
|
processStart(command: string, args: string[], moduleCode: string) {
|
|
return promisify(client, "ProcessStart", { command, args, module_code: moduleCode });
|
|
},
|
|
|
|
processStop(processId: string) {
|
|
return promisify(client, "ProcessStop", { process_id: processId });
|
|
},
|
|
|
|
close() {
|
|
client.close();
|
|
},
|
|
};
|
|
}
|