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

@doync/web

v0.4.0

Published

doync web topology: tab SharedWorker + DB-worker (wa-sqlite/OPFS) browser surface

Readme

@doync/web

Browser client surface for doync. One call boots the tab client (SharedWorker + OPFS-backed DB worker under the hood) and returns a WebClient that @doync/react — or your own adapter — consumes.

Install

pnpm add @doync/web @doync/client @doync/core
# typically also:
pnpm add @doync/react

Boot the client

The tab entry creates the client once and keeps it for the life of the tab. Login, logout, and token refresh ride updateAuth — do not rebuild the client on every auth change.

// tab entry (e.g. zero-init.tsx)
import { createWebClient } from '@doync/web'

const client = createWebClient({
  // MUST hold the inline `new Worker(new URL(...))` literal so the bundler
  // emits the app-bound DB-worker chunk.
  worker: () =>
    new Worker(new URL('./db-worker.ts', import.meta.url), {
      type: 'module',
    }),
  // Optional. Default derives `wss://host/sync` from the page origin.
  url: `${location.protocol === 'https:' ? 'wss:' : 'ws:'}//${location.host}/sync/ws`,
  name: 'tasks', // Database name; default 'doync'
  authData: { userId, token, ctx }, // or null for anonymous
  ctxValidationSchema, // shared-module; validates authData.ctx
  // logoutBehavior: 'keep', // optional; omit leaves a stored choice alone
})

Pass the same stable client into <DoyncProvider> (from @doync/react) for the life of the tab.

DB-worker entry (@doync/web/worker)

The worker factory must point at an entry that calls createWorker with the same shared definition module the rest of the app uses:

// db-worker.ts
import { createWorker } from '@doync/web/worker'
import { schema, queries, mutations } from './shared/data'

createWorker({ schema, queries, mutations /* , name: 'tasks' */ })

name on createWorker must match name on createWebClient when you set either (default 'doync'). One Database name = one Client; an app with several databases runs several clients with distinct names.

Options

| Option | Required | Notes | | --- | --- | --- | | worker | yes* | () => new Worker(new URL('./db-worker.ts', import.meta.url), { type: 'module' }). The inline new Worker(new URL(…)) form is required so the bundler emits the worker chunk. | | authData | yes | AuthData \| null{ userId, token?, ctx } when authenticated, or null anonymous. | | ctxValidationSchema | yes | Standard Schema for your auth-context shape (your shared-module binding); validates authData.ctx at construction / updateAuth. | | url | no | Mirror WebSocket URL. Default derives wss://host/sync from the page origin. | | name | no | Database name; default 'doync'. | | logoutBehavior | no | Initial 'keep' | 'forget' for this identity. Omit leaves a previously stored choice alone across reloads. |

*Required on the real browser path.

Auth

Client identity is one valueauthData: AuthData | null. Drive login / logout / refresh through updateAuth on the live client (shared semantics with @doync/client / @doync/mobile):

client.updateAuth({ userId: nextUserId, token: nextToken, ctx: nextCtx })
client.updateAuth(null) // logout
  • Same userId (including both null) → in-band auth update. A routine JWT rotation is invisible to mounts.
  • Changed userId → the topology swaps the per-identity Replica and reconnects as that identity. The client object stays stable; React mounts do not remount.

Logout retention (LogoutBehavior)

Per identity, stored durably with that identity (same LogoutBehavior policy as @doync/client / @doync/mobile):

  • 'keep' (default) — on a userId change the outgoing Replica and its pending queue stay on disk. Logging back in boots warm; unsynced writes re-push.
  • 'forget' — on a userId change the outgoing Replica is deleted. Use on a shared device where returning local data is not wanted.
const client = createWebClient({
  /* … */,
  logoutBehavior: 'forget', // initial stamp for this identity
})

// Or flip later — written into the live identity, honored at the next swap.
client.setLogoutBehavior('keep')

Subscribe and mutate

WebClient is a full doync client call surface. Prefer @doync/react hooks in UI code; the imperative form is the same shape the hooks wrap:

import { schema, queries, mutations } from './shared/data'

// Subscription — retain while mounted, release on cleanup.
const view = client.subscribe(queries.issues.open({ projectId }))
view.retain()
view.onChange(() => {
  console.log(view.current()) // rows; stable array ref until they move
  console.log(view.status()) // { status: 'unknown' | 'complete' | 'error', … }
})
// later: view.release()

// Once — cache-and-network; dispose when done.
const once = client.once(queries.issueById({ id }))
console.log(once.current()) // local answer immediately
await once.server // network half
once.dispose()

// Local read — arbitrary SQL over the Replica; never registered upstream.
const local = client.local<{ n: number }>(
  'select count(*) as n from issue where open = 1',
)
local.retain()
// later: local.release()

// Mutation — optimistic apply + server confirmation.
const { client: applied, server } = client.mutate(mutations.issue.create, {
  id,
  title,
})
await applied // local apply settled (throws if the local body rejects)
await server // Mirror confirmed (throws if the server rejects)

Recovery verbs on the same object (same resync / forget contract as @doync/client):

client.resync() // wipe Replica / Memberships / cookie; drop pendings; mint fresh clientId
client.forget() // erase the active identity's local data
client.forget('other-user-id') // erase another identity's file only

When you believe the network is back, connect your own navigator.onLine logic or a manual "Reconnect" button to client.reconnect(). It is safe to call anytime.

Connection and schema status for banners:

client.connectionStatus // 'connecting' | 'connected' | 'disconnected' | 'error' | 'needs-auth'
client.onConnectionChange(() => {
  /* re-read client.connectionStatus */
})

client.schemaStatus // null when nominal, else { kind, message }
client.onSchemaChange(() => {
  /* re-read client.schemaStatus */
})

Public surface

| Export | Role | | --- | --- | | createWebClient / CreateWebClientOptions | Boot the tab client | | WebClient | Returned client (subscribe / warmup / once / local / mutate + auth / reconnect / recovery) | | authAction / AuthAction / AuthIdentity | Pure same-user vs identity-change classifier | | createWorker / CreateWorkerOptions | DB-worker entry (@doync/web/worker) | | DoyncClient / View / OnceView / ViewStatus / QueryStatus / ConnectionStatus / SchemaEvent / SchemaEventKind / FalsyQuery / MutationOptions / MutationResult / LogoutBehavior / SubscribeOptions / PreloadOptions / PreloadHandle / WarmupOptions / WarmupHandle | Client call-surface family (re-exported from @doync/client) |

Internal API

The main entry (.) and the DB Worker (./worker) are the semver-governed public surfaces documented here. Anything imported from @doync/web/internal may change in any release, including patches, without notice — use it only if you accept that risk.