Skip to content

Commit 7c849ba

Browse files
committed
feat: add redis adapter
1 parent bf4c02c commit 7c849ba

File tree

5 files changed

+3117
-7068
lines changed

5 files changed

+3117
-7068
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "@automerge/automerge-repo-storage-redis",
3+
"version": "1.0.0",
4+
"description": "Redis storage adapter for Automerge Repo",
5+
"repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo-storage-redis",
6+
"author": "Ryan Kois <[email protected]>",
7+
"license": "MIT",
8+
"type": "module",
9+
"main": "dist/index.js",
10+
"scripts": {
11+
"build": "tsc",
12+
"watch": "tsc -w"
13+
},
14+
"dependencies": {
15+
"@automerge/automerge-repo": "workspace:*",
16+
"debug": "^4.3.4",
17+
"ioredis": "^5.3.2"
18+
},
19+
"publishConfig": {
20+
"access": "public"
21+
},
22+
"devDependencies": {
23+
"typescript": "^5.3.3"
24+
}
25+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* This module provides a storage adapter for Redis.
3+
*
4+
* @packageDocumentation
5+
*/
6+
7+
import {
8+
Chunk,
9+
StorageAdapterInterface,
10+
type StorageKey,
11+
} from "@automerge/automerge-repo"
12+
13+
import { Redis, RedisOptions } from "ioredis"
14+
import debug from "debug"
15+
16+
const log = debug("automerge-repo:storage-redis")
17+
18+
export class RedisStorageAdapter implements StorageAdapterInterface {
19+
private redis: Redis
20+
21+
/**
22+
* Create a new {@link RedisStorageAdapter}.
23+
* @param opts - Options to pass to the Redis client.
24+
*/
25+
constructor(
26+
opts?: RedisOptions
27+
) {
28+
this.redis = new Redis(opts)
29+
// TODO: handle errors
30+
this.redis.on("error", (err) => {
31+
log("redis error %O", err)
32+
})
33+
}
34+
35+
async load(keyArray: StorageKey): Promise<Uint8Array | undefined> {
36+
const data = await this.redis.getBuffer(keyArray.join(":"))
37+
return data
38+
}
39+
40+
async save(keyArray: string[], binary: Uint8Array): Promise<void> {
41+
console.log('save', binary.length, keyArray.join(":"))
42+
await this.redis.set(keyArray.join(":"), Buffer.from(binary))
43+
}
44+
45+
async remove(keyArray: string[]): Promise<void> {
46+
await this.redis.del(keyArray.join(":"))
47+
}
48+
49+
async loadRange(keyPrefix: string[]): Promise<Chunk[]> {
50+
const lowerBound = [...keyPrefix, '*'].join(":")
51+
const result: Chunk[] = []
52+
const stream = this.redis.scanStream({
53+
match: lowerBound,
54+
})
55+
stream.on("data", async (keys: string[]) => {
56+
for (const key of keys) {
57+
stream.pause()
58+
const data = await this.redis.getBuffer(key)
59+
result.push({
60+
key: key.split(":"),
61+
data,
62+
})
63+
stream.resume()
64+
}
65+
})
66+
return await new Promise((resolve, reject) => {
67+
stream.on("end", () => resolve(result))
68+
stream.on("error", (err) => reject(err))
69+
})
70+
}
71+
72+
async removeRange(keyPrefix: string[]): Promise<void> {
73+
const lowerBound = [...keyPrefix, '*'].join(":")
74+
const stream = this.redis.scanStream({
75+
match: lowerBound,
76+
})
77+
stream.on("data", async (keys: string[]) => {
78+
for (const key of keys) {
79+
stream.pause()
80+
await this.redis.del(key)
81+
stream.resume()
82+
}
83+
})
84+
return await new Promise((resolve, reject) => {
85+
stream.on("end", resolve)
86+
stream.on("error", reject)
87+
})
88+
}
89+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ESNext",
4+
"module": "NodeNext",
5+
"moduleResolution": "Node16",
6+
"declaration": true,
7+
"declarationMap": true,
8+
"outDir": "./dist",
9+
"esModuleInterop": true,
10+
"forceConsistentCasingInFileNames": true,
11+
"strict": false,
12+
"skipLibCheck": true
13+
},
14+
"include": ["src/**/*.ts"]
15+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": ["../../typedoc.base.json"],
3+
"entryPoints": ["src/index.ts"],
4+
"readme": "none"
5+
}

0 commit comments

Comments
 (0)