import { LogSink, LogLevel, Context } from '@rocicorp/logger'; /** The values that can be represented in JSON */ type JSONValue = | null | string | boolean | number | Array | 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 | ReadonlyJSONObject; /** Like {@link JSONObject} but deeply readonly */ type ReadonlyJSONObject = { readonly [key: string]: ReadonlyJSONValue | undefined; }; /** * 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]; /** * 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; }; /** * 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 extends AsyncIterable { /** The default AsyncIterable. This is the same as {@link values}. */ [Symbol.asyncIterator](): AsyncIterableIteratorToArray; /** Async iterator over the values of the {@link ReadTransaction.scan | scan} call. */ values(): AsyncIterableIteratorToArray; /** * 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; /** * 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; /** Returns all the values as an array. Same as `values().toArray()` */ toArray(): Promise; } /** * An interface that adds a {@link toArray} method to `AsyncIterableIterator`. * * Usage: * * ```ts * const keys: string[] = await rep.scan().keys().toArray(); * ``` */ interface AsyncIterableIteratorToArray extends AsyncIterableIterator { toArray(): Promise; } type TransactionEnvironment = 'client' | 'server'; type TransactionLocation = TransactionEnvironment; type TransactionReason = 'initial' | 'rebase' | 'authoritative'; /** * Basic deep readonly type. It works for {@link JSONValue}. */ type DeepReadonly = T extends null | boolean | string | number | undefined ? T : DeepReadonlyObject; type DeepReadonlyObject = { readonly [K in keyof T]: DeepReadonly; }; /** * 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; get(key: string): Promise | undefined>; /** Determines if a single `key` is present in the database. */ has(key: string): Promise; /** Whether the database is empty. */ isEmpty(): Promise; /** * 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; scan(options?: ScanNoIndexOptions): ScanResult; scan(options?: ScanOptions): ScanResult; scan(options: ScanIndexOptions): ScanResult>; scan(options?: ScanNoIndexOptions): ScanResult>; scan(options?: ScanOptions): ScanResult>; } /** * 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 { /** * 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; /** * @deprecated Use {@link WriteTransaction.set} instead. */ put(key: string, value: ReadonlyJSONValue): Promise; /** * Removes a `key` and its value from the database. Returns `true` if there was a * `key` to remove. */ del(key: string): Promise; } type MaybePromise = T | Promise; type MutatorReturn = MaybePromise; /** * `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 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; }; /** * An `AuthHandler` should validate that the user authenticated by `auth` is * authorized to access the room with `roomID`. By 'access' we mean create or * connect to the room with `roomID`. * @return A promise which resolves to `AuthData` for the user if authentication * and authorization is successful. If authentication fails you can return * `null`. Exceptions and promise rejections are treated as authentication * failures. The returned `AuthData` is passed via * {@link WriteTransaction.auth} to mutators when they are run on the server, * and can be used to implement fine-grained server-side authorization of * mutations. */ type AuthHandler = (auth: string, roomID: string, env: Env) => MaybePromise; /** * @deprecated Use {@link ClientDisconnectHandler} instead. * */ type DisconnectHandler = ClientDisconnectHandler; /** * A `ClientDisconnectHandler` can modify room state in response to a client * disconnecting from the room. These changes will be synced to all clients of * the room just like mutator changes. `write.clientID` will be the id of the * disconnected client. `write.mutationID` will be -1. */ type ClientDisconnectHandler = (write: WriteTransaction) => Promise; interface DatadogLogSinkOptions { apiKey?: string | undefined; source?: string | undefined; service?: string | undefined; host?: string | undefined; version?: string | undefined; interval?: number | undefined; baseURL?: URL | undefined; } declare class DatadogLogSink implements LogSink { #private; constructor(options: DatadogLogSinkOptions); log(level: LogLevel, context: Context | undefined, ...args: unknown[]): void; flush(): Promise; } declare function createWorkerDatadogLogSink(opts: WorkerDatadogLogSinkOptions): DatadogLogSink; type WorkerDatadogLogSinkOptions = { apiKey: string; service?: string | undefined; host?: string | undefined; version?: string | undefined; }; /** * A `ClientDeleteHandler` can modify room state in response to a client being * garbage collected from the room. A client gets "deleted" when it can no * longer reconnect. These changes will be synced to all clients of the room * just like mutator changes. `write.clientID` will be the id of the deleted * client. `write.mutationID` will be -1. */ type ClientDeleteHandler = (write: WriteTransaction) => Promise; /** * The `RoomStartHandler` is invoked when the room is started, before * any connections are accepted. This is useful for initializing or migrating * room state. * * Note that rooms may be shutdown when idle and restarted when connections * resume, upon which the `RoomStartHandler` will be invoked again. It can * therefore conceivably be used for running logic when a room resumes after * being idle; however, there are no guarantees of timing with respect to idleness. * * A succeeding RoomStartHandler (i.e. no error thrown) is guaranteed to be * invoked exactly once during the lifetime of a room (from start to shutdown). * * If the RoomStartHandler throws an error, it will be retried on the next * connection attempt. Connections will continue to fail until the RoomStartHandler * succeeds. * * As the transaction is not associated with any client, `write.clientID` * will be empty and `write.mutationID` will be -1. */ type RoomStartHandler = (write: WriteTransaction, roomID: string) => Promise; type DatadogMetricsOptions = { apiKey: string; service?: string | undefined; }; interface ReflectServerOptions { mutators: MD; authHandler?: AuthHandler | undefined; roomStartHandler?: RoomStartHandler | undefined; /** * @deprecated Use {@link onClientDisconnect} instead. */ disconnectHandler?: DisconnectHandler | undefined; onClientDisconnect?: ClientDisconnectHandler | undefined; onClientDelete?: ClientDeleteHandler | undefined; /** * Where to send logs. By default logs are sent to `console.log`. */ logSinks?: LogSink[] | undefined; /** * The level to log at. By default the level is 'info'. */ logLevel?: LogLevel | undefined; /** * Options for reporting metrics to Datadog. By default metrics are sent nowhere. */ datadogMetricsOptions?: DatadogMetricsOptions | undefined; /** * If `true`, outgoing network messages are sent before the writes they * reflect are confirmed to be durable. This enables lower latency but can * result in clients losing some mutations in the case of an untimely server * restart. * * Default is `false`. */ allowUnconfirmedWrites?: boolean | undefined; /** * The max number of mutations that will be processed per turn. * Lowering this limit can prevent busy rooms from experiencing "overloaded" * exceptions at the cost of peer-to-peer latency. * * Default is `66` when `allowUnconfirmedWrites` is `false`, or `16` when * `allowUnconfirmedWrites` is `true`. */ maxMutationsPerTurn?: number | undefined; } interface ReflectServerBaseEnv { roomDO: DurableObjectNamespace; authDO: DurableObjectNamespace; REFLECT_API_KEY: string; } type DurableObjectCtor = new (state: DurableObjectState, env: Env) => DurableObject; /** * Creates the different parts of a reflect server. * @param makeOptions Function for creating the options for the server. * IMPORTANT: Do not cache the return value from this function (or any of * its parts, ie a log sink) across invocations. You should return a brand * new instance each time this function is called. * TODO: Add reference to CF bug. */ declare function createReflectServer(makeOptions: (env: Env) => ReflectServerOptions): { worker: ExportedHandler; RoomDO: DurableObjectCtor; AuthDO: DurableObjectCtor; }; type BuildableOptionsEnv = LogFilterEnv & LogLevelEnv & DataDogLogEnv & DataDogMetricsEnv; declare function newOptionsBuilder(base: OptionsMaker): OptionsBuilder; declare function defaultConsoleLogSink(): OptionsAdder; type LogFilterEnv = { DISABLE_LOG_FILTERING?: string; }; declare function logFilter(include: LogPredicate): OptionsAdder; type LogLevelEnv = { LOG_LEVEL?: string; }; declare function logLevel(defaultLogLevel?: LogLevel): OptionsAdder; type DataDogLogEnv = { DATADOG_LOGS_API_KEY?: string; DATADOG_SERVICE_LABEL?: string; }; declare function datadogLogging(defaultServiceLabel: string, host?: string): OptionsAdder; type DataDogMetricsEnv = { DATADOG_METRICS_API_KEY?: string; DATADOG_SERVICE_LABEL?: string; }; declare function datadogMetrics(defaultServiceLabel: string, tags?: Record): OptionsAdder; type LogPredicate = (level: LogLevel, context: Context | undefined, ...args: unknown[]) => boolean; type OptionsMaker = (env: Env) => ReflectServerOptions; type OptionsAdder = (options: ReflectServerOptions, env: Env) => ReflectServerOptions; declare class OptionsBuilder { #private; constructor(base: OptionsMaker); add(adder: OptionsAdder): OptionsBuilder; build(): OptionsMaker; } export { AuthHandler, BuildableOptionsEnv, ClientDisconnectHandler, DisconnectHandler, ReflectServerBaseEnv, ReflectServerOptions, WorkerDatadogLogSinkOptions, createReflectServer, createWorkerDatadogLogSink, datadogLogging, datadogMetrics, defaultConsoleLogSink, logFilter, logLevel, newOptionsBuilder };