export { LogLevel, LogSink, TeeLogSink, consoleLogSink } 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 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];

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

/**
 * Filters an async iterable.
 *
 * This utility function is provided because it is useful when using
 * {@link makeScanResult}. It can be used to filter out tombstones (delete entries)
 * for example.
 */
declare function filterAsyncIterable<V>(iter: IterableUnion<V>, predicate: (v: V) => boolean): AsyncIterable<V>;

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

/**
 * A named in-memory Store implementation.
 *
 * Two (or more) named memory stores with the same name will share the same
 * underlying storage. They will also share the same read/write locks, so that
 * only one write transaction can be running at the same time.
 *
 * @experimental This class is experimental and might be removed or changed
 * in the future without following semver versioning. Please be cautious.
 */
declare class MemStore implements Store {
    #private;
    constructor(name: string);
    read(): Promise<Read>;
    write(): Promise<Write>;
    close(): Promise<void>;
    get closed(): boolean;
}

/**
 * Merges an iterable on to another iterable.
 *
 * The two iterables need to be ordered and the `compare` function is used to
 * compare two different elements.
 *
 * If two elements are equal (`compare` returns `0`) then the element from the
 * second iterable is picked.
 *
 * This utility function is provided because it is useful when using
 * {@link makeScanResult}. It can be used to merge an in memory pending async
 * iterable on to a persistent async iterable for example.
 */
declare function mergeAsyncIterables<A, B>(iterableBase: IterableUnion<A>, iterableOverlay: IterableUnion<B>, compare: (a: A, b: B) => number): AsyncIterable<A | B>;

/**
 * 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;
/**
 * The response from a push can contain information about error conditions.
 */
type PushResponse = ClientStateNotFoundResponse | VersionNotSupportedResponse;

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

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>;
}

type Poke = {
    baseCookie: ReadonlyJSONValue;
    pullResponse: PullResponseV1;
};
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>;
/**
 * 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;
}
/**
 * The reason {@link onUpdateNeeded} was called.
 */
type UpdateNeededReason = {
    type: 'NewClientGroup';
} | {
    type: 'VersionNotSupported';
    versionType?: 'push' | 'pull' | 'schema' | undefined;
};

/**
 * `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, args?: any) => MutatorReturn;
};

declare const ROOM_ID_REGEX: RegExp;
declare function isValidRoomID(roomID: string): boolean;
declare function makeInvalidRoomIDMessage(roomID: string): string;

declare const version: string;

export { AsyncIterableIteratorToArray, AuthData, ClientGroupID, ClientID, ClientStateNotFoundResponse, Cookie, Env, MemStore as ExperimentalMemKVStore, GetIndexScanIterator, GetScanIterator, HTTPRequestInfo, IterableUnion, JSONObject, JSONValue, MaybePromise, MutationV0, MutationV1, MutatorDefs, MutatorReturn, PatchOperation, Poke, PullRequest, PullRequestV0, PullRequestV1, PullResponse, PullResponseOKV0, PullResponseOKV1, PullResponseV0, PullResponseV1, PushRequest, PushRequestV0, PushRequestV1, PushResponse, ROOM_ID_REGEX, ReadTransaction, ReadonlyJSONObject, ReadonlyJSONValue, RequestOptions, TransactionEnvironment, TransactionLocation, TransactionReason, UpdateNeededReason, VersionNotSupportedResponse, WriteTransaction, filterAsyncIterable, isScanIndexOptions, isValidRoomID, makeIDBName, makeInvalidRoomIDMessage, makeScanResult, mergeAsyncIterables, version };
