@spinejs/electron-ipc-gateway
v0.1.5
Published
Electron IPC gateway for SpineJS.
Readme
@spinejs/electron-ipc-gateway
Electron IPC transport for @spinejs/gateway-core. Each handle(channel, …) route becomes an ipcMain.handle(channel, …) listener. It composes the pipeline (it does not extend a base class).
Quick start
Register your context once, write a controller, register it.
// electron-ipc.types.ts — register your context ONCE as the default `ctx` of every route
import type { ElectronIpcBaseContext } from "@spinejs/electron-ipc-gateway";
export interface ElectronIpcContext extends ElectronIpcBaseContext {
session: { userId: string };
}
declare module "@spinejs/electron-ipc-gateway" {
interface IpcContextRegistry {
context: ElectronIpcContext;
}
}// projects.controller.ts
import { z } from "zod";
import { Controller, UseGuards } from "@spinejs/gateway-core";
import { handle } from "@spinejs/electron-ipc-gateway";
import { SessionGuard } from "./session.guard";
@UseGuards(SessionGuard)
@Controller({ inject: [ProjectsService] })
export class ProjectsController {
constructor(private readonly projects: ProjectsService) {}
list = handle("projects:list", {}, (_input, ctx) =>
this.projects.findAll(ctx.session.userId)
);
create = handle(
"projects:create",
{ input: z.object({ name: z.string().min(1) }) },
(input, ctx) => this.projects.create(ctx.session.userId, input.name)
);
}// projects.ipc.module.ts
import { IpcModule } from "./electron-ipc-module";
import { ProjectsController } from "./projects.controller";
@IpcModule({ controllers: [ProjectsController] })
export class ProjectsIpcModule {}On the renderer, discriminate on the envelope:
const res = await ipcRenderer.invoke("projects:list");
if (res.ok) console.log(res.data);
else console.error(res.code); // 'UNAUTHORIZED', 'SERVER', …Wiring the transport module
ElectronIpcGatewayModule wires the three ports and produces the gateway. Build it once per app; it defaults to ZodValidator. Then bind the feature helpers:
// electron-ipc-module.ts
import {
gatewayFeatureFactory,
gatewayModuleDecorator,
} from "@spinejs/gateway-core";
import { ElectronIpcGateway } from "@spinejs/electron-ipc-gateway";
import { ElectronIpcGatewayModule } from "./electron-ipc-gateway.module";
export const ipcFeature = gatewayFeatureFactory(
ElectronIpcGateway,
ElectronIpcGatewayModule
);
export const IpcModule = gatewayModuleDecorator(
ElectronIpcGateway,
ElectronIpcGatewayModule
);Implementing the ports
// ContextFactory — enrich the context
export class SessionContextFactory
implements ContextFactory<ElectronIpcRaw, ElectronIpcContext>
{
constructor(private readonly sessionStore: SessionStore) {}
create(raw: ElectronIpcRaw): ElectronIpcContext {
return { event: raw.event, session: this.sessionStore.current() };
}
}// ErrorMapper — no raw message ever reaches the renderer
import {
ErrorMapper,
UnauthorizedError,
ValidationError,
} from "@spinejs/gateway-core";
type ErrorCode = "UNAUTHORIZED" | "INVALID_INPUT" | "NOT_FOUND" | "SERVER";
export class AppErrorMapper implements ErrorMapper<ErrorCode> {
toCode(err: unknown): ErrorCode {
if (err instanceof UnauthorizedError) return "UNAUTHORIZED";
if (err instanceof ValidationError) return "INVALID_INPUT";
if (err instanceof NotFoundError) return "NOT_FOUND";
return "SERVER";
}
}Reference
- Exports:
ElectronIpcGateway,ElectronIpcGatewayModule, the route helperhandle(and the deprecatedipcRoutesfactory),ipcFeature,IpcModule,IpcLoggingInterceptor,IpcLogRedactor,ZodValidator,DefaultErrorMapper, and theElectronIpcBaseContext/ElectronIpcRaw/IpcContextRegistry/DefaultCtxtypes. - Log redaction:
new IpcLoggingInterceptor(logger, redact)— the optionalIpcLogRedactor(channel, input) => unknownmasks what gets logged (per-channel), never the input passed to the handler. - Raw input:
ipcRenderer.invoke(channel, arg)→argasrawInput; multiple args →[a, b]. Prefer a single object argument per call.
