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()
Related helper surfaces
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
- Start minimal:
config.apiUrlfirst, then add storage/offline as needed. - Use lifecycle hooks in long-lived runtimes to guarantee cleanup.
- Keep
destroy()in explicit shutdown paths for deterministic teardown. - Treat
syncas optional and guard with null checks.