@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 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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/redis.ts"],"mappings":";;;;;;KAMY,sBAAsB;;;;;;;;WAQvB;;;;;cAsBU,iBAAiB;;WAyBpB;;;;EAKhB,YAAmB,cAAc,UAAS;MAU/B;EAcX,KAAkB,IAAI,SAAS,cAAc;EA4B7C,MAAa,IAAI,SAAS;EAY1B,MAAmB,GAAG,SAAS,cAAc,MAAM,WAAW,eAAe;EAS7E,KAAkB,GAAG,SAAS,cAAc,QAAQ,WAAW;EAe/D,OAAoB,GAAG,SAAS,gBAAgB;EAShD,OAAoB,GAAG,SAAS,gBAAgB;EAShD,MAAmB,GAAG,SAAS,eAAe;EAuB9C,YAAmB,GAAG,SAAS,qBAAqB,eAAe,WAAW;EA0E9E,YAAmB,GAAG,SAAS,qBAAqB,eAAe,WAAW"}
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":"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"}
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.13",
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.13"
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
- // 未オープン時は `TypeError` を投げます。
83
- #getConnection(): Connection {
84
- if (this.#con === null) {
85
- throw new TypeError("Redis is not open. Call open() first.");
86
- }
87
-
88
- return this.#con;
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
- * unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。
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
  }