@lndrstrll/pomuku-data
v0.1.2
Published
pomuku's api client for the web apps: copies kept on the device, shown at once and refreshed in the background, outdated saves refused, and saves queued while offline
Readme
@lndrstrll/pomuku-data
the web apps' client for a pomuku backend. it keeps what it reads on the device, so a page opens on the last copy at once and is told when the fresh one arrives; a save changes those copies in place, so a whole table is rarely read twice (the database counts every row it reads).
import { createClient } from '@lndrstrll/pomuku-data'
import type { Book } from './schema'
const client = createClient({ baseUrl: 'https://library.example.workers.dev', app: 'library' })
const books = client.table<Book>('books', { offline: true })
books.peek() // the device's copy, at once; null on a first visit
await books.list() // from memory if read in the last 30 s, otherwise from the backend
books.onFresh((rows) => show(rows)) // every change: a fresh read, a save, the queue going out
await books.update(id, { borrowed: true }, book.rev)in react:
import { useTable } from '@lndrstrll/pomuku-data/react'
const { rows, loading, error, refresh } = useTable(books)
if (!rows) return loading ? <LoadingAvatar /> : <RetryNotice onRetry={refresh} />how it behaves
- reads are shared while on their way and tried again (after 1 s, then 3 s) when the backend can't be reached. what the backend refuses is never tried again.
- saves go once: one that timed out may still have been carried out. each changes the table's copy in place and tells whoever is showing it.
- an outdated save (the row was saved since the copy it was made from, told by its
rev) is refused withOutdatedDataError. the copies take the latest row, so the page shows what the save ran into. - errors say what kind of failure it was:
TransientApiError(no answer: no network, a timeout, a server error),OutdatedDataError, orApiErrorwith the backend's status and message. - an account's rows (
scope: 'account') are kept per signed-in account and leave the device on sign-out. public ones stay. - a visit:
client.visit()tells the backend someone opened the app, so it can look at the sheet. once per page, never fails.
saves made while offline
a table opened with offline: true doesn't fail a save that can't reach the backend. it keeps it in a queue on the device, shows it as done (the row carries pending: true), and sends the queue in order once the backend answers again: when the app is next opened, when the browser comes back online (useOnline(client)), or on client.flush().
- a row added offline can be changed offline too; the changes follow it to its real id.
- nothing is added twice: a new row's save carries a name of its own, and the backend answers a name it has already carried out with the row it made then. an update whose values the backend already holds counts as done.
- a queued save the backend refuses once it can be sent (the row changed meanwhile, or doesn't validate) is dropped and reported through
client.onProblem; the copies show the backend's version again. usePending(client)is the list of saves still waiting, for a "3 changes not sent yet" note.- the queue is kept per account and survives a sign-out.
an app shown with sample data
demoFetch({ tables }) is a backend that lives in the page, for an app run with no server: it answers the table routes closely enough for the whole interface to be tried, changes included.
createClient({ baseUrl: 'https://demo.invalid', app: 'library', storage: null, fetch: demoFetch({ tables: { books: sampleBooks } }) })the app's own endpoints
client.read(key, path) reads one and keeps it like a table's (client.peek(key), client.onFresh(key, …)); client.request(method, path, body) is one request with nothing kept. when such a request returns a table's row, books.keep(row) puts it in the table's copies.
