API reference / @evolu/common / local-first / ClientStorage
Interface: ClientStorage
Defined in: packages/common/src/local-first/Sync.ts:440
Evolu Storage
Evolu protocol using Storage is agnostic to storage implementation details—any storage can be plugged in, as long as it implements this interface. Implementations must handle their own errors; return values only indicate overall success or failure.
The Storage API is synchronous because SQLite's synchronous API is the fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call SQLite's C API directly with no context switching between the event loop and native code, and no promise microtasks or await overhead.
The only exception is Storage#writeMessages, which is async to allow for async validation logic before writing to storage. The write operation itself remains synchronous.
Extends
Properties
| Property | Modifier | Type | Description | Inherited from | Defined in |
|---|---|---|---|---|---|
deleteOwner | readonly | (ownerId) => boolean | Delete all data for the given Owner. Returns true on success, false on failure. | Storage.deleteOwner | packages/common/src/local-first/Storage.ts:167 |
findLowerBound | readonly | (ownerId, begin, end, upperBound) => | number & Brand<"Int"> & Brand<"NonNegative"> | null | - | Storage.findLowerBound | packages/common/src/local-first/Storage.ts:113 |
fingerprint | readonly | (ownerId, begin, end) => | Fingerprint | null | - | Storage.fingerprint | packages/common/src/local-first/Storage.ts:94 |
fingerprintRanges | readonly | (ownerId, buckets, upperBound?) => | readonly FingerprintRange[] | null | Computes fingerprints with their upper bounds in one call. This function can be replaced with many fingerprint/findLowerBound calls, but implementations can leverage it for batching and more efficient fingerprint computation. | Storage.fingerprintRanges | packages/common/src/local-first/Storage.ts:107 |
getExistingTimestamps | readonly | (ownerIdBytes, timestampsBytes) => Result<readonly Uint8Array<ArrayBufferLike> & Brand<"TimestampBytes">[], SqliteError> | Efficiently checks which timestamps already exist in the database using a single CTE query instead of N individual queries. | BaseSqliteStorage.getExistingTimestamps | packages/common/src/local-first/Storage.ts:353 |
getSize | readonly | (ownerId) => | number & Brand<"Int"> & Brand<"NonNegative"> | null | - | Storage.getSize | packages/common/src/local-first/Storage.ts:92 |
insertTimestamp | readonly | (ownerId, timestamp, strategy) => Result<void, SqliteError> | Inserts a timestamp for an owner into the skiplist-based storage. | BaseSqliteStorage.insertTimestamp | packages/common/src/local-first/Storage.ts:343 |
iterate | readonly | (ownerId, begin, end, callback) => void | - | Storage.iterate | packages/common/src/local-first/Storage.ts:120 |
readDbChange | readonly | (ownerId, timestamp) => | EncryptedDbChange | null | Read encrypted DbChanges from storage. | Storage.readDbChange | packages/common/src/local-first/Storage.ts:157 |
setWriteKey | readonly | (ownerId, writeKey) => boolean | Sets the OwnerWriteKey for the given Owner. | Storage.setWriteKey | packages/common/src/local-first/Storage.ts:138 |
validateWriteKey | readonly | (ownerId, writeKey) => boolean | Validates the OwnerWriteKey for the given Owner. Returns true if the write key is valid, false otherwise. | Storage.validateWriteKey | packages/common/src/local-first/Storage.ts:132 |
writeMessages | readonly | (ownerIdBytes, messages) => MaybeAsync<Result<void, | StorageWriteError | StorageQuotaError>> | Write encrypted CrdtMessages to storage. Must use a mutex per ownerId to ensure sequential processing and proper protocol logic handling during sync operations. TODO: Use MaybeAsync | Storage.writeMessages | packages/common/src/local-first/Storage.ts:151 |