AppView in a box as a Vite plugin thing hatk.dev

title: Mutations description: Create, update, and delete records from your frontend using callXrpc.#

Mutations#

Mutations use callXrpc to call hatk's built-in record management endpoints. callXrpc is isomorphic, it works in server load functions, Svelte components, and anywhere else you have access to it.

// Server-side: in a +layout.server.ts or +page.server.ts
export const load = async () => {
  return {
    feed: callXrpc("dev.hatk.getFeed", { feed: "recent", limit: 50 }),
  }
}

// Client-side: in a Svelte component or query helper
const res = await callXrpc("dev.hatk.createRecord", {
  collection: "xyz.statusphere.status" as const,
  repo: viewer.did,
  record: { status: "🚀", createdAt: new Date().toISOString() },
})

Pass SvelteKit's fetch as the optional third argument in load functions for request deduplication:

callXrpc("dev.hatk.getFeed", { feed: "recent" }, fetch)

Record mutations#

hatk generates three built-in procedures for managing records:

Method Purpose
dev.hatk.createRecord Create a new record in a collection
dev.hatk.deleteRecord Delete a record by collection and rkey
dev.hatk.putRecord Create or update a record at a specific rkey

Creating a record#

import { callXrpc } from "$hatk/client";

const result = await callXrpc("dev.hatk.createRecord", {
  collection: "xyz.statusphere.status" as const,
  repo: viewer.did,
  record: { status: "🚀", createdAt: new Date().toISOString() },
});
// result.uri — the AT URI of the new record
// result.cid — the content hash

The collection field uses as const so TypeScript narrows the record type to match that collection's schema. If your lexicon says status is required, you'll get a type error if you omit it.

Deleting a record#

await callXrpc("dev.hatk.deleteRecord", {
  collection: "xyz.statusphere.status" as const,
  rkey: "3abc123",
});

The rkey is the last segment of the record's AT URI. For example, if the URI is at://did:plc:abc/xyz.statusphere.status/3abc123, the rkey is 3abc123.

Updating a record#

await callXrpc("dev.hatk.putRecord", {
  collection: "xyz.statusphere.status" as const,
  rkey: "3abc123",
  record: { status: "☕", createdAt: new Date().toISOString() },
});

putRecord writes a record at a specific rkey, creating it if it doesn't exist or replacing it if it does.

Optimistic UI#

For a responsive feel, update the UI before the server responds and roll back on failure:

<script lang="ts">
  import { callXrpc } from '$hatk/client'
  import type { StatusView } from '$hatk/client'

  let { data } = $props()
  let items = $state(data.items as StatusView[])
  let isMutating = $state(false)

  async function createStatus(emoji: string) {
    if (isMutating) return
    const did = data.viewer!.did

    // 1. Insert optimistic item immediately
    const optimisticItem: StatusView = {
      uri: `at://${did}/xyz.statusphere.status/optimistic-${Date.now()}`,
      status: emoji,
      createdAt: new Date().toISOString(),
      author: { did, handle: did },
    }
    items = [optimisticItem, ...items]
    isMutating = true

    try {
      // 2. Call the server
      const res = await callXrpc('dev.hatk.createRecord', {
        collection: 'xyz.statusphere.status' as const,
        repo: did,
        record: { status: emoji, createdAt: new Date().toISOString() },
      })
      // 3. Replace optimistic item with real URI
      items = items.map(i =>
        i.uri === optimisticItem.uri
          ? { ...optimisticItem, uri: res.uri! }
          : i
      )
    } catch {
      // 4. Roll back on failure
      items = items.filter(i => i.uri !== optimisticItem.uri)
    } finally {
      isMutating = false
    }
  }
</script>

The same pattern works for deletes -- remove the item from the list immediately, then restore it if the server call fails:

<script lang="ts">
  async function deleteStatus(uri: string) {
    if (isMutating) return
    const removed = items.find(i => i.uri === uri)
    items = items.filter(i => i.uri !== uri)
    isMutating = true

    try {
      const rkey = uri.split('/').pop()!
      await callXrpc('dev.hatk.deleteRecord', {
        collection: 'xyz.statusphere.status' as const,
        rkey,
      })
    } catch {
      if (removed) items = [removed, ...items]
    } finally {
      isMutating = false
    }
  }
</script>