createProcedure
Define a single API operation with its scope, schemas, errors, middleware, and handler.
createProcedure builds one procedure: a single operation you place as a key in an extension's router, where
that key becomes a client method typed from the procedure's output. It takes an options object that
describes the operation and a handler that runs it. See Examples for the full setup.
Parameters
createProcedure(options, handler)options
scope and output are required on every procedure. The remaining fields depend on the scope. Pick a tab
for the full, type-checked shape:
Prop
Type
Every schema slot speaks Standard Schema. Use Zod,
Valibot, ArkType, or pass
schemaType<MyType>() to type a slot from a plain TypeScript type
with no runtime validation.
handler
(ctx) => output | Promise<output>. Runs the operation and returns a value matching output. It receives:
Prop
Type
Returns
A Procedure object, the value you assign to a router key. It's never called directly.
Its output type depends on scope. api serializes to JSON, so a Date arrives as a string. remote and
internal preserve your original types end to end.
Examples
One extension can group procedures of every scope in its router. Here an internal server-only operation,
a remote call, and an api endpoint, exposed as client.content.sitemap / .search / .get:
import { createExtension, createProcedure } from "kizlo"
import { z } from "zod"
export const content = () =>
createExtension({
id: "content",
init: () => ({
router: {
// internal: server-only, left out of the browser client
sitemap: createProcedure(
{
scope: "internal",
output: z.array(z.object({ loc: z.string(), lastmod: z.string() })),
},
async ({ context }) => buildSitemap(context),
),
// remote: RPC-style, one input schema, types preserved on the client
search: createProcedure(
{
scope: "remote",
method: "POST",
input: z.object({ term: z.string() }),
output: z.array(z.object({ id: z.number(), title: z.string() })),
},
async ({ input, context, errors }) => {
const { data, error } = await context.wordpress.posts.list({ search: input.term })
if (error) throw errors.INTERNAL_SERVER_ERROR()
return data.map((post) => ({ id: post.id, title: post.title.rendered }))
},
),
// api: a REST endpoint with typed params, callable over HTTP from anywhere
get: createProcedure(
{
scope: "api",
method: "GET",
path: "/featured/{id}",
params: z.object({ id: z.coerce.number() }),
output: z.object({ id: z.number(), headline: z.string() }),
errors: { FEATURED_NOT_FOUND: { status: 404, message: "Featured item not found" } },
},
async ({ input, context, errors }) => {
const { data, error } = await context.wordpress.posts.get({ id: input.params.id })
if (error) throw errors.FEATURED_NOT_FOUND()
return { id: data.id, headline: data.title.rendered }
},
),
},
}),
})