[API reference](https://evolu.dev/docs/api-reference) › [@evolu/common](https://evolu.dev/docs/api-reference/common) › [local‑first/Storage](https://evolu.dev/docs/api-reference/common/local-first/Storage) › BaseSqliteStorage

Defined in: [packages/common/src/local-first/Storage.ts:343](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L343)

Common interface for both client and relay SQLite storages.

Evolu uses a Skiplist, which leverages SQLite indexes. The core logic is
implemented in SQL, so it doesn't have to make roundtrips to the DB.

While the SQL implementation may look sophisticated, it's conceptually simple
and LLMs can explain how it works. The Skiplist data structure is well
explained in [this Stack Overflow
answer](https://stackoverflow.com/questions/61944198/what-is-a-zip-tree-and-how-does-it-work).
The logic resembles [Negentropy's C++
storage](https://github.com/hoytech/negentropy), except we use a Skiplist to
leverage SQLite indexes, which makes the code simpler.

Note: A paid review by the SQLite team is planned, as they use the same
algorithm for their rsync tool.

The ideal storage for a Relay should use an architecture like
[strfry](https://github.com/hoytech/strfry) (a KV storage), but with Skiplist
to ensure that insertion order doesn't matter (local-first apps can often
write in the past.)

The ideal client implementation should probably use the SQLite extension
instead of SQL or even a KV storage, when such a thing for browsers/native
will exist and will be faster than SQLite.

# Scaling

The load can be distributed by deploying multiple relays, synchronized with
each other, if necessary. One relay should handle hundreds of thousands of
users, and when it goes down, nothing happens, because it will be
synchronized later.

## Extends

- [`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)\<[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage), `"validateWriteKey"` \| `"setWriteKey"` \| `"writeMessages"` \| `"readDbChange"`\>

## Properties

<a id="deleteowner"></a>

### deleteOwner

```ts
readonly deleteOwner: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">) => void;
```

Defined in: [packages/common/src/local-first/Storage.ts:167](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L167)

Delete all data for the given [Owner](https://evolu.dev/docs/api-reference/common/local-first/Owner/interfaces/Owner).

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`deleteOwner`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#deleteowner)

---

<a id="findlowerbound"></a>

### findLowerBound

```ts
readonly findLowerBound: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, begin: number & Brand<"Int"> & Brand<"NonNegative">, end: number & Brand<"Int"> & Brand<"NonNegative">, upperBound: RangeUpperBound) => number & Brand<"Int"> & Brand<"NonNegative">;
```

Defined in: [packages/common/src/local-first/Storage.ts:119](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L119)

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`findLowerBound`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#findlowerbound)

---

<a id="fingerprint"></a>

### fingerprint

```ts
readonly fingerprint: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, begin: number & Brand<"Int"> & Brand<"NonNegative">, end: number & Brand<"Int"> & Brand<"NonNegative">) => Fingerprint;
```

Defined in: [packages/common/src/local-first/Storage.ts:100](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L100)

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`fingerprint`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#fingerprint)

---

<a id="fingerprintranges"></a>

### fingerprintRanges

```ts
readonly fingerprintRanges: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, buckets: readonly number & Brand<"Int"> & Brand<"NonNegative">[], upperBound?: RangeUpperBound) => readonly FingerprintRange[];
```

Defined in: [packages/common/src/local-first/Storage.ts:113](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L113)

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.

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`fingerprintRanges`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#fingerprintranges)

---

<a id="getexistingtimestamps"></a>

### getExistingTimestamps

```ts
readonly getExistingTimestamps: (ownerIdBytes: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, timestampsBytes: readonly [Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"TimestampBytes">, Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"TimestampBytes">]) => readonly Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"TimestampBytes">[];
```

Defined in: [packages/common/src/local-first/Storage.ts:358](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L358)

Efficiently checks which timestamps already exist in the database using a
single CTE query instead of N individual queries.

### getSize

```ts
readonly getSize: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">) => number & Brand<"Int"> & Brand<"NonNegative">;
```

Defined in: [packages/common/src/local-first/Storage.ts:98](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L98)

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`getSize`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#getsize)

---

<a id="inserttimestamp"></a>

### insertTimestamp

```ts
readonly insertTimestamp: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, timestamp: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"TimestampBytes">, strategy: StorageInsertTimestampStrategy) => void;
```

Defined in: [packages/common/src/local-first/Storage.ts:348](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L348)

Inserts a timestamp for an owner into the skiplist-based storage.

### iterate

```ts
readonly iterate: (ownerId: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"IdBytes"> & Brand<"OwnerIdBytes">, begin: number & Brand<"Int"> & Brand<"NonNegative">, end: number & Brand<"Int"> & Brand<"NonNegative">, callback: (timestamp: Uint8Array<ArrayBufferLike> & Brand<"Length16"> & Brand<"TimestampBytes">, index: number & Brand<"Int"> & Brand<"NonNegative">) => boolean) => void;
```

Defined in: [packages/common/src/local-first/Storage.ts:126](https://github.com/evoluhq/evolu/blob/e7144e2bbe9069362b62dec1b64a8aa922b8f1b0/packages/common/src/local-first/Storage.ts#L126)

#### Inherited from

[`Storage`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage).[`iterate`](https://evolu.dev/docs/api-reference/common/local-first/Storage/interfaces/Storage#iterate)