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

@flexstore/react

v0.1.14

Published

React provider and hooks for FlexStore local-first sync

Readme

@flexstore/react

React bindings for FlexStore — local-first data that syncs when you're online.

Repository: github.com/flexstoresync/flexstore-react
Monorepo source: flexstore/packages/react

Install

npm install @flexstore/react @flexstore/core @journeyapps/wa-sqlite

Local storage: SQLite WASM with the DB file in IndexedDB (default via @flexstore/core).

Data flow

flowchart TB
  subgraph React["Your React tree"]
    COMP[Components]
    HOOKS[useQuery / useResource\nuseSyncStatus]
    PROV[FlexStoreProvider]
    COMP --> HOOKS
    PROV --> HOOKS
  end

  subgraph Core["@flexstore/core"]
    CLIENT[SyncClient]
    SQL[SQLite WASM]
    IDB[(IndexedDB)]
    CLIENT --> SQL
    SQL --> IDB
  end

  subgraph Server["flexstoresync server"]
    API[Sync API :8088]
    PUB[Pub/sub SSE :8090\noptional]
  end

  HOOKS <-->|subscribe / mutations| CLIENT
  CLIENT <-->|push / pull / check-deltas| API
  PUB -.->|sync hints| CLIENT
sequenceDiagram
  participant T as TodoList.tsx
  participant H as useQuery
  participant C as SyncClient
  participant L as SqliteStore
  participant S as Sync API

  T->>H: render
  H->>C: subscribe todos
  C->>L: getAll todos
  L-->>C: rows
  C-->>H: setState
  H-->>T: todos[]

  T->>H: useResource.create
  H->>C: create
  C->>L: upsert pending row
  C->>S: push when online

Sync backend

| Option | Repo / link | Best for | |--------|-------------|----------| | FlexStore hosted | flexstore (flexstoresync-core/) | Run sync server; use X-Api-Key + X-Tenant-Id + X-Device-Id (auto-provision on first request) | | Self-hosted Docker | flexstore-self-host | Run your own sync server (Docker Compose) |

Both speak the same protocol. Configure two URLs:

| URL | Env var | Default (local) | Purpose | |-----|---------|-----------------|--------| | Sync API | VITE_FLEXSTORE_SYNC_URL | http://localhost:8088 | push / pull / check-deltas | | Pub/sub | VITE_FLEXSTORE_PUBSUB_URL | http://localhost:8090 | SSE sync hints (optional) | | Pub/sub fallback poll | VITE_FLEXSTORE_PUBSUB_FALLBACK_POLL_MS | 60000 | check-deltas safety net while SSE is connected | | Reconnect poll | VITE_FLEXSTORE_POLL_INTERVAL_MS | 4000 | Poll when pub/sub is down or not configured |

Set apiKey, tenantId, and deviceId (or let the SDK auto-generate device id) — see self-host headers.


Recommended folder structure

Define one resource per file, import them into a single registry, and keep sync config separate from UI:

src/
  main.tsx
  index.css
  App.tsx                 # FlexStoreProvider + layout
  App.css
  components/
    TodoList.tsx          # useQuery / useResource hooks
  sync/
    config.ts             # baseUrl, apiKey, tenantId from env
    registry.ts           # resourceRegistry(...imports)
    resources/
      todos.ts            # defineResource({ name: 'todos', ... })
      users.ts            # another resource in its own file

Full working files: examples/todos-app/ in this repo (flexstore-react), also mirrored in the monorepo at developer/tests/mytodo/.


Resources

Use defineResource in each file and resourceRegistry to combine them:

Use defineResource and resourceRegistry from @flexstore/core (not @flexstore/react — Vite and older react versions may not resolve re-exports):

// src/sync/resources/todos.ts
import { defineResource } from '@flexstore/core';

export const todosResource = defineResource({
  name: 'todos',
  attributes: { title: 'string', done: 'boolean' },
});
// src/sync/registry.ts
import { resourceRegistry } from '@flexstore/core';
import { todosResource } from './resources/todos';
import { usersResource } from './resources/users';

export const registry = resourceRegistry(todosResource, usersResource);
// src/sync/config.ts
import type { SyncClientConfig } from '@flexstore/react';
import { parsePollIntervalMs } from '@flexstore/core';
import { registry } from './registry';

export function buildSyncConfig(): SyncClientConfig {
  return {
    baseUrl: import.meta.env.VITE_FLEXSTORE_SYNC_URL,
    pubsubUrl: import.meta.env.VITE_FLEXSTORE_PUBSUB_URL,
    apiKey: import.meta.env.VITE_FLEXSTORE_API_KEY,
    tenantId: import.meta.env.VITE_FLEXSTORE_TENANT_ID,
    pubsubFallbackPollMs: parsePollIntervalMs(
      import.meta.env.VITE_FLEXSTORE_PUBSUB_FALLBACK_POLL_MS,
      60_000,
    ),
    pollIntervalDisconnectedMs: parsePollIntervalMs(
      import.meta.env.VITE_FLEXSTORE_POLL_INTERVAL_MS,
      4_000,
    ),
    resources: registry,
  };
}

ResourceDefinition type

| Field | Required | Description | |-------|----------|-------------| | name | yes | Resource name used in useQuery('todos') | | attributes | yes | Flat scalar fields: 'string' \| 'boolean' \| 'integer' \| 'float' | | dependsOn | no | Parent resources pulled first | | pullSchema | no | Server pull projection (select, where, include) |

TypeScript types ship with the package (ResourceDefinition, SyncClientConfig, etc.).

Relationships (parents, children, FKs, React joins): see docs/relationships.md.


App entry

// src/App.tsx
import { FlexStoreProvider } from '@flexstore/react';
import { buildSyncConfig } from './sync/config';
import { TodoList } from './components/TodoList';
import './App.css';

export function App() {
  return (
    <FlexStoreProvider config={buildSyncConfig()}>
      <TodoList />
    </FlexStoreProvider>
  );
}
// src/components/TodoList.tsx
import { useQuery, useResource, useSyncStatus, useRealtimeStatus } from '@flexstore/react';

export function TodoList() {
  const todos = useQuery('todos', { done: false });
  const { create, update } = useResource('todos');
  const status = useSyncStatus();
  const realtime = useRealtimeStatus(); // { connected, baseUrl, pubsubUrl, enabled }

  // ...
}

Note: useQuery takes a flat filter: { done: false }, not { where: { done: false } }. The { where: ... } shape is for server pull schema, not local queries.

See examples/todos-app/src/App.tsx, App.css, and components/TodoList.tsx.

Adding a new resource? The default local store (IndexedDbStore) maps each resource to its own IndexedDB object store. Object stores can only be created in the onupgradeneeded phase, which fires only when the database version changes. If you ship a new resource to existing users without bumping IDB_SCHEMA_VERSION in packages/core/src/store/idb.js, the first useQuery('newResource') or create('newResource', …) throws Failed to execute 'transaction' on 'IDBDatabase': one of the specified object stores was not found. Reusing SqliteStore skips this issue (it uses CREATE TABLE IF NOT EXISTS). Increment the version every time you add a new resource.


Environment (Vite)

# .env.local
VITE_FLEXSTORE_SYNC_URL=http://localhost:8088
VITE_FLEXSTORE_PUBSUB_URL=http://localhost:8090
VITE_FLEXSTORE_API_KEY=your-api-key
VITE_FLEXSTORE_TENANT_ID=your-tenant-id
# Optional — fallback check-deltas while SSE is connected (default 60000 ms)
VITE_FLEXSTORE_PUBSUB_FALLBACK_POLL_MS=60000
# Optional — poll when pub/sub is down (default 4000 ms)
VITE_FLEXSTORE_POLL_INTERVAL_MS=4000

When pub/sub is connected, the SDK still runs check-deltas on a fallback timer (default 60s) in case hints are missed. When pub/sub is down, it polls every 4s. Hints trigger an immediate targeted pull for the affected resource only.

For self-hosted, sign up at http://localhost:8088/dashboard/, create a project, and copy the API key and tenant id from there.


Related repos

| Repo | Role | |------|------| | flexstore-core | @flexstore/core sync engine | | flexstore-self-host | Docker Compose self-host | | flexstore | Monorepo (server, docs, examples) |


Guides

| Guide | Description | |-------|-------------| | Managing relationships | Foreign keys, dependsOn, registry order, pullSchema.include, React join patterns | | Adding a new resource | End-to-end checklist for shipping a new syncable resource (registry, IDB schema bump, smoke test) |


API

| Export | Description | |--------|-------------| | defineResource(def) | Define one resource (use in resources/*.ts) | | resourceRegistry(...resources) | Merge resources into an array for config | | FlexStoreProvider | Boots the sync client and wraps your app | | useQuery(resource, filter?) | Live local query; re-renders on store changes | | useResource(resource) | { create, update, remove, syncNow } | | useSyncStatus() | Full status incl. realtimeConnected, baseUrl, pubsubUrl | | useRealtimeStatus() | { connected, baseUrl, pubsubUrl, enabled } | | useReady() | true after IndexedDB init | | useClient() | Raw @flexstore/core client | | useSyncNow() | Trigger an immediate sync | | useSetPaused() | Pause/resume background sync |

Legacy aliases SyncProvider and SyncCtx are exported for migration.


License

MIT — see LICENSE.