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

ffdb-client

v0.2.1

Published

Framework-agnostic client for FFDB — works in Node.js, browsers, React Native, and Expo

Readme

ffdb-client

A framework-agnostic client for FFDB. It ships with built-in user authentication via better-auth, a type-safe query builder via Kysely, raw SQL helpers, and optional offline sync.

Works in Node.js, browsers, React Native, and Expo.

How It Works

FFDB has a two-tier authentication model:

  1. You, the developer, connect with an admin token from the FFDB portal. That token lets you make admin requests.
  2. Your users sign in through the auth client methods. After sign-in, the client manages short-lived API keys for scoped access.

The client exposes a db object for type-safe queries, a raw sql helper, and an auth client for user authentication. It also exposes request, sync, getAccess, and subscribe for lower-level control.

Install

npm install ffdb-client

Quick Start

The recommended way to start

  • Follow the prompts and select a quick start template.
npx ffdb-cli init

Any JavaScript environment

import { createClient } from "ffdb-client";

const { db, auth, destroy } = await createClient({
  config: {
    apiUrl: "https://your-ffdb-server.com",
  },
});

await auth.signIn.email({
  email: "[email protected]",
  password: "password",
});

const users = await db.selectFrom("user").selectAll().execute();

await destroy();

React / Next.js

import { FFDBProvider, useFFDB, useRawQuery } from "ffdb-client/react";

function App() {
  return (
    <FFDBProvider
      options={{
        config: {
          apiUrl: import.meta.env.VITE_API_URL, // Vite
        },
      }}
    >
      <Users />
    </FFDBProvider>
  );
}

function Users() {
  const { db, auth, isLoading, error } = useFFDB();

  if (isLoading) return <p>Connecting...</p>;
  if (error) return <p>Connection failed: {error.message}</p>;

  const loadUsers = async () => {
    const rows = await db!.selectFrom("user").selectAll();
    console.log(rows);
  };

  return <button onClick={loadUsers}>Load users</button>;
}

useQuery() is the reactive Kysely hook. useRawQuery() is the raw SQL version, which is useful for searchable pickers and other query-driven UI.

const { data, isLoading } = useQuery((db) => db.selectFrom("user").selectAll().limit(20));

// -- OR something like this --

const { data, isLoading } = useRawQuery<{ id: string; name: string }>(
  {
    sql: "SELECT id, name FROM user WHERE name LIKE ? ORDER BY name LIMIT 20",
    values: [`%${term}%`],
  },
  { deps: [term], enabled: term.length > 1 },
);

Node.js

import { createClient, createNodeLifecycle } from "ffdb-client/node";

const { db, auth } = await createClient({
  config: {
    apiUrl: process.env.API_URL,
  },
  lifecycle: createNodeLifecycle(),
});

API Surface

createClient(options)

Creates a new FFDB client.

type CreateClientOptions = {
  config?: Partial<FFDB_Config>;
  lifecycle?: LifecycleHooks;
  storage?: StorageAdapter; // Very useful for expo (secure store)
  offline?: OfflineConfig;
  skipHealthCheck?: boolean;
};

Returns:

{
  db,
  sql,
  auth,
  request,
  destroy,
  sync,
  getAccess,
  subscribe,
}

sync includes run(), sync(), pull(), push(), waitForIdle(), status, and subscribe().

createFetchClient(options)

Creates the underlying HTTP client if you need direct API access.

React hooks

useFFDB() returns the current client plus readiness state:

{
  client,
  db,
  sql,
  auth,
  destroy,
  sync,
  getAccess,
  subscribe,
  isLoading,
  error,
  clientVersion,
  isReady,
}

useDB() returns the Kysely instance directly. useAuth() returns the auth client directly. useFFDBStatus() returns { isLoading, error }.

useQuery() accepts a Kysely query factory and supports enabled, deps, refetchOnFocus, refetchOnReconnect, and refetchInterval.

useRawQuery() accepts { sql, values, bypassCache } and the same hook options.

Other utilities

generateId() creates a stable text ID for app data and is the recommended default for offline-safe inserts.

memoryStorage() is the default built in storage adapter. browserStorage() supports "local", "session", and "indexeddb" options.

Configuration

createClient() accepts a partial config and merges it with defaults.

type FFDB_Config = {
  origin: string; // Your window.location.origin or scheme://
  apiUrl: string; // Your FFDB instance url
  endpoint: string; // The endpoint for querying, NOT RECOMMENDED TO CHANGE
  authToken: string; // DO NOT USE IN CLIENT CODE
  apiKey: string; // DO NOT USE IN CLIENT CODE - also not necessary if you have authToken
  retryAttempts?: number; // Number of times to retry when encountering 400+ errors
  healthProbeIntervalMs?: number; // How often to poll when offline
  logLevel: "info" | "verbose";
  logEnabled: boolean;
};

In practice you usually only need to provide apiUrl and origin if your code is not running in a browser.

Session Persistence

If you need persisted sessions outside of memory / cookie storage, pass a storage adapter. Without one, the client uses memoryStorage(), which is process-local and resets on reload.

import { createClient, browserStorage } from "ffdb-client";

const { db, auth } = await createClient({
  config: { apiUrl: "...", origin: "..." },
  storage: browserStorage(),
});

browserStorage("local") survives reloads, browserStorage("session") is tab-scoped, and browserStorage("indexeddb") is the fully async browser option.

Querying

The db object is a Kysely instance. All queries are type-safe against your database schema.

await db.selectFrom("user").select(["id", "email", "name"]);
await db.insertInto("user").values({ id: "abc", name: "Alice", email: "[email protected]" });
await db.updateTable("user").set({ name: "Bob" }).where("id", "=", "abc");
await db.deleteFrom("user").where("id", "=", "abc");

If you prefer SQL strings, use client.sql.query(), client.sql.first(), or client.sql.

Authentication

The auth object is a better-auth client with the FFDB API key plugin pre-configured. After sign-in, the client automatically rotates API keys and keeps the session refreshed.

Access & Permissions

getAccess() fetches the authenticated user's permissions, including table access, column constraints, and blocked statement types.

Offline-First

If you enable offline, the client can keep a local SQLite cache, queue mutations, and sync with the server using POST /api/sync/pull.

Lifecycle & Cleanup

Always call destroy() when you are done with the client. For Node.js services, createNodeLifecycle() registers cleanup handlers for SIGINT, SIGTERM, and beforeExit.

Entry Points

| Import Path | Environment | Includes | |---|---|---| | ffdb-client | Universal | createClient, createFetchClient, generateId, storage helpers, types | | ffdb-client/react | React | FFDBProvider, useQuery, useRawQuery, useDB, useAuth, useFFDB, useFFDBStatus | | ffdb-client/node | Node.js | Universal + config, loadEnvConfig, createNodeLifecycle |

License

ISC