Skip to content

Commit

Permalink
feat: return up-to-date values (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kycermann authored Jun 11, 2023
1 parent 52c7245 commit 16ba5dc
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 21 deletions.
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Upgrade your Deno KV code with **confidence** and **peace of mind**.
## 🎁 A quick demo

```js
import { withSafeAtomics } from "https://deno.land/x/kvp/mod.ts";
import { withSafeAtomics } from "https://deno.land/x/kvp@1.1.0/mod.ts";

// Create a KV instance with atomic support
const kv = withSafeAtomics(await Deno.openKv());
Expand All @@ -46,7 +46,7 @@ A convenient version of `setSafeAtomicMany` for updating just one key. This func
#### 🌟 Starter template

```ts
import { withSafeAtomics } from "https://deno.land/x/kvp/mod.ts";
import { withSafeAtomics } from "https://deno.land/x/kvp@1.1.0/mod.ts";

const kv = withSafeAtomics(await Deno.openKv());

Expand All @@ -70,13 +70,14 @@ const { ok, error } = await kv.setAtomic(
| -------- | --------- | ------------------------------------------------------ |
| `ok` | `boolean` | Whether the update was successful. |
| `error` | `string` | The error message if the update failed or was aborted. |
| `value` | `unknown` | The new value in Deno KV |

### `setSafeAtomicMany`

#### 🌟 Starter template

```ts
import { withSafeAtomics } from "https://deno.land/x/kvp/mod.ts";
import { withSafeAtomics } from "https://deno.land/x/kvp@1.1.0/mod.ts";

const kv = withSafeAtomics(await Deno.openKv());

Expand All @@ -100,11 +101,12 @@ const { ok, error } = await kv.setAtomicMany(
| -------- | --------- | ------------------------------------------------------ |
| `ok` | `boolean` | Whether the update was successful. |
| `error` | `string` | The error message if the update failed or was aborted. |
| `values` | `unknown` | The new values in Deno KV |

#### 📚 Complex example

```js
import { withSafeAtomics } from "https://deno.land/x/kvp/mod.ts";
import { withSafeAtomics } from "https://deno.land/x/kvp@1.1.0/mod.ts";

// Create a KV instance with atomicMany support
const kv = withSafeAtomics(await Deno.openKv());
Expand Down
6 changes: 5 additions & 1 deletion deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 14 additions & 8 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ Deno.test("setSafeAtomic update the values", async () => {

// Act
const kv = withSafeAtomics(await Deno.openKv());
await kv.setSafeAtomic(mockKey, updateValue);
const result = await kv.setSafeAtomic(mockKey, updateValue);
const { value: newValue } = await kv.get(mockKey);
await kv.close();

// Assert
assertEquals(newValue, mockValue);
assertEquals(result, { ok: true, error: null, value: newValue });
});

Deno.test("setSafeAtomicMany updates multiple values", async () => {
Expand All @@ -49,14 +50,15 @@ Deno.test("setSafeAtomicMany updates multiple values", async () => {

// Act
const kv = withSafeAtomics(await Deno.openKv());
await kv.setSafeAtomicMany(keys, updateValues, retryCount);
const result = await kv.setSafeAtomicMany(keys, updateValues, retryCount);
const { value: newValue1 } = await kv.get(mockKey1);
const { value: newValue2 } = await kv.get(mockKey2);
await kv.close();

// Assert
assertEquals(newValue1, mockUpdatedValue1);
assertEquals(newValue2, mockUpdatedValue2);
assertEquals(result, { ok: true, error: null, values: [newValue1, newValue2] });
});

Deno.test(
Expand All @@ -66,22 +68,24 @@ Deno.test(
const mockKey = ["test", "abc"];
const mockValue = "123";
const mockUpdatedValue = "new value";
const abortReason = "Testing";

const updateValue = (_value: unknown, abort: () => void): unknown => {
abort();
const updateValue = (_value: unknown, abort: (reason?: string) => void): unknown => {
abort(abortReason);

return mockUpdatedValue;
};

// Act
const kv = withSafeAtomics(await Deno.openKv());
await kv.set(mockKey, mockValue);
await kv.setSafeAtomic(mockKey, updateValue);
const result = await kv.setSafeAtomic(mockKey, updateValue);
const { value: newValue } = await kv.get(mockKey);
await kv.close();

// Assert
assertEquals(newValue, mockValue);
assertEquals(result, { ok: false, error: abortReason, value: mockValue });
},
);

Expand All @@ -96,13 +100,14 @@ Deno.test(
const mockUpdatedValue1 = "new value 1";
const mockUpdatedValue2 = "new value 2";
const mockUpdatedValues = [mockUpdatedValue1, mockUpdatedValue2];
const abortReason = "Testing";

const keys = [mockKey1, mockKey2];
const updateValues = (
_values: unknown[],
abort: () => void,
abort: (reason?: string) => void,
): unknown[] | void => {
abort();
abort(abortReason);
return mockUpdatedValues;
};
const retryCount = 3;
Expand All @@ -113,14 +118,15 @@ Deno.test(
await kv.set(mockKey2, mockValue2);

// Act
await kv.setSafeAtomicMany(keys, updateValues, retryCount);
const result = await kv.setSafeAtomicMany(keys, updateValues, retryCount);
const { value: newValue1 } = await kv.get(mockKey1);
const { value: newValue2 } = await kv.get(mockKey2);
await kv.close();

// Assert
assertEquals(newValue1, mockValue1);
assertEquals(newValue2, mockValue2);
assertEquals(result, { ok: false, error: abortReason, values: [mockValue1, mockValue2] });
},
);

Expand Down
29 changes: 21 additions & 8 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type DenoKvWithSafeAtomics = Deno.Kv & {
abort: (reason?: string) => void,
) => unknown[] | void,
retryCount?: number,
) => Promise<SafeAtomicResponse>;
) => Promise<SafeAtomicManyResponse>;

setSafeAtomic: (
key: any[],
Expand All @@ -15,14 +15,22 @@ export type DenoKvWithSafeAtomics = Deno.Kv & {
abort: (reason?: string) => void,
) => unknown | void,
retryCount?: number,
) => Promise<SafeAtomicResponse>;
) => Promise<SafeAtomicSingleResponse>;
};

export type SafeAtomicResponse = {
ok: boolean;
error: string | null;
};

export type SafeAtomicSingleResponse = SafeAtomicResponse & {
value: unknown;
};

export type SafeAtomicManyResponse = SafeAtomicResponse & {
values: unknown[];
};

/**
* Adds the {setSafeAtomicMany} method to the Deno KV instance.
*
Expand Down Expand Up @@ -58,7 +66,7 @@ async function setSafeAtomicMany(
abort: (reason?: string) => void,
) => unknown[] | void,
retryCount: number = 10,
): Promise<SafeAtomicResponse> {
): Promise<SafeAtomicManyResponse> {
const results = await this.getMany(keys);
const resultValues: unknown[] = results.map(
(result: Deno.KvEntryMaybe<unknown>) => result.value,
Expand All @@ -73,12 +81,15 @@ async function setSafeAtomicMany(
if (reason) abortReason = reason;
};

// Save original values (to return if we abort)
const originalResultValues = structuredClone(resultValues);

// @ts-ignore
const updatedValues = updateValues(
resultValues,
abort,
) as unknown as any as unknown[];
if (aborting) return { ok: false, error: abortReason };
if (aborting) return { ok: false, error: abortReason, values: originalResultValues };

if (!updatedValues) {
throw new Error("updateValues must return an array of values to update");
Expand All @@ -100,10 +111,10 @@ async function setSafeAtomicMany(

const { ok } = await tx.commit();

if (ok) return { ok, error: null };
if (ok) return { ok, error: null, values: updatedValues };

if (!ok && retryCount === 0) {
return { ok, error: "Failed to commit transaction" };
return { ok, error: "Failed to commit transaction", values: originalResultValues };
}

return this.setSafeAtomicMany(keys, updateValues, retryCount - 1);
Expand All @@ -117,12 +128,14 @@ async function setSafeAtomic(
abort: (reason?: string) => void,
) => unknown | void,
retryCount: number,
) {
return this.setSafeAtomicMany(
): Promise<SafeAtomicSingleResponse> {
const { ok, error, values } = await this.setSafeAtomicMany(
[key],
(values: unknown[], abort: (reason?: string) => void) => {
return [updateValue(values[0], abort)];
},
retryCount,
);

return { ok, error, value: values[0] };
}

0 comments on commit 16ba5dc

Please sign in to comment.