runic-pocketbase-collection

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]) // 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

example task app example task app
<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>

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