import { Mutex } from 'async-mutex'; import { ILogger } from 'js-logger'; import { DBAdapter, QueryResult, Transaction } from '../db/DBAdapter.js'; import { SyncStatus } from '../db/crud/SyncStatus.js'; import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js'; import { Schema } from '../db/schema/Schema.js'; import { BaseObserver } from '../utils/BaseObserver.js'; import { ConnectionManager, CreateSyncImplementationOptions } from './ConnectionManager.js'; import { ArrayQueryDefinition, Query } from './Query.js'; import { SQLOpenFactory, SQLOpenOptions } from './SQLOpenFactory.js'; import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js'; import { BucketStorageAdapter } from './sync/bucket/BucketStorageAdapter.js'; import { CrudBatch } from './sync/bucket/CrudBatch.js'; import { CrudTransaction } from './sync/bucket/CrudTransaction.js'; import { InternalConnectionOptions, StreamingSyncImplementation, StreamingSyncImplementationListener, type AdditionalConnectionOptions, type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js'; import { SyncStream } from './sync/sync-streams.js'; import { TriggerManager } from './triggers/TriggerManager.js'; import { WatchCompatibleQuery } from './watched/WatchedQuery.js'; import { WatchedQueryComparator } from './watched/processors/comparators.js'; export interface DisconnectAndClearOptions { /** When set to false, data in local-only tables is preserved. */ clearLocal?: boolean; } export interface BasePowerSyncDatabaseOptions extends AdditionalConnectionOptions { /** Schema used for the local database. */ schema: Schema; /** * @deprecated Use {@link retryDelayMs} instead as this will be removed in future releases. */ retryDelay?: number; logger?: ILogger; } export interface PowerSyncDatabaseOptions extends BasePowerSyncDatabaseOptions { /** * Source for a SQLite database connection. * This can be either: * - A {@link DBAdapter} if providing an instantiated SQLite connection * - A {@link SQLOpenFactory} which will be used to open a SQLite connection * - {@link SQLOpenOptions} for opening a SQLite connection with a default {@link SQLOpenFactory} */ database: DBAdapter | SQLOpenFactory | SQLOpenOptions; } export interface PowerSyncDatabaseOptionsWithDBAdapter extends BasePowerSyncDatabaseOptions { database: DBAdapter; } export interface PowerSyncDatabaseOptionsWithOpenFactory extends BasePowerSyncDatabaseOptions { database: SQLOpenFactory; } export interface PowerSyncDatabaseOptionsWithSettings extends BasePowerSyncDatabaseOptions { database: SQLOpenOptions; } export interface SQLOnChangeOptions { signal?: AbortSignal; tables?: string[]; /** The minimum interval between queries. */ throttleMs?: number; /** * @deprecated All tables specified in {@link tables} will be watched, including PowerSync tables with prefixes. * * Allows for watching any SQL table * by not removing PowerSync table name prefixes */ rawTableNames?: boolean; /** * Emits an empty result set immediately */ triggerImmediate?: boolean; } export interface SQLWatchOptions extends SQLOnChangeOptions { /** * Optional comparator which will be used to compare the results of the query. * The watched query will only yield results if the comparator returns false. */ comparator?: WatchedQueryComparator; } export interface WatchOnChangeEvent { changedTables: string[]; } export interface WatchHandler { onResult: (results: QueryResult) => void; onError?: (error: Error) => void; } export interface WatchOnChangeHandler { onChange: (event: WatchOnChangeEvent) => Promise | void; onError?: (error: Error) => void; } export interface PowerSyncDBListener extends StreamingSyncImplementationListener { initialized: () => void; schemaChanged: (schema: Schema) => void; closing: () => Promise | void; closed: () => Promise | void; } export interface PowerSyncCloseOptions { /** * Disconnect the sync stream client if connected. * This is usually true, but can be false for Web when using * multiple tabs and a shared sync provider. */ disconnect?: boolean; } export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions; export declare const DEFAULT_POWERSYNC_DB_OPTIONS: { retryDelayMs: number; crudUploadThrottleMs: number; }; export declare const DEFAULT_CRUD_BATCH_LIMIT = 100; /** * Requesting nested or recursive locks can block the application in some circumstances. * This default lock timeout will act as a failsafe to throw an error if a lock cannot * be obtained. */ export declare const DEFAULT_LOCK_TIMEOUT_MS = 120000; /** * Tests if the input is a {@link PowerSyncDatabaseOptionsWithSettings} * @internal */ export declare const isPowerSyncDatabaseOptionsWithSettings: (test: any) => test is PowerSyncDatabaseOptionsWithSettings; export declare abstract class AbstractPowerSyncDatabase extends BaseObserver { protected options: PowerSyncDatabaseOptions; /** * Returns true if the connection is closed. */ closed: boolean; ready: boolean; /** * Current connection status. */ currentStatus: SyncStatus; sdkVersion: string; protected bucketStorageAdapter: BucketStorageAdapter; protected _isReadyPromise: Promise; protected connectionManager: ConnectionManager; private subscriptions; get syncStreamImplementation(): StreamingSyncImplementation | null; /** * The connector used to connect to the PowerSync service. * * @returns The connector used to connect to the PowerSync service or null if `connect()` has not been called. */ get connector(): PowerSyncBackendConnector | null; /** * The resolved connection options used to connect to the PowerSync service. * * @returns The resolved connection options used to connect to the PowerSync service or null if `connect()` has not been called. */ get connectionOptions(): InternalConnectionOptions | null; protected _schema: Schema; private _database; protected runExclusiveMutex: Mutex; /** * @experimental * Allows creating SQLite triggers which can be used to track various operations on SQLite tables. */ readonly triggers: TriggerManager; logger: ILogger; constructor(options: PowerSyncDatabaseOptionsWithDBAdapter); constructor(options: PowerSyncDatabaseOptionsWithOpenFactory); constructor(options: PowerSyncDatabaseOptionsWithSettings); constructor(options: PowerSyncDatabaseOptions); /** * Schema used for the local database. */ get schema(): Schema<{ [x: string]: import("../index.js").Table; }>; /** * The underlying database. * * For the most part, behavior is the same whether querying on the underlying database, or on {@link AbstractPowerSyncDatabase}. */ get database(): DBAdapter; /** * Whether a connection to the PowerSync service is currently open. */ get connected(): boolean; get connecting(): boolean; /** * Opens the DBAdapter given open options using a default open factory */ protected abstract openDBAdapter(options: PowerSyncDatabaseOptionsWithSettings): DBAdapter; protected abstract generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: CreateSyncImplementationOptions & RequiredAdditionalConnectionOptions): StreamingSyncImplementation; protected abstract generateBucketStorageAdapter(): BucketStorageAdapter; /** * @returns A promise which will resolve once initialization is completed. */ waitForReady(): Promise; /** * Wait for the first sync operation to complete. * * @param request Either an abort signal (after which the promise will complete regardless of * whether a full sync was completed) or an object providing an abort signal and a priority target. * When a priority target is set, the promise may complete when all buckets with the given (or higher) * priorities have been synchronized. This can be earlier than a complete sync. * @returns A promise which will resolve once the first full sync has completed. */ waitForFirstSync(request?: AbortSignal | { signal?: AbortSignal; priority?: number; }): Promise; /** * Waits for the first sync status for which the `status` callback returns a truthy value. */ waitForStatus(predicate: (status: SyncStatus) => any, signal?: AbortSignal): Promise; /** * Allows for extended implementations to execute custom initialization * logic as part of the total init process */ abstract _initialize(): Promise; /** * Entry point for executing initialization logic. * This is to be automatically executed in the constructor. */ protected initialize(): Promise; private _loadVersion; protected resolveOfflineSyncStatus(): Promise; /** * Replace the schema with a new version. This is for advanced use cases - typically the schema should just be specified once in the constructor. * * Cannot be used while connected - this should only be called before {@link AbstractPowerSyncDatabase.connect}. */ updateSchema(schema: Schema): Promise; /** * Wait for initialization to complete. * While initializing is automatic, this helps to catch and report initialization errors. */ init(): Promise; protected resolvedConnectionOptions(options: CreateSyncImplementationOptions): CreateSyncImplementationOptions & RequiredAdditionalConnectionOptions; /** * @deprecated Use {@link AbstractPowerSyncDatabase#close} instead. * Clears all listeners registered by {@link AbstractPowerSyncDatabase#registerListener}. */ dispose(): void; /** * Locking mechanism for exclusively running critical portions of connect/disconnect operations. * Locking here is mostly only important on web for multiple tab scenarios. */ protected runExclusive(callback: () => Promise): Promise; /** * Connects to stream of events from the PowerSync instance. */ connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions): Promise; /** * Close the sync connection. * * Use {@link connect} to connect again. */ disconnect(): Promise; /** * Disconnect and clear the database. * Use this when logging out. * The database can still be queried after this is called, but the tables * would be empty. * * To preserve data in local-only tables, set clearLocal to false. */ disconnectAndClear(options?: DisconnectAndClearOptions): Promise; /** * Create a sync stream to query its status or to subscribe to it. * * @param name The name of the stream to subscribe to. * @param params Optional parameters for the stream subscription. * @returns A {@link SyncStream} instance that can be subscribed to. * @experimental Sync streams are currently in alpha. */ syncStream(name: string, params?: Record): SyncStream; /** * Close the database, releasing resources. * * Also disconnects any active connection. * * Once close is called, this connection cannot be used again - a new one * must be constructed. */ close(options?: PowerSyncCloseOptions): Promise; /** * Get upload queue size estimate and count. */ getUploadQueueStats(includeSize?: boolean): Promise; /** * Get a batch of CRUD data to upload. * * Returns null if there is no data to upload. * * Use this from the {@link PowerSyncBackendConnector.uploadData} callback. * * Once the data have been successfully uploaded, call {@link CrudBatch.complete} before * requesting the next batch. * * Use {@link limit} to specify the maximum number of updates to return in a single * batch. * * This method does include transaction ids in the result, but does not group * data by transaction. One batch may contain data from multiple transactions, * and a single transaction may be split over multiple batches. * * @param limit Maximum number of CRUD entries to include in the batch * @returns A batch of CRUD operations to upload, or null if there are none */ getCrudBatch(limit?: number): Promise; /** * Get the next recorded transaction to upload. * * Returns null if there is no data to upload. * * Use this from the {@link PowerSyncBackendConnector.uploadData} callback. * * Once the data have been successfully uploaded, call {@link CrudTransaction.complete} before * requesting the next transaction. * * Unlike {@link getCrudBatch}, this only returns data from a single transaction at a time. * All data for the transaction is loaded into memory. * * @returns A transaction of CRUD operations to upload, or null if there are none */ getNextCrudTransaction(): Promise; /** * Returns an async iterator of completed transactions with local writes against the database. * * This is typically used from the {@link PowerSyncBackendConnector.uploadData} callback. Each entry emitted by the * returned iterator is a full transaction containing all local writes made while that transaction was active. * * Unlike {@link getNextCrudTransaction}, which always returns the oldest transaction that hasn't been * {@link CrudTransaction.complete}d yet, this iterator can be used to receive multiple transactions. Calling * {@link CrudTransaction.complete} will mark that and all prior transactions emitted by the iterator as completed. * * This can be used to upload multiple transactions in a single batch, e.g with: * * ```JavaScript * let lastTransaction = null; * let batch = []; * * for await (const transaction of database.getCrudTransactions()) { * batch.push(...transaction.crud); * lastTransaction = transaction; * * if (batch.length > 10) { * break; * } * } * ``` * * If there is no local data to upload, the async iterator complete without emitting any items. * * Note that iterating over async iterables requires a [polyfill](https://github.com/powersync-ja/powersync-js/tree/main/packages/react-native#babel-plugins-watched-queries) * for React Native. */ getCrudTransactions(): AsyncIterable; /** * Get an unique client id for this database. * * The id is not reset when the database is cleared, only when the database is deleted. * * @returns A unique identifier for the database instance */ getClientId(): Promise; private handleCrudCheckpoint; /** * Execute a SQL write (INSERT/UPDATE/DELETE) query * and optionally return results. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @returns The query result as an object with structured key-value pairs */ execute(sql: string, parameters?: any[]): Promise; /** * Execute a SQL write (INSERT/UPDATE/DELETE) query directly on the database without any PowerSync processing. * This bypasses certain PowerSync abstractions and is useful for accessing the raw database results. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @returns The raw query result from the underlying database as a nested array of raw values, where each row is * represented as an array of column values without field names. */ executeRaw(sql: string, parameters?: any[]): Promise; /** * Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set * and optionally return results. * This is faster than executing separately with each parameter set. * * @param sql The SQL query to execute * @param parameters Optional 2D array of parameter sets, where each inner array is a set of parameters for one execution * @returns The query result */ executeBatch(sql: string, parameters?: any[][]): Promise; /** * Execute a read-only query and return results. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @returns An array of results */ getAll(sql: string, parameters?: any[]): Promise; /** * Execute a read-only query and return the first result, or null if the ResultSet is empty. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @returns The first result if found, or null if no results are returned */ getOptional(sql: string, parameters?: any[]): Promise; /** * Execute a read-only query and return the first result, error if the ResultSet is empty. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @returns The first result matching the query * @throws Error if no rows are returned */ get(sql: string, parameters?: any[]): Promise; /** * Takes a read lock, without starting a transaction. * In most cases, {@link readTransaction} should be used instead. */ readLock(callback: (db: DBAdapter) => Promise): Promise; /** * Takes a global lock, without starting a transaction. * In most cases, {@link writeTransaction} should be used instead. */ writeLock(callback: (db: DBAdapter) => Promise): Promise; /** * Open a read-only transaction. * Read transactions can run concurrently to a write transaction. * Changes from any write transaction are not visible to read transactions started before it. * * @param callback Function to execute within the transaction * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error * @returns The result of the callback * @throws Error if the lock cannot be obtained within the timeout period */ readTransaction(callback: (tx: Transaction) => Promise, lockTimeout?: number): Promise; /** * Open a read-write transaction. * This takes a global lock - only one write transaction can execute against the database at a time. * Statements within the transaction must be done on the provided {@link Transaction} interface. * * @param callback Function to execute within the transaction * @param lockTimeout Time in milliseconds to wait for a lock before throwing an error * @returns The result of the callback * @throws Error if the lock cannot be obtained within the timeout period */ writeTransaction(callback: (tx: Transaction) => Promise, lockTimeout?: number): Promise; /** * This version of `watch` uses {@link AsyncGenerator}, for documentation see {@link watchWithAsyncGenerator}. * Can be overloaded to use a callback handler instead, for documentation see {@link watchWithCallback}. * * @example * ```javascript * async *attachmentIds() { * for await (const result of this.powersync.watch( * `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`, * [] * )) { * yield result.rows?._array.map((r) => r.id) ?? []; * } * } * ``` */ watch(sql: string, parameters?: any[], options?: SQLWatchOptions): AsyncIterable; /** * See {@link watchWithCallback}. * * @example * ```javascript * onAttachmentIdsChange(onResult) { * this.powersync.watch( * `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`, * [], * { * onResult: (result) => onResult(result.rows?._array.map((r) => r.id) ?? []) * } * ); * } * ``` */ watch(sql: string, parameters?: any[], handler?: WatchHandler, options?: SQLWatchOptions): void; /** * Allows defining a query which can be used to build a {@link WatchedQuery}. * The defined query will be executed with {@link AbstractPowerSyncDatabase#getAll}. * An optional mapper function can be provided to transform the results. * * @example * ```javascript * const watchedTodos = powersync.query({ * sql: `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`, * parameters: [], * mapper: (row) => ({ * ...row, * created_at: new Date(row.created_at as string) * }) * }) * .watch() * // OR use .differentialWatch() for fine-grained watches. * ``` */ query(query: ArrayQueryDefinition): Query; /** * Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}. * The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results. * * @example * ```javascript * * // Potentially a query from an ORM like Drizzle * const query = db.select().from(lists); * * const watchedTodos = powersync.customQuery(query) * .watch() * // OR use .differentialWatch() for fine-grained watches. * ``` */ customQuery(query: WatchCompatibleQuery): Query; /** * Execute a read query every time the source tables are modified. * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries. * Source tables are automatically detected using `EXPLAIN QUERY PLAN`. * * Note that the `onChange` callback member of the handler is required. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @param handler Callbacks for handling results and errors * @param options Options for configuring watch behavior */ watchWithCallback(sql: string, parameters?: any[], handler?: WatchHandler, options?: SQLWatchOptions): void; /** * Execute a read query every time the source tables are modified. * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries. * Source tables are automatically detected using `EXPLAIN QUERY PLAN`. * * @param sql The SQL query to execute * @param parameters Optional array of parameters to bind to the query * @param options Options for configuring watch behavior * @returns An AsyncIterable that yields QueryResults whenever the data changes */ watchWithAsyncGenerator(sql: string, parameters?: any[], options?: SQLWatchOptions): AsyncIterable; /** * Resolves the list of tables that are used in a SQL query. * If tables are specified in the options, those are used directly. * Otherwise, analyzes the query using EXPLAIN to determine which tables are accessed. * * @param sql The SQL query to analyze * @param parameters Optional parameters for the SQL query * @param options Optional watch options that may contain explicit table list * @returns Array of table names that the query depends on */ resolveTables(sql: string, parameters?: any[], options?: SQLWatchOptions): Promise; /** * This version of `onChange` uses {@link AsyncGenerator}, for documentation see {@link onChangeWithAsyncGenerator}. * Can be overloaded to use a callback handler instead, for documentation see {@link onChangeWithCallback}. * * @example * ```javascript * async monitorChanges() { * for await (const event of this.powersync.onChange({tables: ['todos']})) { * console.log('Detected change event:', event); * } * } * ``` */ onChange(options?: SQLOnChangeOptions): AsyncIterable; /** * See {@link onChangeWithCallback}. * * @example * ```javascript * monitorChanges() { * this.powersync.onChange({ * onChange: (event) => { * console.log('Change detected:', event); * } * }, { tables: ['todos'] }); * } * ``` */ onChange(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void; /** * Invoke the provided callback on any changes to any of the specified tables. * * This is preferred over {@link watchWithCallback} when multiple queries need to be performed * together when data is changed. * * Note that the `onChange` callback member of the handler is required. * * @param handler Callbacks for handling change events and errors * @param options Options for configuring watch behavior * @returns A dispose function to stop watching for changes */ onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void; /** * Create a Stream of changes to any of the specified tables. * * This is preferred over {@link watchWithAsyncGenerator} when multiple queries need to be performed * together when data is changed. * * Note: do not declare this as `async *onChange` as it will not work in React Native. * * @param options Options for configuring watch behavior * @returns An AsyncIterable that yields change events whenever the specified tables change */ onChangeWithAsyncGenerator(options?: SQLWatchOptions): AsyncIterable; private handleTableChanges; private processTableUpdates; /** * @ignore */ private executeReadOnly; }