Implement RPC contract validation baseline
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
"./rpc": {
|
||||
"types": "./src/rpc.ts",
|
||||
"import": "./src/rpc.ts"
|
||||
},
|
||||
"./rpcSchemas": {
|
||||
"types": "./src/rpcSchemas.ts",
|
||||
"import": "./src/rpcSchemas.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,15 @@ export const MemoSectionReviewSchema = z.object({
|
||||
});
|
||||
export type MemoSectionReview = z.infer<typeof MemoSectionReviewSchema>;
|
||||
|
||||
export const MemoSchema = z.object({
|
||||
status: z.enum(["draft", "review", "final"]),
|
||||
sections: z.array(MemoSectionSchema),
|
||||
citations: z.array(MemoCitationSchema),
|
||||
annotations: z.array(MemoAnnotationSchema),
|
||||
sectionReviews: z.array(MemoSectionReviewSchema)
|
||||
});
|
||||
export type Memo = z.infer<typeof MemoSchema>;
|
||||
|
||||
export const CatalystSchema = z.object({
|
||||
id: z.string(),
|
||||
date: z.string(),
|
||||
@@ -174,19 +183,9 @@ export const SnapshotSchema = z.object({
|
||||
});
|
||||
export type Snapshot = z.infer<typeof SnapshotSchema>;
|
||||
|
||||
export type ClientSettings = {
|
||||
theme: "light" | "dark" | "system";
|
||||
density: "comfortable" | "compact" | "dense";
|
||||
sidebarWidth: number;
|
||||
navCollapsed: Record<string, boolean>;
|
||||
keybindings: Record<string, string>;
|
||||
};
|
||||
export type ClientSettings = z.infer<typeof import("./rpcSchemas.js").ClientSettingsSchema>;
|
||||
|
||||
export type ServerSettings = {
|
||||
agentConfigs: Record<string, unknown>;
|
||||
dataSources: Record<string, boolean>;
|
||||
exportPipelines: Record<string, unknown>;
|
||||
};
|
||||
export type ServerSettings = z.infer<typeof import("./rpcSchemas.js").ServerSettingsSchema>;
|
||||
|
||||
export type RpcRequestMap = {
|
||||
"portfolio.get": undefined;
|
||||
@@ -239,11 +238,13 @@ export type RpcRequestMap = {
|
||||
"agent.configure": { agentId: string; config: Record<string, unknown> };
|
||||
"agent.getTrace": { agentId: string; runId: string };
|
||||
"agent.runPipeline": { companyId: string; pipeline: string };
|
||||
"validation.run": { companyId: string; agentType?: "sv" | "qa" | "rt" | "all" };
|
||||
"validation.getStatus": { companyId: string; sectionId?: string };
|
||||
"export.list": { companyId?: string };
|
||||
"export.create": { type: string; companyId: string; options?: Record<string, unknown> };
|
||||
"export.create": { type: "pdf" | "excel" | "ppt"; companyId: string; options?: Record<string, unknown> };
|
||||
"export.download": { exportId: string };
|
||||
"settings.get": { scope: "client" | "server" };
|
||||
"settings.update": { scope: "client" | "server"; changes: Record<string, unknown> };
|
||||
"settings.update": import("./rpcSchemas.js").SettingsUpdatePayload;
|
||||
};
|
||||
|
||||
export type RpcResponseMap = {
|
||||
@@ -289,6 +290,12 @@ export type RpcResponseMap = {
|
||||
"agent.configure": { ok: boolean };
|
||||
"agent.getTrace": { steps: Array<{ step: number; label: string; detail: string }> };
|
||||
"agent.runPipeline": { runIds: string[] };
|
||||
"validation.run": {
|
||||
sourceVerification?: { passed: boolean; confidence: string; issues: Array<{ severity: string; message: string; suggestion?: string }>; notes: string; timestamp: string };
|
||||
modelQA?: { passed: boolean; confidence: string; issues: Array<{ severity: string; message: string; suggestion?: string }>; notes: string; timestamp: string };
|
||||
redTeam?: { passed: boolean; confidence: string; issues: Array<{ severity: string; message: string; suggestion?: string }>; notes: string; timestamp: string };
|
||||
};
|
||||
"validation.getStatus": { validationState: "verified" | "flagged" | "unverified" | "failed"; lastValidated?: string };
|
||||
"export.list": { exports: ExportRecord[] };
|
||||
"export.create": { exportId: string };
|
||||
"export.download": { data: ArrayBuffer };
|
||||
@@ -311,3 +318,114 @@ export type RpcResult<T extends RpcMethod> =
|
||||
export type RpcClient = {
|
||||
call<T extends RpcMethod>(method: T, payload: RpcRequestMap[T]): Promise<RpcResult<T>>;
|
||||
};
|
||||
|
||||
// ============== SSE Events ==============
|
||||
|
||||
export type ServerEventType =
|
||||
| "agent.progress"
|
||||
| "agent.completed"
|
||||
| "agent.failed"
|
||||
| "agent.started"
|
||||
| "agent.streaming"
|
||||
| "validation.updated"
|
||||
| "memo.updated"
|
||||
| "model.updated";
|
||||
|
||||
export type AgentProgressEvent = {
|
||||
type: "agent.progress";
|
||||
data: {
|
||||
runId: string;
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
progress: number;
|
||||
action: string;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AgentCompletedEvent = {
|
||||
type: "agent.completed";
|
||||
data: {
|
||||
runId: string;
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
output: unknown;
|
||||
duration: number;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AgentFailedEvent = {
|
||||
type: "agent.failed";
|
||||
data: {
|
||||
runId: string;
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
error: string;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AgentStartedEvent = {
|
||||
type: "agent.started";
|
||||
data: {
|
||||
runId: string;
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
pipeline?: string;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type AgentStreamingEvent = {
|
||||
type: "agent.streaming";
|
||||
data: {
|
||||
runId: string;
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
chunk: string;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ValidationUpdatedEvent = {
|
||||
type: "validation.updated";
|
||||
data: {
|
||||
companyId: string;
|
||||
sectionId?: string;
|
||||
validationState: "verified" | "flagged" | "unverified" | "failed";
|
||||
agentId: string;
|
||||
notes?: string;
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type MemoUpdatedEvent = {
|
||||
type: "memo.updated";
|
||||
data: {
|
||||
companyId: string;
|
||||
sectionId: string;
|
||||
content: string;
|
||||
updatedAt: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ModelUpdatedEvent = {
|
||||
type: "model.updated";
|
||||
data: {
|
||||
companyId: string;
|
||||
tab: string;
|
||||
cell?: { row: number; col: number };
|
||||
timestamp: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ServerEvent =
|
||||
| AgentProgressEvent
|
||||
| AgentCompletedEvent
|
||||
| AgentFailedEvent
|
||||
| AgentStartedEvent
|
||||
| AgentStreamingEvent
|
||||
| ValidationUpdatedEvent
|
||||
| MemoUpdatedEvent
|
||||
| ModelUpdatedEvent;
|
||||
|
||||
33
packages/contracts/src/rpcSchemas.test.ts
Normal file
33
packages/contracts/src/rpcSchemas.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { RpcRequestSchemas, RpcResponseSchemas, parseRpcRequest } from "./rpcSchemas.js";
|
||||
|
||||
describe("RPC schemas", () => {
|
||||
it("defines request and response schemas for every method", () => {
|
||||
expect(Object.keys(RpcRequestSchemas).sort()).toEqual(Object.keys(RpcResponseSchemas).sort());
|
||||
});
|
||||
|
||||
it("accepts valid payloads and trims bounded strings", () => {
|
||||
expect(parseRpcRequest("portfolio.addHolding", { ticker: " cost " })).toEqual({ ticker: "cost" });
|
||||
expect(parseRpcRequest("model.updateCell", { companyId: "cost", tab: "base", row: 0, col: 1, value: "42" })).toEqual({
|
||||
companyId: "cost",
|
||||
tab: "base",
|
||||
row: 0,
|
||||
col: 1,
|
||||
value: "42",
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid payloads", () => {
|
||||
expect(() => parseRpcRequest("portfolio.addHolding", { ticker: "" })).toThrow();
|
||||
expect(() => parseRpcRequest("model.updateCell", { companyId: "cost", tab: "base", row: -1, col: 0, value: "42" })).toThrow();
|
||||
});
|
||||
|
||||
it("rejects scope-mismatched settings updates", () => {
|
||||
expect(() => parseRpcRequest("settings.update", { scope: "client", changes: { sidebarWidth: 40 } })).toThrow();
|
||||
expect(() => parseRpcRequest("settings.update", { scope: "server", changes: { dataSources: { sec: "yes" } } })).toThrow();
|
||||
});
|
||||
|
||||
it("catches malformed handler output", () => {
|
||||
expect(RpcResponseSchemas["memo.updateSection"].safeParse({ section: {}, status: "draft", savedAt: "now" }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
270
packages/contracts/src/rpcSchemas.ts
Normal file
270
packages/contracts/src/rpcSchemas.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import { z } from "zod";
|
||||
import {
|
||||
AgentSchema,
|
||||
AlertSchema,
|
||||
CatalystSchema,
|
||||
CompanySchema,
|
||||
EarningsScheduleSchema,
|
||||
ExportRecordSchema,
|
||||
FilingSchema,
|
||||
HoldingSchema,
|
||||
MemoAnnotationSchema,
|
||||
MemoCitationSchema,
|
||||
MemoSectionReviewSchema,
|
||||
MemoSectionSchema,
|
||||
ModelRowSchema,
|
||||
RiskSchema,
|
||||
WorkspaceSectionSchema,
|
||||
type ClientSettings,
|
||||
type RpcMethod,
|
||||
type RpcRequestMap,
|
||||
type RpcResponseMap,
|
||||
type ServerSettings,
|
||||
} from "./rpc.js";
|
||||
|
||||
const nonEmptyString = z.string().trim().min(1);
|
||||
const idString = nonEmptyString;
|
||||
const tickerString = z.string().trim().min(1).max(16);
|
||||
const nonNegativeIndex = z.number().int().min(0);
|
||||
const unknownRecord = z.record(z.unknown());
|
||||
|
||||
export const ClientSettingsSchema = z.object({
|
||||
theme: z.enum(["light", "dark", "system"]),
|
||||
density: z.enum(["comfortable", "compact", "dense"]),
|
||||
sidebarWidth: z.number().int().min(160).max(520),
|
||||
navCollapsed: z.record(z.boolean()),
|
||||
keybindings: z.record(z.string()),
|
||||
});
|
||||
|
||||
export const ServerSettingsSchema = z.object({
|
||||
agentConfigs: z.record(z.unknown()),
|
||||
dataSources: z.record(z.boolean()),
|
||||
exportPipelines: z.record(z.unknown()),
|
||||
});
|
||||
|
||||
const RiskInputSchema = RiskSchema.omit({ id: true, companyId: true });
|
||||
const SettingsUpdateSchema = z.discriminatedUnion("scope", [
|
||||
z.object({ scope: z.literal("client"), changes: ClientSettingsSchema.partial() }),
|
||||
z.object({ scope: z.literal("server"), changes: ServerSettingsSchema.partial() }),
|
||||
]);
|
||||
|
||||
export const RpcRequestSchemas = {
|
||||
"portfolio.get": z.undefined(),
|
||||
"portfolio.addHolding": z.object({ ticker: tickerString }),
|
||||
"portfolio.removeHolding": z.object({ ticker: tickerString }),
|
||||
"company.get": z.object({ companyId: idString }),
|
||||
"company.search": z.object({ query: nonEmptyString }),
|
||||
"company.setActive": z.object({ companyId: idString }),
|
||||
"workspace.getSection": z.object({ companyId: idString, section: nonEmptyString }),
|
||||
"workspace.listSources": z.object({ companyId: idString }),
|
||||
"catalyst.list": z.object({ companyId: idString }),
|
||||
"alert.list": z.object({ companyId: idString.optional(), since: z.string().optional() }),
|
||||
"risk.list": z.object({ companyId: idString }),
|
||||
"risk.add": z.object({ companyId: idString, risk: RiskInputSchema }),
|
||||
"earnings.getSchedule": z.object({ companyId: idString }),
|
||||
"filing.list": z.object({ companyId: idString, since: z.string().optional() }),
|
||||
"model.get": z.object({ companyId: idString, tab: nonEmptyString }),
|
||||
"model.updateCell": z.object({
|
||||
companyId: idString,
|
||||
tab: nonEmptyString,
|
||||
row: nonNegativeIndex,
|
||||
col: nonNegativeIndex,
|
||||
value: z.string(),
|
||||
}),
|
||||
"model.runScenario": z.object({
|
||||
companyId: idString,
|
||||
scenario: nonEmptyString,
|
||||
overrides: z.record(z.string()),
|
||||
}),
|
||||
"memo.get": z.object({ companyId: idString }),
|
||||
"memo.updateSection": z.object({
|
||||
companyId: idString,
|
||||
sectionId: idString,
|
||||
title: nonEmptyString.optional(),
|
||||
content: nonEmptyString,
|
||||
}),
|
||||
"memo.addAnnotation": z.object({
|
||||
companyId: idString,
|
||||
sectionId: idString,
|
||||
kind: z.enum(["highlight", "comment", "strike"]),
|
||||
selectedText: nonEmptyString,
|
||||
comment: nonEmptyString.optional(),
|
||||
}),
|
||||
"memo.resolveAnnotation": z.object({ companyId: idString, annotationId: idString }),
|
||||
"memo.updateSectionReview": z.object({
|
||||
companyId: idString,
|
||||
sectionId: idString,
|
||||
status: z.enum(["pending", "in_review", "approved", "changes_requested"]),
|
||||
}),
|
||||
"memo.acceptEdit": z.object({ companyId: idString, editId: idString }),
|
||||
"memo.rejectEdit": z.object({ companyId: idString, editId: idString, reason: z.string().optional() }),
|
||||
"agent.list": z.object({ companyId: idString.optional() }),
|
||||
"agent.start": z.object({ agentId: idString, companyId: idString }),
|
||||
"agent.pause": z.object({ agentId: idString }),
|
||||
"agent.restart": z.object({ agentId: idString }),
|
||||
"agent.chat": z.object({ agentId: idString, message: nonEmptyString }),
|
||||
"agent.configure": z.object({ agentId: idString, config: unknownRecord }),
|
||||
"agent.getTrace": z.object({ agentId: idString, runId: idString }),
|
||||
"agent.runPipeline": z.object({
|
||||
companyId: idString,
|
||||
pipeline: z.enum(["research", "competitive", "cross-cutting"]),
|
||||
}),
|
||||
"validation.run": z.object({ companyId: idString, agentType: z.enum(["sv", "qa", "rt", "all"]).optional() }),
|
||||
"validation.getStatus": z.object({ companyId: idString, sectionId: idString.optional() }),
|
||||
"export.list": z.object({ companyId: idString.optional() }),
|
||||
"export.create": z.object({ type: z.enum(["pdf", "excel", "ppt"]), companyId: idString, options: unknownRecord.optional() }),
|
||||
"export.download": z.object({ exportId: idString }),
|
||||
"settings.get": z.object({ scope: z.enum(["client", "server"]) }),
|
||||
"settings.update": SettingsUpdateSchema,
|
||||
} satisfies Record<RpcMethod, z.ZodTypeAny>;
|
||||
|
||||
const SettingsResponseSchema = z.union([ClientSettingsSchema, ServerSettingsSchema]);
|
||||
const ValidationIssueSchema = z.object({
|
||||
severity: z.string(),
|
||||
message: z.string(),
|
||||
suggestion: z.string().optional(),
|
||||
});
|
||||
const ValidationResultSchema = z.object({
|
||||
passed: z.boolean(),
|
||||
confidence: z.string(),
|
||||
issues: z.array(ValidationIssueSchema),
|
||||
notes: z.string(),
|
||||
timestamp: z.string(),
|
||||
});
|
||||
|
||||
export const RpcResponseSchemas = {
|
||||
"portfolio.get": z.object({ id: z.string(), name: z.string(), holdings: z.array(HoldingSchema), activeCompanyId: z.string() }),
|
||||
"portfolio.addHolding": z.object({ holding: HoldingSchema }),
|
||||
"portfolio.removeHolding": z.object({ ok: z.boolean() }),
|
||||
"company.get": z.object({ company: CompanySchema }),
|
||||
"company.search": z.object({ results: z.array(z.object({ ticker: z.string(), name: z.string(), sector: z.string() })) }),
|
||||
"company.setActive": z.object({ ok: z.boolean() }),
|
||||
"workspace.getSection": z.object({ content: WorkspaceSectionSchema, validationState: z.string() }),
|
||||
"workspace.listSources": z.object({ sources: z.array(z.object({ type: z.string(), title: z.string(), metadata: z.string() })) }),
|
||||
"catalyst.list": z.object({ catalysts: z.array(CatalystSchema) }),
|
||||
"alert.list": z.object({ alerts: z.array(AlertSchema) }),
|
||||
"risk.list": z.object({ risks: z.array(RiskSchema) }),
|
||||
"risk.add": z.object({ risk: RiskSchema }),
|
||||
"earnings.getSchedule": z.object({ schedule: z.array(EarningsScheduleSchema) }),
|
||||
"filing.list": z.object({ filings: z.array(FilingSchema) }),
|
||||
"model.get": z.object({ headers: z.array(z.string()), rows: z.array(ModelRowSchema) }),
|
||||
"model.updateCell": z.object({ ok: z.boolean(), affectedCells: z.array(z.string()) }),
|
||||
"model.runScenario": z.object({ headers: z.array(z.string()), rows: z.array(ModelRowSchema) }),
|
||||
"memo.get": z.object({
|
||||
status: z.enum(["draft", "review", "final"]),
|
||||
sections: z.array(MemoSectionSchema),
|
||||
citations: z.array(MemoCitationSchema),
|
||||
annotations: z.array(MemoAnnotationSchema),
|
||||
sectionReviews: z.array(MemoSectionReviewSchema),
|
||||
}),
|
||||
"memo.updateSection": z.object({
|
||||
section: MemoSectionSchema,
|
||||
status: z.enum(["draft", "review", "final"]),
|
||||
savedAt: z.string(),
|
||||
}),
|
||||
"memo.addAnnotation": z.object({ annotation: MemoAnnotationSchema }),
|
||||
"memo.resolveAnnotation": z.object({ annotation: MemoAnnotationSchema }),
|
||||
"memo.updateSectionReview": z.object({ review: MemoSectionReviewSchema }),
|
||||
"memo.acceptEdit": z.object({ ok: z.boolean() }),
|
||||
"memo.rejectEdit": z.object({ ok: z.boolean() }),
|
||||
"agent.list": z.object({ agents: z.array(AgentSchema) }),
|
||||
"agent.start": z.object({ runId: z.string() }),
|
||||
"agent.pause": z.object({ ok: z.boolean() }),
|
||||
"agent.restart": z.object({ runId: z.string() }),
|
||||
"agent.chat": z.object({ response: z.string() }),
|
||||
"agent.configure": z.object({ ok: z.boolean() }),
|
||||
"agent.getTrace": z.object({ steps: z.array(z.object({ step: z.number(), label: z.string(), detail: z.string() })) }),
|
||||
"agent.runPipeline": z.object({ runIds: z.array(z.string()) }),
|
||||
"validation.run": z.object({
|
||||
sourceVerification: ValidationResultSchema.optional(),
|
||||
modelQA: ValidationResultSchema.optional(),
|
||||
redTeam: ValidationResultSchema.optional(),
|
||||
}),
|
||||
"validation.getStatus": z.object({
|
||||
validationState: z.enum(["verified", "flagged", "unverified", "failed"]),
|
||||
lastValidated: z.string().optional(),
|
||||
}),
|
||||
"export.list": z.object({ exports: z.array(ExportRecordSchema) }),
|
||||
"export.create": z.object({ exportId: z.string() }),
|
||||
"export.download": z.object({ data: z.instanceof(ArrayBuffer) }),
|
||||
"settings.get": z.object({ settings: SettingsResponseSchema }),
|
||||
"settings.update": z.object({ ok: z.boolean() }),
|
||||
} satisfies Record<RpcMethod, z.ZodTypeAny>;
|
||||
|
||||
export const AgentProgressEventSchema = z.object({
|
||||
type: z.literal("agent.progress"),
|
||||
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), progress: z.number(), action: z.string(), timestamp: z.string() }),
|
||||
});
|
||||
export const AgentCompletedEventSchema = z.object({
|
||||
type: z.literal("agent.completed"),
|
||||
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), output: z.unknown(), duration: z.number(), timestamp: z.string() }),
|
||||
});
|
||||
export const AgentFailedEventSchema = z.object({
|
||||
type: z.literal("agent.failed"),
|
||||
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), error: z.string(), timestamp: z.string() }),
|
||||
});
|
||||
export const AgentStartedEventSchema = z.object({
|
||||
type: z.literal("agent.started"),
|
||||
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), pipeline: z.string().optional(), timestamp: z.string() }),
|
||||
});
|
||||
export const AgentStreamingEventSchema = z.object({
|
||||
type: z.literal("agent.streaming"),
|
||||
data: z.object({ runId: z.string(), agentId: z.string(), companyId: z.string(), chunk: z.string(), timestamp: z.string() }),
|
||||
});
|
||||
export const ValidationUpdatedEventSchema = z.object({
|
||||
type: z.literal("validation.updated"),
|
||||
data: z.object({
|
||||
companyId: z.string(),
|
||||
sectionId: z.string().optional(),
|
||||
validationState: z.enum(["verified", "flagged", "unverified", "failed"]),
|
||||
agentId: z.string(),
|
||||
notes: z.string().optional(),
|
||||
timestamp: z.string(),
|
||||
}),
|
||||
});
|
||||
export const MemoUpdatedEventSchema = z.object({
|
||||
type: z.literal("memo.updated"),
|
||||
data: z.object({ companyId: z.string(), sectionId: z.string(), content: z.string(), updatedAt: z.string() }),
|
||||
});
|
||||
export const ModelUpdatedEventSchema = z.object({
|
||||
type: z.literal("model.updated"),
|
||||
data: z.object({ companyId: z.string(), tab: z.string(), cell: z.object({ row: z.number(), col: z.number() }).optional(), timestamp: z.string() }),
|
||||
});
|
||||
|
||||
export const ServerEventSchema = z.discriminatedUnion("type", [
|
||||
AgentProgressEventSchema,
|
||||
AgentCompletedEventSchema,
|
||||
AgentFailedEventSchema,
|
||||
AgentStartedEventSchema,
|
||||
AgentStreamingEventSchema,
|
||||
ValidationUpdatedEventSchema,
|
||||
MemoUpdatedEventSchema,
|
||||
ModelUpdatedEventSchema,
|
||||
]);
|
||||
|
||||
export const ServerEventTypeSchema = z.enum([
|
||||
"agent.progress",
|
||||
"agent.completed",
|
||||
"agent.failed",
|
||||
"agent.started",
|
||||
"agent.streaming",
|
||||
"validation.updated",
|
||||
"memo.updated",
|
||||
"model.updated",
|
||||
]);
|
||||
|
||||
export function isRpcMethod(method: unknown): method is RpcMethod {
|
||||
return typeof method === "string" && method in RpcRequestSchemas;
|
||||
}
|
||||
|
||||
export function parseRpcRequest<T extends RpcMethod>(method: T, payload: unknown): RpcRequestMap[T] {
|
||||
return RpcRequestSchemas[method].parse(payload) as RpcRequestMap[T];
|
||||
}
|
||||
|
||||
export function parseRpcResponse<T extends RpcMethod>(method: T, data: unknown): RpcResponseMap[T] {
|
||||
return RpcResponseSchemas[method].parse(data) as RpcResponseMap[T];
|
||||
}
|
||||
|
||||
export type SettingsUpdatePayload =
|
||||
| { scope: "client"; changes: Partial<ClientSettings> }
|
||||
| { scope: "server"; changes: Partial<ServerSettings> };
|
||||
Reference in New Issue
Block a user