@unikvs/redis.node 0.0.0 → 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/LICENSE +21 -0
- package/README.md +1 -43
- package/dist/index.d.ts +222 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +430 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -8
- package/src/errors.ts +161 -0
- package/src/index.ts +19 -0
- package/src/redis.ts +544 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { ErrorBase, InvalidUsageErrorBase, KeyNotFoundError, KeyNotFoundError as KeyNotFoundError$1, setErrorMessage } from "@unikvs/core";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { Redis as Redis$1 } from "ioredis";
|
|
4
|
+
//#region ../src/errors.ts
|
|
5
|
+
var _ClusterNotSupportedError;
|
|
6
|
+
var _InvalidCloseTimeoutError;
|
|
7
|
+
var _ConnectTimeoutError;
|
|
8
|
+
var _CloseTimeoutError;
|
|
9
|
+
/**
|
|
10
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-cluster-not-supported)
|
|
11
|
+
*/
|
|
12
|
+
var ClusterNotSupportedError = class extends InvalidUsageErrorBase {
|
|
13
|
+
/**
|
|
14
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-cluster-not-supported)
|
|
15
|
+
*/
|
|
16
|
+
constructor(options) {
|
|
17
|
+
super("Redis storage v1 supports standalone mode only: the `cluster` option is not supported.", options);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
_ClusterNotSupportedError = ClusterNotSupportedError;
|
|
21
|
+
_ClusterNotSupportedError.prototype.name = "RedisClusterNotSupportedError";
|
|
22
|
+
setErrorMessage(ClusterNotSupportedError, "Redis ストレージ v1 は standalone モードのみに対応します。`cluster` オプションはサポートされていません。", "ja");
|
|
23
|
+
/**
|
|
24
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)
|
|
25
|
+
*/
|
|
26
|
+
var InvalidCloseTimeoutError = class extends ErrorBase {
|
|
27
|
+
/**
|
|
28
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)
|
|
29
|
+
*/
|
|
30
|
+
constructor(args) {
|
|
31
|
+
const { actual, ...options } = args;
|
|
32
|
+
super({ actual }, ({ actual: messageActual }) => `\`closeTimeout\` must be a finite positive number (ms), but got ${String(messageActual)}.`, options);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
_InvalidCloseTimeoutError = InvalidCloseTimeoutError;
|
|
36
|
+
_InvalidCloseTimeoutError.prototype.name = "RedisInvalidCloseTimeoutError";
|
|
37
|
+
setErrorMessage(InvalidCloseTimeoutError, ({ actual }) => `\`closeTimeout\` は有限の正数 (ミリ秒) でなければなりません。受け取った値: ${String(actual)}`, "ja");
|
|
38
|
+
/**
|
|
39
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)
|
|
40
|
+
*/
|
|
41
|
+
var ConnectTimeoutError = class extends ErrorBase {
|
|
42
|
+
/**
|
|
43
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)
|
|
44
|
+
*/
|
|
45
|
+
constructor(args) {
|
|
46
|
+
const { timeoutMs, ...options } = args;
|
|
47
|
+
super({ timeoutMs }, ({ timeoutMs: messageTimeoutMs }) => `Timed out connecting to Redis after ${messageTimeoutMs}ms`, options);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
_ConnectTimeoutError = ConnectTimeoutError;
|
|
51
|
+
_ConnectTimeoutError.prototype.name = "RedisConnectTimeoutError";
|
|
52
|
+
setErrorMessage(ConnectTimeoutError, ({ timeoutMs }) => `Redis への接続が ${timeoutMs}ms でタイムアウトしました`, "ja");
|
|
53
|
+
/**
|
|
54
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)
|
|
55
|
+
*/
|
|
56
|
+
var CloseTimeoutError = class extends ErrorBase {
|
|
57
|
+
/**
|
|
58
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)
|
|
59
|
+
*/
|
|
60
|
+
constructor(args) {
|
|
61
|
+
const { timeoutMs, ...options } = args;
|
|
62
|
+
super({ timeoutMs }, ({ timeoutMs: messageTimeoutMs }) => `Timed out closing Redis after ${messageTimeoutMs}ms`, options);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
_CloseTimeoutError = CloseTimeoutError;
|
|
66
|
+
_CloseTimeoutError.prototype.name = "RedisCloseTimeoutError";
|
|
67
|
+
setErrorMessage(CloseTimeoutError, ({ timeoutMs }) => `Redis からの切断処理が ${timeoutMs}ms でタイムアウトしました`, "ja");
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/checkPrivateRedeclaration.js
|
|
70
|
+
function _checkPrivateRedeclaration(e, t) {
|
|
71
|
+
if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateMethodInitSpec.js
|
|
75
|
+
function _classPrivateMethodInitSpec(e, a) {
|
|
76
|
+
_checkPrivateRedeclaration(e, a), a.add(e);
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldInitSpec.js
|
|
80
|
+
function _classPrivateFieldInitSpec(e, t, a) {
|
|
81
|
+
_checkPrivateRedeclaration(e, t), t.set(e, a);
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/assertClassBrand.js
|
|
85
|
+
function _assertClassBrand(e, t, n) {
|
|
86
|
+
if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
|
|
87
|
+
throw new TypeError("Private element is not present on this object");
|
|
88
|
+
}
|
|
89
|
+
//#endregion
|
|
90
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldSet2.js
|
|
91
|
+
function _classPrivateFieldSet2(s, a, r) {
|
|
92
|
+
return s.set(_assertClassBrand(s, a), r), r;
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
//#region \0@oxc-project+runtime@0.154.0/helpers/esm/classPrivateFieldGet2.js
|
|
96
|
+
function _classPrivateFieldGet2(s, a) {
|
|
97
|
+
return s.get(_assertClassBrand(s, a));
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region ../src/redis.ts
|
|
101
|
+
/**
|
|
102
|
+
* `SCAN` 1 回あたりにヒントとして渡すキー数です。
|
|
103
|
+
*
|
|
104
|
+
* 削除の往復回数と 1 回の `DEL` に渡す引数数を抑えるために使用します。
|
|
105
|
+
*/
|
|
106
|
+
const SCAN_COUNT = 1e3;
|
|
107
|
+
/**
|
|
108
|
+
* 接続確立の既定タイムアウト (ミリ秒) です。
|
|
109
|
+
*/
|
|
110
|
+
const DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
111
|
+
/**
|
|
112
|
+
* 切断処理の既定タイムアウト (ミリ秒) です。
|
|
113
|
+
*/
|
|
114
|
+
const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
|
|
115
|
+
var _con = /* @__PURE__ */ new WeakMap();
|
|
116
|
+
var _url = /* @__PURE__ */ new WeakMap();
|
|
117
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
118
|
+
var _closeTimeout = /* @__PURE__ */ new WeakMap();
|
|
119
|
+
var _keyPrefix = /* @__PURE__ */ new WeakMap();
|
|
120
|
+
var _Redis_brand = /* @__PURE__ */ new WeakSet();
|
|
121
|
+
/**
|
|
122
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redis)
|
|
123
|
+
*/
|
|
124
|
+
var Redis = class {
|
|
125
|
+
/**
|
|
126
|
+
* [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redis)
|
|
127
|
+
*/
|
|
128
|
+
constructor(url, options = {}) {
|
|
129
|
+
_classPrivateMethodInitSpec(this, _Redis_brand);
|
|
130
|
+
_classPrivateFieldInitSpec(this, _con, void 0);
|
|
131
|
+
_classPrivateFieldInitSpec(this, _url, void 0);
|
|
132
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
133
|
+
_classPrivateFieldInitSpec(this, _closeTimeout, void 0);
|
|
134
|
+
_classPrivateFieldInitSpec(this, _keyPrefix, void 0);
|
|
135
|
+
const { cluster, keyPrefix = "unikvs:", closeTimeout = DEFAULT_CLOSE_TIMEOUT_MS, ...redisOptions } = options;
|
|
136
|
+
if (cluster !== void 0) throw new ClusterNotSupportedError();
|
|
137
|
+
if (typeof closeTimeout !== "number" || !Number.isFinite(closeTimeout) || closeTimeout <= 0) throw new InvalidCloseTimeoutError({ actual: closeTimeout });
|
|
138
|
+
this.name = "Redis";
|
|
139
|
+
_classPrivateFieldSet2(_con, this, null);
|
|
140
|
+
_classPrivateFieldSet2(_url, this, url);
|
|
141
|
+
_classPrivateFieldSet2(_options, this, redisOptions);
|
|
142
|
+
_classPrivateFieldSet2(_closeTimeout, this, closeTimeout);
|
|
143
|
+
_classPrivateFieldSet2(_keyPrefix, this, keyPrefix);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* ストレージがオープン済みかどうかです。
|
|
147
|
+
*
|
|
148
|
+
* @returns オープン済みなら真です。
|
|
149
|
+
*/
|
|
150
|
+
get isOpen() {
|
|
151
|
+
return _classPrivateFieldGet2(_con, this) !== null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* ストレージをオープンします。
|
|
155
|
+
*
|
|
156
|
+
* `ioredis` クライアントを作成して接続します。
|
|
157
|
+
*
|
|
158
|
+
* @param _c オープンコンテキストです。
|
|
159
|
+
*
|
|
160
|
+
* @returns オープンが完了したら解決します。
|
|
161
|
+
*/
|
|
162
|
+
async open(_c) {
|
|
163
|
+
if (_classPrivateFieldGet2(_con, this)) await this.close(_c);
|
|
164
|
+
const explicitUrl = _classPrivateFieldGet2(_url, this) === void 0 || _classPrivateFieldGet2(_url, this) === "" ? void 0 : _classPrivateFieldGet2(_url, this);
|
|
165
|
+
const redisUrl = process.env["REDIS_URL"] === void 0 || process.env["REDIS_URL"] === "" ? void 0 : process.env["REDIS_URL"];
|
|
166
|
+
const valkeyUrl = process.env["VALKEY_URL"] === void 0 || process.env["VALKEY_URL"] === "" ? void 0 : process.env["VALKEY_URL"];
|
|
167
|
+
const url = explicitUrl ?? redisUrl ?? valkeyUrl;
|
|
168
|
+
const connectTimeout = _classPrivateFieldGet2(_options, this).connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
169
|
+
const options = {
|
|
170
|
+
..._classPrivateFieldGet2(_options, this),
|
|
171
|
+
lazyConnect: true,
|
|
172
|
+
connectTimeout
|
|
173
|
+
};
|
|
174
|
+
const raw = url === void 0 ? new Redis$1(options) : new Redis$1(url, options);
|
|
175
|
+
raw.on("error", () => {});
|
|
176
|
+
let timer;
|
|
177
|
+
try {
|
|
178
|
+
await Promise.race([raw.connect(), new Promise((_, reject) => {
|
|
179
|
+
timer = setTimeout(() => {
|
|
180
|
+
reject(new ConnectTimeoutError({ timeoutMs: connectTimeout }));
|
|
181
|
+
}, connectTimeout);
|
|
182
|
+
})]);
|
|
183
|
+
} catch (ex) {
|
|
184
|
+
raw.disconnect();
|
|
185
|
+
throw ex;
|
|
186
|
+
} finally {
|
|
187
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
188
|
+
}
|
|
189
|
+
_classPrivateFieldSet2(_con, this, { client: raw });
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* ストレージをクローズします。
|
|
193
|
+
*
|
|
194
|
+
* `quit` で丁寧に閉じ、失敗時は `disconnect` で掃除します。
|
|
195
|
+
*
|
|
196
|
+
* @param _c クローズコンテキストです。
|
|
197
|
+
*
|
|
198
|
+
* @returns クローズが完了したら解決します。
|
|
199
|
+
*/
|
|
200
|
+
async close(_c) {
|
|
201
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
202
|
+
_classPrivateFieldSet2(_con, this, null);
|
|
203
|
+
const closeTimeout = _classPrivateFieldGet2(_closeTimeout, this);
|
|
204
|
+
let timer;
|
|
205
|
+
try {
|
|
206
|
+
await Promise.race([client.quit(), new Promise((_, reject) => {
|
|
207
|
+
timer = setTimeout(() => {
|
|
208
|
+
reject(new CloseTimeoutError({ timeoutMs: closeTimeout }));
|
|
209
|
+
}, closeTimeout);
|
|
210
|
+
})]);
|
|
211
|
+
} catch {
|
|
212
|
+
client.disconnect();
|
|
213
|
+
} finally {
|
|
214
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* バイト列を書き込みます。
|
|
219
|
+
*
|
|
220
|
+
* 指定したキーで値を上書きします。
|
|
221
|
+
*
|
|
222
|
+
* @param c 書き込みコンテキストです。
|
|
223
|
+
* @param data 保存するバイト列です。
|
|
224
|
+
*
|
|
225
|
+
* @returns 書き込みが完了したら解決します。
|
|
226
|
+
*/
|
|
227
|
+
async write(c, data) {
|
|
228
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
229
|
+
c.signal.throwIfAborted();
|
|
230
|
+
await client.set(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key), Buffer.from(data));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* バイト列を読み取ります。
|
|
234
|
+
*
|
|
235
|
+
* 指定したキーの値を取得します。
|
|
236
|
+
*
|
|
237
|
+
* @param c 読み取りコンテキストです。
|
|
238
|
+
*
|
|
239
|
+
* @returns 読み取ったバイト列です。
|
|
240
|
+
*/
|
|
241
|
+
async read(c) {
|
|
242
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
243
|
+
c.signal.throwIfAborted();
|
|
244
|
+
const data = await client.getBuffer(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key));
|
|
245
|
+
if (data === null) throw new KeyNotFoundError$1({ key: c.key });
|
|
246
|
+
return new Uint8Array(data);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* キーの存在を確認します。
|
|
250
|
+
*
|
|
251
|
+
* @param c 存在確認コンテキストです。
|
|
252
|
+
*
|
|
253
|
+
* @returns 存在すれば真です。
|
|
254
|
+
*/
|
|
255
|
+
async exists(c) {
|
|
256
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
257
|
+
c.signal.throwIfAborted();
|
|
258
|
+
return await client.exists(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key)) > 0;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* キーを削除します。
|
|
262
|
+
*
|
|
263
|
+
* 存在しないキーも成功扱いにします。
|
|
264
|
+
*
|
|
265
|
+
* @param c 削除コンテキストです。
|
|
266
|
+
*
|
|
267
|
+
* @returns 削除が完了したら解決します。
|
|
268
|
+
*/
|
|
269
|
+
async delete(c) {
|
|
270
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
271
|
+
c.signal.throwIfAborted();
|
|
272
|
+
await client.del(_assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key));
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 接頭辞に一致するキーをすべて削除します。
|
|
276
|
+
*
|
|
277
|
+
* `SCAN` で列挙しながら削除します。
|
|
278
|
+
*
|
|
279
|
+
* @param c クリアコンテキストです。
|
|
280
|
+
*
|
|
281
|
+
* @returns 削除が完了したら解決します。
|
|
282
|
+
*/
|
|
283
|
+
async clear(c) {
|
|
284
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
285
|
+
c.signal.throwIfAborted();
|
|
286
|
+
const pattern = `${_classPrivateFieldGet2(_keyPrefix, this)}*`;
|
|
287
|
+
await _assertClassBrand(_Redis_brand, this, _clearPattern).call(this, client, pattern, c.signal);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 書き込み用ストリームを取得します。
|
|
291
|
+
*
|
|
292
|
+
* 一時キーに追記し、`close` 時に最終キーへ置き換えます。
|
|
293
|
+
*
|
|
294
|
+
* @param c 書き込みストリーム取得コンテキストです。
|
|
295
|
+
*
|
|
296
|
+
* @returns 書き込み用ストリームです。
|
|
297
|
+
*/
|
|
298
|
+
getWritable(c) {
|
|
299
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
300
|
+
c.signal.throwIfAborted();
|
|
301
|
+
const dest = _assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key);
|
|
302
|
+
const tmp = `${dest}.${randomUUID()}.tmp`;
|
|
303
|
+
let finished = false;
|
|
304
|
+
const removeAbortListener = () => {
|
|
305
|
+
c.signal.removeEventListener("abort", onAbort);
|
|
306
|
+
};
|
|
307
|
+
const discardTemporary = async () => {
|
|
308
|
+
try {
|
|
309
|
+
await client.del(tmp);
|
|
310
|
+
} catch {}
|
|
311
|
+
};
|
|
312
|
+
const finish = async () => {
|
|
313
|
+
if (finished) return;
|
|
314
|
+
finished = true;
|
|
315
|
+
removeAbortListener();
|
|
316
|
+
await discardTemporary();
|
|
317
|
+
};
|
|
318
|
+
const onAbort = () => {
|
|
319
|
+
finish();
|
|
320
|
+
};
|
|
321
|
+
c.signal.addEventListener("abort", onAbort, { once: true });
|
|
322
|
+
return new WritableStream({
|
|
323
|
+
async abort() {
|
|
324
|
+
await finish();
|
|
325
|
+
},
|
|
326
|
+
async close() {
|
|
327
|
+
c.signal.throwIfAborted();
|
|
328
|
+
try {
|
|
329
|
+
await client.rename(tmp, dest);
|
|
330
|
+
} catch (ex) {
|
|
331
|
+
await finish();
|
|
332
|
+
throw ex;
|
|
333
|
+
}
|
|
334
|
+
finished = true;
|
|
335
|
+
removeAbortListener();
|
|
336
|
+
},
|
|
337
|
+
async start() {
|
|
338
|
+
try {
|
|
339
|
+
c.signal.throwIfAborted();
|
|
340
|
+
await client.set(tmp, "");
|
|
341
|
+
} catch (ex) {
|
|
342
|
+
await finish();
|
|
343
|
+
throw ex;
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
async write(chunk) {
|
|
347
|
+
c.signal.throwIfAborted();
|
|
348
|
+
try {
|
|
349
|
+
await client.append(tmp, Buffer.from(chunk));
|
|
350
|
+
} catch (ex) {
|
|
351
|
+
await finish();
|
|
352
|
+
throw ex;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* 読み取り用ストリームを取得します。
|
|
359
|
+
*
|
|
360
|
+
* 取得した値を単一チャンクとして送出します。
|
|
361
|
+
*
|
|
362
|
+
* @param c 読み取りストリーム取得コンテキストです。
|
|
363
|
+
*
|
|
364
|
+
* @returns 読み取り用ストリームです。
|
|
365
|
+
*/
|
|
366
|
+
getReadable(c) {
|
|
367
|
+
const { client } = _assertClassBrand(_Redis_brand, this, _getConnection).call(this);
|
|
368
|
+
c.signal.throwIfAborted();
|
|
369
|
+
const dest = _assertClassBrand(_Redis_brand, this, _toKey).call(this, c.key);
|
|
370
|
+
let sent = false;
|
|
371
|
+
return new ReadableStream({ async pull(controller) {
|
|
372
|
+
if (sent) {
|
|
373
|
+
controller.close();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
sent = true;
|
|
377
|
+
c.signal.throwIfAborted();
|
|
378
|
+
const data = await client.getBuffer(dest);
|
|
379
|
+
if (data === null) throw new KeyNotFoundError$1({ key: c.key });
|
|
380
|
+
controller.enqueue(new Uint8Array(data));
|
|
381
|
+
controller.close();
|
|
382
|
+
} });
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
/**
|
|
386
|
+
* 開いている接続を返します。
|
|
387
|
+
*
|
|
388
|
+
* 未オープン時は例外を投げます。
|
|
389
|
+
*
|
|
390
|
+
* @returns オープン済みの接続です。
|
|
391
|
+
*/
|
|
392
|
+
function _getConnection() {
|
|
393
|
+
const con = _classPrivateFieldGet2(_con, this);
|
|
394
|
+
if (con === null) throw new TypeError("Redis is not open. Call open() first.");
|
|
395
|
+
return con;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* unikvs のキーにプレフィックスを付与し、Redis のキーへ変換します。
|
|
399
|
+
*
|
|
400
|
+
* @param key 変換する論理キーです。
|
|
401
|
+
*
|
|
402
|
+
* @returns Redis 上の実キーです。
|
|
403
|
+
*/
|
|
404
|
+
function _toKey(key) {
|
|
405
|
+
return `${_classPrivateFieldGet2(_keyPrefix, this)}${key}`;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* 接頭辞に一致するキーを走査して削除します。
|
|
409
|
+
*
|
|
410
|
+
* `SCAN` で頁送りしながら `DEL` します。
|
|
411
|
+
*
|
|
412
|
+
* @param client 操作に使うクライアントです。
|
|
413
|
+
* @param pattern 削除対象の一致パターンです。
|
|
414
|
+
* @param signal 中断を知らせるシグナルです。
|
|
415
|
+
*
|
|
416
|
+
* @returns 削除が完了したら解決します。
|
|
417
|
+
*/
|
|
418
|
+
async function _clearPattern(client, pattern, signal) {
|
|
419
|
+
let cursor = "0";
|
|
420
|
+
do {
|
|
421
|
+
signal.throwIfAborted();
|
|
422
|
+
const [nextCursor, keys] = await client.scan(cursor, "MATCH", pattern, "COUNT", SCAN_COUNT);
|
|
423
|
+
if (keys.length > 0) await client.del(...keys);
|
|
424
|
+
cursor = nextCursor;
|
|
425
|
+
} while (cursor !== "0");
|
|
426
|
+
}
|
|
427
|
+
//#endregion
|
|
428
|
+
export { CloseTimeoutError, ClusterNotSupportedError, ConnectTimeoutError, InvalidCloseTimeoutError, KeyNotFoundError, Redis };
|
|
429
|
+
|
|
430
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["Ioredis","KeyNotFoundError"],"sources":["../src/errors.ts","../src/redis.ts"],"sourcesContent":["import { type ErrorOptions, ErrorBase, setErrorMessage, InvalidUsageErrorBase } from \"@unikvs/core\";\n\nexport type { KeyNotFoundErrorArgs, KeyNotFoundErrorMeta } from \"@unikvs/core\";\nexport { KeyNotFoundError } from \"@unikvs/core\";\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-cluster-not-supported)\n */\nexport class ClusterNotSupportedError extends InvalidUsageErrorBase<undefined> {\n static {\n this.prototype.name = \"RedisClusterNotSupportedError\";\n }\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-cluster-not-supported)\n */\n public constructor(options?: ErrorOptions) {\n super(\n \"Redis storage v1 supports standalone mode only: the `cluster` option is not supported.\",\n options,\n );\n }\n}\n\nsetErrorMessage(\n ClusterNotSupportedError,\n \"Redis ストレージ v1 は standalone モードのみに対応します。`cluster` オプションはサポートされていません。\",\n \"ja\",\n);\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)\n */\nexport type InvalidCloseTimeoutErrorMeta = {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#invalid-close-timeout-actual)\n */\n readonly actual: unknown;\n};\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)\n */\nexport type InvalidCloseTimeoutErrorArgs = ErrorOptions & InvalidCloseTimeoutErrorMeta;\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)\n */\nexport class InvalidCloseTimeoutError extends ErrorBase<InvalidCloseTimeoutErrorMeta> {\n static {\n this.prototype.name = \"RedisInvalidCloseTimeoutError\";\n }\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-invalid-close-timeout)\n */\n public constructor(args: InvalidCloseTimeoutErrorArgs) {\n const { actual, ...options } = args;\n const meta: InvalidCloseTimeoutErrorMeta = { actual };\n super(\n meta,\n ({ actual: messageActual }) =>\n `\\`closeTimeout\\` must be a finite positive number (ms), but got ${String(messageActual)}.`,\n options,\n );\n }\n}\n\nsetErrorMessage(\n InvalidCloseTimeoutError,\n ({ actual }) =>\n `\\`closeTimeout\\` は有限の正数 (ミリ秒) でなければなりません。受け取った値: ${String(actual)}`,\n \"ja\",\n);\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)\n */\nexport type ConnectTimeoutErrorMeta = {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#connect-timeout-timeoutms)\n */\n readonly timeoutMs: number;\n};\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)\n */\nexport type ConnectTimeoutErrorArgs = ErrorOptions & ConnectTimeoutErrorMeta;\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)\n */\nexport class ConnectTimeoutError extends ErrorBase<ConnectTimeoutErrorMeta> {\n static {\n this.prototype.name = \"RedisConnectTimeoutError\";\n }\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-connect-timeout)\n */\n public constructor(args: ConnectTimeoutErrorArgs) {\n const { timeoutMs, ...options } = args;\n const meta: ConnectTimeoutErrorMeta = { timeoutMs };\n super(\n meta,\n ({ timeoutMs: messageTimeoutMs }) =>\n `Timed out connecting to Redis after ${messageTimeoutMs}ms`,\n options,\n );\n }\n}\n\nsetErrorMessage(\n ConnectTimeoutError,\n ({ timeoutMs }) => `Redis への接続が ${timeoutMs}ms でタイムアウトしました`,\n \"ja\",\n);\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)\n */\nexport type CloseTimeoutErrorMeta = {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#close-timeout-timeoutms)\n */\n readonly timeoutMs: number;\n};\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)\n */\nexport type CloseTimeoutErrorArgs = ErrorOptions & CloseTimeoutErrorMeta;\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)\n */\nexport class CloseTimeoutError extends ErrorBase<CloseTimeoutErrorMeta> {\n static {\n this.prototype.name = \"RedisCloseTimeoutError\";\n }\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#error-close-timeout)\n */\n public constructor(args: CloseTimeoutErrorArgs) {\n const { timeoutMs, ...options } = args;\n const meta: CloseTimeoutErrorMeta = { timeoutMs };\n super(\n meta,\n ({ timeoutMs: messageTimeoutMs }) => `Timed out closing Redis after ${messageTimeoutMs}ms`,\n options,\n );\n }\n}\n\nsetErrorMessage(\n CloseTimeoutError,\n ({ timeoutMs }) => `Redis からの切断処理が ${timeoutMs}ms でタイムアウトしました`,\n \"ja\",\n);\n","import { randomUUID } from \"node:crypto\";\n\nimport { type IStorage, KeyNotFoundError } from \"@unikvs/core\";\nimport {\n type ClusterNode,\n type RedisOptions,\n type ClusterOptions,\n Redis as Ioredis,\n} from \"ioredis\";\n\nimport {\n CloseTimeoutError,\n ConnectTimeoutError,\n ClusterNotSupportedError,\n InvalidCloseTimeoutError,\n} from \"./errors.js\";\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisclusterspec)\n */\nexport type RedisClusterSpec = {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisclusterspec-nodes)\n */\n readonly nodes: readonly ClusterNode[];\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisclusterspec-options)\n */\n readonly options?: ClusterOptions | undefined;\n};\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisstorageoptions)\n */\nexport type RedisStorageOptions = Omit<RedisOptions, \"keyPrefix\" | \"lazyConnect\"> & {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisstorageoptions-keyprefix)\n */\n readonly keyPrefix?: string | undefined;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisstorageoptions-cluster)\n */\n readonly cluster?: RedisClusterSpec | undefined;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redisstorageoptions-closetimeout)\n */\n readonly closeTimeout?: number | undefined;\n};\n\n/**\n * `SCAN` 1 回あたりにヒントとして渡すキー数です。\n *\n * 削除の往復回数と 1 回の `DEL` に渡す引数数を抑えるために使用します。\n */\nconst SCAN_COUNT = 1000;\n\n/**\n * 接続確立の既定タイムアウト (ミリ秒) です。\n */\nconst DEFAULT_CONNECT_TIMEOUT_MS = 5000;\n\n/**\n * 切断処理の既定タイムアウト (ミリ秒) です。\n */\nconst DEFAULT_CLOSE_TIMEOUT_MS = 5000;\n\n/**\n * ストレージの動作に必要な接続を保持する型定義です。\n */\ntype Connection = {\n /**\n * 操作に使うクライアントです。\n */\n readonly client: Ioredis;\n};\n\n/**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redis)\n */\nexport default class Redis implements IStorage {\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#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 * `closeTimeout` は本パッケージ固有の拡張です。\n *\n * `this.#options` には含めず、別フィールドで保管します。\n * `this.#options` は `new Ioredis` にそのまま spread できる透過バッグです。\n */\n readonly #options: Omit<RedisStorageOptions, \"keyPrefix\" | \"cluster\" | \"closeTimeout\">;\n\n /**\n * `close` 時の切断待ち時間 (ミリ秒) です。\n *\n * `ioredis` の `disconnectTimeout` (ソケット切断待機) とは別物です。\n */\n readonly #closeTimeout: number;\n\n /**\n * すべてのキーの先頭に付与するプレフィックスです。\n */\n readonly #keyPrefix: string;\n\n /**\n * [API Reference](https://tai-kun.github.io/unikvs/packages/redis-node#redis)\n */\n public constructor(url?: string, options: RedisStorageOptions = {}) {\n const {\n cluster,\n keyPrefix = \"unikvs:\",\n closeTimeout = DEFAULT_CLOSE_TIMEOUT_MS,\n ...redisOptions\n } = options;\n\n // v1 は standalone のみに対応します。\n // `cluster` を指定したときは、`closeTimeout` 検証より先に拒否します。\n if (cluster !== undefined) {\n throw new ClusterNotSupportedError();\n }\n\n // `undefined` は既定値 5000ms になるため対象外とします。\n // 明示値が有限の正数でない場合のみ拒否します。\n if (typeof closeTimeout !== \"number\" || !Number.isFinite(closeTimeout) || closeTimeout <= 0) {\n throw new InvalidCloseTimeoutError({ actual: closeTimeout });\n }\n\n this.name = \"Redis\";\n this.#con = null;\n this.#url = url;\n this.#options = redisOptions;\n this.#closeTimeout = closeTimeout;\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 * `ioredis` クライアントを作成して接続します。\n *\n * @param _c オープンコンテキストです。\n *\n * @returns オープンが完了したら解決します。\n */\n public async open(_c: IStorage.OpenContext): Promise<void> {\n // 並行 `open` 同士および `open` 接続待ち中の `close` の同時呼び出しは禁止 (事前条件) です。\n // 呼び出し側で直列化してください。\n // 並行 `close` 同士は一方が `TypeError` reject する形で直列化されます。\n if (this.#con) {\n await this.close(_c as unknown as IStorage.CloseContext);\n }\n\n // 空文字の `url`・環境変数は未指定として扱います (本パッケージ仕様)。\n // `url` は `redis://` / `rediss://` 形式での指定を要求します。\n // 実行時の形式検証は行いません (bare 形式は非サポート・動作未保証)。\n const explicitUrl = this.#url === undefined || this.#url === \"\" ? undefined : this.#url;\n const redisUrl =\n process.env[\"REDIS_URL\"] === undefined || process.env[\"REDIS_URL\"] === \"\"\n ? undefined\n : process.env[\"REDIS_URL\"];\n const valkeyUrl =\n process.env[\"VALKEY_URL\"] === undefined || process.env[\"VALKEY_URL\"] === \"\"\n ? undefined\n : process.env[\"VALKEY_URL\"];\n const url = explicitUrl ?? redisUrl ?? valkeyUrl;\n const connectTimeout = this.#options.connectTimeout ?? DEFAULT_CONNECT_TIMEOUT_MS;\n\n // `ioredis` v6 のコンストラクタ型は `replyMapping` に明示 `undefined` を許しません。\n // `exactOptionalPropertyTypes` 下では spread のまま渡せないため型表明を付けます。\n // 実行時の値は変わらず、`replyMapping` を含む透過は保たれます。\n type ConnectOptions = RedisOptions & { replyMapping?: \"legacy\" };\n\n const options = {\n ...this.#options,\n lazyConnect: true,\n connectTimeout,\n } as ConnectOptions;\n const raw = url === undefined ? new Ioredis(options) : new Ioredis(url, options);\n\n // `ioredis` は接続失敗・再試行時に `error` イベントを emit します。\n // unhandled `error` によるクラッシュを抑止するため no-op リスナを付けます。\n // 可観測性は v1 では提供しません。\n raw.on(\"error\", () => {});\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n await Promise.race([\n raw.connect(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new ConnectTimeoutError({ timeoutMs: connectTimeout }));\n }, connectTimeout);\n }),\n ]);\n } catch (ex) {\n raw.disconnect();\n throw ex;\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n }\n\n this.#con = { client: raw };\n }\n\n /**\n * ストレージをクローズします。\n *\n * `quit` で丁寧に閉じ、失敗時は `disconnect` で掃除します。\n *\n * @param _c クローズコンテキストです。\n *\n * @returns クローズが完了したら解決します。\n */\n public async close(_c: IStorage.CloseContext): Promise<void> {\n // 未 `open`・二重 `close` は `TypeError` で reject されます。\n // 必ず `await` し、結果を浮かせないでください。\n const { client } = this.#getConnection();\n\n // 先行 null 化により、並行 `close` 同士は二重 `quit` になりません。\n this.#con = null;\n\n const closeTimeout = this.#closeTimeout;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n try {\n await Promise.race([\n client.quit(),\n new Promise<never>((_, reject) => {\n timer = setTimeout(() => {\n reject(new CloseTimeoutError({ timeoutMs: closeTimeout }));\n }, closeTimeout);\n }),\n ]);\n } catch {\n // `quit()` の失敗全般 (タイムアウト含む) は `disconnect()` で掃除して正常復帰します。\n client.disconnect();\n } finally {\n if (timer !== undefined) {\n clearTimeout(timer);\n }\n }\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 await client.set(this.#toKey(c.key), Buffer.from(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 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 return (await client.exists(this.#toKey(c.key))) > 0;\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 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 await this.#clearPattern(client, pattern, c.signal);\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}.${randomUUID()}.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, Buffer.from(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 controller.enqueue(new Uint8Array(data));\n controller.close();\n },\n });\n }\n\n /**\n * 開いている接続を返します。\n *\n * 未オープン時は例外を投げます。\n *\n * @returns オープン済みの接続です。\n */\n #getConnection(): Connection {\n const con = this.#con;\n\n if (con === null) {\n throw new TypeError(\"Redis is not open. Call open() first.\");\n }\n\n return 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 /**\n * 接頭辞に一致するキーを走査して削除します。\n *\n * `SCAN` で頁送りしながら `DEL` します。\n *\n * @param client 操作に使うクライアントです。\n * @param pattern 削除対象の一致パターンです。\n * @param signal 中断を知らせるシグナルです。\n *\n * @returns 削除が完了したら解決します。\n */\n async #clearPattern(client: Ioredis, pattern: string, signal: AbortSignal): Promise<void> {\n let cursor = \"0\";\n\n do {\n 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"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unikvs/redis.node",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
}
|
|
3
|
+
"version": "0.0.14",
|
|
4
|
+
"description": "Storage plugin that stores byte arrays in Redis on Node.js.",
|
|
5
|
+
"homepage": "https://tai-kun.github.io/unikvs/",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"url": "https://github.com/tai-kun/unikvs",
|
|
9
|
+
"directory": "packages/redis.node"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"src",
|
|
14
|
+
"package.json",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@unikvs/core": "0.0.14",
|
|
28
|
+
"ioredis": "^6.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@unikvs/testing": "0.0.0",
|
|
32
|
+
"@vitest/coverage-v8": "5.0.5",
|
|
33
|
+
"valibot": "1.5.0",
|
|
34
|
+
"vite-plus": "1.3.0"
|
|
35
|
+
},
|
|
36
|
+
"peerDependencies": {
|
|
37
|
+
"@logtape/logtape": "^2.0.0"
|
|
38
|
+
},
|
|
39
|
+
"readme": "README.md",
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "vp -C .config pack"
|
|
42
|
+
}
|
|
43
|
+
}
|