/** * @syncular/tauri — the JS bridge to the native syncular instance running * inside the Tauri process (see `tauri-plugin-syncular`). * * The Tauri host runs a REAL Rust syncular client (file DB + native HTTP+WS * transport). This module is a thin webview-side proxy that implements the SAME * `SyncClientLike` interface the React package normalizes — so the hooks * (`useRawSql`, `useMutation`, `usePresence`, …) work UNCHANGED against a * Tauri app. It is the fourth host of one interface, after the direct * `SyncClient`, the worker-leader `SyncClientHandle`, and the multi-tab * follower. * * Every method forwards to the plugin's `syncular_command` command (the whole * command surface in one JSON envelope — `{method, params}`), mirroring the FFI * and the conformance shim. `query` uses the dedicated `syncular_query` fast * path; atomic reactive reads use `syncular_query_snapshot`, backed by an * independent read-only SQLite connection so network sync cannot stall local * UI reads. Client-observable * events (`change` / `presence`) arrive on * the `syncular://event` Tauri event and fan out to the registered listeners. * * Bytes cross the command JSON as the established `{$bytes: hex}` envelope, the * same convention the Rust command router and the driver protocol use. * * `@tauri-apps/api` is a required peer dependency: the bridge takes * `invoke`/`listen` either from its ESM entry points, from the ambient * `window.__TAURI__`, or via injected doubles (tests). */ import type { ClientChangeListener, ClientDiagnosticsListener, ClientDiagnosticsRequest, ClientDiagnosticsSnapshot, CommitOutcome, CommitOutcomeQuery, ConflictRecord, EncryptionKeyringConfig, InvalidationListener, LeaseState, LocalDataPurgeInput, LocalDataPurgeResult, LocalDataRebootstrapInput, LocalDataRebootstrapResult, MutationInput, PresencePeer, QueryReadSpec, QuerySnapshot, RejectionRecord, ResolveCommitOutcomeInput, SchemaFloor, SecurityLifecycle, SqlRow, SqlValue, SyncStatusSnapshot, WindowBase, WindowState } from '@syncular/client'; /** One event pushed on `syncular://event` (the derived client-observable set). */ interface SyncularEvent { readonly type: string; readonly [key: string]: unknown; } /** The two Tauri primitives the bridge needs — injectable for tests. */ export interface TauriApi { invoke(cmd: string, args?: Record): Promise; listen(event: string, handler: (event: { payload: T; }) => void): Promise<() => void>; } /** The plugin's Tauri event name — mirror of `tauri-plugin-syncular`. */ export declare const SYNCULAR_EVENT = "syncular://event"; /** Config for {@link createTauriSyncClient}. */ export interface TauriSyncClientConfig { /** The generated schema JSON (the app passes `schema` from typegen). */ readonly schema: unknown; /** * Client id for this device/actor. If omitted, the native client generates * one and persists it in the database. Supplying a different id when opening * an existing database fails with `client.identity_mismatch`. */ readonly clientId?: string; /** §4.2 client limits, forwarded to the native `create`. */ readonly limits?: Record; /** * Portable E2EE keys and declarative per-row key-id columns. Raw keys are * encoded into the native command envelope and never sent to the server. */ readonly encryption?: EncryptionKeyringConfig; /** Open the native replica behind the fail-closed security gate. */ readonly securityPreflight?: boolean; /** * The Tauri primitives. Omit in a real Tauri webview to auto-resolve from * `@tauri-apps/api` (peer dep) or the ambient `window.__TAURI__`; inject in * tests. Resolution is async, so construction is a factory (below). */ readonly tauri?: TauriApi; } /** The bytes envelope both sides share. */ export type BytesEnvelope = { readonly $bytes: string; }; /** * The webview-side proxy implementing `SyncClientLike` over the plugin. Every * method is a promise (an IPC round trip); the React `normalizeClient` already * wraps sync and async members uniformly, so the hooks accept it directly. */ export declare class TauriSyncClient { #private; /** @internal — use {@link createTauriSyncClient}. */ constructor(tauri: TauriApi, unlisten: () => void, securityLifecycle?: SecurityLifecycle); /** @internal — fan an incoming plugin event out to the local listeners. */ __dispatchEvent(event: SyncularEvent): void; securityLifecycle(): Promise; beginSecurityPreflight(): Promise; /** * Complete the security preflight. `headers` optionally carries a fresh * FULL transport header set that the native host applies atomically with * the activation, before the startup sync it enqueues — so a preflight * that outlives the boot token starts its first round with valid * credentials (the direct `setHeaders` path stays refused while gated). */ activateSecurity(options?: { readonly encryption?: EncryptionKeyringConfig; readonly headers?: Readonly>; }): Promise; onInvalidate(listener: InvalidationListener): () => void; onChange(listener: ClientChangeListener): () => void; onDiagnostics(listener: ClientDiagnosticsListener): () => void; onPresence(listener: (scopeKey: string) => void): () => void; query(sql: string, params?: readonly SqlValue[]): Promise; querySnapshot(spec: QueryReadSpec): Promise>; localRevision(): Promise; statusSnapshot(): Promise; diagnosticsSnapshot(request?: ClientDiagnosticsRequest): Promise; mutate(mutations: readonly MutationInput[]): Promise; patch(table: string, rowId: string, partial: Readonly>, options?: { readonly baseVersion?: number; }): Promise; purgeLocalData(input: LocalDataPurgeInput): Promise; rebootstrapLocalData(input: LocalDataRebootstrapInput): Promise; /** * Replace the native transport's request headers at runtime — the auth * rotation path. Pass the FULL header set (it replaces, * it does not merge). HTTP requests use the new set from the next call; * the realtime socket applies it on its next (re)connect. */ setHeaders(headers: Readonly>): Promise; /** Materialize a `crdt` column's collaborative text — decoded from the * stored (server-merged) Yjs bytes. `name` selects the shared text * (default `"text"`). An absent row / NULL column is the empty document. */ crdtText(table: string, rowId: string, column: string, name?: string): Promise; /** Insert `value` at UTF-16 offset `index` in a `crdt` column's text and * push the resulting Yjs update (baseVersion-less). Returns the commit id. */ crdtInsertText(table: string, rowId: string, column: string, index: number, value: string, name?: string): Promise; /** Delete `len` UTF-16 code units at `index` in a `crdt` column's text. */ crdtDeleteText(table: string, rowId: string, column: string, index: number, len: number, name?: string): Promise; /** Escape hatch: apply an arbitrary Yjs update onto a `crdt` column. */ crdtApplyUpdate(table: string, rowId: string, column: string, update: Uint8Array): Promise; subscribe(input: { readonly id: string; readonly table: string; readonly scopes?: Record; readonly params?: string; }): Promise; unsubscribe(id: string): Promise; setWindow(base: WindowBase, units: readonly string[]): Promise; windowState(base: WindowBase): Promise; sync(): Promise; syncUntilIdle(maxRounds?: number): Promise; conflicts(): Promise; rejections(): Promise; commitOutcome(clientCommitId: string): Promise; commitOutcomes(query?: CommitOutcomeQuery): Promise; resolveCommitOutcome(input: ResolveCommitOutcomeInput): Promise; schemaFloor(): Promise; leaseState(): Promise; upgrading(): Promise; syncNeeded(): Promise; pendingCommits(): Promise; presence(scopeKey: string): Promise; setPresence(scopeKey: string, doc: Record | null): Promise; connectRealtime(): Promise; disconnectRealtime(): Promise; /** Shut down the native core, release its keyring, then detach listeners. */ close(): Promise; } /** The error a `{error}` reply surfaces (mirrors the web-client `ClientSyncError`). */ export declare class TauriSyncError extends Error { readonly code: string; constructor(code: string, message: string); } /** * Construct the bridge and issue the native `create` (opening/attaching the * file DB on the Rust side per the plugin config). Returns a ready * `TauriSyncClient` that satisfies `SyncClientLike` — pass it straight to the * React ``. */ export declare function createTauriSyncClient(config: TauriSyncClientConfig): Promise; export {};