A pocketbase collection wrapper for Svelte
- rune based reactivity
- real time sync
- optimistic updates via diff
Installation
pnpm i -D runic-pocketbase-collection
Usage
import PocketBase, { type RecordModel } from "pocketbase"
import { Collection, pbid } from "runic-pocketbase-collection"
// minimal pocketbase setup
let pb = new PocketBase("http://127.0.0.1:8090")
pb.autoCancellation(false)
let tasks = new Collection<RecordModel & { text?: string; done?: boolean }>(pb.collection("tasks"))
// creates 15 character alphanumeric id
let id = pbid()
// create
tasks.update({ [id]: { text: "New task" } })
// read
console.log(tasks.records[id].text) // logs: "New task"
// update
tasks.update({ [id]: { done: true } })
// delete
tasks.update({ [id]: null })
// reactivity
let task = $derived(tasks.records[id])
$effect(() => {
console.log(`${task.done ? "done" : "todo"} : ${task.text}`)
})
Task App Example


<script lang="ts">
import { type RecordModel } from "pocketbase"
import { Collection, pbid } from "runic-pocketbase-collection"
let { tasks }: { tasks: Collection<RecordModel & { text?: string; done?: boolean }> } = $props()
let taskList = $derived(Object.values(tasks.records))
let newTaskText = $state("")
</script>
<table>
<thead>
<tr>
<th>
<input
type="checkbox"
indeterminate={taskList.some((task) => task.done) && taskList.some((task) => !task.done)}
checked={!!taskList.length && taskList.every((task) => task.done)}
onclick={() =>
tasks.update(
Object.fromEntries(
taskList.map((task) => [task.id, { done: !taskList.every((task) => task.done) }])
)
)}
/>
</th>
<th>
{taskList.filter((task) => task.done).length}/{taskList.length} done
</th>
<th>
<button
onclick={() => tasks.update(Object.fromEntries(taskList.map((task) => [task.id, null])))}
>
❌
</button>
</th>
</tr>
</thead>
<tbody>
{#each taskList as task (task.id)}
<tr>
<td>
<input
type="checkbox"
checked={task.done}
onclick={() => tasks.update({ [task.id]: { done: !task.done } })}
/>
</td>
<td>
<input
type="text"
value={task.text}
oninput={(event) => tasks.update({ [task.id]: { text: event.currentTarget.value } })}
/>
</td>
<td><button onclick={() => tasks.update({ [task.id]: null })}>❌</button></td>
</tr>
{/each}
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<input type="text" bind:value={newTaskText} placeholder="New task" />
</td>
<td>
<button
disabled={!newTaskText}
onclick={() => {
tasks.update({ [pbid()]: { text: newTaskText } })
newTaskText = ""
}}
>
✚
</button>
</td>
</tr>
</tfoot>
</table>
<style>
table,
input[type="text"] {
width: 100%;
}
</style>
Advanced Usage
import PocketBase from "pocketbase"
import { Collection, pbid } from "runic-pocketbase-collection"
// explicit model type
interface TaskModel {
id: string
collectionId: string
collectionName: string
created: string
updated: string
text?: string
done?: boolean
subtasks: string[]
expand: { subtasks: TaskModel[] }
}
// minimal pocketbase setup
let pb = new PocketBase("http://127.0.0.1:8090")
pb.autoCancellation(false)
// wrap task collection and expand subtasks
let tasks = new Collection<TaskModel>(pb.collection("tasks"), { expand: "subtasks" })
let parentTaskId = pbid()
let childTaskId = pbid()
// create a parent and child task
await tasks.update({
[parentTaskId]: { text: "Parent task", subtasks: [childTaskId] },
[childTaskId]: { text: "Child task" }
})
// read expanded task
console.log(
tasks.records[parentTaskId].expand.subtasks.some((subtask) => subtask.id === childTaskId)
) // logs: true
Development
You'll need to add a pocketbase executable to the database directory.
The following start script assumes you have pnpm / tmux / fish installed.
pnpm i
pnpm run start
# pocketbase now running at http://127.0.0.1:8090/_
# sveltekit app now running at http://localhost:5173