A create endpoint isn’t much without a read endpoint. Add the read RPC and return a declared service-local error when the order does not exist.
Add the schemas to schemas.ts:
export const GetOrderRequest = Type.Object({
orderId: Type.String(),
});
export type GetOrderRequest = Static<typeof GetOrderRequest>;
export const GetOrderResponse = Type.Object({
orderId: Type.String(),
customerId: Type.String(),
status: Type.String(),
items: Type.Array(Type.Object({
productId: Type.String(),
quantity: Type.Number(),
})),
});
export type GetOrderResponse = Static<typeof GetOrderResponse>; When an order does not exist, return a declared service error instead of an ad-hoc { type, message } object. Built-in Trellis errors cover platform failures such as validation, auth, transport, and unexpected internal errors. Domain failures belong to your service.
Create errors.ts next to schemas.ts:
import { defineError } from "@qlever-llc/trellis";
import { Type } from "@sinclair/typebox";
export const NotFoundError = defineError({
type: "NotFoundError",
fields: {
resource: Type.String(),
resourceId: Type.String(),
},
message: ({ resource, resourceId }) => `${resource} ${resourceId} not found`,
});
export const errors = {
NotFoundError,
} as const; Register Orders.Get in the contract’s rpc section alongside Orders.Create. Also import and pass the error map to defineServiceContract(...):
import { defineServiceContract } from "@qlever-llc/trellis";
import { errors } from "../errors.ts";
import * as schemas from "../schemas.ts";
export const ordersService = defineServiceContract(
{ schemas, errors },
(ref) => ({
// ...
capabilities: {
"orders.write": { /* ... */ },
"orders.read": {
displayName: "Read orders",
description: "Read order records.",
},
},
rpc: {
"Orders.Create": { /* ... */ },
"Orders.Get": {
version: "v1",
input: ref.schema("GetOrderRequest"),
output: ref.schema("GetOrderResponse"),
capabilities: { call: ["orders.read"] },
errors: [ref.error("NotFoundError"), ref.error("UnexpectedError")],
},
},
}),
); Now add the typed read handler to handlers/orders.ts:
import { err, isErr, ok } from "@qlever-llc/trellis";
import { NotFoundError } from "../errors.ts";
import type {
RpcHandlerArgs,
RpcHandlerResult,
} from "../contracts/orders_service.ts";
type Args = RpcHandlerArgs<"Orders.Get">;
type Result = RpcHandlerResult<"Orders.Get">;
export async function getOrderHandler({
input,
client,
}: Args): Promise<Result> {
const result = await client.kv.orders.get(input.orderId).take();
if (isErr(result)) {
return err(new NotFoundError({
resource: "Order",
resourceId: input.orderId,
}));
}
return ok(result.value);
} Mount it from main.ts:
import { getOrderHandler } from "./handlers/orders.ts";
await app.handle.rpc.orders.get(getOrderHandler); client.kv.orders.get(key) returns a Result<TypedKVEntry<Order>, KVError>. When the key doesn’t exist, it returns an Err. Translate that into a declared service-local NotFoundError for the caller.
Callers can now branch on the declared error class:
import { isErr } from "@qlever-llc/trellis";
import { NotFoundError } from "./errors.ts";
const result = await client.rpc.orders.get({ orderId: "ord_123" });
const value = result.take();
if (isErr(value) && value.error instanceof NotFoundError) {
console.log(value.error.resource, value.error.resourceId);
} Upgrade and restart as before.