npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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 with OutdatedDataError. 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, or ApiError with 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.