import { object, type ObjectSchema, type Schema, type Shape } from "./schema.ts"; import { isNsid } from "./util/syntax.ts"; export type ParamsSchema = Omit, "type"> & { readonly type: "params"; }; export function params(properties: S): ParamsSchema { for (const [key, field] of Object.entries(properties)) { const inner = field.type === "optional" ? field.inner : field; const value = inner.type === "array" ? inner.items : inner; if (value.type !== "string" && value.type !== "integer" && value.type !== "boolean") { throw new Error(`Parameter ${key} must be a string, integer, boolean, or array of those`); } } const base = object(properties); return Object.setPrototypeOf({ ...base, type: "params" }, Object.getPrototypeOf(base)); } export type RpcError = { readonly name: string; readonly description?: string; }; export type QueryOptions = { readonly description?: string; readonly parameters?: ParamsSchema; readonly output?: Schema; readonly errors?: readonly RpcError[]; }; export type ProcedureOptions = QueryOptions & { readonly input?: Schema }; export type Rpc = & { readonly id: string } & ( | ({ readonly type: "query" } & QueryOptions) | ({ readonly type: "procedure" } & ProcedureOptions) ); function checkRpc(id: string, options: ProcedureOptions): void { if (!isNsid(id)) throw new Error(`Invalid RPC NSID: ${id}`); for (const body of [options.input, options.output]) { if (body === undefined) continue; const schema = body.type === "ref" ? body.target : body; if (schema.type !== "object" && schema.type !== "union" && schema.type !== "record") { throw new Error("JSON RPC bodies must be objects, unions, or references to objects/records"); } } const names = new Set(); for (const error of options.errors ?? []) { if (!/^[A-Za-z][A-Za-z0-9]*$/.test(error.name)) { throw new Error(`Invalid RPC error: ${error.name}`); } if (names.has(error.name)) throw new Error(`Duplicate RPC error: ${error.name}`); names.add(error.name); } } export function query( id: Id, options: O & { readonly input?: never }, ): O & { readonly type: "query"; readonly id: Id } { if ("input" in options) throw new Error("Queries cannot have input bodies"); checkRpc(id, options); return { ...options, type: "query", id }; } export function procedure( id: Id, options: O, ): O & { readonly type: "procedure"; readonly id: Id } { checkRpc(id, options); return { ...options, type: "procedure", id }; }