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

@byearlybird/starling-react

v0.4.0

Published

React bindings for Starling: a provider for the session, and hooks for live queries, sync status and files

Downloads

75

Readme

React

@byearlybird/starling-react: React bindings for a Starling app opened with @byearlybird/starling-client's openClient. @byearlybird/starling-client and React 19 are peer dependencies. See decision 0009.

| Export | File | Contents | | ----------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------- | | ClientProvider, useClient | src/client.tsx | The provider for an opened Client, and the hook that reads it with its session, actions and encryption actions | | useSyncStatus | src/client.tsx | What a space's sync is doing, kept current | | useQuery | src/use-query.ts | A live read: reruns after commits to the collections it read | | useFileStatus | src/use-file-status.ts | Where an attached file is: uploading, on the relay, or still on its way from another device |

import { createRoot } from "react-dom/client";
import { absurd } from "@byearlybird/starling-client";
import { ClientProvider, useQuery, useClient } from "@byearlybird/starling-react";
import { client } from "./client"; // await openClient(...)

function Todos() {
  const { space } = useClient<typeof client>();
  const todos = useQuery(
    space,
    (tx) =>
      tx.query("todos", { filter: (q) => q.eq("done", false), orderBy: { field: "createdAt" } }),
    [],
  );
  if (todos.kind !== "ready") return <p>Loading…</p>;
  return (
    <ul>
      {todos.data.data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

function Account() {
  const { session, actions } = useClient<typeof client>();
  switch (session.kind) {
    case "signed-out":
      return <SignIn onEmail={actions.requestCode} onCode={actions.verifyCode} />;
    case "signed-in":
      return (
        <button onClick={() => void actions.signOut()}>Sign out {session.account.email}</button>
      );
    case "starting":
    case "ended":
      return null;
    default:
      return absurd(session);
  }
}

// With `encryption: passphraseEncryption()`, `session.encryption` says what the passphrase needs,
// and `useClient().encryption` has its actions:
function Passphrase() {
  const { session, encryption } = useClient<typeof client>();
  if (session.kind !== "signed-in") return null;
  switch (session.encryption.kind) {
    case "needs-passphrase":
      return <NewPassphrase onSubmit={encryption.setUpPassphrase} />;
    case "locked":
      return <Unlock onSubmit={encryption.unlock} onForgot={encryption.resetPassphrase} />;
    case "ready":
    case "unavailable":
      return null;
    default:
      return absurd(session.encryption);
  }
}

createRoot(document.getElementById("root")!).render(
  <ClientProvider client={client}>
    <Account />
    <Todos />
  </ClientProvider>,
);

The provider never opens or closes the session: the app does, once, so StrictMode and remounts keep it. useQuery(db, read, deps) works like useEffect: it starts over, from a loading state, only when db or a value in deps changes. Its state's kind is loading, ready with data, or error with the error and the last data. pnpm build writes the published dist/.

Requirements

Session (client.tsx)

  • Components anywhere below the provider can read the space, the session and its actions, and rerender when the session changes
  • Components can watch what a space's sync is doing, such as needing an app update
  • Mounting, unmounting and StrictMode never open or close the session
  • Using the session without a provider fails with a clear error

useQuery (use-query.ts)

  • Components can watch any read across one or more collections and receive data, loading, and error state
  • A read reruns when its declared dependencies change, and not when only its callback is recreated
  • Changing a dependency exposes loading state instead of results from the previous read
  • Read errors are exposed with the last successful data, and changing dependencies allows recovery
  • Unmounting a component or changing its dependencies stops its previous observation

useFileStatus (use-file-status.ts)

  • Components can watch where a file is, and the status updates as it uploads or appears elsewhere
  • A status from one file never shows for another