Skip to content

Commit ab3e7db

Browse files
committed
feat: kv api
1 parent ed37d98 commit ab3e7db

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

rivetkit-typescript/packages/rivetkit/src/actor/contexts/base/actor.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Conn, ConnId } from "../../conn/mod";
66
import type { AnyDatabaseProvider, InferDatabaseClient } from "../../database";
77
import type { ActorDefinition, AnyActorDefinition } from "../../definition";
88
import type { ActorInstance, SaveStateOptions } from "../../instance/mod";
9+
import { ActorKv } from "../../instance/kv";
910
import type { Schedule } from "../../schedule";
1011

1112
/**
@@ -27,6 +28,7 @@ export class ActorContext<
2728
TInput,
2829
TDatabase
2930
>;
31+
#kv: ActorKv | undefined;
3032

3133
constructor(
3234
actor: ActorInstance<
@@ -41,6 +43,16 @@ export class ActorContext<
4143
this.#actor = actor;
4244
}
4345

46+
/**
47+
* Gets the KV storage interface.
48+
*/
49+
get kv(): ActorKv {
50+
if (!this.#kv) {
51+
this.#kv = new ActorKv(this.#actor.driver, this.#actor.id);
52+
}
53+
return this.#kv;
54+
}
55+
4456
/**
4557
* Get the actor state
4658
*

rivetkit-typescript/packages/rivetkit/src/actor/instance/kv.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,103 @@
1+
import type { ActorDriver } from "../driver";
2+
13
export const KEYS = {
24
PERSIST_DATA: Uint8Array.from([1]),
35
CONN_PREFIX: Uint8Array.from([2]), // Prefix for connection keys
46
INSPECTOR_TOKEN: Uint8Array.from([3]), // Inspector token key
7+
KV: Uint8Array.from([4]), // Prefix for user-facing KV storage
58
};
69

10+
// Helper to create a prefixed key for user-facing KV storage
11+
function makePrefixedKey(key: Uint8Array): Uint8Array {
12+
const prefixed = new Uint8Array(KEYS.KV.length + key.length);
13+
prefixed.set(KEYS.KV, 0);
14+
prefixed.set(key, KEYS.KV.length);
15+
return prefixed;
16+
}
17+
18+
// Helper to remove the prefix from a key
19+
function removePrefixFromKey(prefixedKey: Uint8Array): Uint8Array {
20+
return prefixedKey.slice(KEYS.KV.length);
21+
}
22+
23+
/**
24+
* User-facing KV storage interface exposed on ActorContext.
25+
*/
26+
export class ActorKv {
27+
#driver: ActorDriver;
28+
#actorId: string;
29+
30+
constructor(driver: ActorDriver, actorId: string) {
31+
this.#driver = driver;
32+
this.#actorId = actorId;
33+
}
34+
35+
/**
36+
* Get a single value by key.
37+
*/
38+
async get(key: Uint8Array): Promise<Uint8Array | null> {
39+
const results = await this.#driver.kvBatchGet(this.#actorId, [
40+
makePrefixedKey(key),
41+
]);
42+
return results[0] ?? null;
43+
}
44+
45+
/**
46+
* Get multiple values by keys.
47+
*/
48+
async getBatch(keys: Uint8Array[]): Promise<(Uint8Array | null)[]> {
49+
const prefixedKeys = keys.map(makePrefixedKey);
50+
return this.#driver.kvBatchGet(this.#actorId, prefixedKeys);
51+
}
52+
53+
/**
54+
* Put a single key-value pair.
55+
*/
56+
async put(key: Uint8Array, value: Uint8Array): Promise<void> {
57+
await this.#driver.kvBatchPut(this.#actorId, [
58+
[makePrefixedKey(key), value],
59+
]);
60+
}
61+
62+
/**
63+
* Put multiple key-value pairs.
64+
*/
65+
async putBatch(entries: [Uint8Array, Uint8Array][]): Promise<void> {
66+
const prefixedEntries: [Uint8Array, Uint8Array][] = entries.map(
67+
([key, value]) => [makePrefixedKey(key), value],
68+
);
69+
await this.#driver.kvBatchPut(this.#actorId, prefixedEntries);
70+
}
71+
72+
/**
73+
* Delete a single key.
74+
*/
75+
async delete(key: Uint8Array): Promise<void> {
76+
await this.#driver.kvBatchDelete(this.#actorId, [makePrefixedKey(key)]);
77+
}
78+
79+
/**
80+
* Delete multiple keys.
81+
*/
82+
async deleteBatch(keys: Uint8Array[]): Promise<void> {
83+
const prefixedKeys = keys.map(makePrefixedKey);
84+
await this.#driver.kvBatchDelete(this.#actorId, prefixedKeys);
85+
}
86+
87+
/**
88+
* List all keys with a given prefix.
89+
* Returns key-value pairs where keys have the user prefix removed.
90+
*/
91+
async list(prefix: Uint8Array): Promise<[Uint8Array, Uint8Array][]> {
92+
const prefixedPrefix = makePrefixedKey(prefix);
93+
const results = await this.#driver.kvListPrefix(
94+
this.#actorId,
95+
prefixedPrefix,
96+
);
97+
return results.map(([key, value]) => [removePrefixFromKey(key), value]);
98+
}
99+
}
100+
7101
// Helper to create a connection key
8102
export function makeConnKey(connId: string): Uint8Array {
9103
const encoder = new TextEncoder();

rivetkit-typescript/packages/rivetkit/src/actor/mod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export type { AnyConn, Conn } from "./conn/mod";
7676
export type { ActorDefinition, AnyActorDefinition } from "./definition";
7777
export { lookupInRegistry } from "./definition";
7878
export { UserError, type UserErrorOptions } from "./errors";
79-
export { KEYS as KV_KEYS } from "./instance/kv";
79+
export { ActorKv, KEYS as KV_KEYS } from "./instance/kv";
8080
export type { AnyActorInstance } from "./instance/mod";
8181
export {
8282
type ActorRouter,

0 commit comments

Comments
 (0)