@unikvs/redis.bun 0.0.13 → 0.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +86 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/redis.ts +112 -21
package/dist/index.d.ts
CHANGED
|
@@ -6,10 +6,6 @@ import { RedisOptions } from "bun";
|
|
|
6
6
|
*/
|
|
7
7
|
type RedisStorageOptions = RedisOptions & {
|
|
8
8
|
/**
|
|
9
|
-
* すべてのキーの先頭に付与するプレフィックスです。
|
|
10
|
-
*
|
|
11
|
-
* キーを名前空間で分離し、`clear()` が削除する範囲を限定します。空文字を指定するとキーをそのまま使います。
|
|
12
|
-
*
|
|
13
9
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions-keyprefix)
|
|
14
10
|
*/
|
|
15
11
|
readonly keyPrefix?: string;
|
|
@@ -19,20 +15,106 @@ type RedisStorageOptions = RedisOptions & {
|
|
|
19
15
|
*/
|
|
20
16
|
declare class Redis implements IStorage {
|
|
21
17
|
#private;
|
|
18
|
+
/**
|
|
19
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)
|
|
20
|
+
*/
|
|
22
21
|
readonly name: string;
|
|
23
22
|
/**
|
|
24
23
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)
|
|
25
24
|
*/
|
|
26
25
|
constructor(url?: string, options?: RedisStorageOptions);
|
|
26
|
+
/**
|
|
27
|
+
* ストレージがオープン済みかどうかです。
|
|
28
|
+
*
|
|
29
|
+
* @returns オープン済みなら真です。
|
|
30
|
+
*/
|
|
27
31
|
get isOpen(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* ストレージをオープンします。
|
|
34
|
+
*
|
|
35
|
+
* Bun の Redis クライアントを接続します。
|
|
36
|
+
*
|
|
37
|
+
* @param _c オープンコンテキストです。
|
|
38
|
+
*
|
|
39
|
+
* @returns オープンが完了したら解決します。
|
|
40
|
+
*/
|
|
28
41
|
open(_c: IStorage.OpenContext): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* ストレージをクローズします。
|
|
44
|
+
*
|
|
45
|
+
* クライアントを閉じて参照を外します。
|
|
46
|
+
*
|
|
47
|
+
* @param _c クローズコンテキストです。
|
|
48
|
+
*/
|
|
29
49
|
close(_c: IStorage.CloseContext): void;
|
|
50
|
+
/**
|
|
51
|
+
* バイト列を書き込みます。
|
|
52
|
+
*
|
|
53
|
+
* 指定したキーで値を上書きします。
|
|
54
|
+
*
|
|
55
|
+
* @param c 書き込みコンテキストです。
|
|
56
|
+
* @param data 保存するバイト列です。
|
|
57
|
+
*
|
|
58
|
+
* @returns 書き込みが完了したら解決します。
|
|
59
|
+
*/
|
|
30
60
|
write(c: IStorage.WriteContext, data: Uint8Array<ArrayBuffer>): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* バイト列を読み取ります。
|
|
63
|
+
*
|
|
64
|
+
* 指定したキーの値を取得します。
|
|
65
|
+
*
|
|
66
|
+
* @param c 読み取りコンテキストです。
|
|
67
|
+
*
|
|
68
|
+
* @returns 読み取ったバイト列です。
|
|
69
|
+
*/
|
|
31
70
|
read(c: IStorage.ReadContext): Promise<Uint8Array<ArrayBuffer>>;
|
|
71
|
+
/**
|
|
72
|
+
* キーの存在を確認します。
|
|
73
|
+
*
|
|
74
|
+
* @param c 存在確認コンテキストです。
|
|
75
|
+
*
|
|
76
|
+
* @returns 存在すれば真です。
|
|
77
|
+
*/
|
|
32
78
|
exists(c: IStorage.ExistsContext): Promise<boolean>;
|
|
79
|
+
/**
|
|
80
|
+
* キーを削除します。
|
|
81
|
+
*
|
|
82
|
+
* 存在しないキーも成功扱いにします。
|
|
83
|
+
*
|
|
84
|
+
* @param c 削除コンテキストです。
|
|
85
|
+
*
|
|
86
|
+
* @returns 削除が完了したら解決します。
|
|
87
|
+
*/
|
|
33
88
|
delete(c: IStorage.DeleteContext): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* 接頭辞に一致するキーをすべて削除します。
|
|
91
|
+
*
|
|
92
|
+
* `SCAN` で列挙しながら削除します。
|
|
93
|
+
*
|
|
94
|
+
* @param c クリアコンテキストです。
|
|
95
|
+
*
|
|
96
|
+
* @returns 削除が完了したら解決します。
|
|
97
|
+
*/
|
|
34
98
|
clear(c: IStorage.ClearContext): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* 書き込み用ストリームを取得します。
|
|
101
|
+
*
|
|
102
|
+
* 一時キーに追記し、`close` 時に最終キーへ置き換えます。
|
|
103
|
+
*
|
|
104
|
+
* @param c 書き込みストリーム取得コンテキストです。
|
|
105
|
+
*
|
|
106
|
+
* @returns 書き込み用ストリームです。
|
|
107
|
+
*/
|
|
35
108
|
getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array<ArrayBuffer>>;
|
|
109
|
+
/**
|
|
110
|
+
* 読み取り用ストリームを取得します。
|
|
111
|
+
*
|
|
112
|
+
* 取得した値を単一チャンクとして送出します。
|
|
113
|
+
*
|
|
114
|
+
* @param c 読み取りストリーム取得コンテキストです。
|
|
115
|
+
*
|
|
116
|
+
* @returns 読み取り用ストリームです。
|
|
117
|
+
*/
|
|
36
118
|
getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
37
119
|
}
|
|
38
120
|
//#endregion
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/redis.ts"],"mappings":";;;;;;KAMY,sBAAsB
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/redis.ts"],"mappings":";;;;;;KAMY,sBAAsB;;;;WAIvB;;;;;cAsBU,iBAAiB;;;;;WAIpB;;;;EA6BhB,YAAmB,cAAc,UAAS;;;;;;MAe/B;;;;;;;;;;EAaX,KAAkB,IAAI,SAAS,cAAc;;;;;;;;EAmC7C,MAAa,IAAI,SAAS;;;;;;;;;;;EAe1B,MAAmB,GAAG,SAAS,cAAc,MAAM,WAAW,eAAe;;;;;;;;;;EAkB7E,KAAkB,GAAG,SAAS,cAAc,QAAQ,WAAW;;;;;;;;EAsB/D,OAAoB,GAAG,SAAS,gBAAgB;;;;;;;;;;EAkBhD,OAAoB,GAAG,SAAS,gBAAgB;;;;;;;;;;EAkBhD,MAAmB,GAAG,SAAS,eAAe;;;;;;;;;;EAgC9C,YAAmB,GAAG,SAAS,qBAAqB,eAAe,WAAW;;;;;;;;;;EAmF9E,YAAmB,GAAG,SAAS,qBAAqB,eAAe,WAAW"}
|
package/dist/index.js
CHANGED
|
@@ -61,9 +61,23 @@ var Redis = class {
|
|
|
61
61
|
_classPrivateFieldSet2(_options, this, redisOptions);
|
|
62
62
|
_classPrivateFieldSet2(_keyPrefix, this, keyPrefix);
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* ストレージがオープン済みかどうかです。
|
|
66
|
+
*
|
|
67
|
+
* @returns オープン済みなら真です。
|
|
68
|
+
*/
|
|
64
69
|
get isOpen() {
|
|
65
70
|
return _classPrivateFieldGet2(_con, this) !== null;
|
|
66
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* ストレージをオープンします。
|
|
74
|
+
*
|
|
75
|
+
* Bun の Redis クライアントを接続します。
|
|
76
|
+
*
|
|
77
|
+
* @param _c オープンコンテキストです。
|
|
78
|
+
*
|
|
79
|
+
* @returns オープンが完了したら解決します。
|
|
80
|
+
*/
|
|
67
81
|
async open(_c) {
|
|
68
82
|
if (typeof Bun === "undefined") throw new UnsupportedRuntimeError$1({
|
|
69
83
|
name: this.name,
|
|
@@ -80,15 +94,41 @@ var Redis = class {
|
|
|
80
94
|
}
|
|
81
95
|
_classPrivateFieldSet2(_con, this, { client });
|
|
82
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* ストレージをクローズします。
|
|
99
|
+
*
|
|
100
|
+
* クライアントを閉じて参照を外します。
|
|
101
|
+
*
|
|
102
|
+
* @param _c クローズコンテキストです。
|
|
103
|
+
*/
|
|
83
104
|
close(_c) {
|
|
84
105
|
_assertClassBrand(_Redis_brand, this, _getConnection).call(this).client.close();
|
|
85
106
|
_classPrivateFieldSet2(_con, this, null);
|
|
86
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* バイト列を書き込みます。
|
|
110
|
+
*
|
|
111
|
+
* 指定したキーで値を上書きします。
|
|
112
|
+
*
|
|
113
|
+
* @param c 書き込みコンテキストです。
|
|
114
|
+
* @param data 保存するバイト列です。
|
|
115
|
+
*
|
|
116
|
+
* @returns 書き込みが完了したら解決します。
|
|
117
|
+
*/
|
|
87
118
|
async write(c, data) {
|
|
88
119
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
89
120
|
c.signal.throwIfAborted();
|
|
90
121
|
await client.set(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key), data);
|
|
91
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* バイト列を読み取ります。
|
|
125
|
+
*
|
|
126
|
+
* 指定したキーの値を取得します。
|
|
127
|
+
*
|
|
128
|
+
* @param c 読み取りコンテキストです。
|
|
129
|
+
*
|
|
130
|
+
* @returns 読み取ったバイト列です。
|
|
131
|
+
*/
|
|
92
132
|
async read(c) {
|
|
93
133
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
94
134
|
c.signal.throwIfAborted();
|
|
@@ -96,16 +136,41 @@ var Redis = class {
|
|
|
96
136
|
if (data === null) throw new KeyNotFoundError$1({ key: c.key });
|
|
97
137
|
return new Uint8Array(data);
|
|
98
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* キーの存在を確認します。
|
|
141
|
+
*
|
|
142
|
+
* @param c 存在確認コンテキストです。
|
|
143
|
+
*
|
|
144
|
+
* @returns 存在すれば真です。
|
|
145
|
+
*/
|
|
99
146
|
async exists(c) {
|
|
100
147
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
101
148
|
c.signal.throwIfAborted();
|
|
102
149
|
return client.exists(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key));
|
|
103
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* キーを削除します。
|
|
153
|
+
*
|
|
154
|
+
* 存在しないキーも成功扱いにします。
|
|
155
|
+
*
|
|
156
|
+
* @param c 削除コンテキストです。
|
|
157
|
+
*
|
|
158
|
+
* @returns 削除が完了したら解決します。
|
|
159
|
+
*/
|
|
104
160
|
async delete(c) {
|
|
105
161
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
106
162
|
c.signal.throwIfAborted();
|
|
107
163
|
await client.del(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key));
|
|
108
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* 接頭辞に一致するキーをすべて削除します。
|
|
167
|
+
*
|
|
168
|
+
* `SCAN` で列挙しながら削除します。
|
|
169
|
+
*
|
|
170
|
+
* @param c クリアコンテキストです。
|
|
171
|
+
*
|
|
172
|
+
* @returns 削除が完了したら解決します。
|
|
173
|
+
*/
|
|
109
174
|
async clear(c) {
|
|
110
175
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
111
176
|
c.signal.throwIfAborted();
|
|
@@ -118,6 +183,15 @@ var Redis = class {
|
|
|
118
183
|
cursor = nextCursor;
|
|
119
184
|
} while (cursor !== "0");
|
|
120
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* 書き込み用ストリームを取得します。
|
|
188
|
+
*
|
|
189
|
+
* 一時キーに追記し、`close` 時に最終キーへ置き換えます。
|
|
190
|
+
*
|
|
191
|
+
* @param c 書き込みストリーム取得コンテキストです。
|
|
192
|
+
*
|
|
193
|
+
* @returns 書き込み用ストリームです。
|
|
194
|
+
*/
|
|
121
195
|
getWritable(c) {
|
|
122
196
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
123
197
|
c.signal.throwIfAborted();
|
|
@@ -177,6 +251,15 @@ var Redis = class {
|
|
|
177
251
|
}
|
|
178
252
|
});
|
|
179
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* 読み取り用ストリームを取得します。
|
|
256
|
+
*
|
|
257
|
+
* 取得した値を単一チャンクとして送出します。
|
|
258
|
+
*
|
|
259
|
+
* @param c 読み取りストリーム取得コンテキストです。
|
|
260
|
+
*
|
|
261
|
+
* @returns 読み取り用ストリームです。
|
|
262
|
+
*/
|
|
180
263
|
getReadable(c) {
|
|
181
264
|
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
182
265
|
c.signal.throwIfAborted();
|
|
@@ -196,12 +279,23 @@ var Redis = class {
|
|
|
196
279
|
} });
|
|
197
280
|
}
|
|
198
281
|
};
|
|
282
|
+
/**
|
|
283
|
+
* オープン済みかを確認します。
|
|
284
|
+
*
|
|
285
|
+
* 未オープン時は例外を投げます。
|
|
286
|
+
*
|
|
287
|
+
* @returns オープン済みの接続です。
|
|
288
|
+
*/
|
|
199
289
|
function _getConnection() {
|
|
200
290
|
if (_classPrivateFieldGet2(_con, this) === null) throw new TypeError("Redis is not open. Call open() first.");
|
|
201
291
|
return _classPrivateFieldGet2(_con, this);
|
|
202
292
|
}
|
|
203
293
|
/**
|
|
204
294
|
* unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。
|
|
295
|
+
*
|
|
296
|
+
* @param key 変換する論理キーです。
|
|
297
|
+
*
|
|
298
|
+
* @returns Redis 上の実キーです。
|
|
205
299
|
*/
|
|
206
300
|
function _toKey(key) {
|
|
207
301
|
return `${_classPrivateFieldGet2(_keyPrefix, this)}${key}`;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["UnsupportedRuntimeError","KeyNotFoundError"],"sources":["../src/redis.ts"],"sourcesContent":["import { type IStorage, KeyNotFoundError, UnsupportedRuntimeError } from \"@unikvs/core\";\nimport type { RedisClient, RedisOptions } from \"bun\";\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions)\n */\nexport type RedisStorageOptions = RedisOptions & {\n /**\n * すべてのキーの先頭に付与するプレフィックスです。\n *\n * キーを名前空間で分離し、`clear()` が削除する範囲を限定します。空文字を指定するとキーをそのまま使います。\n *\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions-keyprefix)\n */\n readonly keyPrefix?: string;\n};\n\n/**\n * ストレージの動作に必要な接続を保持する型定義です。\n */\ntype Connection = {\n /**\n * Bun の Redis クライアントです。\n */\n readonly client: RedisClient;\n};\n\n/**\n * Redis の `SCAN` 1 回あたりにヒントとして渡すキー数です。\n * 削除の往復回数と 1 回の `DEL` に渡す引数数を抑えるために使用します。\n */\nconst SCAN_COUNT = 1000;\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)\n */\nexport default class Redis implements IStorage {\n /**\n * Redis クライアントのインスタンスを保持します。\n *\n * ストレージがオープンされるまで null です。\n */\n #con: Connection | null;\n\n /**\n * 接続先の URL です。\n *\n * 未指定の場合はクライアントが環境変数 (`REDIS_URL`、`VALKEY_URL`) から解決します。\n */\n readonly #url: string | undefined;\n\n /**\n * `RedisClient` の初期化に使用する設定オブジェクトです。\n */\n readonly #options: RedisOptions;\n\n /**\n * すべてのキーの先頭に付与するプレフィックスです。\n */\n readonly #keyPrefix: string;\n\n public readonly name: string;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)\n */\n public constructor(url?: string, options: RedisStorageOptions = {}) {\n const { keyPrefix = \"unikvs:\", ...redisOptions } = options;\n\n this.name = \"Redis\";\n this.#con = null;\n this.#url = url;\n this.#options = redisOptions;\n this.#keyPrefix = keyPrefix;\n }\n\n public get isOpen(): boolean {\n return this.#con !== null;\n }\n\n // オープン済みかを確認します。\n // 未オープン時は `TypeError` を投げます。\n #getConnection(): Connection {\n if (this.#con === null) {\n throw new TypeError(\"Redis is not open. Call open() first.\");\n }\n\n return this.#con;\n }\n\n public async open(_c: IStorage.OpenContext): Promise<void> {\n // Bun 以外のランタイムで誤って使われた場合に、原因の分かるエラーを返します。\n if (typeof Bun === \"undefined\") {\n throw new UnsupportedRuntimeError({\n name: this.name,\n runtime: \"Bun\",\n });\n }\n\n // オープン済みの場合は古い接続を閉じ、開き直しでリークしないようにします。\n if (this.#con) {\n this.close(_c as unknown as IStorage.CloseContext);\n }\n\n const { RedisClient: RedisClientImpl } = await import(\"bun\");\n const client = new RedisClientImpl(this.#url, this.#options);\n\n try {\n // 最初のコマンドまで接続しないクライアントのため、open の時点で接続を確立します。\n await client.connect();\n } catch (ex) {\n client.close();\n throw ex;\n }\n\n this.#con = { client };\n }\n\n public close(_c: IStorage.CloseContext): void {\n this.#getConnection().client.close();\n this.#con = null;\n }\n\n /**\n * unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。\n */\n #toKey(key: string): string {\n return `${this.#keyPrefix}${key}`;\n }\n\n public async write(c: IStorage.WriteContext, data: Uint8Array<ArrayBuffer>): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // Bun のクライアントは ArrayBufferView をそのままバイナリーとして送信します。\n await client.set(this.#toKey(c.key), data);\n }\n\n public async read(c: IStorage.ReadContext): Promise<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const data = await client.getBuffer(this.#toKey(c.key));\n\n if (data === null) {\n throw new KeyNotFoundError({ key: c.key });\n }\n\n // Bun は Buffer を返すため、契約どおりの Uint8Array へ正規化します。\n return new Uint8Array(data);\n }\n\n public async exists(c: IStorage.ExistsContext): Promise<boolean> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // Bun のクライアントは EXISTS の整数を真偽値に変換して返します。\n return client.exists(this.#toKey(c.key));\n }\n\n public async delete(c: IStorage.DeleteContext): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // 存在しないキーの削除は 0 を返すだけでエラーになりません。\n await client.del(this.#toKey(c.key));\n }\n\n public async clear(c: IStorage.ClearContext): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // prefix が空文字の場合は MATCH \"*\" となり、選択中のデータベースの全キーが対象になります。\n const pattern = `${this.#keyPrefix}*`;\n\n let cursor = \"0\";\n\n do {\n c.signal.throwIfAborted();\n\n const [nextCursor, keys] = await client.scan(cursor, \"MATCH\", pattern, \"COUNT\", SCAN_COUNT);\n\n if (keys.length > 0) {\n await client.del(...keys);\n }\n\n cursor = nextCursor;\n } while (cursor !== \"0\");\n }\n\n public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const dest = this.#toKey(c.key);\n // 一意なサフィックスにより、同一キーへの並行書き込みでも一時キーが衝突しないようにします。\n const tmp = `${dest}.${Bun.randomUUIDv7()}.tmp`;\n\n // 中断と失敗のどちらからでも後始末が走るため、完了処理は一度だけ行います。\n let finished = false;\n const removeAbortListener = (): void => {\n c.signal.removeEventListener(\"abort\", onAbort);\n };\n const discardTemporary = async (): Promise<void> => {\n try {\n await client.del(tmp);\n } catch {\n // 後始末としての削除なので、失敗しても無視します。\n }\n };\n const finish = async (): Promise<void> => {\n if (finished) {\n return;\n }\n\n finished = true;\n removeAbortListener();\n await discardTemporary();\n };\n const onAbort = (): void => {\n void finish();\n };\n c.signal.addEventListener(\"abort\", onAbort, { once: true });\n\n return new WritableStream<Uint8Array<ArrayBuffer>>({\n async abort() {\n await finish();\n },\n async close() {\n c.signal.throwIfAborted();\n try {\n // 全チャンクの追記が完了した時点で初めて最終キーへ置き換えます (swap-on-close)。\n await client.rename(tmp, dest);\n } catch (ex) {\n await finish();\n throw ex;\n }\n // rename 後は一時キーが存在しないため、完了済みとして中断時の削除を行いません。\n finished = true;\n removeAbortListener();\n },\n async start() {\n try {\n c.signal.throwIfAborted();\n // 0 バイトの書き込みでも close 時の RENAME が成功するよう、空の文字列で一時キーを作ります。\n await client.set(tmp, \"\");\n } catch (ex) {\n await finish();\n throw ex;\n }\n },\n async write(chunk) {\n c.signal.throwIfAborted();\n try {\n await client.append(tmp, chunk);\n } catch (ex) {\n await finish();\n throw ex;\n }\n },\n });\n }\n\n public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const dest = this.#toKey(c.key);\n let sent = false;\n\n // Redis の GET は値を一括で返すため、取得したバイト列を単一のチャンクとして送出します。\n return new ReadableStream<Uint8Array<ArrayBuffer>>({\n async pull(controller) {\n if (sent) {\n controller.close();\n\n return;\n }\n\n sent = true;\n c.signal.throwIfAborted();\n\n const data = await client.getBuffer(dest);\n\n if (data === null) {\n throw new KeyNotFoundError({ key: c.key });\n }\n\n // Bun は Buffer を返すため、契約どおりの Uint8Array へ正規化します。\n controller.enqueue(new Uint8Array(data));\n controller.close();\n },\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,aAAa;;;;;;;;;AAKnB,IAAqB,QAArB,MAA+C;;;;CA8B7C,YAAmB,KAAc,UAA+B,CAAC,GAAG;;;;;;EAClE,MAAM,EAAE,YAAY,WAAW,GAAG,iBAAiB;EAEnD,KAAK,OAAO;EACZ,uBAAA,MAAA,MAAY,IAAA;EACZ,uBAAA,MAAA,MAAY,GAAA;EACZ,uBAAA,UAAA,MAAgB,YAAA;EAChB,uBAAA,YAAA,MAAkB,SAAA;CACpB;CAEA,IAAW,SAAkB;EAC3B,OAAA,uBAAA,MAAO,IAAA,MAAc;CACvB;CAYA,MAAa,KAAK,IAAyC;EAEzD,IAAI,OAAO,QAAQ,aACjB,MAAM,IAAIA,0BAAwB;GAChC,MAAM,KAAK;GACX,SAAS;EACX,CAAC;EAIH,IAAA,uBAAA,MAAI,IAAA,GACF,KAAK,MAAM,EAAsC;EAGnD,MAAM,EAAE,aAAa,oBAAoB,MAAM,OAAO;EACtD,MAAM,SAAS,IAAI,gBAAA,uBAAA,MAAgB,IAAA,GAAA,uBAAA,UAAW,IAAA,CAAa;EAE3D,IAAI;GAEF,MAAM,OAAO,QAAQ;EACvB,SAAS,IAAI;GACX,OAAO,MAAM;GACb,MAAM;EACR;EAEA,uBAAA,MAAA,MAAY,EAAE,OAAO,CAAA;CACvB;CAEA,MAAa,IAAiC;EAC5C,kBAAA,cAAA,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB,CAAC,CAAC,OAAO,MAAM;EACnC,uBAAA,MAAA,MAAY,IAAA;CACd;CASA,MAAa,MAAM,GAA0B,MAA8C;EACzF,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAGxB,MAAM,OAAO,IAAA,kBAAA,cAAI,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG,GAAG,IAAI;CAC3C;CAEA,MAAa,KAAK,GAA2D;EAC3E,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAExB,MAAM,OAAO,MAAM,OAAO,UAAA,kBAAA,cAAU,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG,CAAC;EAEtD,IAAI,SAAS,MACX,MAAM,IAAIC,mBAAiB,EAAE,KAAK,EAAE,IAAI,CAAC;EAI3C,OAAO,IAAI,WAAW,IAAI;CAC5B;CAEA,MAAa,OAAO,GAA6C;EAC/D,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAGxB,OAAO,OAAO,OAAA,kBAAA,cAAO,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG,CAAC;CACzC;CAEA,MAAa,OAAO,GAA0C;EAC5D,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAGxB,MAAM,OAAO,IAAA,kBAAA,cAAI,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG,CAAC;CACrC;CAEA,MAAa,MAAM,GAAyC;EAC1D,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAGxB,MAAM,UAAU,GAAA,uBAAA,YAAG,IAAA,EAAgB;EAEnC,IAAI,SAAS;EAEb,GAAG;GACD,EAAE,OAAO,eAAe;GAExB,MAAM,CAAC,YAAY,QAAQ,MAAM,OAAO,KAAK,QAAQ,SAAS,SAAS,SAAS,UAAU;GAE1F,IAAI,KAAK,SAAS,GAChB,MAAM,OAAO,IAAI,GAAG,IAAI;GAG1B,SAAS;EACX,SAAS,WAAW;CACtB;CAEA,YAAmB,GAAyE;EAC1F,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAExB,MAAM,OAAA,kBAAA,cAAO,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG;EAE9B,MAAM,MAAM,GAAG,KAAK,GAAG,IAAI,aAAa,EAAE;EAG1C,IAAI,WAAW;EACf,MAAM,4BAAkC;GACtC,EAAE,OAAO,oBAAoB,SAAS,OAAO;EAC/C;EACA,MAAM,mBAAmB,YAA2B;GAClD,IAAI;IACF,MAAM,OAAO,IAAI,GAAG;GACtB,QAAQ,CAER;EACF;EACA,MAAM,SAAS,YAA2B;GACxC,IAAI,UACF;GAGF,WAAW;GACX,oBAAoB;GACpB,MAAM,iBAAiB;EACzB;EACA,MAAM,gBAAsB;GAC1B,OAAY;EACd;EACA,EAAE,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAE1D,OAAO,IAAI,eAAwC;GACjD,MAAM,QAAQ;IACZ,MAAM,OAAO;GACf;GACA,MAAM,QAAQ;IACZ,EAAE,OAAO,eAAe;IACxB,IAAI;KAEF,MAAM,OAAO,OAAO,KAAK,IAAI;IAC/B,SAAS,IAAI;KACX,MAAM,OAAO;KACb,MAAM;IACR;IAEA,WAAW;IACX,oBAAoB;GACtB;GACA,MAAM,QAAQ;IACZ,IAAI;KACF,EAAE,OAAO,eAAe;KAExB,MAAM,OAAO,IAAI,KAAK,EAAE;IAC1B,SAAS,IAAI;KACX,MAAM,OAAO;KACb,MAAM;IACR;GACF;GACA,MAAM,MAAM,OAAO;IACjB,EAAE,OAAO,eAAe;IACxB,IAAI;KACF,MAAM,OAAO,OAAO,KAAK,KAAK;IAChC,SAAS,IAAI;KACX,MAAM,OAAO;KACb,MAAM;IACR;GACF;EACF,CAAC;CACH;CAEA,YAAmB,GAAyE;EAC1F,MAAM,EAAE,WAAA,kBAAA,cAAW,MAAA,cAAA,CAAA,CAAA,KAAA,IAAoB;EAEvC,EAAE,OAAO,eAAe;EAExB,MAAM,OAAA,kBAAA,cAAO,MAAA,MAAA,CAAA,CAAA,KAAA,MAAY,EAAE,GAAG;EAC9B,IAAI,OAAO;EAGX,OAAO,IAAI,eAAwC,EACjD,MAAM,KAAK,YAAY;GACrB,IAAI,MAAM;IACR,WAAW,MAAM;IAEjB;GACF;GAEA,OAAO;GACP,EAAE,OAAO,eAAe;GAExB,MAAM,OAAO,MAAM,OAAO,UAAU,IAAI;GAExC,IAAI,SAAS,MACX,MAAM,IAAIA,mBAAiB,EAAE,KAAK,EAAE,IAAI,CAAC;GAI3C,WAAW,QAAQ,IAAI,WAAW,IAAI,CAAC;GACvC,WAAW,MAAM;EACnB,EACF,CAAC;CACH;AACF;AA3NE,SAAA,iBAA6B;CAC3B,IAAA,uBAAA,MAAI,IAAA,MAAc,MAChB,MAAM,IAAI,UAAU,uCAAuC;CAG7D,OAAA,uBAAA,MAAO,IAAA;AACT;;;;AAsCA,SAAA,OAAO,KAAqB;CAC1B,OAAO,GAAA,uBAAA,YAAG,IAAA,IAAkB;AAC9B"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["UnsupportedRuntimeError","KeyNotFoundError"],"sources":["../src/redis.ts"],"sourcesContent":["import { type IStorage, KeyNotFoundError, UnsupportedRuntimeError } from \"@unikvs/core\";\nimport type { RedisClient, RedisOptions } from \"bun\";\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions)\n */\nexport type RedisStorageOptions = RedisOptions & {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions-keyprefix)\n */\n readonly keyPrefix?: string;\n};\n\n/**\n * ストレージの動作に必要な接続を保持する型定義です。\n */\ntype Connection = {\n /**\n * Bun の Redis クライアントです。\n */\n readonly client: RedisClient;\n};\n\n/**\n * Redis の `SCAN` 1 回あたりにヒントとして渡すキー数です。\n * 削除の往復回数と 1 回の `DEL` に渡す引数数を抑えるために使用します。\n */\nconst SCAN_COUNT = 1000;\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)\n */\nexport default class Redis implements IStorage {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)\n */\n public readonly name: string;\n\n /**\n * Redis クライアントのインスタンスを保持します。\n *\n * ストレージがオープンされるまで null です。\n */\n #con: Connection | null;\n\n /**\n * 接続先の URL です。\n *\n * 未指定の場合はクライアントが環境変数 (`REDIS_URL`、`VALKEY_URL`) から解決します。\n */\n readonly #url: string | undefined;\n\n /**\n * `RedisClient` の初期化に使用する設定オブジェクトです。\n */\n readonly #options: RedisOptions;\n\n /**\n * すべてのキーの先頭に付与するプレフィックスです。\n */\n readonly #keyPrefix: string;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)\n */\n public constructor(url?: string, options: RedisStorageOptions = {}) {\n const { keyPrefix = \"unikvs:\", ...redisOptions } = options;\n\n this.name = \"Redis\";\n this.#con = null;\n this.#url = url;\n this.#options = redisOptions;\n this.#keyPrefix = keyPrefix;\n }\n\n /**\n * ストレージがオープン済みかどうかです。\n *\n * @returns オープン済みなら真です。\n */\n public get isOpen(): boolean {\n return this.#con !== null;\n }\n\n /**\n * ストレージをオープンします。\n *\n * Bun の Redis クライアントを接続します。\n *\n * @param _c オープンコンテキストです。\n *\n * @returns オープンが完了したら解決します。\n */\n public async open(_c: IStorage.OpenContext): Promise<void> {\n // Bun 以外のランタイムで誤って使われた場合に、原因の分かるエラーを返します。\n if (typeof Bun === \"undefined\") {\n throw new UnsupportedRuntimeError({\n name: this.name,\n runtime: \"Bun\",\n });\n }\n\n // オープン済みの場合は古い接続を閉じ、開き直しでリークしないようにします。\n if (this.#con) {\n this.close(_c as unknown as IStorage.CloseContext);\n }\n\n const { RedisClient: RedisClientImpl } = await import(\"bun\");\n const client = new RedisClientImpl(this.#url, this.#options);\n\n try {\n // 最初のコマンドまで接続しないクライアントのため、open の時点で接続を確立します。\n await client.connect();\n } catch (ex) {\n client.close();\n throw ex;\n }\n\n this.#con = { client };\n }\n\n /**\n * ストレージをクローズします。\n *\n * クライアントを閉じて参照を外します。\n *\n * @param _c クローズコンテキストです。\n */\n public close(_c: IStorage.CloseContext): void {\n this.#getConnection().client.close();\n this.#con = null;\n }\n\n /**\n * バイト列を書き込みます。\n *\n * 指定したキーで値を上書きします。\n *\n * @param c 書き込みコンテキストです。\n * @param data 保存するバイト列です。\n *\n * @returns 書き込みが完了したら解決します。\n */\n public async write(c: IStorage.WriteContext, data: Uint8Array<ArrayBuffer>): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // Bun のクライアントは ArrayBufferView をそのままバイナリーとして送信します。\n await client.set(this.#toKey(c.key), data);\n }\n\n /**\n * バイト列を読み取ります。\n *\n * 指定したキーの値を取得します。\n *\n * @param c 読み取りコンテキストです。\n *\n * @returns 読み取ったバイト列です。\n */\n public async read(c: IStorage.ReadContext): Promise<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const data = await client.getBuffer(this.#toKey(c.key));\n\n if (data === null) {\n throw new KeyNotFoundError({ key: c.key });\n }\n\n // Bun は Buffer を返すため、契約どおりの Uint8Array へ正規化します。\n return new Uint8Array(data);\n }\n\n /**\n * キーの存在を確認します。\n *\n * @param c 存在確認コンテキストです。\n *\n * @returns 存在すれば真です。\n */\n public async exists(c: IStorage.ExistsContext): Promise<boolean> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // Bun のクライアントは EXISTS の整数を真偽値に変換して返します。\n return client.exists(this.#toKey(c.key));\n }\n\n /**\n * キーを削除します。\n *\n * 存在しないキーも成功扱いにします。\n *\n * @param c 削除コンテキストです。\n *\n * @returns 削除が完了したら解決します。\n */\n public async delete(c: IStorage.DeleteContext): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // 存在しないキーの削除は 0 を返すだけでエラーになりません。\n await client.del(this.#toKey(c.key));\n }\n\n /**\n * 接頭辞に一致するキーをすべて削除します。\n *\n * `SCAN` で列挙しながら削除します。\n *\n * @param c クリアコンテキストです。\n *\n * @returns 削除が完了したら解決します。\n */\n public async clear(c: IStorage.ClearContext): Promise<void> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n // prefix が空文字の場合は MATCH \"*\" となり、選択中のデータベースの全キーが対象になります。\n const pattern = `${this.#keyPrefix}*`;\n\n let cursor = \"0\";\n\n do {\n c.signal.throwIfAborted();\n\n const [nextCursor, keys] = await client.scan(cursor, \"MATCH\", pattern, \"COUNT\", SCAN_COUNT);\n\n if (keys.length > 0) {\n await client.del(...keys);\n }\n\n cursor = nextCursor;\n } while (cursor !== \"0\");\n }\n\n /**\n * 書き込み用ストリームを取得します。\n *\n * 一時キーに追記し、`close` 時に最終キーへ置き換えます。\n *\n * @param c 書き込みストリーム取得コンテキストです。\n *\n * @returns 書き込み用ストリームです。\n */\n public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const dest = this.#toKey(c.key);\n // 一意なサフィックスにより、同一キーへの並行書き込みでも一時キーが衝突しないようにします。\n const tmp = `${dest}.${Bun.randomUUIDv7()}.tmp`;\n\n // 中断と失敗のどちらからでも後始末が走るため、完了処理は一度だけ行います。\n let finished = false;\n const removeAbortListener = (): void => {\n c.signal.removeEventListener(\"abort\", onAbort);\n };\n const discardTemporary = async (): Promise<void> => {\n try {\n await client.del(tmp);\n } catch {\n // 後始末としての削除なので、失敗しても無視します。\n }\n };\n const finish = async (): Promise<void> => {\n if (finished) {\n return;\n }\n\n finished = true;\n removeAbortListener();\n await discardTemporary();\n };\n const onAbort = (): void => {\n void finish();\n };\n c.signal.addEventListener(\"abort\", onAbort, { once: true });\n\n return new WritableStream<Uint8Array<ArrayBuffer>>({\n async abort() {\n await finish();\n },\n async close() {\n c.signal.throwIfAborted();\n try {\n // 全チャンクの追記が完了した時点で初めて最終キーへ置き換えます (swap-on-close)。\n await client.rename(tmp, dest);\n } catch (ex) {\n await finish();\n throw ex;\n }\n // rename 後は一時キーが存在しないため、完了済みとして中断時の削除を行いません。\n finished = true;\n removeAbortListener();\n },\n async start() {\n try {\n c.signal.throwIfAborted();\n // 0 バイトの書き込みでも close 時の RENAME が成功するよう、空の文字列で一時キーを作ります。\n await client.set(tmp, \"\");\n } catch (ex) {\n await finish();\n throw ex;\n }\n },\n async write(chunk) {\n c.signal.throwIfAborted();\n try {\n await client.append(tmp, chunk);\n } catch (ex) {\n await finish();\n throw ex;\n }\n },\n });\n }\n\n /**\n * 読み取り用ストリームを取得します。\n *\n * 取得した値を単一チャンクとして送出します。\n *\n * @param c 読み取りストリーム取得コンテキストです。\n *\n * @returns 読み取り用ストリームです。\n */\n public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array<ArrayBuffer>> {\n const { client } = this.#getConnection();\n\n c.signal.throwIfAborted();\n\n const dest = this.#toKey(c.key);\n let sent = false;\n\n // Redis の GET は値を一括で返すため、取得したバイト列を単一のチャンクとして送出します。\n return new ReadableStream<Uint8Array<ArrayBuffer>>({\n async pull(controller) {\n if (sent) {\n controller.close();\n\n return;\n }\n\n sent = true;\n c.signal.throwIfAborted();\n\n const data = await client.getBuffer(dest);\n\n if (data === null) {\n throw new KeyNotFoundError({ key: c.key });\n }\n\n // Bun は Buffer を返すため、契約どおりの Uint8Array へ正規化します。\n controller.enqueue(new Uint8Array(data));\n controller.close();\n },\n });\n }\n\n /**\n * オープン済みかを確認します。\n *\n * 未オープン時は例外を投げます。\n *\n * @returns オープン済みの接続です。\n */\n #getConnection(): Connection {\n if (this.#con === null) {\n throw new TypeError(\"Redis is not open. Call open() first.\");\n }\n\n return this.#con;\n }\n\n /**\n * unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。\n *\n * @param key 変換する論理キーです。\n *\n * @returns Redis 上の実キーです。\n */\n #toKey(key: string): string {\n return `${this.#keyPrefix}${key}`;\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unikvs/redis.bun",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"description": "Storage plugin that stores byte arrays in Redis on Bun.",
|
|
5
5
|
"homepage": "https://tai-kun.github.io/unikvs/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@unikvs/core": "0.0.
|
|
27
|
+
"@unikvs/core": "0.0.14"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/bun": "1.4.3",
|
package/src/redis.ts
CHANGED
|
@@ -6,10 +6,6 @@ import type { RedisClient, RedisOptions } from "bun";
|
|
|
6
6
|
*/
|
|
7
7
|
export type RedisStorageOptions = RedisOptions & {
|
|
8
8
|
/**
|
|
9
|
-
* すべてのキーの先頭に付与するプレフィックスです。
|
|
10
|
-
*
|
|
11
|
-
* キーを名前空間で分離し、`clear()` が削除する範囲を限定します。空文字を指定するとキーをそのまま使います。
|
|
12
|
-
*
|
|
13
9
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redisstorageoptions-keyprefix)
|
|
14
10
|
*/
|
|
15
11
|
readonly keyPrefix?: string;
|
|
@@ -35,6 +31,11 @@ const SCAN_COUNT = 1000;
|
|
|
35
31
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)
|
|
36
32
|
*/
|
|
37
33
|
export default class Redis implements IStorage {
|
|
34
|
+
/**
|
|
35
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)
|
|
36
|
+
*/
|
|
37
|
+
public readonly name: string;
|
|
38
|
+
|
|
38
39
|
/**
|
|
39
40
|
* Redis クライアントのインスタンスを保持します。
|
|
40
41
|
*
|
|
@@ -59,8 +60,6 @@ export default class Redis implements IStorage {
|
|
|
59
60
|
*/
|
|
60
61
|
readonly #keyPrefix: string;
|
|
61
62
|
|
|
62
|
-
public readonly name: string;
|
|
63
|
-
|
|
64
63
|
/**
|
|
65
64
|
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-bun#redis)
|
|
66
65
|
*/
|
|
@@ -74,20 +73,24 @@ export default class Redis implements IStorage {
|
|
|
74
73
|
this.#keyPrefix = keyPrefix;
|
|
75
74
|
}
|
|
76
75
|
|
|
76
|
+
/**
|
|
77
|
+
* ストレージがオープン済みかどうかです。
|
|
78
|
+
*
|
|
79
|
+
* @returns オープン済みなら真です。
|
|
80
|
+
*/
|
|
77
81
|
public get isOpen(): boolean {
|
|
78
82
|
return this.#con !== null;
|
|
79
83
|
}
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
/**
|
|
86
|
+
* ストレージをオープンします。
|
|
87
|
+
*
|
|
88
|
+
* Bun の Redis クライアントを接続します。
|
|
89
|
+
*
|
|
90
|
+
* @param _c オープンコンテキストです。
|
|
91
|
+
*
|
|
92
|
+
* @returns オープンが完了したら解決します。
|
|
93
|
+
*/
|
|
91
94
|
public async open(_c: IStorage.OpenContext): Promise<void> {
|
|
92
95
|
// Bun 以外のランタイムで誤って使われた場合に、原因の分かるエラーを返します。
|
|
93
96
|
if (typeof Bun === "undefined") {
|
|
@@ -116,18 +119,28 @@ export default class Redis implements IStorage {
|
|
|
116
119
|
this.#con = { client };
|
|
117
120
|
}
|
|
118
121
|
|
|
122
|
+
/**
|
|
123
|
+
* ストレージをクローズします。
|
|
124
|
+
*
|
|
125
|
+
* クライアントを閉じて参照を外します。
|
|
126
|
+
*
|
|
127
|
+
* @param _c クローズコンテキストです。
|
|
128
|
+
*/
|
|
119
129
|
public close(_c: IStorage.CloseContext): void {
|
|
120
130
|
this.#getConnection().client.close();
|
|
121
131
|
this.#con = null;
|
|
122
132
|
}
|
|
123
133
|
|
|
124
134
|
/**
|
|
125
|
-
*
|
|
135
|
+
* バイト列を書き込みます。
|
|
136
|
+
*
|
|
137
|
+
* 指定したキーで値を上書きします。
|
|
138
|
+
*
|
|
139
|
+
* @param c 書き込みコンテキストです。
|
|
140
|
+
* @param data 保存するバイト列です。
|
|
141
|
+
*
|
|
142
|
+
* @returns 書き込みが完了したら解決します。
|
|
126
143
|
*/
|
|
127
|
-
#toKey(key: string): string {
|
|
128
|
-
return `${this.#keyPrefix}${key}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
144
|
public async write(c: IStorage.WriteContext, data: Uint8Array<ArrayBuffer>): Promise<void> {
|
|
132
145
|
const { client } = this.#getConnection();
|
|
133
146
|
|
|
@@ -137,6 +150,15 @@ export default class Redis implements IStorage {
|
|
|
137
150
|
await client.set(this.#toKey(c.key), data);
|
|
138
151
|
}
|
|
139
152
|
|
|
153
|
+
/**
|
|
154
|
+
* バイト列を読み取ります。
|
|
155
|
+
*
|
|
156
|
+
* 指定したキーの値を取得します。
|
|
157
|
+
*
|
|
158
|
+
* @param c 読み取りコンテキストです。
|
|
159
|
+
*
|
|
160
|
+
* @returns 読み取ったバイト列です。
|
|
161
|
+
*/
|
|
140
162
|
public async read(c: IStorage.ReadContext): Promise<Uint8Array<ArrayBuffer>> {
|
|
141
163
|
const { client } = this.#getConnection();
|
|
142
164
|
|
|
@@ -152,6 +174,13 @@ export default class Redis implements IStorage {
|
|
|
152
174
|
return new Uint8Array(data);
|
|
153
175
|
}
|
|
154
176
|
|
|
177
|
+
/**
|
|
178
|
+
* キーの存在を確認します。
|
|
179
|
+
*
|
|
180
|
+
* @param c 存在確認コンテキストです。
|
|
181
|
+
*
|
|
182
|
+
* @returns 存在すれば真です。
|
|
183
|
+
*/
|
|
155
184
|
public async exists(c: IStorage.ExistsContext): Promise<boolean> {
|
|
156
185
|
const { client } = this.#getConnection();
|
|
157
186
|
|
|
@@ -161,6 +190,15 @@ export default class Redis implements IStorage {
|
|
|
161
190
|
return client.exists(this.#toKey(c.key));
|
|
162
191
|
}
|
|
163
192
|
|
|
193
|
+
/**
|
|
194
|
+
* キーを削除します。
|
|
195
|
+
*
|
|
196
|
+
* 存在しないキーも成功扱いにします。
|
|
197
|
+
*
|
|
198
|
+
* @param c 削除コンテキストです。
|
|
199
|
+
*
|
|
200
|
+
* @returns 削除が完了したら解決します。
|
|
201
|
+
*/
|
|
164
202
|
public async delete(c: IStorage.DeleteContext): Promise<void> {
|
|
165
203
|
const { client } = this.#getConnection();
|
|
166
204
|
|
|
@@ -170,6 +208,15 @@ export default class Redis implements IStorage {
|
|
|
170
208
|
await client.del(this.#toKey(c.key));
|
|
171
209
|
}
|
|
172
210
|
|
|
211
|
+
/**
|
|
212
|
+
* 接頭辞に一致するキーをすべて削除します。
|
|
213
|
+
*
|
|
214
|
+
* `SCAN` で列挙しながら削除します。
|
|
215
|
+
*
|
|
216
|
+
* @param c クリアコンテキストです。
|
|
217
|
+
*
|
|
218
|
+
* @returns 削除が完了したら解決します。
|
|
219
|
+
*/
|
|
173
220
|
public async clear(c: IStorage.ClearContext): Promise<void> {
|
|
174
221
|
const { client } = this.#getConnection();
|
|
175
222
|
|
|
@@ -193,6 +240,15 @@ export default class Redis implements IStorage {
|
|
|
193
240
|
} while (cursor !== "0");
|
|
194
241
|
}
|
|
195
242
|
|
|
243
|
+
/**
|
|
244
|
+
* 書き込み用ストリームを取得します。
|
|
245
|
+
*
|
|
246
|
+
* 一時キーに追記し、`close` 時に最終キーへ置き換えます。
|
|
247
|
+
*
|
|
248
|
+
* @param c 書き込みストリーム取得コンテキストです。
|
|
249
|
+
*
|
|
250
|
+
* @returns 書き込み用ストリームです。
|
|
251
|
+
*/
|
|
196
252
|
public getWritable(c: IStorage.GetWritableContext): WritableStream<Uint8Array<ArrayBuffer>> {
|
|
197
253
|
const { client } = this.#getConnection();
|
|
198
254
|
|
|
@@ -267,6 +323,15 @@ export default class Redis implements IStorage {
|
|
|
267
323
|
});
|
|
268
324
|
}
|
|
269
325
|
|
|
326
|
+
/**
|
|
327
|
+
* 読み取り用ストリームを取得します。
|
|
328
|
+
*
|
|
329
|
+
* 取得した値を単一チャンクとして送出します。
|
|
330
|
+
*
|
|
331
|
+
* @param c 読み取りストリーム取得コンテキストです。
|
|
332
|
+
*
|
|
333
|
+
* @returns 読み取り用ストリームです。
|
|
334
|
+
*/
|
|
270
335
|
public getReadable(c: IStorage.GetReadableContext): ReadableStream<Uint8Array<ArrayBuffer>> {
|
|
271
336
|
const { client } = this.#getConnection();
|
|
272
337
|
|
|
@@ -299,4 +364,30 @@ export default class Redis implements IStorage {
|
|
|
299
364
|
},
|
|
300
365
|
});
|
|
301
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* オープン済みかを確認します。
|
|
370
|
+
*
|
|
371
|
+
* 未オープン時は例外を投げます。
|
|
372
|
+
*
|
|
373
|
+
* @returns オープン済みの接続です。
|
|
374
|
+
*/
|
|
375
|
+
#getConnection(): Connection {
|
|
376
|
+
if (this.#con === null) {
|
|
377
|
+
throw new TypeError("Redis is not open. Call open() first.");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return this.#con;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。
|
|
385
|
+
*
|
|
386
|
+
* @param key 変換する論理キーです。
|
|
387
|
+
*
|
|
388
|
+
* @returns Redis 上の実キーです。
|
|
389
|
+
*/
|
|
390
|
+
#toKey(key: string): string {
|
|
391
|
+
return `${this.#keyPrefix}${key}`;
|
|
392
|
+
}
|
|
302
393
|
}
|