Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/keyv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ Returns a promise which resolves to `true`.

## .setMany(entries)

Set multiple values using KeyvEntrys `{ key: string, value: any, ttl?: number }`.
Set multiple values using `KeyvEntry<Value>` objects (`{ key: string, value: Value, ttl?: number }`). The `Value` type is inferred from the entries provided.

## .get(key, [options])

Expand Down
4 changes: 3 additions & 1 deletion core/keyv/src/generic-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ export class KeyvGenericStore extends Hookified implements KeyvStorageAdapter {
* Stores multiple entries in the store at once.
* @param entries - Array of entries containing key, value, and optional TTL
*/
async setMany(entries: KeyvEntry[]): Promise<boolean[] | undefined> {
async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
const results: boolean[] = [];
for (const entry of entries) {
const result = await this.set(entry.key, entry.value, entry.ttl);
Expand Down
7 changes: 3 additions & 4 deletions core/keyv/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ export class Keyv<GenericValue = any> extends Hookified {

/**
* Set an item to the store
* @param {string | Array<KeyvEntry>} key the key to use. If you pass in an array of KeyvEntry it will set many items
* @param {string | Array<KeyvEntry<Value>>} key the key to use. If you pass in an array of KeyvEntry it will set many items
* @param {Value} value the value of the key
* @param {number} [ttl] time to live in milliseconds
* @returns {boolean} if it sets then it will return a true. On failure will return false.
Expand Down Expand Up @@ -798,12 +798,11 @@ export class Keyv<GenericValue = any> extends Hookified {

/**
* Set many items to the store
* @param {Array<KeyvEntry>} entries the entries to set
* @param {Array<KeyvEntry<Value>>} entries the entries to set
* @returns {boolean[]} will return an array of booleans if it sets then it will return a true. On failure will return false.
*/
// biome-ignore lint/correctness/noUnusedVariables: type format
async setMany<Value = GenericValue>(
entries: KeyvEntry[],
entries: KeyvEntry<Value>[],
): Promise<boolean[]> {
let results: boolean[] = [];

Expand Down
10 changes: 3 additions & 7 deletions core/keyv/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,15 @@ export enum KeyvHooks {
AFTER_DELETE = "after:delete",
}

export type KeyvEntry = {
export type KeyvEntry<Value> = {
/**
* Key to set.
*/
key: string;
/**
* Value to set.
*/
// biome-ignore lint/suspicious/noExplicitAny: type format
value: any;
value: Value;
/**
* Time to live in milliseconds.
*/
Expand All @@ -101,10 +100,7 @@ export type KeyvStorageAdapter = {
get<Value>(key: string): Promise<StoredData<Value> | undefined>;
// biome-ignore lint/suspicious/noExplicitAny: type format
set(key: string, value: any, ttl?: number): any;
setMany?(
// biome-ignore lint/suspicious/noExplicitAny: type format
values: Array<{ key: string; value: any; ttl?: number }>,
): Promise<boolean[] | undefined>;
setMany?<Value>(values: KeyvEntry<Value>[]): Promise<boolean[] | undefined>;
delete(key: string): Promise<boolean>;
clear(): Promise<void>;
has?(key: string): Promise<boolean>;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build:keyv": "pnpm recursive --filter=keyv run build",
"build": "pnpm build:keyv && pnpm recursive --filter '!keyv' run build",
"test": "pnpm -r test",
"test:sequential": "pnpm -r --workspace-concurrency 1 test",
"test:ci": "pnpm -r test:ci",
"test:services:start": "chmod +x ./scripts/test-services-start.sh && ./scripts/test-services-start.sh",
"test:services:stop": "chmod +x ./scripts/test-services-stop.sh && ./scripts/test-services-stop.sh",
Expand Down
2 changes: 1 addition & 1 deletion storage/dynamo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ await store.set('foo', 'bar', 60000);

### .setMany(entries)

Stores multiple values in DynamoDB using `BatchWriteItem` in chunks of 25. Each entry is an object with `key`, `value`, and optional `ttl` properties. Returns a `boolean[]` with per-entry success tracking — any items reported as `UnprocessedItems` by DynamoDB are marked as `false`.
Stores multiple values in DynamoDB using `BatchWriteItem` in chunks of 25. Each entry is a `KeyvEntry<Value>` object (`{ key: string, value: Value, ttl?: number }`), where `Value` is inferred from the entries provided. Returns a `boolean[]` with per-entry success tracking — any items reported as `UnprocessedItems` by DynamoDB are marked as `false`.

```js
const store = new KeyvDynamo({ endpoint: 'http://localhost:8000' });
Expand Down
11 changes: 8 additions & 3 deletions storage/dynamo/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import {
type ScanCommandOutput,
} from "@aws-sdk/lib-dynamodb";
import { Hookified } from "hookified";
import { Keyv, type KeyvStorageAdapter, type StoredData } from "keyv";
import {
Keyv,
type KeyvEntry,
type KeyvStorageAdapter,
type StoredData,
} from "keyv";

export class KeyvDynamo extends Hookified implements KeyvStorageAdapter {
private _sixHoursInMilliseconds = 6 * 60 * 60 * 1000;
Expand Down Expand Up @@ -203,8 +208,8 @@ export class KeyvDynamo extends Hookified implements KeyvStorageAdapter {
* Stores multiple values in DynamoDB.
* @param entries - An array of objects containing key, value, and optional ttl
*/
public async setMany(
entries: Array<{ key: string; value: unknown; ttl?: number }>,
public async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
try {
await this._tableReady;
Expand Down
10 changes: 4 additions & 6 deletions storage/dynamo/test/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,10 +548,8 @@ test.it("setMany returns false entries when batchWrite fails", async (t) => {
// Wait for table to be ready before mocking
await dynamo.set("_warmup", "ok");
// Mock batchWrite to simulate failure
const originalBatchWrite = dynamo["_client"].batchWrite.bind(
dynamo["_client"],
);
dynamo["_client"].batchWrite = async () => {
const originalBatchWrite = dynamo._client.batchWrite.bind(dynamo._client);
dynamo._client.batchWrite = async () => {
throw new Error("batchWrite failure");
};

Expand All @@ -560,7 +558,7 @@ test.it("setMany returns false entries when batchWrite fails", async (t) => {
{ key: "key2", value: "val2" },
]);
t.expect(result).toEqual([false, false]);
dynamo["_client"].batchWrite = originalBatchWrite;
dynamo._client.batchWrite = originalBatchWrite;
});

test.it("setMany marks unprocessed items as false", async (t) => {
Expand All @@ -570,7 +568,7 @@ test.it("setMany marks unprocessed items as false", async (t) => {
await dynamo.set("_warmup", "ok");
// Mock batchWrite to return UnprocessedItems for the second key
const key2Formatted = dynamo.formatKey("key2");
dynamo["_client"].batchWrite = async (input: any) => {
dynamo._client.batchWrite = async (input: any) => {
const tableName = Object.keys(input.RequestItems)[0];
return {
UnprocessedItems: {
Expand Down
2 changes: 1 addition & 1 deletion storage/etcd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ await store.set('foo', 'bar');

### .setMany(entries)

Stores multiple values in the etcd server. Each entry is an object with `key` and `value` properties. Returns a `boolean[]` indicating whether each entry was set successfully.
Stores multiple values in the etcd server. Each entry is a `KeyvEntry<Value>` object (`{ key: string, value: Value, ttl?: number }`), where `Value` is inferred from the entries provided. Returns a `boolean[]` indicating whether each entry was set successfully.

```js
const store = new KeyvEtcd('etcd://localhost:2379');
Expand Down
26 changes: 14 additions & 12 deletions storage/etcd/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Etcd3, type Lease } from "etcd3";
import { Hookified } from "hookified";
import { Keyv, type StoredData } from "keyv";
import { Keyv, type KeyvEntry, type StoredData } from "keyv";
import type {
ClearOutput,
DeleteOutput,
Expand Down Expand Up @@ -38,7 +38,7 @@ export type KeyvEtcdOptions = {
* ```
*/
// biome-ignore lint/suspicious/noExplicitAny: any is allowed
export class KeyvEtcd<Value = any> extends Hookified {
export class KeyvEtcd<GenericValue = any> extends Hookified {
private _client!: Etcd3;
private _lease?: Lease;
private _url = "127.0.0.1:2379";
Expand Down Expand Up @@ -269,11 +269,11 @@ export class KeyvEtcd<Value = any> extends Hookified {
* @param key - The key to retrieve
* @returns The stored value, or `undefined` if the key does not exist.
*/
public async get(key: string): GetOutput<Value> {
public async get(key: string): GetOutput<GenericValue> {
try {
return (await this._client.get(
this.formatKey(key),
)) as unknown as GetOutput<Value>;
)) as unknown as GetOutput<GenericValue>;
} catch (error) {
this.emit("error", error);
}
Expand All @@ -284,21 +284,23 @@ export class KeyvEtcd<Value = any> extends Hookified {
* @param keys - An array of keys to retrieve
* @returns An array of stored data corresponding to each key.
*/
public async getMany(keys: string[]): Promise<Array<StoredData<Value>>> {
public async getMany(
keys: string[],
): Promise<Array<StoredData<GenericValue>>> {
const promises = [];
for (const key of keys) {
promises.push(this.get(key));
}

return Promise.allSettled(promises).then((values) => {
const data: Array<StoredData<Value>> = [];
const data: Array<StoredData<GenericValue>> = [];
for (const value of values) {
// @ts-expect-error - value is an object
if (value.value === null) {
data.push(undefined);
} else {
// @ts-expect-error - value is an object
data.push(value.value as StoredData<Value>);
data.push(value.value as StoredData<GenericValue>);
}
}

Expand All @@ -311,7 +313,7 @@ export class KeyvEtcd<Value = any> extends Hookified {
* @param key - The key to store
* @param value - The value to store
*/
public async set(key: string, value: Value): SetOutput {
public async set(key: string, value: GenericValue): SetOutput {
try {
const target = this._ttl ? this._lease : this._client;

Expand All @@ -326,11 +328,11 @@ export class KeyvEtcd<Value = any> extends Hookified {
* Stores multiple values in the etcd server.
* @param entries - An array of objects containing key and value
*/
public async setMany(
entries: Array<{ key: string; value: Value }>,
public async setMany<Value = GenericValue>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
const promises = entries.map(async ({ key, value }) =>
this.set(key, value),
this.set(key, value as unknown as GenericValue),
);
const results = await Promise.allSettled(promises);
const boolResults: boolean[] = [];
Expand Down Expand Up @@ -413,7 +415,7 @@ export class KeyvEtcd<Value = any> extends Hookified {

for await (const key of iterator) {
try {
const value = (await this._client.get(key)) as unknown as Value;
const value = (await this._client.get(key)) as unknown as GenericValue;
const unprefixedKey = this.removeKeyPrefix(key, this._namespace);
yield [unprefixedKey, value];
/* v8 ignore start -- @preserve */
Expand Down
2 changes: 1 addition & 1 deletion storage/memcache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ await memcache.set('foo', 'bar', 5000); // expires in 5 seconds

### .setMany(entries)

Stores multiple values in the memcache server. Each entry can have an optional `ttl` in milliseconds. Returns a `boolean[]` indicating whether each entry was set successfully.
Stores multiple values in the memcache server. Each entry is a `KeyvEntry<Value>` object (`{ key: string, value: Value, ttl?: number }`), where `Value` is inferred from the entries provided. Returns a `boolean[]` indicating whether each entry was set successfully.

```js
const memcache = new KeyvMemcache('localhost:11211');
Expand Down
7 changes: 3 additions & 4 deletions storage/memcache/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Hookified } from "hookified";
import type { KeyvStorageAdapter, StoredData } from "keyv";
import type { KeyvEntry, KeyvStorageAdapter, StoredData } from "keyv";
import { Keyv } from "keyv";
import { Memcache, type MemcacheOptions } from "memcache";

Expand Down Expand Up @@ -119,9 +119,8 @@ export class KeyvMemcache extends Hookified implements KeyvStorageAdapter {
* Stores multiple values in the memcache server.
* @param entries - An array of objects containing key, value, and optional ttl
*/
async setMany(
// biome-ignore lint/suspicious/noExplicitAny: type format
entries: Array<{ key: string; value: any; ttl?: number }>,
async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
const promises = entries.map(async ({ key, value, ttl }) =>
this.set(key, value, ttl),
Expand Down
2 changes: 1 addition & 1 deletion storage/mongo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ await keyv.set('foo', 'bar', 5000); // expires in 5 seconds

`setMany(entries)` - Set multiple values in the store at once.

- `entries` *(Array<{ key: string, value: any, ttl?: number }>)* - Array of entries to set. Each entry has a `key`, `value`, and optional `ttl` in milliseconds.
- `entries` *(KeyvEntry<Value>[])* - Array of entries to set. Each entry has a `key`, `value`, and optional `ttl` in milliseconds. `Value` is inferred from the entries provided.
- Returns: `Promise<boolean[]>` - An array of booleans indicating whether each entry was set successfully.

In standard mode, uses a single unordered MongoDB `bulkWrite` operation for efficiency with per-entry error tracking — if individual writes fail, only those entries return `false`. In GridFS mode, each entry is set individually in parallel using `Promise.allSettled`, providing per-entry success tracking.
Expand Down
11 changes: 7 additions & 4 deletions storage/mongo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Buffer } from "node:buffer";
import { Hookified } from "hookified";
import Keyv, { type KeyvStorageAdapter, type StoredData } from "keyv";
import Keyv, {
type KeyvEntry,
type KeyvStorageAdapter,
type StoredData,
} from "keyv";
import {
type Document,
GridFSBucket,
Expand Down Expand Up @@ -369,9 +373,8 @@ export class KeyvMongo extends Hookified implements KeyvStorageAdapter {
* In GridFS mode, each entry is set individually in parallel.
* @param entries - Array of entries to set. Each entry has a `key`, `value`, and optional `ttl` in milliseconds.
*/
public async setMany(
// biome-ignore lint/suspicious/noExplicitAny: type format
entries: Array<{ key: string; value: any; ttl?: number }>,
public async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
if (this._useGridFS) {
const settled = await Promise.allSettled(
Expand Down
2 changes: 1 addition & 1 deletion storage/mysql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ await keyvMysql.set('foo', 'bar');

### .setMany(entries)

Set multiple key-value pairs at once using a single atomic `INSERT ... ON DUPLICATE KEY UPDATE` statement. Returns a `boolean[]` indicating whether each entry was set successfully. Since the SQL statement is atomic, all entries either succeed (`true`) or all fail (`false`) together. On failure, an `error` event is emitted.
Set multiple key-value pairs at once using a single atomic `INSERT ... ON DUPLICATE KEY UPDATE` statement. Each entry is a `KeyvEntry<Value>` object (`{ key: string, value: Value, ttl?: number }`), where `Value` is inferred from the entries provided. Returns a `boolean[]` indicating whether each entry was set successfully. Since the SQL statement is atomic, all entries either succeed (`true`) or all fail (`false`) together. On failure, an `error` event is emitted.

```js
const results = await keyvMysql.setMany([
Expand Down
4 changes: 3 additions & 1 deletion storage/mysql/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,9 @@ export class KeyvMysql extends Hookified implements KeyvStorageAdapter {
* @param entries - Array of key-value entry objects
* @returns Promise that resolves when the operation completes
*/
public async setMany(entries: KeyvEntry[]): Promise<boolean[] | undefined> {
public async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
if (entries.length === 0) {
return entries.map(() => true);
}
Expand Down
2 changes: 1 addition & 1 deletion storage/postgres/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ await keyv.set('foo', 'bar');

## .setMany(entries)

Set multiple key-value pairs at once using a single atomic PostgreSQL `INSERT ... UNNEST ... ON CONFLICT` statement. Returns a `boolean[]` indicating whether each entry was set successfully. Since the SQL statement is atomic, all entries either succeed (`true`) or all fail (`false`) together. On failure, an `error` event is emitted.
Set multiple key-value pairs at once using a single atomic PostgreSQL `INSERT ... UNNEST ... ON CONFLICT` statement. Each entry is a `KeyvEntry<Value>` object (`{ key: string, value: Value, ttl?: number }`), where `Value` is inferred from the entries provided. Returns a `boolean[]` indicating whether each entry was set successfully. Since the SQL statement is atomic, all entries either succeed (`true`) or all fail (`false`) together. On failure, an `error` event is emitted.

```js
const results = await keyv.setMany([
Expand Down
4 changes: 3 additions & 1 deletion storage/postgres/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,9 @@ export class KeyvPostgres extends Hookified implements KeyvStorageAdapter {
* Sets multiple key-value pairs at once using PostgreSQL `UNNEST` for efficient bulk operations.
* @param entries - An array of key-value entry objects.
*/
public async setMany(entries: KeyvEntry[]): Promise<boolean[] | undefined> {
public async setMany<Value>(
entries: KeyvEntry<Value>[],
): Promise<boolean[] | undefined> {
try {
const keys = [];
const values = [];
Expand Down
2 changes: 1 addition & 1 deletion storage/redis/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ export type KeyvRedisOptions = {
* **useUnlink** - Use the `UNLINK` command for deleting keys isntead of `DEL`.
* **noNamespaceAffectsAll**: Whether to allow clearing all keys when no namespace is set (default is `false`).
* **set** - Set a key.
* **setMany** - Set multiple keys using `MULTI/EXEC` transactions. Returns `boolean[]` with per-entry success tracking by inspecting each command's result. In cluster mode, entries are grouped by hash slot with results mapped back to the original order.
* **setMany** - Set multiple keys using `KeyvEntry<Value>` objects (`{ key: string, value: Value, ttl?: number }`) via `MULTI/EXEC` transactions. Returns `boolean[]` with per-entry success tracking by inspecting each command's result. In cluster mode, entries are grouped by hash slot with results mapped back to the original order.
* **get** - Get a key.
* **getMany** - Get multiple keys.
* **has** - Check if a key exists.
Expand Down
Loading
Loading