replicache
Version:
Realtime sync for any backend stack
1,284 lines (1,244 loc) • 49.4 kB
TypeScript
import { LogContext, LogLevel, LogSink } from '@rocicorp/logger';
/** The values that can be represented in JSON */
type JSONValue =
| null
| string
| boolean
| number
| Array<JSONValue>
| JSONObject;
/**
* A JSON object. This is a map from strings to JSON values or `undefined`. We
* allow `undefined` values as a convenience... but beware that the `undefined`
* values do not round trip to the server. For example:
*
* ```
* // Time t1
* await tx.set('a', {a: undefined});
*
* // time passes, in a new transaction
* const v = await tx.get('a');
* console.log(v); // either {a: undefined} or {}
* ```
*/
type JSONObject = {[key: string]: JSONValue | undefined};
/** Like {@link JSONValue} but deeply readonly */
type ReadonlyJSONValue =
| null
| string
| boolean
| number
| ReadonlyArray<ReadonlyJSONValue>
| ReadonlyJSONObject;
/** Like {@link JSONObject} but deeply readonly */
type ReadonlyJSONObject = {
readonly [key: string]: ReadonlyJSONValue | undefined;
};
type MaybePromise<T> = T | Promise<T>;
declare const hashTag: unique symbol;
/**
* Opaque type representing a hash. The only way to create one is using `parse`
* or `hashOf` (except for static unsafe cast of course).
*/
type Hash = string & {
[hashTag]: true;
};
interface Release$1 {
release(): void;
}
declare const refsTag: unique symbol;
/**
* Opaque type representing a Refs. The reason to use an opaque type here is to
* make sure that Refs are always sorted and have no duplicates.
*/
type Refs = [] | readonly [Hash] | (readonly Hash[] & {
[refsTag]: true;
});
declare class Chunk<V = unknown> {
readonly hash: Hash;
readonly data: V;
/**
* Meta is an array of refs. If there are no refs we do not write a meta
* chunk.
*/
readonly meta: Refs;
constructor(hash: Hash, data: V, refs: Refs);
}
type ChunkHasher = () => Hash;
interface Store$1 {
read(): Promise<Read$2>;
write(): Promise<Write$1>;
close(): Promise<void>;
}
interface GetChunk {
getChunk(hash: Hash): Promise<Chunk | undefined>;
}
interface MustGetChunk {
mustGetChunk(hash: Hash): Promise<Chunk>;
}
interface Read$2 extends GetChunk, MustGetChunk, Release$1 {
hasChunk(hash: Hash): Promise<boolean>;
getHead(name: string): Promise<Hash | undefined>;
get closed(): boolean;
}
interface Write$1 extends Read$2 {
createChunk<V>(data: V, refs: Refs): Chunk<V>;
putChunk<V>(c: Chunk<V>): Promise<void>;
setHead(name: string, hash: Hash): Promise<void>;
removeHead(name: string): Promise<void>;
assertValidHash(hash: Hash): void;
commit(): Promise<void>;
}
declare const enum FormatVersion {
SDD = 4,
DD31 = 5,
V6 = 6,
V7 = 7,
Latest = 7
}
/**
* A cookie is a value that is used to determine the order of snapshots. It
* needs to be comparable. This can be a `string`, `number` or if you want to
* use a more complex value, you can use an object with an `order` property. The
* value `null` is considered to be less than any other cookie and it is used
* for the first pull when no cookie has been set.
*
* The order is the natural order of numbers and strings. If one of the cookies
* is an object then the value of the `order` property is treated as the cookie
* when doing comparison.
*
* If one of the cookies is a string and the other is a number, the number is
* fist converted to a string (using `toString()`).
*/
type Cookie = null | string | number | (ReadonlyJSONValue & {
readonly order: number | string;
});
declare const frozenJSONTag: unique symbol;
/**
* Used to mark a type as having been frozen.
*/
type FrozenTag<T> = T & {
readonly [frozenJSONTag]: true;
};
type FrozenJSONValue = null | string | boolean | number | FrozenJSONArray | FrozenJSONObject;
type FrozenJSONArray = FrozenTag<ReadonlyArray<FrozenJSONValue>>;
type FrozenJSONObject = FrozenTag<{
readonly [key: string]: FrozenJSONValue;
}>;
declare class BTreeRead implements AsyncIterable<Entry$1<FrozenJSONValue>> {
protected readonly _cache: Map<Hash, DataNodeImpl | InternalNodeImpl>;
protected readonly _dagRead: Read$2;
protected readonly _formatVersion: FormatVersion;
rootHash: Hash;
readonly getEntrySize: <K, V>(k: K, v: V) => number;
readonly chunkHeaderSize: number;
constructor(dagRead: Read$2, formatVersion: FormatVersion, root?: Hash, getEntrySize?: <K, V>(k: K, v: V) => number, chunkHeaderSize?: number);
getNode(hash: Hash): Promise<DataNodeImpl | InternalNodeImpl>;
get(key: string): Promise<FrozenJSONValue | undefined>;
has(key: string): Promise<boolean>;
isEmpty(): Promise<boolean>;
scan(fromKey: string): AsyncIterableIterator<Entry$1<FrozenJSONValue>>;
keys(): AsyncIterableIterator<string>;
entries(): AsyncIterableIterator<Entry$1<FrozenJSONValue>>;
[Symbol.asyncIterator](): AsyncIterableIterator<Entry$1<FrozenJSONValue>>;
diff(last: BTreeRead): AsyncIterableIterator<InternalDiffOperation>;
}
declare class BTreeWrite extends BTreeRead {
#private;
protected _dagRead: Write$1;
readonly minSize: number;
readonly maxSize: number;
constructor(dagWrite: Write$1, formatVersion: FormatVersion, root?: Hash, minSize?: number, maxSize?: number, getEntrySize?: <K, V>(k: K, v: V) => number, chunkHeaderSize?: number);
updateNode(node: DataNodeImpl | InternalNodeImpl): void;
newInternalNodeImpl(entries: Array<Entry$1<Hash>>, level: number): InternalNodeImpl;
newDataNodeImpl(entries: Entry$1<FrozenJSONValue>[]): DataNodeImpl;
newNodeImpl(entries: Entry$1<FrozenJSONValue>[], level: number): DataNodeImpl;
newNodeImpl(entries: Entry$1<Hash>[], level: number): InternalNodeImpl;
newNodeImpl(entries: Entry$1<Hash>[] | Entry$1<FrozenJSONValue>[], level: number): InternalNodeImpl | DataNodeImpl;
put(key: string, value: FrozenJSONValue): Promise<void>;
del(key: string): Promise<boolean>;
clear(): Promise<void>;
flush(): Promise<Hash>;
}
/**
* The definition of a single index.
*/
type IndexDefinition = {
/**
* The prefix, if any, to limit the index over. If not provided the values of
* all keys are indexed.
*/
readonly prefix?: string | undefined;
/**
* A [JSON Pointer](https://tools.ietf.org/html/rfc6901) pointing at the sub
* value inside each value to index over.
*
* For example, one might index over users' ages like so:
* `{prefix: '/user/', jsonPointer: '/age'}`
*/
readonly jsonPointer: string;
/**
* If `true`, indexing empty values will not emit a warning. Defaults to `false`.
*/
readonly allowEmpty?: boolean | undefined;
};
/**
* An object as a map defining the indexes. The keys are the index names and the
* values are the index definitions.
*/
type IndexDefinitions = {
readonly [name: string]: IndexDefinition;
};
/**
* The ID describing a group of clients. All clients in the same group share a
* persistent storage (IDB).
*/
type ClientGroupID = string;
/**
* The ID describing a client.
*/
type ClientID = string;
/**
* This is the type used for index definitions as defined in the Commit chunk data.
*
* Changing this requires a REPLICACHE_FORMAT_VERSION bump.
*/
type ChunkIndexDefinition = {
readonly name: string;
readonly keyPrefix: string;
readonly jsonPointer: string;
readonly allowEmpty?: boolean;
};
type IndexRecord = {
readonly definition: ChunkIndexDefinition;
readonly valueHash: Hash;
};
declare class IndexRead<BTree = BTreeRead> {
readonly meta: IndexRecord;
readonly map: BTree;
constructor(meta: IndexRecord, map: BTree);
}
/**
* When using indexes the key is a tuple of the secondary key and the primary
* key.
*/
type IndexKey = readonly [secondary: string, primary: string];
type Entry$1<V> = readonly [key: string, value: V, sizeOfEntry: number];
/**
* Describes the changes that happened to Replicache after a
* {@link WriteTransaction} was committed.
*
* @experimental This type is experimental and may change in the future.
*/
type Diff = IndexDiff | NoIndexDiff;
/**
* @experimental This type is experimental and may change in the future.
*/
type IndexDiff = readonly DiffOperation<IndexKey>[];
/**
* @experimental This type is experimental and may change in the future.
*/
type NoIndexDiff = readonly DiffOperation<string>[];
/**
* InternalDiff uses string keys even for the secondary index maps.
*/
type InternalDiff = readonly InternalDiffOperation[];
type DiffOperationAdd<Key, Value = ReadonlyJSONValue> = {
readonly op: 'add';
readonly key: Key;
readonly newValue: Value;
};
type DiffOperationDel<Key, Value = ReadonlyJSONValue> = {
readonly op: 'del';
readonly key: Key;
readonly oldValue: Value;
};
type DiffOperationChange<Key, Value = ReadonlyJSONValue> = {
readonly op: 'change';
readonly key: Key;
readonly oldValue: Value;
readonly newValue: Value;
};
/**
* The individual parts describing the changes that happened to the Replicache
* data. There are three different kinds of operations:
* - `add`: A new entry was added.
* - `del`: An entry was deleted.
* - `change`: An entry was changed.
*
* @experimental This type is experimental and may change in the future.
*/
type DiffOperation<Key> = DiffOperationAdd<Key> | DiffOperationDel<Key> | DiffOperationChange<Key>;
type InternalDiffOperation<Key = string, Value = FrozenJSONValue> = DiffOperationAdd<Key, Value> | DiffOperationDel<Key, Value> | DiffOperationChange<Key, Value>;
declare abstract class NodeImpl<Value> {
#private;
entries: Array<Entry$1<Value>>;
hash: Hash;
abstract readonly level: number;
readonly isMutable: boolean;
constructor(entries: Array<Entry$1<Value>>, hash: Hash, isMutable: boolean);
abstract set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<NodeImpl<Value>>;
abstract del(key: string, tree: BTreeWrite): Promise<NodeImpl<Value> | DataNodeImpl>;
maxKey(): string;
getChildNodeSize(tree: BTreeRead): number;
protected _updateNode(tree: BTreeWrite): void;
}
declare class DataNodeImpl extends NodeImpl<FrozenJSONValue> {
#private;
readonly level = 0;
set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<DataNodeImpl>;
del(key: string, tree: BTreeWrite): Promise<DataNodeImpl>;
keys(_tree: BTreeRead): AsyncGenerator<string, void>;
entriesIter(_tree: BTreeRead): AsyncGenerator<Entry$1<FrozenJSONValue>, void>;
}
declare class InternalNodeImpl extends NodeImpl<Hash> {
#private;
readonly level: number;
constructor(entries: Array<Entry$1<Hash>>, hash: Hash, level: number, isMutable: boolean);
set(key: string, value: FrozenJSONValue, entrySize: number, tree: BTreeWrite): Promise<InternalNodeImpl>;
del(key: string, tree: BTreeWrite): Promise<InternalNodeImpl | DataNodeImpl>;
keys(tree: BTreeRead): AsyncGenerator<string, void>;
entriesIter(tree: BTreeRead): AsyncGenerator<Entry$1<FrozenJSONValue>, void>;
getChildren(start: number, length: number, tree: BTreeRead): Promise<Array<InternalNodeImpl | DataNodeImpl>>;
getCompositeChildren(start: number, length: number, tree: BTreeRead): Promise<InternalNodeImpl | DataNodeImpl>;
}
/**
* In certain scenarios the server can signal that it does not know about the
* client. For example, the server might have lost all of its state (this might
* happen during the development of the server).
*/
type ClientStateNotFoundResponse = {
error: 'ClientStateNotFound';
};
/**
* The server endpoint may respond with a `VersionNotSupported` error if it does
* not know how to handle the {@link pullVersion}, {@link pushVersion} or the
* {@link schemaVersion}.
*/
type VersionNotSupportedResponse = {
error: 'VersionNotSupported';
versionType?: 'pull' | 'push' | 'schema' | undefined;
};
type IterableUnion<T> = AsyncIterable<T> | Iterable<T>;
type HTTPRequestInfo = {
httpStatusCode: number;
errorMessage: string;
};
type PatchOperationInternal = {
readonly op: 'put';
readonly key: string;
readonly value: ReadonlyJSONValue;
} | {
readonly op: 'update';
readonly key: string;
readonly merge?: ReadonlyJSONObject | undefined;
readonly constrain?: string[] | undefined;
} | {
readonly op: 'del';
readonly key: string;
} | {
readonly op: 'clear';
};
/**
* This type describes the patch field in a {@link PullResponse} and it is used
* to describe how to update the Replicache key-value store.
*/
type PatchOperation = {
readonly op: 'put';
readonly key: string;
readonly value: ReadonlyJSONValue;
} | {
readonly op: 'del';
readonly key: string;
} | {
readonly op: 'clear';
};
/**
* Interface allowing different diff functions to skip costly diff computations.
*/
interface DiffComputationConfig {
shouldComputeDiffs(): boolean;
shouldComputeDiffsForIndex(name: string): boolean;
}
/**
* The diffs in different indexes. The key of the map is the index name.
* "" is used for the primary index.
*/
declare class DiffsMap extends Map<string, InternalDiff> {
set(key: string, value: InternalDiff): this;
}
/**
* The JSON value used as the body when doing a POST to the [pull
* endpoint](/reference/server-pull).
*/
type PullRequest = PullRequestV1 | PullRequestV0;
/**
* The JSON value used as the body when doing a POST to the [pull
* endpoint](/reference/server-pull). This is the legacy version (V0) and it is
* still used when recovering mutations from old clients.
*/
type PullRequestV0 = {
pullVersion: 0;
schemaVersion: string;
profileID: string;
cookie: ReadonlyJSONValue;
clientID: ClientID;
lastMutationID: number;
};
/**
* The JSON value used as the body when doing a POST to the [pull
* endpoint](/reference/server-pull).
*/
type PullRequestV1 = {
pullVersion: 1;
schemaVersion: string;
profileID: string;
cookie: Cookie;
clientGroupID: ClientGroupID;
};
type PullerResultV0 = {
response?: PullResponseV0 | undefined;
httpRequestInfo: HTTPRequestInfo;
};
type PullerResultV1 = {
response?: PullResponseV1 | undefined;
httpRequestInfo: HTTPRequestInfo;
};
type PullerResult = PullerResultV1 | PullerResultV0;
/**
* Puller is the function type used to do the fetch part of a pull.
*
* Puller needs to support dealing with pull request of version 0 and 1. Version
* 0 is used when doing mutation recovery of old clients. If a
* {@link PullRequestV1} is passed in the n a {@link PullerResultV1} should
* be returned. We do a runtime assert to make this is the case.
*
* If you do not support old clients you can just throw if `pullVersion` is `0`,
*/
type Puller = (requestBody: PullRequest, requestID: string) => Promise<PullerResult>;
/**
* The shape of a pull response under normal circumstances.
*/
type PullResponseOKV0 = {
cookie?: ReadonlyJSONValue | undefined;
lastMutationID: number;
patch: PatchOperation[];
};
/**
* The shape of a pull response under normal circumstances.
*/
type PullResponseOKV1 = {
cookie: Cookie;
lastMutationIDChanges: Record<ClientID, number>;
patch: PatchOperation[];
};
type PullResponseOKV1Internal = {
cookie: Cookie;
lastMutationIDChanges: Record<ClientID, number>;
patch: PatchOperationInternal[];
};
/**
* PullResponse defines the shape and type of the response of a pull. This is
* the JSON you should return from your pull server endpoint.
*/
type PullResponseV0 = PullResponseOKV0 | ClientStateNotFoundResponse | VersionNotSupportedResponse;
/**
* PullResponse defines the shape and type of the response of a pull. This is
* the JSON you should return from your pull server endpoint.
*/
type PullResponseV1 = PullResponseOKV1 | ClientStateNotFoundResponse | VersionNotSupportedResponse;
type PullResponseV1Internal = PullResponseOKV1Internal | ClientStateNotFoundResponse | VersionNotSupportedResponse;
type PullResponse = PullResponseV1 | PullResponseV0;
/**
* Store defines a transactional key/value store that Replicache stores all data
* within.
*
* For correct operation of Replicache, implementations of this interface must
* provide [strict
* serializable](https://jepsen.io/consistency/models/strict-serializable)
* transactions.
*
* Informally, read and write transactions must behave like a ReadWrite Lock -
* multiple read transactions are allowed in parallel, or one write.
* Additionally writes from a transaction must appear all at one, atomically.
*
*/
interface Store {
read(): Promise<Read$1>;
write(): Promise<Write>;
close(): Promise<void>;
closed: boolean;
}
/**
* Factory function for creating {@link Store} instances.
*
* The name is used to identify the store. If the same name is used for multiple
* stores, they should share the same data. It is also desirable to have these
* stores share an {@link RWLock}.
*
*/
type CreateStore = (name: string) => Store;
/**
* Function for deleting {@link Store} instances.
*
* The name is used to identify the store. If the same name is used for multiple
* stores, they should share the same data.
*
*/
type DropStore = (name: string) => Promise<void>;
/**
* Provider for creating and deleting {@link Store} instances.
*
*/
type StoreProvider = {
create: CreateStore;
drop: DropStore;
};
/**
* This interface is used so that we can release the lock when the transaction
* is done.
*
* @experimental This interface is experimental and might be removed or changed
* in the future without following semver versioning. Please be cautious.
*/
interface Release {
release(): void;
}
/**
* @experimental This interface is experimental and might be removed or changed
* in the future without following semver versioning. Please be cautious.
*/
interface Read$1 extends Release {
has(key: string): Promise<boolean>;
get(key: string): Promise<ReadonlyJSONValue | undefined>;
closed: boolean;
}
/**
* @experimental This interface is experimental and might be removed or changed
* in the future without following semver versioning. Please be cautious.
*/
interface Write extends Read$1 {
put(key: string, value: ReadonlyJSONValue): Promise<void>;
del(key: string): Promise<void>;
commit(): Promise<void>;
}
type PendingMutation = {
readonly name: string;
readonly id: number;
readonly args: ReadonlyJSONValue;
readonly clientID: ClientID;
};
/**
* Mutation describes a single mutation done on the client. This is the legacy
* version (V0) and it is used when recovering mutations from old clients.
*/
type MutationV0 = {
readonly id: number;
readonly name: string;
readonly args: ReadonlyJSONValue;
readonly timestamp: number;
};
/**
* Mutation describes a single mutation done on the client.
*/
type MutationV1 = {
readonly id: number;
readonly name: string;
readonly args: ReadonlyJSONValue;
readonly timestamp: number;
readonly clientID: ClientID;
};
/**
* The JSON value used as the body when doing a POST to the [push
* endpoint](/reference/server-push). This is the legacy version (V0) and it is
* still used when recovering mutations from old clients.
*/
type PushRequestV0 = {
pushVersion: 0;
/**
* `schemaVersion` can optionally be used to specify to the push endpoint
* version information about the mutators the app is using (e.g., format of
* mutator args).
*/
schemaVersion: string;
profileID: string;
clientID: ClientID;
mutations: MutationV0[];
};
/**
* The JSON value used as the body when doing a POST to the [push
* endpoint](/reference/server-push).
*/
type PushRequestV1 = {
pushVersion: 1;
/**
* `schemaVersion` can optionally be used to specify to the push endpoint
* version information about the mutators the app is using (e.g., format of
* mutator args).
*/
schemaVersion: string;
profileID: string;
clientGroupID: ClientGroupID;
mutations: MutationV1[];
};
type PushRequest = PushRequestV0 | PushRequestV1;
type PusherResult = {
response?: PushResponse | undefined;
httpRequestInfo: HTTPRequestInfo;
};
/**
* The response from a push can contain information about error conditions.
*/
type PushResponse = ClientStateNotFoundResponse | VersionNotSupportedResponse;
/**
* Pusher is the function type used to do the fetch part of a push. The request
* is a POST request where the body is JSON with the type {@link PushRequest}.
*
* The return value should either be a {@link HTTPRequestInfo} or a
* {@link PusherResult}. The reason for the two different return types is that
* we didn't use to care about the response body of the push request. The
* default pusher implementation checks if the response body is JSON and if it
* matches the type {@link PusherResponse}. If it does, it is included in the
* return value.
*/
type Pusher = (requestBody: PushRequest, requestID: string) => Promise<PusherResult>;
/**
* This error is thrown when the pusher fails for any reason.
*/
declare class PushError extends Error {
name: string;
causedBy?: Error | undefined;
constructor(causedBy?: Error);
}
declare class Read {
#private;
map: BTreeRead;
readonly indexes: Map<string, IndexRead>;
constructor(dagRead: Read$2, map: BTreeRead, indexes: Map<string, IndexRead>);
has(key: string): Promise<boolean>;
get(key: string): Promise<FrozenJSONValue | undefined>;
isEmpty(): Promise<boolean>;
getMapForIndex(indexName: string): BTreeRead;
get closed(): boolean;
close(): void;
}
type ScanOptions$1 = {
prefix?: string | undefined;
startSecondaryKey?: string | undefined;
startKey?: string | undefined;
startExclusive?: boolean | undefined;
limit?: number | undefined;
indexName?: string | undefined;
};
/**
* Options for {@link ReadTransaction.scan | scan}
*/
type ScanOptions = ScanIndexOptions | ScanNoIndexOptions;
/**
* Options for {@link ReadTransaction.scan | scan} when scanning over the entire key
* space.
*/
type ScanNoIndexOptions = {
/** Only include keys starting with `prefix`. */
prefix?: string | undefined;
/** Only include up to `limit` results. */
limit?: number | undefined;
/** When provided the scan starts at this key. */
start?: {
key: string;
/** Whether the `key` is exclusive or inclusive. */
exclusive?: boolean | undefined;
} | undefined;
};
/**
* Options for {@link ReadTransaction.scan | scan} when scanning over an index. When
* scanning over and index you need to provide the `indexName` and the `start`
* `key` is now a tuple consisting of secondary and primary key
*/
type ScanIndexOptions = {
/** Only include results starting with the *secondary* keys starting with `prefix`. */
prefix?: string | undefined;
/** Only include up to `limit` results. */
limit?: number | undefined;
/** Do a {@link ReadTransaction.scan | scan} over a named index. The `indexName` is
* the name of an index defined when creating the {@link Replicache} instance using
* {@link ReplicacheOptions.indexes}. */
indexName: string;
/** When provided the scan starts at this key. */
start?: {
key: ScanOptionIndexedStartKey;
/** Whether the `key` is exclusive or inclusive. */
exclusive?: boolean | undefined;
} | undefined;
};
/**
* Type narrowing of {@link ScanOptions}.
*/
declare function isScanIndexOptions(options: ScanOptions): options is ScanIndexOptions;
/**
* If the options contains an `indexName` then the key type is a tuple of
* secondary and primary.
*/
type KeyTypeForScanOptions<O extends ScanOptions> = O extends ScanIndexOptions ? IndexKey : string;
/**
* The key to start scanning at.
*
* If you are scanning the primary index (i.e., you did not specify
* `indexName`), then pass a single string for this field, which is the key in
* the primary index to scan at.
*
* If you are scanning a secondary index (i.e., you specified `indexName`), then
* use the tuple form. In that case, `secondary` is the secondary key to start
* scanning at, and `primary` (if any) is the primary key to start scanning at.
*/
type ScanOptionIndexedStartKey = readonly [secondary: string, primary?: string | undefined] | string;
type ScanKey = string | IndexKey;
interface ScanResult<K extends ScanKey, V> extends AsyncIterable<V> {
/** The default AsyncIterable. This is the same as {@link values}. */
[Symbol.asyncIterator](): AsyncIterableIteratorToArray<V>;
/** Async iterator over the values of the {@link ReadTransaction.scan | scan} call. */
values(): AsyncIterableIteratorToArray<V>;
/**
* Async iterator over the keys of the {@link ReadTransaction.scan | scan}
* call. If the {@link ReadTransaction.scan | scan} is over an index the key
* is a tuple of `[secondaryKey: string, primaryKey]`
*/
keys(): AsyncIterableIteratorToArray<K>;
/**
* Async iterator over the entries of the {@link ReadTransaction.scan | scan}
* call. An entry is a tuple of key values. If the
* {@link ReadTransaction.scan | scan} is over an index the key is a tuple of
* `[secondaryKey: string, primaryKey]`
*/
entries(): AsyncIterableIteratorToArray<readonly [K, V]>;
/** Returns all the values as an array. Same as `values().toArray()` */
toArray(): Promise<V[]>;
}
/**
* An interface that adds a {@link toArray} method to `AsyncIterableIterator`.
*
* Usage:
*
* ```ts
* const keys: string[] = await rep.scan().keys().toArray();
* ```
*/
interface AsyncIterableIteratorToArray<V> extends AsyncIterableIterator<V> {
toArray(): Promise<V[]>;
}
type Entry<V> = readonly [key: string, value: V];
/**
* This is called when doing a {@link ReadTransaction.scan | scan} without an
* `indexName`.
*
* @param fromKey The `fromKey` is computed by `scan` and is the key of the
* first entry to return in the iterator. It is based on `prefix` and
* `start.key` of the {@link ScanNoIndexOptions}.
*/
type GetScanIterator = (fromKey: string) => IterableUnion<Entry<ReadonlyJSONValue>>;
/**
* When using {@link makeScanResult} this is the type used for the function called when doing a {@link ReadTransaction.scan | scan} with an
* `indexName`.
*
* @param indexName The name of the index we are scanning over.
* @param fromSecondaryKey The `fromSecondaryKey` is computed by `scan` and is
* the secondary key of the first entry to return in the iterator. It is based
* on `prefix` and `start.key` of the {@link ScanIndexOptions}.
* @param fromPrimaryKey The `fromPrimaryKey` is computed by `scan` and is the
* primary key of the first entry to return in the iterator. It is based on
* `prefix` and `start.key` of the {@link ScanIndexOptions}.
*/
type GetIndexScanIterator = (indexName: string, fromSecondaryKey: string, fromPrimaryKey: string | undefined) => IterableUnion<readonly [key: IndexKey, value: ReadonlyJSONValue]>;
/**
* A helper function that makes it easier to implement {@link ReadTransaction.scan}
* with a custom backend.
*
* If you are implementing a custom backend and have an in memory pending async
* iterable we provide two helper functions to make it easier to merge these
* together. {@link mergeAsyncIterables} and {@link filterAsyncIterable}.
*
* For example:
*
* ```ts
* const scanResult = makeScanResult(
* options,
* options.indexName
* ? () => {
* throw Error('not implemented');
* }
* : fromKey => {
* const persisted: AsyncIterable<Entry<ReadonlyJSONValue>> = ...;
* const pending: AsyncIterable<Entry<ReadonlyJSONValue | undefined>> = ...;
* const iter = await mergeAsyncIterables(persisted, pending);
* const filteredIter = await filterAsyncIterable(
* iter,
* entry => entry[1] !== undefined,
* );
* return filteredIter;
* },
* );
* ```
*/
declare function makeScanResult<Options extends ScanOptions>(options: Options, getScanIterator: Options extends ScanIndexOptions ? GetIndexScanIterator : GetScanIterator): ScanResult<KeyTypeForScanOptions<Options>, ReadonlyJSONValue>;
declare const enum InvokeKind {
InitialRun = 0,
Regular = 1
}
interface Subscription<R> {
hasIndexSubscription(indexName: string): boolean;
invoke(tx: ReadTransaction, kind: InvokeKind, diffs: DiffsMap | undefined): Promise<R>;
matches(diffs: DiffsMap): boolean;
updateDeps(keys: ReadonlySet<string>, scans: ReadonlyArray<Readonly<ScanSubscriptionInfo>>): void;
readonly onData: (result: R) => void;
readonly onError: ((error: unknown) => void) | undefined;
readonly onDone: (() => void) | undefined;
}
/**
* Function that gets passed into {@link Replicache.experimentalWatch} and gets
* called when the data in Replicache changes.
*
* @experimental This type is experimental and may change in the future.
*/
type WatchNoIndexCallback = (diff: NoIndexDiff) => void;
type WatchCallbackForOptions<Options extends WatchOptions> = Options extends WatchIndexOptions ? WatchIndexCallback : WatchNoIndexCallback;
/**
* Function that gets passed into {@link Replicache.experimentalWatch} when doing a
* watch on a secondary index map and gets called when the data in Replicache
* changes.
*
* @experimental This type is experimental and may change in the future.
*/
type WatchIndexCallback = (diff: IndexDiff) => void;
/**
* Options for {@link Replicache.experimentalWatch}.
*
* @experimental This interface is experimental and may change in the future.
*/
type WatchOptions = WatchIndexOptions | WatchNoIndexOptions;
/**
* Options object passed to {@link Replicache.experimentalWatch}. This is for an
* index watch.
*/
type WatchIndexOptions = WatchNoIndexOptions & {
/**
* When provided, the `watch` is limited to the changes that apply to the index map.
*/
indexName: string;
};
/**
* Options object passed to {@link Replicache.experimentalWatch}. This is for a non
* index watch.
*/
type WatchNoIndexOptions = {
/**
* When provided, the `watch` is limited to changes where the `key` starts
* with `prefix`.
*/
prefix?: string | undefined;
/**
* When this is set to `true` (default is `false`), the `watch` callback will
* be called once asynchronously when watch is called. The arguments in that
* case is a diff where we consider all the existing values in Replicache as
* being added.
*/
initialValuesInFirstDiff?: boolean | undefined;
};
/**
* The options passed to {@link Replicache.subscribe}.
*/
interface SubscribeOptions<R> {
/**
* Called when the return value of the body function changes.
*/
onData: (result: R) => void;
/**
* If present, called when an error occurs.
*/
onError?: ((error: unknown) => void) | undefined;
/**
* If present, called when the subscription is removed/done.
*/
onDone?: (() => void) | undefined;
/**
* If present this function is used to determine if the value returned by the
* body function has changed. If not provided a JSON deep equality check is
* used.
*/
isEqual?: ((a: R, b: R) => boolean) | undefined;
}
interface SubscriptionsManager extends DiffComputationConfig {
clear(): void;
fire(diffs: DiffsMap): Promise<void>;
hasPendingSubscriptionRuns: boolean;
add<R>(subscription: Subscription<R>): () => void;
}
type ScanSubscriptionInfo = {
options: ScanOptions$1;
inclusiveLimitKey?: string | undefined;
};
type TransactionEnvironment = 'client' | 'server';
type TransactionLocation = TransactionEnvironment;
type TransactionReason = 'initial' | 'rebase' | 'authoritative';
/**
* Basic deep readonly type. It works for {@link JSONValue}.
*/
type DeepReadonly<T> = T extends null | boolean | string | number | undefined ? T : DeepReadonlyObject<T>;
type DeepReadonlyObject<T> = {
readonly [K in keyof T]: DeepReadonly<T[K]>;
};
/**
* ReadTransactions are used with {@link Replicache.query} and
* {@link Replicache.subscribe} and allows read operations on the
* database.
*/
interface ReadTransaction {
readonly clientID: ClientID;
/** @deprecated Use {@link ReadTransaction.location} instead. */
readonly environment: TransactionLocation;
readonly location: TransactionLocation;
/**
* Get a single value from the database. If the `key` is not present this
* returns `undefined`.
*
* Important: The returned JSON is readonly and should not be modified. This
* is only enforced statically by TypeScript and there are no runtime checks
* for performance reasons. If you mutate the return value you will get
* undefined behavior.
*/
get(key: string): Promise<ReadonlyJSONValue | undefined>;
get<T extends JSONValue>(key: string): Promise<DeepReadonly<T> | undefined>;
/** Determines if a single `key` is present in the database. */
has(key: string): Promise<boolean>;
/** Whether the database is empty. */
isEmpty(): Promise<boolean>;
/**
* Gets many values from the database. This returns a {@link ScanResult} which
* implements `AsyncIterable`. It also has methods to iterate over the
* {@link ScanResult.keys | keys} and {@link ScanResult.entries | entries}.
*
* If `options` has an `indexName`, then this does a scan over an index with
* that name. A scan over an index uses a tuple for the key consisting of
* `[secondary: string, primary: string]`.
*
* If the {@link ScanResult} is used after the `ReadTransaction` has been closed
* it will throw a {@link TransactionClosedError}.
*
* Important: The returned JSON is readonly and should not be modified. This
* is only enforced statically by TypeScript and there are no runtime checks
* for performance reasons. If you mutate the return value you will get
* undefined behavior.
*/
scan(options: ScanIndexOptions): ScanResult<IndexKey, ReadonlyJSONValue>;
scan(options?: ScanNoIndexOptions): ScanResult<string, ReadonlyJSONValue>;
scan(options?: ScanOptions): ScanResult<IndexKey | string, ReadonlyJSONValue>;
scan<V extends ReadonlyJSONValue>(options: ScanIndexOptions): ScanResult<IndexKey, DeepReadonly<V>>;
scan<V extends ReadonlyJSONValue>(options?: ScanNoIndexOptions): ScanResult<string, DeepReadonly<V>>;
scan<V extends ReadonlyJSONValue>(options?: ScanOptions): ScanResult<IndexKey | string, DeepReadonly<V>>;
}
declare class ReadTransactionImpl implements ReadTransaction {
readonly clientID: ClientID;
readonly dbtx: Read;
protected readonly _lc: LogContext;
/**
* The location in which this transaction is being used. This is either `client` or `server`.
*/
readonly location: TransactionLocation;
/** @deprecated Use {@link ReadTransaction.location} instead. */
readonly environment: TransactionLocation;
constructor(clientID: ClientID, dbRead: Read, lc: LogContext, rpcName?: string);
get(key: string): Promise<ReadonlyJSONValue | undefined>;
has(key: string): Promise<boolean>;
isEmpty(): Promise<boolean>;
scan(options: ScanIndexOptions): ScanResult<IndexKey, ReadonlyJSONValue>;
scan(options?: ScanNoIndexOptions): ScanResult<string, ReadonlyJSONValue>;
scan(options?: ScanOptions): ScanResult<IndexKey | string, ReadonlyJSONValue>;
scan<V extends ReadonlyJSONValue>(options: ScanIndexOptions): ScanResult<IndexKey, DeepReadonly<V>>;
scan<V extends ReadonlyJSONValue>(options?: ScanNoIndexOptions): ScanResult<string, DeepReadonly<V>>;
scan<V extends ReadonlyJSONValue>(options?: ScanOptions): ScanResult<IndexKey | string, DeepReadonly<V>>;
}
/**
* WriteTransactions are used with *mutators* which are registered using
* {@link ReplicacheOptions.mutators} and allows read and write operations on the
* database.
*/
interface WriteTransaction extends ReadTransaction {
/**
* The ID of the mutation that is being applied.
*/
readonly mutationID: number;
/**
* The reason for the transaction. This can be `initial`, `rebase` or `authoriative`.
*/
readonly reason: TransactionReason;
/**
* Sets a single `value` in the database. The value will be frozen (using
* `Object.freeze`) in debug mode.
*/
set(key: string, value: ReadonlyJSONValue): Promise<void>;
/**
* @deprecated Use {@link WriteTransaction.set} instead.
*/
put(key: string, value: ReadonlyJSONValue): Promise<void>;
/**
* Removes a `key` and its value from the database. Returns `true` if there was a
* `key` to remove.
*/
del(key: string): Promise<boolean>;
}
type CreateIndexDefinition = IndexDefinition & {
name: string;
};
type BeginPullResult = {
requestID: string;
syncHead: Hash;
ok: boolean;
};
type Poke = {
baseCookie: ReadonlyJSONValue;
pullResponse: PullResponseV1;
};
type PokeInternal = {
baseCookie: ReadonlyJSONValue;
pullResponse: PullResponseV1Internal;
};
type MutatorReturn<T extends ReadonlyJSONValue = ReadonlyJSONValue> = MaybePromise<T | void>; /**
* The type used to describe the mutator definitions passed into [Replicache](classes/Replicache)
* constructor as part of the {@link ReplicacheOptions}.
*
* See {@link ReplicacheOptions} {@link ReplicacheOptions.mutators | mutators} for more
* info.
*/
type MutatorDefs = {
[key: string]: (tx: WriteTransaction, args?: any) => MutatorReturn;
};
type MakeMutator<F extends (tx: WriteTransaction, ...args: [] | [ReadonlyJSONValue]) => MutatorReturn> = F extends (tx: WriteTransaction, ...args: infer Args) => infer Ret ? (...args: Args) => ToPromise<Ret> : never; /**
* Base options for {@link PullOptions} and {@link PushOptions}
*/
interface RequestOptions {
/**
* When there are pending pull or push requests this is the _minimum_ amount
* of time to wait until we try another pull/push.
*/
minDelayMs?: number;
/**
* When there are pending pull or push requests this is the _maximum_ amount
* of time to wait until we try another pull/push.
*/
maxDelayMs?: number;
}
type MakeMutators<T extends MutatorDefs> = {
readonly [P in keyof T]: MakeMutator<T[P]>;
};
type ToPromise<P> = P extends Promise<unknown> ? P : Promise<P>;
type QueryInternal = <R>(body: (tx: ReadTransactionImpl) => MaybePromise<R>) => Promise<R>; /**
* The reason {@link onUpdateNeeded} was called.
*/
type UpdateNeededReason = {
type: 'NewClientGroup';
} | {
type: 'VersionNotSupported';
versionType?: 'push' | 'pull' | 'schema' | undefined;
};
/**
* The options passed to {@link Replicache}.
*/
interface ReplicacheOptions<MD extends MutatorDefs> {
/**
* This is the URL to the server endpoint dealing with the push updates. See
* [Push Endpoint Reference](https://doc.replicache.dev/reference/server-push) for more
* details.
*
* If not provided, push requests will not be made unless a custom
* {@link ReplicacheOptions.pusher} is provided.
*/
pushURL?: string | undefined;
/**
* This is the authorization token used when doing a
* [pull](https://doc.replicache.dev/reference/server-pull#authorization) and
* [push](https://doc.replicache.dev/reference/server-push#authorization).
*/
auth?: string | undefined;
/**
* This is the URL to the server endpoint dealing with pull. See [Pull
* Endpoint Reference](https://doc.replicache.dev/reference/server-pull) for more
* details.
*
* If not provided, pull requests will not be made unless a custom
* {@link ReplicacheOptions.puller} is provided.
*/
pullURL?: string | undefined;
/**
* The name of the Replicache database.
*
* It is important to use user specific names so that if there are multiple
* tabs open for different distinct users their data is kept separate.
*
* For efficiency and performance, a new {@link Replicache} instance will
* initialize its state from the persisted state of an existing {@link Replicache}
* instance with the same `name`, domain and browser profile.
*
* Mutations from one {@link Replicache} instance may be pushed using the
* {@link ReplicacheOptions.auth}, {@link ReplicacheOptions.pushURL},
* {@link ReplicacheOptions.pullURL}, {@link ReplicacheOptions.pusher}, and
* {@link ReplicacheOptions.puller} of another Replicache instance with the same
* `name`, domain and browser profile.
*
* You can use multiple Replicache instances for the same user as long as the
* names are unique. e.g. `name: `$userID:$roomID`
*/
name: string;
/**
* The schema version of the data understood by this application. This enables
* versioning of mutators (in the push direction) and the client view (in the
* pull direction).
*/
schemaVersion?: string | undefined;
/**
* The duration between each {@link pull} in milliseconds. Set this to `null` to
* prevent pulling in the background. Defaults to 60 seconds.
*/
pullInterval?: number | null | undefined;
/**
* The delay between when a change is made to Replicache and when Replicache
* attempts to push that change.
*/
pushDelay?: number | undefined;
/**
* Determines how much logging to do. When this is set to `'debug'`,
* Replicache will also log `'info'` and `'error'` messages. When set to
* `'info'` we log `'info'` and `'error'` but not `'debug'`. When set to
* `'error'` we only log `'error'` messages.
* Default is `'info'`.
*/
logLevel?: LogLevel | undefined;
/**
* Enables custom handling of logs.
*
* By default logs are logged to the console. If you would like logs to be
* sent elsewhere (e.g. to a cloud logging service like DataDog) you can
* provide an array of {@link LogSink}s. Logs at or above
* {@link ReplicacheOptions.logLevel} are sent to each of these {@link LogSink}s.
* If you would still like logs to go to the console, include
* `consoleLogSink` in the array.
*
* ```ts
* logSinks: [consoleLogSink, myCloudLogSink],
* ```
*/
logSinks?: LogSink[] | undefined;
/**
* An object used as a map to define the *mutators*. These gets registered at
* startup of {@link Replicache}.
*
* *Mutators* are used to make changes to the data.
*
* #### Example
*
* The registered *mutations* are reflected on the
* {@link Replicache.mutate | mutate} property of the {@link Replicache} instance.
*
* ```ts
* const rep = new Replicache({
* name: 'user-id',
* mutators: {
* async createTodo(tx: WriteTransaction, args: JSONValue) {
* const key = `/todo/${args.id}`;
* if (await tx.has(key)) {
* throw new Error('Todo already exists');
* }
* await tx.set(key, args);
* },
* async deleteTodo(tx: WriteTransaction, id: number) {
* ...
* },
* },
* });
* ```
*
* This will create the function to later use:
*
* ```ts
* await rep.mutate.createTodo({
* id: 1234,
* title: 'Make things work offline',
* complete: true,
* });
* ```
*
* #### Replays
*
* *Mutators* run once when they are initially invoked, but they might also be
* *replayed* multiple times during sync. As such *mutators* should not modify
* application state directly. Also, it is important that the set of
* registered mutator names only grows over time. If Replicache syncs and
* needed *mutator* is not registered, it will substitute a no-op mutator, but
* this might be a poor user experience.
*
* #### Server application
*
* During push, a description of each mutation is sent to the server's [push
* endpoint](https://doc.replicache.dev/reference/server-push) where it is applied. Once
* the *mutation* has been applied successfully, as indicated by the client
* view's
* [`lastMutationId`](https://doc.replicache.dev/reference/server-pull#lastmutationid)
* field, the local version of the *mutation* is removed. See the [design
* doc](https://doc.replicache.dev/design#commits) for additional details on
* the sync protocol.
*
* #### Transactionality
*
* *Mutators* are atomic: all their changes are applied together, or none are.
* Throwing an exception aborts the transaction. Otherwise, it is committed.
* As with {@link query} and {@link subscribe} all reads will see a consistent view of
* the cache while they run.
*/
mutators?: MD | undefined;
/**
* Options to use when doing pull and push requests.
*/
requestOptions?: RequestOptions | undefined;
/**
* Allows passing in a custom implementation of a {@link Puller} function. This
* function is called when doing a pull and it is responsible for
* communicating with the server.
*
* Normally, this is just a POST to a URL with a JSON body but you can provide
* your own function if you need to do things differently.
*/
puller?: Puller | undefined;
/**
* Allows passing in a custom implementation of a {@link Pusher} function. This
* function is called when doing a push and it is responsible for
* communicating with the server.
*
* Normally, this is just a POST to a URL with a JSON body but you can provide
* your own function if you need to do things differently.
*/
pusher?: Pusher | undefined;
/**
* @deprecated Replicache no longer uses a license key. This option is now
* ignored and will be removed in a future release.
*/
licenseKey?: string | undefined;
/**
* Allows providing a custom implementation of the underlying storage layer.
*/
kvStore?: 'mem' | 'idb' | StoreProvider | undefined;
/**
* Defines the indexes, if any, to use on the data.
*/
readonly indexes?: IndexDefinitions | undefined;
/**
* The maximum age of a client in milliseconds. If a client hasn't been seen
* and has no pending mutations for this long, it will be removed from the
* cache. Default is 24 hours.
*
* This means that this is the maximum time a tab can be in the background
* (frozen or in fbcache) and still be able to sync when it comes back to the
* foreground. If tab comes back after this time the
* {@linkcode onClientStateNotFound} callback is called on the Replicache
* instance.
*/
clientMaxAgeMs?: number | undefined;
}
export { type AsyncIterableIteratorToArray as $, type Write as A, type PatchOperation as B, type Cookie as C, type Diff as D, type PullResponse as E, type PullResponseOKV0 as F, type PullResponseOKV1 as G, type HTTPRequestInfo as H, type IterableUnion as I, type JSONObject as J, type PullResponseV0 as K, type PullResponseV1 as L, type MutatorDefs as M, type NoIndexDiff as N, type PullerResult as O, type Puller as P, type PullerResultV0 as Q, type ReplicacheOptions as R, type StoreProvider as S, type PullerResultV1 as T, type UpdateNeededReason as U, type VersionNotSupportedResponse as V, type WatchNoIndexCallback as W, PushError as X, type PushResponse as Y, type PusherResult as Z, makeScanResult as _, type MakeMutators as a, type GetIndexScanIterator as a0, type GetScanIterator as a1, type ScanResult as a2, isScanIndexOptions as a3, type KeyTypeForScanOptions as a4, type ScanIndexOptions as a5, type ScanNoIndexOptions as a6, type ScanOptionIndexedStartKey as a7, type ScanOptions as a8, type WatchIndexCallback as a9, type Write$1 as aA, type Refs as aB, type QueryInternal as aC, type SubscriptionsManager as aD, type PokeInternal as aE, type BeginPullResult as aF, type WatchIndexOptions as aa, type WatchNoIndexOptions as ab, type ClientGroupID as ac, type ClientID as ad, type PullRequest as ae, type PullRequestV0 as af, type PullRequestV1 as ag, type MutationV0 as ah, type MutationV1 as ai, type PushRequest as aj, type PushRequestV0 as ak, type PushRequestV1 as al, type CreateIndexDefinition as am, type DeepReadonly as an, type DeepReadonlyObject as ao, type TransactionEnvironment as ap, type TransactionLocation as aq, type TransactionReason as ar, type WriteTransaction as as, type MakeMutator as at, type MutatorReturn as au, type Hash as av, type Store$1 as aw, Chunk as ax, type ChunkHasher as ay, type Read$2 as az, type Pusher as b, type RequestOptions as c, type MaybePromise as d, type Poke as e, type ReadTransaction as f, type SubscribeOptions as g, type WatchOptions as h, type WatchCallbackForOptions as i, type PendingMutation as j, type JSONValue as k, type ReadonlyJSONObject as l, type ReadonlyJSONValue as m, type DiffOperation as n, type DiffOperationAdd as o, type DiffOperationChange as p, type DiffOperationDel as q, type IndexDiff as r, type IndexKey as s, type ClientStateNotFoundResponse as t, type IndexDefinition as u, type IndexDefinitions as v, type CreateStore as w, type DropStore as x, type Read$1 as y, type Store as z };