.claude/skills/pikku-rpc/SKILL.md
Use when making internal function-to-function calls within a Pikku app, composing functions, or exposing RPC endpoints. Covers rpc.invoke, rpc.remote, rpc.exposed, and generated RPC client. TRIGGER when: code uses wire.rpc or expose: true, user asks about calling one Pikku function from another, function composition, or RPC endpoints. DO NOT TRIGGER when: user asks about HTTP routes (use pikku-http) or addon cross-package calls (use pikku-addon).
npx skillsauth add pikkujs/pikku pikku-rpcInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Call Pikku functions from other Pikku functions internally with full type safety. Use RPC to compose business logic without importing functions directly.
pikku info functions --verbose # See existing functions and which could be called via RPC
pikku info tags --verbose # Understand project organization
See pikku-concepts for the core mental model.
wire.rpc)Four ways to call functions via RPC:
| Method | Purpose |
| -------------------------------- | ----------------------------------------- |
| rpc.invoke(name, data) | Internal call to any wired function |
| rpc.remote(name, data) | Remote call via DeploymentService |
| rpc.exposed(name, data) | Call functions marked with expose: true |
| rpc.startWorkflow(name, input) | Start a workflow |
Mark a function as externally callable via RPC:
const greet = pikkuSessionlessFunc({
title: 'Greet',
expose: true, // ← callable via rpc.exposed()
func: async ({}, { name }) => {
return { message: `Hello, ${name}!` }
},
})
Expose all expose: true functions over HTTP:
wireHTTP({
route: '/rpc/:rpcName',
method: 'post',
auth: false,
func: rpcCaller,
})
const calculateTax = pikkuSessionlessFunc({
title: 'Calculate Tax',
func: async ({}, { amount, rate }) => {
return { tax: amount * rate }
},
})
const processOrder = pikkuFunc({
title: 'Process Order',
func: async ({ db }, { orderId }, { rpc }) => {
const order = await db.getOrder(orderId)
// Call another pikku function internally — fully typed
const { tax } = await rpc.invoke('calculateTax', {
amount: order.total,
rate: 0.08,
})
return { orderId, total: order.total + tax }
},
})
| Approach | Use When |
| -------------- | -------------------------------------------------------------------------------------------- |
| rpc.invoke() | Cross-domain calls, maintaining separation of concerns, function may be in different package |
| Direct import | Same module, tightly coupled logic, performance critical |
RPC calls go through Pikku's middleware and permission pipeline. Direct imports skip them.
After npx pikku prebuild:
import { pikkuRPC } from '.pikku/pikku-rpc.gen.js'
pikkuRPC.setServerUrl('http://localhost:4002')
const result = await pikkuRPC.invoke('calculateTax', {
amount: 100,
rate: 0.08,
})
pikkuRPC.setAuthorizationJWT(token)
// functions/billing.functions.ts
export const calculateTax = pikkuSessionlessFunc({
title: 'Calculate Tax',
func: async ({}, { amount, region }) => {
const rates = { US: 0.08, EU: 0.2, UK: 0.2 }
return { tax: amount * (rates[region] || 0) }
},
})
export const calculateShipping = pikkuSessionlessFunc({
title: 'Calculate Shipping',
func: async ({}, { weight, region }) => {
const base = region === 'US' ? 5 : 15
return { shipping: base + weight * 0.5 }
},
})
// functions/orders.functions.ts
export const processOrder = pikkuFunc({
title: 'Process Order',
func: async ({ db }, { orderId }, { rpc }) => {
const order = await db.getOrder(orderId)
const { tax } = await rpc.invoke('calculateTax', {
amount: order.total,
region: order.region,
})
const { shipping } = await rpc.invoke('calculateShipping', {
weight: order.totalWeight,
region: order.region,
})
const finalTotal = order.total + tax + shipping
await db.updateOrder(orderId, { tax, shipping, finalTotal })
return { orderId, total: finalTotal, tax, shipping }
},
})
documentation
Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.
testing
Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic. TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong. DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.
testing
Use when adding any middleware to a Pikku app — global HTTP middleware, tag-scoped middleware (including service-to-service bearer auth), per-route middleware, session-setting middleware, or understanding middleware execution order and priority. TRIGGER when: user wants middleware on some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about permissions/authorization checks (use pikku-permissions), auth strategies like authBearer/authCookie (use pikku-security), or deployment.
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.