import { LogLevel } 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;
};

/**
 * 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;
});

/**
 * 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;

/**
 * When using indexes the key is a tuple of the secondary key and the primary
 * key.
 */
type IndexKey = readonly [secondary: string, primary: string];

/**
 * 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>[];
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>;

/**
 * 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 HTTPRequestInfo = {
    httpStatusCode: number;
    errorMessage: string;
};

/**
 * 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';
};

/**
 * 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[];
};
/**
 * 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;

/**
 * This creates a default puller which uses HTTP POST to send the pull request.
 */
declare function getDefaultPuller(rep: {
    pullURL: string;
    auth: string;
}): Puller;

/**
 * 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.
 *
 * @experimental This interface is experimental and might be removed or changed
 * in the future without following semver versioning. Please be cautious.
 */
interface Store {
    read(): Promise<Read>;
    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}.
 *
 * @experimental This type is experimental and might be removed or changed
 * in the future without following semver versioning. Please be cautious.
 */
type CreateStore = (name: string) => Store;
/**
 * 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 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 {
    put(key: string, value: ReadonlyJSONValue): Promise<void>;
    del(key: string): Promise<void>;
    commit(): Promise<void>;
}

/**
 * This {@link Error} is thrown when we detect that the IndexedDB has been
 * removed. This does not normally happen but can happen during development if
 * the user has DevTools open and deletes the IndexedDB from there.
 */
declare class IDBNotFoundError extends Error {
    name: string;
}

type PendingMutation = {
    readonly name: string;
    readonly id: number;
    readonly args: ReadonlyJSONValue;
    readonly clientID: ClientID;
};

/**
 * Deletes a single Replicache database.
 * @param dbName
 * @param createKVStore
 */
declare function dropDatabase(dbName: string, createKVStore?: CreateStore): Promise<void>;
/**
 * Deletes all IndexedDB data associated with Replicache.
 *
 * Returns an object with the names of the successfully dropped databases
 * and any errors encountered while dropping.
 */
declare function dropAllDatabases(createKVStore?: CreateStore): Promise<{
    dropped: string[];
    errors: unknown[];
}>;

/**
 * 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);
}

/**
 * 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;

/**
 * This error is thrown when you try to call methods on a closed transaction.
 */
declare class TransactionClosedError extends Error {
    constructor();
}

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 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$1 {
    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>>;
}
/**
 * WriteTransactions are used with *mutators* which are registered using
 * {@link ReplicacheOptions.mutators} and allows read and write operations on the
 * database.
 */
interface WriteTransaction$1 extends ReadTransaction$1 {
    /**
     * 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>;
}

/**
 * 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;
    /**
     * 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;
};
/**
 * 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;
}
type MaybePromise<T> = T | Promise<T>;
/**
 * Returns the name of the IDB database that will be used for a particular Replicache instance.
 * @param name The name of the Replicache instance (i.e., the `name` field of `ReplicacheOptions`).
 * @param schemaVersion The schema version of the database (i.e., the `schemaVersion` field of `ReplicacheOptions`).
 * @returns
 */
declare function makeIDBName(name: string, schemaVersion?: string): string;
type MutatorReturn<T extends ReadonlyJSONValue = ReadonlyJSONValue> = MaybePromise<T | void>;

/**
 * This error is thrown when the puller fails for any reason.
 */
declare class PullError extends Error {
    name: string;
    causedBy?: Error | undefined;
    constructor(causedBy?: Error);
}

/**
 * `AuthData` must include a `userID` which is unique stable identifier
 * for the user.
 * `AuthData` has a size limit of 6 KB.
 * `AuthData` is passed via {@link WriteTransaction.auth} to mutators
 * when they are run on the server, which can use it to supplement
 * mutator args and to authorize the mutation.
 */
type AuthData = ReadonlyJSONObject & {readonly userID: string};

/** Environment variables. */
type Env = {readonly [key: string]: string};

interface ReadTransaction extends ReadTransaction$1 {
  /**
   * When a mutator is run on the server, the `AuthData` for the connection
   * that pushed the mutation (i.e. the `AuthData` returned by the
   * {@link ReflectServerOptions.authHandler} when it authenticated the
   * connection).  Always undefined on the client. This can be used to implement
   * fine-grained server-side authorization of mutations.
   */
  readonly auth?: AuthData | undefined;

  /**
   * When a mutator is run on the server, `Env` contains environment variables.
   */
  readonly env?: Env | undefined;
}

interface WriteTransaction extends WriteTransaction$1 {
  /**
   * When a mutator is run on the server, the `AuthData` for the connection
   * that pushed the mutation (i.e. the `AuthData` returned by the
   * {@link ReflectServerOptions.authHandler} when it authenticated the
   * connection).  Always undefined on the client. This can be used to implement
   * fine-grained server-side authorization of mutations.
   */
  readonly auth?: AuthData | undefined;

  /**
   * When a mutator is run on the server, `Env` contains environment variables.
   */
  readonly env?: Env | undefined;
}

type MutatorDefs = {
  [key: string]: (
    tx: WriteTransaction,
    // Not sure how to not use any here...
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    args?: any,
  ) => MutatorReturn;
};

/**
 * Configuration for [[Reflect]].
 */
interface ReflectOptions<MD extends MutatorDefs> {
    /**
     * Server to connect to, for example "https://myapp-myteam.reflect.net/".
     */
    server?: string | null | undefined;
    /**
     * Server to connect to, for example "wss://myapp-myteam.reflect.net/".
     * @deprecated Use {@code server} instead.
     */
    socketOrigin?: string | null | undefined;
    /**
     * Identifies and authenticates the user.
     *
     * This value is required when you provide a `authHandler` to your ReflectServer.
     * During connection this value is passed to your provided `authHandler`, which should use it to
     * authenticate the user. The `userID` returned by your `authHandler` for this value
     * must be equal to [[ReflectOptions.userID]].
     *
     * In the case authentication fails, the connection to the server will be
     * closed and Reflect will retry connecting with exponential backoff.
     *
     * If a function is provided here, that function is invoked before each
     * attempt. This provides the application the opportunity to calculate or
     * fetch a fresh token.
     */
    auth?: string | (() => MaybePromise<string>) | undefined;
    /**
     * A unique identifier for the user. Must be non-empty.
     *
     * For efficiency, a new Reflect instance will initialize its state from
     * the persisted state of an existing Reflect instance with the same
     * `userID`, `roomID`, domain and browser profile.
     */
    userID: string;
    /**
     * A unique identifier for the room.
     *
     * Must be non-empty and must only contain characters from the character set
     * `[A-Za-z0-9_/-]`.
     *
     * For efficiency, a new Reflect instance will initialize its state from
     * the persisted state of an existing Reflect instance with the same
     * `userID`, `roomID`, domain and browser profile.
     *
     * Mutations from one Reflect instance may be pushed using the
     * [[Reflect.auth]] of another Reflect instance with the same
     * `userID`, `roomID`, domain and browser profile.
     */
    roomID: string;
    /**
     * The server side data can be restricted to a jurisdiction. This is
     * useful for GDPR compliance.
     */
    jurisdiction?: 'eu' | undefined;
    /**
     * The schema version of the data understood by this application. This enables
     * versioning of mutators and the client view.
     */
    schemaVersion?: string | undefined;
    /**
     * Determines the level of detail at which Reflect logs messages about
     * its operation. Messages are logged to the `console`.
     *
     * When this is set to `'debug'`, `'info'` and `'error'` messages are also
     * logged. When set to `'info'`, `'info'` and `'error'` but not
     * `'debug'` messages are logged. When set to `'error'` only `'error'`
     * messages are logged.
     *
     * Default is `'error'`.
     */
    logLevel?: LogLevel | undefined;
    /**
     * An object used as a map to define the *mutators* for this application.
     *
     * *Mutators* are used to make changes to the Reflect data.
     *
     * The registered *mutations* are reflected on the
     * [[Reflect.mutate|mutate]] property of the [[Reflect]] instance.
     *
     * #### Example
     *
     * ```ts
     * const reflect = new Reflect({
     *   server: 'https://example.com/',
     *   userID: 'user-id',
     *   roomID: 'room-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 reflect.mutate.createTodo({
     *   id: 1234,
     *   title: 'Make things realtime',
     *   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 Reflect syncs and a
     * needed *mutator* is not registered, it will substitute a no-op mutator, but
     * this might be a poor user experience.
     *
     * #### Server application
     *
     * During sync, a description of each mutation is sent to the server where it
     * is applied. Once the *mutation* has been applied successfully, 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 [[query]] and [[subscribe]] all reads will see a consistent view of
     * the cache while they run.
     */
    mutators?: MD | undefined;
    /**
     * `onOnlineChange` is called when the Reflect instance's online status changes
     */
    onOnlineChange?: ((online: boolean) => void) | undefined;
    /**
     * The number of milliseconds to wait before disconnecting a Reflect
     * instance whose tab has become hidden.
     *
     * Instances in hidden tabs are disconnected to save resources.
     *
     * Default is 5_000.
     */
    hiddenTabDisconnectDelay?: number | undefined;
    /**
     * Help Reflect improve its service by automatically sending diagnostic and
     * usage data.
     *
     * Default is true.
     */
    enableAnalytics?: boolean | undefined;
    /**
     * Determines what kind of storage implementation to use on the client.
     *
     * Defaults to `'mem'` which means that Reflect uses an in memory storage and
     * the data is not persisted on the client.
     *
     * By setting this to `'idb'` the data is persisted on the client using
     * IndexedDB, allowing faster syncs between application restarts.
     *
     * You can also set this to a function that is used to create new KV stores,
     * allowing a custom implementation of the underlying storage layer.
     */
    kvStore?: 'mem' | 'idb' | CreateStore | undefined;
}

type SubscribeToPresenceCallback = (presentClientIDs: ReadonlyArray<ClientID>) => void;

/**
 * The reason {@link onUpdateNeeded} was called.
 */
type UpdateNeededReason = {
    type: 'NewClientGroup';
} | {
    type: 'VersionNotSupported';
};
declare class Reflect<MD extends MutatorDefs> {
    #private;
    readonly version: string;
    readonly userID: string;
    readonly roomID: string;
    /**
     * `onOnlineChange` is called when the Reflect instance's online status
     * changes.
     */
    onOnlineChange: ((online: boolean) => void) | null | undefined;
    /**
     * `onUpdateNeeded` is called when a code update is needed.
     *
     * A code update can be needed because:
     * - the server no longer supports the protocol version of the current code,
     * - a new Reflect client has created a new client group, because its code
     *   has different mutators, indexes, schema version and/or format version
     *   from this Reflect client. This is likely due to the new client having
     *   newer code. A code update is needed to be able to locally sync with this
     *   new Reflect client (i.e. to sync while offline, the clients can can
     *   still sync with each other via the server).
     *
     * The default behavior is to reload the page (using `location.reload()`). Set
     * this to `null` or provide your own function to prevent the page from
     * reloading automatically. You may want to provide your own function to
     * display a toast to inform the end user there is a new version of your app
     * available and prompting them to refresh.
     */
    get onUpdateNeeded(): ((reason: UpdateNeededReason) => void) | null;
    set onUpdateNeeded(callback: ((reason: UpdateNeededReason) => void) | null);
    /**
     * Constructs a new Reflect client.
     */
    constructor(options: ReflectOptions<MD>);
    /**
     * The name of the IndexedDB database in which the data of this
     * instance of Reflect is stored.
     */
    get idbName(): string;
    /**
     * The schema version of the data understood by this application.
     * See [[ReflectOptions.schemaVersion]].
     */
    get schemaVersion(): string;
    /**
     * The client ID for this instance of Reflect. Each instance
     * gets a unique client ID.
     */
    get clientID(): ClientID;
    get clientGroupID(): Promise<ClientGroupID>;
    /**
     * The registered mutators (see [[ReflectOptions.mutators]]).
     */
    get mutate(): { readonly [P in keyof MD]: MD[P] extends infer T ? T extends MD[P] ? T extends (tx: WriteTransaction$1, ...args: infer Args) => infer Ret ? (...args: Args) => Ret extends Promise<unknown> ? Ret : Promise<Ret> : never : never : never; };
    /**
     * Whether this Reflect instance has been closed. Once a Reflect instance has
     * been closed it no longer syncs and you can no longer read or write data out
     * of it. After it has been closed it is pretty much useless and should not be
     * used any more.
     */
    get closed(): boolean;
    /**
     * Closes this Reflect instance.
     *
     * When closed all subscriptions end and no more read or writes are allowed.
     */
    close(): Promise<void>;
    /**
     * Subscribe to changes to Reflect data. Every time the underlying data
     * changes `body` is called and if the result of `body` changes compared to
     * last time `onData` is called. The function is also called once the first
     * time the subscription is added.
     *
     * This returns a function that can be used to cancel the subscription.
     *
     * If an error occurs in the `body` the `onError` function is called if
     * present. Otherwise, the error is thrown.
     */
    subscribe<R>(body: (tx: ReadTransaction) => Promise<R>, options: SubscribeOptions<R> | ((result: R) => void)): () => void;
    /**
     * Transactionally read Reflect data.
     */
    query<R>(body: (tx: ReadTransaction) => Promise<R> | R): Promise<R>;
    /**
     * Watches Reflect for changes.
     *
     * The `callback` gets called whenever the underlying data changes and the
     * `key` changes matches the
     * [[ExperimentalWatchNoIndexOptions|ExperimentalWatchOptions.prefix]]
     * if present. If a change occurs to the data but the change does not impact
     * the key space the callback is not called. In other words, the callback is
     * never called with an empty diff.
     *
     * This gets called after commit (a mutation or a rebase).
     *
     * @experimental This method is under development and its semantics will
     * change.
     */
    experimentalWatch(callback: WatchNoIndexCallback): () => void;
    experimentalWatch<Options extends WatchOptions>(callback: WatchCallbackForOptions<Options>, options?: Options): () => void;
    /**
     * Subscribe to the set of present client ids.
     *
     * When offline this set will contain just this
     * Reflect instances clientID.
     *
     * To cancel the subscription call the returned function.
     */
    subscribeToPresence(callback: SubscribeToPresenceCallback): () => void;
    /**
     * A rough heuristic for whether the client is currently online and
     * authenticated.
     */
    get online(): boolean;
}

export { CreateStore as CreateKVStore, Diff as ExperimentalDiff, DiffOperation as ExperimentalDiffOperation, DiffOperationAdd as ExperimentalDiffOperationAdd, DiffOperationChange as ExperimentalDiffOperationChange, DiffOperationDel as ExperimentalDiffOperationDel, IndexDiff as ExperimentalIndexDiff, NoIndexDiff as ExperimentalNoIndexDiff, WatchCallbackForOptions as ExperimentalWatchCallbackForOptions, WatchIndexCallback as ExperimentalWatchIndexCallback, WatchIndexOptions as ExperimentalWatchIndexOptions, WatchNoIndexCallback as ExperimentalWatchNoIndexCallback, WatchNoIndexOptions as ExperimentalWatchNoIndexOptions, WatchOptions as ExperimentalWatchOptions, IDBNotFoundError, IndexDefinition, IndexDefinitions, IndexKey, Read as KVRead, Store as KVStore, Write as KVWrite, KeyTypeForScanOptions, PendingMutation, PullError, Puller, PullerResult, PullerResultV0, PullerResultV1, PushError, Pusher, PusherResult, Reflect, ReflectOptions, ScanIndexOptions, ScanNoIndexOptions, ScanOptionIndexedStartKey, ScanOptions, ScanResult, SubscribeOptions, SubscribeToPresenceCallback, TransactionClosedError, dropAllDatabases, dropDatabase, getDefaultPuller, isScanIndexOptions, makeIDBName };
