Implement local SQLite backend and reactive UI
This commit is contained in:
@@ -12,6 +12,8 @@ export type IpcMethod<Input, Output> = {
|
||||
input: z.ZodType<Input>;
|
||||
output: z.ZodType<Output>;
|
||||
handler: (input: Input) => Promise<Output> | Output;
|
||||
rawArgs?: boolean;
|
||||
rawResult?: boolean;
|
||||
};
|
||||
|
||||
function validationDetail(error: z.ZodError): unknown {
|
||||
@@ -22,10 +24,11 @@ function validationDetail(error: z.ZodError): unknown {
|
||||
}
|
||||
|
||||
export function registerIpcMethod<Input, Output>(method: IpcMethod<Input, Output>): void {
|
||||
ipcMain.handle(method.channel, async (_event, rawInput): Promise<IpcResult<Output>> => {
|
||||
ipcMain.handle(method.channel, async (_event, ...rawArgs): Promise<IpcResult<Output> | Output> => {
|
||||
const rawInput = method.rawArgs ? rawArgs : rawArgs[0];
|
||||
const parsedInput = method.input.safeParse(rawInput);
|
||||
if (!parsedInput.success) {
|
||||
return {
|
||||
const errorResult: IpcResult<Output> = {
|
||||
ok: false,
|
||||
error: {
|
||||
code: "VALIDATION_ERROR",
|
||||
@@ -33,29 +36,32 @@ export function registerIpcMethod<Input, Output>(method: IpcMethod<Input, Output
|
||||
detail: validationDetail(parsedInput.error),
|
||||
},
|
||||
};
|
||||
return method.rawResult ? errorResult as Output : errorResult;
|
||||
}
|
||||
|
||||
try {
|
||||
const output = await method.handler(parsedInput.data);
|
||||
const parsedOutput = method.output.safeParse(output);
|
||||
if (!parsedOutput.success) {
|
||||
return {
|
||||
const errorResult: IpcResult<Output> = {
|
||||
ok: false,
|
||||
error: {
|
||||
code: "INTERNAL_ERROR",
|
||||
message: "Invalid IPC response.",
|
||||
},
|
||||
};
|
||||
return method.rawResult ? errorResult as Output : errorResult;
|
||||
}
|
||||
return { ok: true, data: parsedOutput.data };
|
||||
return method.rawResult ? parsedOutput.data : { ok: true, data: parsedOutput.data };
|
||||
} catch (error) {
|
||||
return {
|
||||
const errorResult: IpcResult<Output> = {
|
||||
ok: false,
|
||||
error: {
|
||||
code: "INTERNAL_ERROR",
|
||||
message: error instanceof Error ? error.message : "Unhandled IPC failure.",
|
||||
},
|
||||
};
|
||||
return method.rawResult ? errorResult as Output : errorResult;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import { startAutoSnapshot } from "./snapshotService.js";
|
||||
import { registerFileHandlers } from "./fileHandler.js";
|
||||
import type { ClientSettings, RpcMethod, RpcResult } from "@mosaiciq/contracts/rpc";
|
||||
import { isRpcMethod, parseRpcRequest, parseRpcResponse } from "@mosaiciq/contracts/rpcSchemas";
|
||||
import { getDatabase, seedDatabase, getDataDir, updateClientSettings, getClientSettings } from "@mosaiciq/shared/db";
|
||||
import { getDatabase, bootstrapDatabase, getDataDir, updateClientSettings, getClientSettings } from "@mosaiciq/shared/db";
|
||||
import { eventEmitter } from "@mosaiciq/shared/agents";
|
||||
import { z } from "zod";
|
||||
import { registerIpcMethod } from "./ipc.js";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const isDev = process.env.VITE_DEV_SERVER_URL || !app.isPackaged;
|
||||
@@ -44,34 +45,49 @@ function rpcValidationDetail(error: z.ZodError): unknown {
|
||||
return error.issues.map((issue) => ({ path: issue.path.join("."), message: issue.message }));
|
||||
}
|
||||
|
||||
ipcMain.handle("rpc:call", async (_event, method: unknown, payload: unknown): Promise<RpcResult<RpcMethod>> => {
|
||||
if (!isRpcMethod(method)) {
|
||||
return { ok: false, error: { code: "VALIDATION_ERROR", message: "Unknown RPC method." } };
|
||||
}
|
||||
registerIpcMethod<unknown[], RpcResult<RpcMethod>>({
|
||||
channel: "rpc:call",
|
||||
input: z.tuple([z.unknown(), z.unknown()]),
|
||||
output: z.custom<RpcResult<RpcMethod>>((value) => {
|
||||
if (!value || typeof value !== "object" || !("ok" in value)) return false;
|
||||
const result = value as RpcResult<RpcMethod>;
|
||||
return result.ok ? "data" in result : "error" in result;
|
||||
}),
|
||||
rawArgs: true,
|
||||
rawResult: true,
|
||||
handler: async ([method, payload]): Promise<RpcResult<RpcMethod>> => {
|
||||
if (!isRpcMethod(method)) {
|
||||
return { ok: false, error: { code: "VALIDATION_ERROR", message: "Unknown RPC method." } };
|
||||
}
|
||||
|
||||
let parsedPayload;
|
||||
try {
|
||||
parsedPayload = parseRpcRequest(method, payload);
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
code: "VALIDATION_ERROR",
|
||||
message: "Invalid RPC payload.",
|
||||
let parsedPayload;
|
||||
try {
|
||||
parsedPayload = parseRpcRequest(method, payload);
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
code: "VALIDATION_ERROR",
|
||||
message: "Invalid RPC payload.",
|
||||
detail: error instanceof z.ZodError ? rpcValidationDetail(error) : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const result = await handleRpc(method, parsedPayload);
|
||||
if (!result.ok) return result;
|
||||
|
||||
try {
|
||||
parseRpcResponse(method, result.data);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error("[RPC] Invalid response:", {
|
||||
method,
|
||||
detail: error instanceof z.ZodError ? rpcValidationDetail(error) : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const result = await handleRpc(method, parsedPayload);
|
||||
if (!result.ok) return result;
|
||||
|
||||
try {
|
||||
parseRpcResponse(method, result.data);
|
||||
return result;
|
||||
} catch {
|
||||
return { ok: false, error: { code: "INTERNAL_ERROR", message: "Invalid RPC response." } };
|
||||
}
|
||||
});
|
||||
return { ok: false, error: { code: "INTERNAL_ERROR", message: "Invalid RPC response." } };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
@@ -91,11 +107,7 @@ app.whenReady().then(async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Seed demo data if empty (check if any companies exist)
|
||||
const companyCount = db.prepare("SELECT COUNT(*) as count FROM companies").get() as { count: number };
|
||||
if (companyCount.count === 0) {
|
||||
seedDatabase(db);
|
||||
}
|
||||
bootstrapDatabase(db);
|
||||
|
||||
const win = await createWindow();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user