RPC
RPC lets you call server functions directly from the browser — no API endpoints, no fetch(), no JSON marshalling. You export a function, wire it to a DOM event with rpc:call, and the framework handles the rest.
When triggered:
- The browser sends the payload to the server
- The server function runs
- The framework re-renders any dirty fragments in the component
- The diff is pushed to the browser via SSE and patched in place
No full reload. No client router. No boilerplate.
Defining Server Functions
RPC functions live in — they run once on server load and are shared across all requests:
<script context="module">
const items = [];
export function addItem(payload) {
items.push({ id: Date.now(), text: payload.text });
}
</script>
<script>
export const data = items;
</script>
<form rpc:call={addItem}>
<input name="text" placeholder="New item" />
<button type="submit">Add</button>
</form>
<fragment name="list">
{#each data as item}
<li>{item.text}</li>
{/each}
</fragment>The server script ( without context) reads from module state and exposes it as props. The fragment re-renders against fresh props every time the function runs.
Payload
The payload passed to your function comes from the triggering element:
From a form — all form field values as an object:
<script context="module">
export function save(payload) {
// payload = { title: '...', body: '...' }
}
</script>
<form rpc:call={save}>
<input name="title" />
<textarea name="body"></textarea>
<button type="submit">Save</button>
</form>From a button — the button's name and value attributes:
<script context="module">
export function like(payload) {
// payload = { comment_id: '42' }
}
</script>
<button rpc:call={like} name="comment_id" value={c.id}>
Like
</button>From a hidden input alongside a button — include hidden fields in the same form or fieldset.
Fragments
A ` marks a region of the page that gets re-rendered when RPC runs. Give it a uniquename`:
<fragment name="counter">
Count: {count}
</fragment>The
namemust be unique within the component. The fragment re-renders with fresh server-script state after every RPC call.
Fragments support a few patching options:
| Prop | Default | Description |
|---|---|---|
name |
(required) | Unique identifier within the component |
mode |
append |
How updates are applied: append, prepend, replace |
interval |
0 |
Push updates every N milliseconds |
timeout |
50 |
Pause iterators after N ms, resume later |
limit |
100 |
Pause after N rendered items |
Full Example — Nested Comments
The canonical example: a recursive comment thread where likes and replies update in place without a reload.
<script context="module">
const _comments = [
{ id: 1, body: 'Great post!', likes: 0, msgs: [] },
];
export function addComment(payload) {
const parent = findById(_comments, +payload.message_id);
if (parent) {
parent.msgs.unshift({ id: Date.now(), body: payload.message, likes: 0, msgs: [] });
parent.count = (parent.count || 0) + 1;
}
}
export function incrementLikes(payload) {
const comment = findById(_comments, +payload.comment_id);
if (comment) comment.likes++;
}
function findById(list, id) {
for (const c of list) {
if (c.id === id) return c;
const found = findById(c.msgs, id);
if (found) return found;
}
}
</script>
<script>
import Comments from './comments.html';
export const data = _comments;
export const key = 0;
</script>
<fragment name="live-comments" key="comments:{key}">
{#each data as c}
<li>
<p>{c.body}</p>
<form class="reply-form" rpc:call={addComment}>
<input type="hidden" name="message_id" value={c.id} />
<input type="text" name="message" placeholder="Reply..." />
<button type="submit">Reply</button>
</form>
<button rpc:call={incrementLikes} name="comment_id" value={c.id}>
Like ({c.likes})
</button>
{#if c.count > 0}
<Comments data={c.msgs} key="{key}.{c.id}" />
{/if}
</li>
{/each}
</fragment>The key prop
When a component is used recursively, each instance needs a unique fragment namespace. The key prop namespaces the fragment name — key="comments:0.1.6" — so the framework can target the right one.
Export a key from the server script and the parent passes it down with a dot-separated suffix:
export const key = 0; <!-- root instance -->
<Comments key="{key}.{c.id}" /> <!-- child gets "0.42", "0.43", etc. -->REST-style RPC
For client-side scripts that need to call server functions directly, import rpc from jamrock:
<script context="client">
import { rpc } from 'jamrock';
async function handleClick() {
await rpc('comments/incrementLikes', { comment_id: 42 });
}
</script>
<button onclick={handleClick}>Like</button>This sends a POST to /_rpc/{module}/{fn} and returns the server function's return value. Fragments are still re-rendered server-side — the client just receives confirmation.
Prefer
rpc:callfor most cases — it's declarative and wires directly to DOM events with no client script required. Reach forrpc()when you need async control flow on the client.