import { type Bindable, type ClientSession, MaterializeError, type StorageMode, type SyncState, UnknownError } from '@livestore/common'; import type { LiveStoreSchema } from '@livestore/common/schema'; import { LiveStoreEvent } from '@livestore/common/schema'; import { Cause, Effect, Inspectable, Schema, Stream } from '@livestore/utils/effect'; import * as otel from '@opentelemetry/api'; import type { SignalDef } from '../live-queries/base-class.ts'; import { type Queryable, type RefreshReason, type StoreCommitOptions, type StoreConstructorParams, type StoreEventsOptions, type StoreInternals, StoreInternalsSymbol, type SubscribeOptions, type SyncStatus, type Unsubscribe } from './store-types.ts'; export type SubscribeFn = { (query: Queryable, onUpdate: (value: TResult) => void, options?: SubscribeOptions): Unsubscribe; (query: Queryable, options?: SubscribeOptions): AsyncIterable; }; /** * Default parameters for the Store. Also used in `create-store.ts` */ export declare const STORE_DEFAULT_PARAMS: { leaderPushBatchSize: number; eventQueryBatchSize: number; }; /** * Central interface to a LiveStore database providing reactive queries, event commits, and sync. * * A `Store` instance wraps a local SQLite database that is kept in sync with other clients via * an event log. Instead of mutating state directly, you commit events that get materialized * into database rows. Queries automatically re-run when their underlying tables change. * * ## Creating a Store * * Use `createStore` (Effect-based) or `createStorePromise` to obtain a Store instance. * In React applications, use `StoreRegistry` with `` and the `useStore()` hook * which manages the Store lifecycle. * * ## Querying Data * * Use {@link Store.query} for one-shot reads or {@link Store.subscribe} for reactive subscriptions. * Both accept query builders (e.g. `tables.todo.where({ complete: true })`) or custom `LiveQueryDef`s. * * ## Committing Events * * Use {@link Store.commit} to persist events. Events are immediately materialized locally and * asynchronously synced to other clients. Multiple events can be committed atomically. * * ## Lifecycle * * The Store must be shut down when no longer needed via {@link Store.shutdown} or * {@link Store.shutdownPromise}. Framework integrations (React, Effect) handle this automatically. * * @typeParam TSchema - The LiveStore schema defining tables and events * @typeParam TContext - Optional user-defined context attached to the Store (e.g. for dependency injection) * * @example * ```ts * // Query data * const todos = store.query(tables.todo.where({ complete: false })) * * // Subscribe to changes * const unsubscribe = store.subscribe(tables.todo.all(), (todos) => { * console.log('Todos updated:', todos) * }) * * // Commit an event * store.commit(events.todoCreated({ id: nanoid(), text: 'Buy milk' })) * ``` */ export declare class Store extends Inspectable.Class { /** Unique identifier for this Store instance, stable for its lifetime. */ readonly storeId: string; /** The LiveStore schema defining tables, events, and materializers. */ readonly schema: LiveStoreSchema; /** User-defined context attached to this Store (e.g. for dependency injection). */ readonly context: TContext; /** Options provided to the Store constructor. */ readonly params: StoreConstructorParams['params']; /** * Reactive connectivity updates emitted by the backing sync backend. * * @example * ```ts * import { Effect, Stream } from 'effect' * * const status = await store.networkStatus.pipe(Effect.runPromise) * * await store.networkStatus.changes.pipe( * Stream.tap((next) => console.log('network status update', next)), * Stream.runDrain, * Effect.scoped, * Effect.runPromise, * ) * ``` */ readonly networkStatus: ClientSession['leaderThread']['networkStatus']; /** * Indicates how data is being stored. * * - `persisted`: Data is persisted to disk (e.g., via OPFS on web, SQLite file on native) * - `in-memory`: Data is only stored in memory and will be lost on page refresh * * The store operates in `in-memory` mode when persistent storage is unavailable, * such as in Safari/Firefox private browsing mode where OPFS is restricted. * * @example * ```tsx * if (store.storageMode === 'in-memory') { * showWarning('Data will not be persisted in private browsing mode') * } * ``` */ readonly storageMode: StorageMode; /** * Store internals. Not part of the public API — shapes and semantics may change without notice. */ readonly [StoreInternalsSymbol]: StoreInternals; constructor({ clientSession, schema, otelOptions, context, batchUpdates, storeId, effectContext, params, confirmUnsavedChanges, __runningInDevtools, }: StoreConstructorParams); /** * Current session identifier for this Store instance. * * - Stable for the lifetime of the Store * - Useful for correlating events or scoping per-session data */ get sessionId(): string; /** * Stable client identifier for the process/device using this Store. * * - Shared across Store instances created by the same client * - Useful for diagnostics and multi-client correlation */ get clientId(): string; private checkShutdown; /** * Subscribe to the results of a query. * * - When providing an `onUpdate` callback it returns an {@link Unsubscribe} function. * - Without a callback it returns an {@link AsyncIterable} that yields query results. * * @example * ```ts * const unsubscribe = store.subscribe(query$, (result) => console.log(result)) * ``` * * @example * ```ts * for await (const result of store.subscribe(query$)) { * console.log(result) * } * ``` */ subscribe: SubscribeFn; private subscribeWithCallback; private subscribeAsAsyncIterable; subscribeStream: (query: Queryable, options?: SubscribeOptions) => Stream.Stream; /** * Synchronously queries the database without creating a LiveQuery. * This is useful for queries that don't need to be reactive. * * Example: Query builder * ```ts * const completedTodos = store.query(tables.todo.where({ complete: true })) * ``` * * Example: Raw SQL query * ```ts * const completedTodos = store.query({ query: 'SELECT * FROM todo WHERE complete = 1', bindValues: {} }) * ``` */ query: (query: Queryable | { query: string; bindValues: Bindable; schema?: Schema.Schema; }, options?: { otelContext?: otel.Context; debugRefreshReason?: RefreshReason; }) => TResult; /** * Set the value of a signal * * @example * ```ts * const count$ = signal(0, { label: 'count$' }) * store.setSignal(count$, 2) * ``` * * @example * ```ts * const count$ = signal(0, { label: 'count$' }) * store.setSignal(count$, (prev) => prev + 1) * ``` */ setSignal: (signalDef: SignalDef, value: T | ((prev: T) => T)) => void; /** * Commit a list of events to the store which will immediately update the local database * and sync the events across other clients (similar to a `git commit`). * * @example * ```ts * store.commit(events.todoCreated({ id: nanoid(), text: 'Make coffee' })) * ``` * * You can call `commit` with multiple events to apply them in a single database transaction. * * @example * ```ts * const todoId = nanoid() * store.commit( * events.todoCreated({ id: todoId, text: 'Make coffee' }), * events.todoCompleted({ id: todoId })) * ``` * * For more advanced transaction scenarios, you can pass a synchronous function to `commit` which will receive a callback * to which you can pass multiple events to be committed in the same database transaction. * Under the hood this will simply collect all events and apply them in a single database transaction. * * @example * ```ts * store.commit((commit) => { * const todoId = nanoid() * if (Math.random() > 0.5) { * commit(events.todoCreated({ id: todoId, text: 'Make coffee' })) * } else { * commit(events.todoCompleted({ id: todoId })) * } * }) * ``` * * When committing a large batch of events, you can also skip the database refresh to improve performance * and call `store.manualRefresh()` after all events have been committed. * * @example * ```ts * const todos = [ * { id: nanoid(), text: 'Make coffee' }, * { id: nanoid(), text: 'Buy groceries' }, * // ... 1000 more todos * ] * for (const todo of todos) { * store.commit({ skipRefresh: true }, events.todoCreated({ id: todo.id, text: todo.text })) * } * store.manualRefresh() * ``` */ commit: { >>(...list: TCommitArg): void; (txn: >>(...list: TCommitArg) => void): void; >>(options: StoreCommitOptions, ...list: TCommitArg): void; (options: StoreCommitOptions, txn: >>(...list: TCommitArg) => void): void; }; /** * Returns an async iterable of events from the eventlog. * Currently only events confirmed by the sync backend is supported. * * Defaults to tracking upstreamHead as it advances. If an `until` event is * supplied the stream finalizes upon reaching it. * * To start streaming from a specific point in the eventlog * you can provide a `since` event. * * Allows filtering by: * - `filter`: event types * - `clientIds`: client identifiers * - `sessionIds`: session identifiers * * The batchSize option controls the maximum amount of events that are fetched * from the eventlog in each query. Defaults to 100 and has a max allowed * value of 1000. * * TODO: * - Support streaming unconfirmed events * - Leader level * - Session level * - Support streaming client-only events * * @example * ```ts * // Stream todoCompleted events from the start * for await (const event of store.events(filter: ['todoCompleted'])) { * console.log(event) * } * ``` * * @example * ```ts * // Start streaming from a specific event * for await (const event of store.events({ since: EventSequenceNumber.Client.fromString('e3') })) { * console.log(event) * } * ``` */ events: (options?: StoreEventsOptions) => AsyncIterable>; /** * Returns an Effect Stream of events from the eventlog. * See `store.events` for details on options and behaviour. */ eventsStream: (options?: StoreEventsOptions) => Stream.Stream, UnknownError>; /** * Returns the current synchronization status of the store. * * This is a synchronous operation that returns the sync state between the * client session and the leader thread. Use this to display sync indicators * or check if local changes have been pushed to the leader. * * @example * ```ts * const status = store.syncStatus() * console.log(status.isSynced ? 'Synced' : `${status.pendingCount} pending`) * ``` * * @example * ```ts * // Health check for backend connectivity * const status = store.syncStatus() * if (!status.isSynced && status.pendingCount > 100) { * console.warn('Large backlog of unsynced events') * } * ``` */ syncStatus: () => SyncStatus; /** * Returns an Effect Stream of sync status updates. * * Emits the current status immediately and then whenever the sync state changes. * Use this for Effect-based workflows or when you need more control over the stream. * * @example * ```ts * store.syncStatusStream().pipe( * Stream.tap((status) => Effect.log(`Sync status: ${status.isSynced}`)), * Stream.runDrain, * ) * ``` */ syncStatusStream: () => Stream.Stream; /** * Subscribes to sync status changes. * * The callback is invoked immediately with the current status and then * whenever the sync state changes (e.g., when events are pushed or confirmed). * * @param onUpdate - Callback invoked with the current sync status * @returns Unsubscribe function to stop receiving updates * * @example * ```ts * const unsubscribe = store.subscribeSyncStatus((status) => { * updateUI(status.isSynced ? 'Synced' : 'Syncing...') * }) * * // Later, stop listening * unsubscribe() * ``` */ subscribeSyncStatus: (onUpdate: (status: SyncStatus) => void) => Unsubscribe; private makeSyncStatus; /** * This can be used in combination with `skipRefresh` when committing events. * We might need a better solution for this. Let's see. */ manualRefresh: (options?: { label?: string; }) => void; /** * Shuts down the store and closes the client session. * * This is called automatically when the store was created using the React or Effect API. */ shutdownPromise: (cause?: UnknownError) => Promise; /** * Shuts down the store and closes the client session. * * This is called automatically when the store was created using the React or Effect API. */ shutdown: (cause?: Cause.Cause) => Effect.Effect; /** * Helper methods useful during development * * @internal */ _dev: { downloadDb: (source?: "local" | "leader") => void; downloadEventlogDb: () => void; hardReset: (mode?: "all-data" | "only-app-db") => void; overrideNetworkStatus: (status: "online" | "offline") => void; syncStates: () => Promise<{ session: SyncState.SyncState; leader: SyncState.SyncState; }>; printSyncStates: () => void; version: string; otel: { rootSpanContext: () => otel.SpanContext | undefined; }; }; toJSON: () => { _tag: string; reactivityGraph: import("../reactive.ts").ReactiveGraphSnapshot; }; private runEffectFork; private runEffectPromise; private getCommitArgs; } //# sourceMappingURL=store.d.ts.map