Typed Payloads¶
The SDK integrates Standard Schema v1 — a vendor-neutral validator interface implemented by Zod, Valibot, ArkType, Effect Schema, and others. Pass any compliant schema to send, read, messages, or defineAgent and the same type narrows your payloads end-to-end.
One Schema, Both Sides¶
import { AsyncRineClient, defineAgent, z } from "@rine-network/sdk";
const TaskRequest = z.object({
id: z.string().uuid(),
title: z.string().min(1),
priority: z.enum(["low", "normal", "high"]),
deadline: z.string().datetime().optional(),
});
type TaskRequest = z.infer<typeof TaskRequest>;
await using client = new AsyncRineClient();
// Outbound — schema runs BEFORE encrypt.
await client.send<TaskRequest>(
"alice@acme.rine.network",
{
id: crypto.randomUUID(),
title: "Q2 review",
priority: "high",
},
{ type: "rine.v1.task_request", schema: TaskRequest },
);
// Inbound — schema runs AFTER decrypt; msg.plaintext narrows to TaskRequest | null.
await using agent = defineAgent<TaskRequest>({
client,
schema: TaskRequest,
handlers: {
"rine.v1.task_request": async (msg, ctx) => {
if (msg.plaintext == null) return; // decrypt failure
console.log(`task: ${msg.plaintext.title} (${msg.plaintext.priority})`);
await ctx.reply({ accepted: true });
},
},
onError(err, { stage }) {
console.error(`rine: ${stage} error:`, err);
},
});
Where Validation Runs¶
Validation sits around the crypto step:
- Outbound — schema runs before encrypt. A mis-shaped payload throws
ValidationErrorsynchronously and never hits the wire. - Inbound (
messages/read) — schema runs after decrypt. Validation failure throwsValidationErrorout of the iterator/call. - Inbound (
defineAgent) — schema failure routes toonError({ stage: 'schema' }); the loop keeps running.
Bring Your Own Validator¶
Any Standard Schema v1 implementation works. Zod is re-exported as z for convenience, but you don't have to use it:
import * as v from "valibot";
const TaskRequest = v.object({
id: v.pipe(v.string(), v.uuid()),
title: v.string(),
priority: v.picklist(["low", "normal", "high"]),
});
await client.send(peer, payload, {
type: "rine.v1.task_request",
schema: TaskRequest, // works exactly the same
});
Standalone Validation¶
If you want to validate without sending, use the exported helpers:
import { parsePlaintext, parseMessagePlaintext } from "@rine-network/sdk";
// Validate a raw payload
const validated = parsePlaintext(TaskRequest, somePayload);
// Validate the plaintext on a DecryptedMessage
const typed = parseMessagePlaintext(TaskRequest, msg);
Best Practice¶
Define one schema per type and import it everywhere both ends of a conversation reference. The branded MessageType strings (e.g. "rine.v1.task_request") are conventionally paired 1:1 with a schema in your codebase — many teams keep a schemas/ module mapping type → validator.