UNPKG

@deno/kv

Version:

A Deno KV client library optimized for Node.js.

561 lines (559 loc) 22.6 kB
export interface KvService { /** * Open a new {@linkcode Kv} connection to persist data. * * @tags allow-read, allow-write * @category KV * * Meant to shadow the Deno-specific: Deno.openKv */ openKv(path?: string): Promise<Kv>; } export interface Kv { /** * Retrieve the value and versionstamp for the given key from the database * in the form of a {@linkcode KvEntryMaybe}. If no value exists for * the key, the returned entry will have a `null` value and versionstamp. * * ```ts * const db = await openKv(); * const result = await db.get(["foo"]); * result.key; // ["foo"] * result.value; // "bar" * result.versionstamp; // "00000000000000010000" * ``` * * The `consistency` option can be used to specify the consistency level * for the read operation. The default consistency level is "strong". Some * use cases can benefit from using a weaker consistency level. For more * information on consistency levels, see the documentation for * {@linkcode KvConsistencyLevel}. */ get<T = unknown>(key: KvKey, options?: { consistency?: KvConsistencyLevel; }): Promise<KvEntryMaybe<T>>; /** * Retrieve multiple values and versionstamps from the database in the form * of an array of {@linkcode KvEntryMaybe} objects. The returned array * will have the same length as the `keys` array, and the entries will be in * the same order as the keys. If no value exists for a given key, the * returned entry will have a `null` value and versionstamp. * * ```ts * const db = await openKv(); * const result = await db.getMany([["foo"], ["baz"]]); * result[0].key; // ["foo"] * result[0].value; // "bar" * result[0].versionstamp; // "00000000000000010000" * result[1].key; // ["baz"] * result[1].value; // null * result[1].versionstamp; // null * ``` * * The `consistency` option can be used to specify the consistency level * for the read operation. The default consistency level is "strong". Some * use cases can benefit from using a weaker consistency level. For more * information on consistency levels, see the documentation for * {@linkcode KvConsistencyLevel}. */ getMany<T extends readonly unknown[]>(keys: readonly [...{ [K in keyof T]: KvKey; }], options?: { consistency?: KvConsistencyLevel; }): Promise<{ [K in keyof T]: KvEntryMaybe<T[K]>; }>; /** * Set the value for the given key in the database. If a value already * exists for the key, it will be overwritten. * * ```ts * const db = await openKv(); * await db.set(["foo"], "bar"); * ``` * * Optionally an `expireIn` option can be specified to set a time-to-live * (TTL) for the key. The TTL is specified in milliseconds, and the key will * be deleted from the database at earliest after the specified number of * milliseconds have elapsed. Once the specified duration has passed, the * key may still be visible for some additional time. If the `expireIn` * option is not specified, the key will not expire. */ set(key: KvKey, value: unknown, options?: { expireIn?: number; }): Promise<KvCommitResult>; /** * Delete the value for the given key from the database. If no value exists * for the key, this operation is a no-op. * * ```ts * const db = await openKv(); * await db.delete(["foo"]); * ``` */ delete(key: KvKey): Promise<void>; /** * Retrieve a list of keys in the database. The returned list is an * {@linkcode KvListIterator} which can be used to iterate over the * entries in the database. * * Each list operation must specify a selector which is used to specify the * range of keys to return. The selector can either be a prefix selector, or * a range selector: * * - A prefix selector selects all keys that start with the given prefix of * key parts. For example, the selector `["users"]` will select all keys * that start with the prefix `["users"]`, such as `["users", "alice"]` * and `["users", "bob"]`. Note that you can not partially match a key * part, so the selector `["users", "a"]` will not match the key * `["users", "alice"]`. A prefix selector may specify a `start` key that * is used to skip over keys that are lexicographically less than the * start key. * - A range selector selects all keys that are lexicographically between * the given start and end keys (including the start, and excluding the * end). For example, the selector `["users", "a"], ["users", "n"]` will * select all keys that start with the prefix `["users"]` and have a * second key part that is lexicographically between `a` and `n`, such as * `["users", "alice"]`, `["users", "bob"]`, and `["users", "mike"]`, but * not `["users", "noa"]` or `["users", "zoe"]`. * * ```ts * const db = await openKv(); * const entries = db.list({ prefix: ["users"] }); * for await (const entry of entries) { * entry.key; // ["users", "alice"] * entry.value; // { name: "Alice" } * entry.versionstamp; // "00000000000000010000" * } * ``` * * The `options` argument can be used to specify additional options for the * list operation. See the documentation for {@linkcode KvListOptions} * for more information. */ list<T = unknown>(selector: KvListSelector, options?: KvListOptions): KvListIterator<T>; /** * Add a value into the database queue to be delivered to the queue * listener via {@linkcode Kv.listenQueue}. * * ```ts * const db = await openKv(); * await db.enqueue("bar"); * ``` * * The `delay` option can be used to specify the delay (in milliseconds) * of the value delivery. The default delay is 0, which means immediate * delivery. * * ```ts * const db = await openKv(); * await db.enqueue("bar", { delay: 60000 }); * ``` * * The `keysIfUndelivered` option can be used to specify the keys to * be set if the value is not successfully delivered to the queue * listener after several attempts. The values are set to the value of * the queued message. * * ```ts * const db = await openKv(); * await db.enqueue("bar", { keysIfUndelivered: [["foo", "bar"]] }); * ``` */ enqueue(value: unknown, options?: { delay?: number; keysIfUndelivered?: KvKey[]; }): Promise<KvCommitResult>; /** * Listen for queue values to be delivered from the database queue, which * were enqueued with {@linkcode .enqueue}. The provided handler * callback is invoked on every dequeued value. A failed callback * invocation is automatically retried multiple times until it succeeds * or until the maximum number of retries is reached. * * ```ts * const db = await openKv(); * db.listenQueue(async (msg: unknown) => { * await db.set(["foo"], msg); * }); * ``` */ listenQueue(handler: (value: unknown) => Promise<void> | void): Promise<void>; /** * Create a new {@linkcode AtomicOperation} object which can be used to * perform an atomic transaction on the database. This does not perform any * operations on the database - the atomic transaction must be committed * explicitly using the {@linkcode AtomicOperation.commit} method once * all checks and mutations have been added to the operation. */ atomic(): AtomicOperation; /** * Watch for changes to the given keys in the database. The returned stream * is a {@linkcode ReadableStream} that emits a new value whenever any of * the watched keys change their versionstamp. The emitted value is an array * of {@linkcode KvEntryMaybe} objects, with the same length and order * as the `keys` array. If no value exists for a given key, the returned * entry will have a `null` value and versionstamp. * * The returned stream does not return every single intermediate state of * the watched keys, but rather only keeps you up to date with the latest * state of the keys. This means that if a key is modified multiple times * quickly, you may not receive a notification for every single change, but * rather only the latest state of the key. * * ```ts * const db = await openKv(); * * const stream = db.watch([["foo"], ["bar"]]); * for await (const entries of stream) { * entries[0].key; // ["foo"] * entries[0].value; // "bar" * entries[0].versionstamp; // "00000000000000010000" * entries[1].key; // ["bar"] * entries[1].value; // null * entries[1].versionstamp; // null * } * ``` * * The `options` argument can be used to specify additional options for the * watch operation. The `raw` option can be used to specify whether a new * value should be emitted whenever a mutation occurs on any of the watched * keys (even if the value of the key does not change, such as deleting a * deleted key), or only when entries have observably changed in some way. * When `raw: true` is used, it is possible for the stream to occasionally * emit values even if no mutations have occurred on any of the watched * keys. The default value for this option is `false`. */ watch<T extends readonly unknown[]>(keys: readonly [...{ [K in keyof T]: KvKey; }], options?: { raw?: boolean; }): ReadableStream<{ [K in keyof T]: KvEntryMaybe<T[K]>; }>; /** * Close the database connection. This will prevent any further operations * from being performed on the database, and interrupt any in-flight * operations immediately. */ close(): void; [Symbol.dispose](): void; } /** **UNSTABLE**: New API, yet to be vetted. * * A key to be persisted in a {@linkcode Kv}. A key is a sequence * of {@linkcode KvKeyPart}s. * * Keys are ordered lexicographically by their parts. The first part is the * most significant, and the last part is the least significant. The order of * the parts is determined by both the type and the value of the part. The * relative significance of the types can be found in documentation for the * {@linkcode KvKeyPart} type. * * Keys have a maximum size of 2048 bytes serialized. If the size of the key * exceeds this limit, an error will be thrown on the operation that this key * was passed to. * * @category KV */ export type KvKey = readonly KvKeyPart[]; /** **UNSTABLE**: New API, yet to be vetted. * * A single part of a {@linkcode KvKey}. Parts are ordered * lexicographically, first by their type, and within a given type by their * value. * * The ordering of types is as follows: * * 1. `Uint8Array` * 2. `string` * 3. `number` * 4. `bigint` * 5. `boolean` * * Within a given type, the ordering is as follows: * * - `Uint8Array` is ordered by the byte ordering of the array * - `string` is ordered by the byte ordering of the UTF-8 encoding of the * string * - `number` is ordered following this pattern: `-NaN` * < `-Infinity` < `-100.0` < `-1.0` < -`0.5` < `-0.0` < `0.0` < `0.5` * < `1.0` < `100.0` < `Infinity` < `NaN` * - `bigint` is ordered by mathematical ordering, with the largest negative * number being the least first value, and the largest positive number * being the last value * - `boolean` is ordered by `false` < `true` * * This means that the part `1.0` (a number) is ordered before the part `2.0` * (also a number), but is greater than the part `0n` (a bigint), because * `1.0` is a number and `0n` is a bigint, and type ordering has precedence * over the ordering of values within a type. * * @category KV */ export type KvKeyPart = Uint8Array | string | number | bigint | boolean; /** **UNSTABLE**: New API, yet to be vetted. * * Consistency level of a KV operation. * * - `strong` - This operation must be strongly-consistent. * - `eventual` - Eventually-consistent behavior is allowed. * * @category KV */ export type KvConsistencyLevel = "strong" | "eventual"; /** * **UNSTABLE**: New API, yet to be vetted. * * An optional versioned pair of key and value in a {@linkcode Kv}. * * This is the same as a {@linkcode KvEntry}, but the `value` and `versionstamp` * fields may be `null` if no value exists for the given key in the KV store. * * @category KV */ export type KvEntryMaybe<T> = KvEntry<T> | { key: KvKey; value: null; versionstamp: null; }; /** **UNSTABLE**: New API, yet to be vetted. * * A versioned pair of key and value in a {@linkcode Kv}. * * The `versionstamp` is a string that represents the current version of the * key-value pair. It can be used to perform atomic operations on the KV store * by passing it to the `check` method of a {@linkcode AtomicOperation}. * * @category KV */ export type KvEntry<T> = { key: KvKey; value: T; versionstamp: string; }; /** @category KV */ export interface KvCommitResult { ok: true; /** The versionstamp of the value committed to KV. */ versionstamp: string; } /** **UNSTABLE**: New API, yet to be vetted. * * A selector that selects the range of data returned by a list operation on a * {@linkcode Kv}. * * The selector can either be a prefix selector or a range selector. A prefix * selector selects all keys that start with the given prefix (optionally * starting at a given key). A range selector selects all keys that are * lexicographically between the given start and end keys. * * @category KV */ export type KvListSelector = { prefix: KvKey; } | { prefix: KvKey; start: KvKey; } | { prefix: KvKey; end: KvKey; } | { start: KvKey; end: KvKey; }; /** **UNSTABLE**: New API, yet to be vetted. * * Options for listing key-value pairs in a {@linkcode Kv}. * * @category KV */ export interface KvListOptions { /** * The maximum number of key-value pairs to return. If not specified, all * matching key-value pairs will be returned. */ limit?: number; /** * The cursor to resume the iteration from. If not specified, the iteration * will start from the beginning. */ cursor?: string; /** * Whether to reverse the order of the returned key-value pairs. If not * specified, the order will be ascending from the start of the range as per * the lexicographical ordering of the keys. If `true`, the order will be * descending from the end of the range. * * The default value is `false`. */ reverse?: boolean; /** * The consistency level of the list operation. The default consistency * level is "strong". Some use cases can benefit from using a weaker * consistency level. For more information on consistency levels, see the * documentation for {@linkcode KvConsistencyLevel}. * * List operations are performed in batches (in sizes specified by the * `batchSize` option). The consistency level of the list operation is * applied to each batch individually. This means that while each batch is * guaranteed to be consistent within itself, the entire list operation may * not be consistent across batches because a mutation may be applied to a * key-value pair between batches, in a batch that has already been returned * by the list operation. */ consistency?: KvConsistencyLevel; /** * The size of the batches in which the list operation is performed. Larger * or smaller batch sizes may positively or negatively affect the * performance of a list operation depending on the specific use case and * iteration behavior. Slow iterating queries may benefit from using a * smaller batch size for increased overall consistency, while fast * iterating queries may benefit from using a larger batch size for better * performance. * * The default batch size is equal to the `limit` option, or 100 if this is * unset. The maximum value for this option is 500. Larger values will be * clamped. */ batchSize?: number; } /** **UNSTABLE**: New API, yet to be vetted. * * An iterator over a range of data entries in a {@linkcode Kv}. * * The cursor getter returns the cursor that can be used to resume the * iteration from the current position in the future. * * @category KV */ export interface KvListIterator<T> extends AsyncIterableIterator<KvEntry<T>> { /** * Returns the cursor of the current position in the iteration. This cursor * can be used to resume the iteration from the current position in the * future by passing it to the `cursor` option of the `list` method. */ get cursor(): string; next(): Promise<IteratorResult<KvEntry<T>, undefined>>; [Symbol.asyncIterator](): AsyncIterableIterator<KvEntry<T>>; } /** **UNSTABLE**: New API, yet to be vetted. * * An operation on a {@linkcode Kv} that can be performed * atomically. Atomic operations do not auto-commit, and must be committed * explicitly by calling the `commit` method. * * Atomic operations can be used to perform multiple mutations on the KV store * in a single atomic transaction. They can also be used to perform * conditional mutations by specifying one or more * {@linkcode AtomicCheck}s that ensure that a mutation is only performed * if the key-value pair in the KV has a specific versionstamp. If any of the * checks fail, the entire operation will fail and no mutations will be made. * * The ordering of mutations is guaranteed to be the same as the ordering of * the mutations specified in the operation. Checks are performed before any * mutations are performed. The ordering of checks is unobservable. * * Atomic operations can be used to implement optimistic locking, where a * mutation is only performed if the key-value pair in the KV store has not * been modified since the last read. This can be done by specifying a check * that ensures that the versionstamp of the key-value pair matches the * versionstamp that was read. If the check fails, the mutation will not be * performed and the operation will fail. One can then retry the read-modify- * write operation in a loop until it succeeds. * * The `commit` method of an atomic operation returns a value indicating * whether checks passed and mutations were performed. If the operation failed * because of a failed check, the return value will be a * {@linkcode KvCommitError} with an `ok: false` property. If the * operation failed for any other reason (storage error, invalid value, etc.), * an exception will be thrown. If the operation succeeded, the return value * will be a {@linkcode KvCommitResult} object with a `ok: true` property * and the versionstamp of the value committed to KV. * * @category KV */ export interface AtomicOperation { /** * Add to the operation a check that ensures that the versionstamp of the * key-value pair in the KV store matches the given versionstamp. If the * check fails, the entire operation will fail and no mutations will be * performed during the commit. */ check(...checks: AtomicCheck[]): this; /** * Shortcut for creating a `sum` mutation, the value of `n` must be in the range * `[0, 2^64-1]`. */ sum(key: KvKey, n: bigint): this; /** * Shortcut for creating a `min` mutation, the value of `n` must be in the range * `[0, 2^64-1]`. */ min(key: KvKey, n: bigint): this; /** * Shortcut for creating a `max` mutation, the value of `n` must be in the range * `[0, 2^64-1]`. */ max(key: KvKey, n: bigint): this; /** * Add to the operation a mutation that sets the value of the specified key * to the specified value if all checks pass during the commit. * * Optionally an `expireIn` option can be specified to set a time-to-live * (TTL) for the key. The TTL is specified in milliseconds, and the key will * be deleted from the database at earliest after the specified number of * milliseconds have elapsed. Once the specified duration has passed, the * key may still be visible for some additional time. If the `expireIn` * option is not specified, the key will not expire. */ set(key: KvKey, value: unknown, options?: { expireIn?: number; }): this; /** * Add to the operation a mutation that deletes the specified key if all * checks pass during the commit. */ delete(key: KvKey): this; /** * Add to the operation a mutation that enqueues a value into the queue * if all checks pass during the commit. */ enqueue(value: unknown, options?: { delay?: number; keysIfUndelivered?: KvKey[]; }): this; /** * Commit the operation to the KV store. Returns a value indicating whether * checks passed and mutations were performed. If the operation failed * because of a failed check, the return value will be a {@linkcode * KvCommitError} with an `ok: false` property. If the operation failed * for any other reason (storage error, invalid value, etc.), an exception * will be thrown. If the operation succeeded, the return value will be a * {@linkcode KvCommitResult} object with a `ok: true` property and the * versionstamp of the value committed to KV. * * If the commit returns `ok: false`, one may create a new atomic operation * with updated checks and mutations and attempt to commit it again. See the * note on optimistic locking in the documentation for * {@linkcode AtomicOperation}. */ commit(): Promise<KvCommitResult | KvCommitError>; } /** **UNSTABLE**: New API, yet to be vetted. * * A check to perform as part of a {@linkcode AtomicOperation}. The check * will fail if the versionstamp for the key-value pair in the KV store does * not match the given versionstamp. A check with a `null` versionstamp checks * that the key-value pair does not currently exist in the KV store. * * @category KV */ export interface AtomicCheck { key: KvKey; versionstamp: string | null; } /** @category KV */ export interface KvCommitError { ok: false; }