createClient

createClient is the primary entry point for FFDB SDK usage. It initializes database access, auth, request helpers, optional offline sync, and lifecycle cleanup in one call.

Signature

createClient<ExtraDatabase>(options: CreateClientOptions): Promise<FFDBClient<ExtraDatabase>>

Input: CreateClientOptions

type CreateClientOptions = {
  config?: Partial<FFDB_Config>
  lifecycle?: LifecycleHooks
  storage?: StorageAdapter
  offline?: OfflineConfig
  skipHealthCheck?: boolean
}

Option guidance

  • Name
    config
    Description

    Runtime config such as apiUrl, retries, and logging.

  • Name
    lifecycle
    Description

    Hooks for runtime lifecycle events (especially useful in Node services).

  • Name
    storage
    Description

    Session/token persistence adapter. Defaults to in-memory storage.

  • Name
    offline
    Description

    Enables local adapter + sync features for offline-first behavior.

  • Name
    skipHealthCheck
    Description

    Skips startup health probe. Useful for specialized bootstrap flows.

Output: FFDBClient

type FFDBClient<ExtraDatabase = unknown> = {
  db: Kysely<Database & ExtraDatabase>
  sql: SqlHelper
  auth: AuthClientInstance
  request: <T = unknown>(url: string, options?: ClientRequestOptions) => Promise<T>
  destroy: () => Promise<void>
  sync: SyncHandle | null
  getAccess: (opts?: { force?: boolean }) => Promise<AccessInfo>
  subscribe: (listener: () => void) => () => void
  files: FilesNamespace
}

End-to-end usage pattern

import { createClient } from 'ffdb-client'
import type { Database } from './ffdb.types'

const client = await createClient<Database>({
  config: {
    apiUrl: process.env.API_URL ?? import.meta.env.VITE_FFDB_API_URL,
  },
})

const { db, auth, sync, getAccess, destroy } = client

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

const rows = await db.selectFrom('user').selectAll().execute()
console.log(rows)

const access = await getAccess()
console.log(access.role)

await sync?.run()
await destroy()

sql includes typed raw-query helpers:

type SqlHelper = {
  execute: <TRow>(input: { sql: string; values?: unknown[]; bypassCache?: boolean }) => Promise<{ rows: TRow[]; meta: QueryMeta }>
  query: <TRow>(input: { sql: string; values?: unknown[]; bypassCache?: boolean }) => Promise<TRow[]>
  first: <TRow>(input: { sql: string; values?: unknown[]; bypassCache?: boolean }) => Promise<TRow | undefined>
}

sync is available when offline mode is configured:

type SyncHandle = {
  run: () => Promise<{ pushed: number; failed: number }>
  sync: () => Promise<{ pushed: number; failed: number }>
  pull: () => Promise<void>
  push: () => Promise<{ pushed: number; failed: number }>
  waitForIdle: (timeoutMs?: number) => Promise<void>
  status: Readonly<SyncStatus>
  subscribe: (listener: () => void) => () => void
}

Practical decisions

  1. Start minimal: config.apiUrl first, then add storage/offline as needed.
  2. Use lifecycle hooks in long-lived runtimes to guarantee cleanup.
  3. Keep destroy() in explicit shutdown paths for deterministic teardown.
  4. Treat sync as optional and guard with null checks.

See also

  1. Reference: React hooks
  2. Reference: storage adapters
  3. Reference: errors

Was this page helpful?