@frontmcp/utils 1.10.16 → 1.11.0-rc.2

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.
Files changed (135) hide show
  1. package/README.md +1 -0
  2. package/browser/async-context/browser-async-context.mjs +177 -0
  3. package/browser/async-context/index.mjs +15 -0
  4. package/browser/async-context/overlap-error.mjs +10 -0
  5. package/browser/class-brand/class-brand.mjs +27 -0
  6. package/browser/class-brand/index.mjs +6 -0
  7. package/browser/content/content.mjs +151 -0
  8. package/browser/content/index.mjs +8 -0
  9. package/browser/crypto/browser.mjs +103 -0
  10. package/browser/crypto/encrypted-blob.mjs +119 -0
  11. package/browser/crypto/hmac-signing.mjs +70 -0
  12. package/browser/crypto/index.mjs +281 -0
  13. package/browser/crypto/jwt-alg.mjs +40 -0
  14. package/browser/crypto/key-persistence/factory.mjs +134 -0
  15. package/browser/crypto/key-persistence/index.mjs +26 -0
  16. package/browser/crypto/key-persistence/key-persistence.mjs +185 -0
  17. package/browser/crypto/key-persistence/schemas.mjs +82 -0
  18. package/browser/crypto/pkce/index.mjs +25 -0
  19. package/browser/crypto/pkce/pkce.mjs +59 -0
  20. package/browser/crypto/runtime.mjs +17 -0
  21. package/browser/crypto/secret-persistence/index.mjs +29 -0
  22. package/browser/crypto/secret-persistence/persistence.mjs +181 -0
  23. package/browser/crypto/secret-persistence/schema.mjs +40 -0
  24. package/browser/crypto/web-rsa.mjs +36 -0
  25. package/browser/env/browser-env.mjs +56 -0
  26. package/browser/env/browser-runtime-context.mjs +85 -0
  27. package/browser/env/build-target.mjs +25 -0
  28. package/browser/env/edge-scope.mjs +17 -0
  29. package/browser/env/index.mjs +47 -0
  30. package/browser/env/provider.mjs +46 -0
  31. package/browser/escape/escape.mjs +43 -0
  32. package/browser/escape/index.mjs +9 -0
  33. package/browser/event-emitter/browser-event-emitter.mjs +49 -0
  34. package/browser/event-emitter/index.mjs +5 -0
  35. package/browser/fs/fs.mjs +227 -0
  36. package/browser/fs/index.mjs +65 -0
  37. package/browser/http/cookie.mjs +85 -0
  38. package/browser/http/http.mjs +18 -0
  39. package/browser/http/index.mjs +11 -0
  40. package/browser/index.mjs +526 -0
  41. package/browser/llm/index.mjs +5 -0
  42. package/browser/llm/llm-tool-handlers.mjs +55 -0
  43. package/browser/machine-id/index.mjs +7 -0
  44. package/browser/machine-id/machine-id.mjs +108 -0
  45. package/browser/naming/index.mjs +10 -0
  46. package/browser/naming/naming.mjs +68 -0
  47. package/browser/net/index.mjs +10 -0
  48. package/browser/net/ip-address.mjs +128 -0
  49. package/browser/net/redirect.mjs +7 -0
  50. package/browser/path/browser-path.mjs +47 -0
  51. package/browser/path/index.mjs +24 -0
  52. package/browser/path/path.mjs +13 -0
  53. package/browser/process/child-process.mjs +23 -0
  54. package/browser/process/index.mjs +15 -0
  55. package/browser/process/owned-process.mjs +175 -0
  56. package/browser/process/process-tree.mjs +149 -0
  57. package/browser/process/process-watch.mjs +48 -0
  58. package/browser/redaction/index.mjs +6 -0
  59. package/browser/redaction/redaction.mjs +74 -0
  60. package/browser/regex/index.mjs +3 -0
  61. package/browser/regex/patterns.mjs +167 -0
  62. package/browser/regex/safe-regex.mjs +93 -0
  63. package/browser/serialization/index.mjs +5 -0
  64. package/browser/serialization/serialization.mjs +22 -0
  65. package/browser/stdio-bridge/errors.mjs +27 -0
  66. package/browser/stdio-bridge/index.mjs +24 -0
  67. package/browser/stdio-bridge/list-changed.mjs +21 -0
  68. package/browser/stdio-bridge/state-machine.mjs +211 -0
  69. package/browser/stdio-bridge/stdio-framer.mjs +91 -0
  70. package/browser/stdio-bridge/upstream-client.mjs +189 -0
  71. package/browser/storage/adapters/base.mjs +139 -0
  72. package/browser/storage/adapters/cloudflare-kv.mjs +149 -0
  73. package/browser/storage/adapters/filesystem.mjs +279 -0
  74. package/browser/storage/adapters/index.mjs +23 -0
  75. package/browser/storage/adapters/indexeddb.mjs +198 -0
  76. package/browser/storage/adapters/localstorage.mjs +222 -0
  77. package/browser/storage/adapters/memory.mjs +350 -0
  78. package/browser/storage/adapters/redis.mjs +497 -0
  79. package/browser/storage/adapters/upstash.mjs +299 -0
  80. package/browser/storage/adapters/vercel-kv.mjs +226 -0
  81. package/browser/storage/change-feed.mjs +189 -0
  82. package/browser/storage/encrypted-typed-storage.mjs +329 -0
  83. package/browser/storage/errors.mjs +92 -0
  84. package/browser/storage/factory.mjs +155 -0
  85. package/browser/storage/index.mjs +96 -0
  86. package/browser/storage/namespace.mjs +186 -0
  87. package/browser/storage/redis-error-listener.mjs +32 -0
  88. package/browser/storage/redis-url.mjs +66 -0
  89. package/browser/storage/typed-storage.mjs +171 -0
  90. package/browser/storage/utils/compare-and-delete.mjs +9 -0
  91. package/browser/storage/utils/index.mjs +24 -0
  92. package/browser/storage/utils/pattern.mjs +68 -0
  93. package/browser/storage/utils/ttl.mjs +48 -0
  94. package/browser/uri/index.mjs +19 -0
  95. package/browser/uri/uri-template.mjs +86 -0
  96. package/browser/uri/uri-validation.mjs +18 -0
  97. package/crypto/browser.d.ts +1 -13
  98. package/crypto/browser.js +1 -1
  99. package/crypto/node.js +1 -1
  100. package/crypto/web-rsa.d.ts +13 -0
  101. package/env/browser-runtime-context.js +1 -1
  102. package/env/runtime-context.js +1 -1
  103. package/esm/crypto/browser.mjs +1 -1
  104. package/esm/crypto/node.mjs +1 -1
  105. package/esm/env/browser-runtime-context.mjs +1 -1
  106. package/esm/env/runtime-context.mjs +1 -1
  107. package/esm/index.mjs +1 -1
  108. package/fs/fs.d.ts +20 -4
  109. package/fs/index.d.ts +1 -1
  110. package/index.d.ts +9 -3
  111. package/index.js +1 -1
  112. package/package.json +24 -8
  113. package/process/child-process.d.ts +2 -0
  114. package/process/index.d.ts +6 -0
  115. package/process/owned-process.d.ts +59 -0
  116. package/process/process-tree.d.ts +78 -0
  117. package/process/process-watch.d.ts +41 -0
  118. package/redaction/index.d.ts +2 -0
  119. package/redaction/redaction.d.ts +24 -0
  120. package/stdio-bridge/errors.d.ts +23 -0
  121. package/stdio-bridge/index.d.ts +9 -0
  122. package/stdio-bridge/list-changed.d.ts +3 -0
  123. package/stdio-bridge/state-machine.d.ts +61 -0
  124. package/stdio-bridge/stdio-framer.d.ts +49 -0
  125. package/stdio-bridge/types.d.ts +10 -0
  126. package/stdio-bridge/upstream-client.d.ts +66 -0
  127. package/storage/adapters/base.d.ts +2 -0
  128. package/storage/adapters/memory.d.ts +1 -0
  129. package/storage/adapters/redis.d.ts +3 -0
  130. package/storage/change-feed.d.ts +47 -0
  131. package/storage/factory.d.ts +6 -0
  132. package/storage/index.d.ts +3 -1
  133. package/storage/namespace.d.ts +2 -0
  134. package/storage/types.d.ts +46 -1
  135. package/storage/utils/compare-and-delete.d.ts +2 -0
@@ -0,0 +1,497 @@
1
+ var __frontmcpModule = typeof process !== "undefined" && typeof process.getBuiltinModule === "function" ? process.getBuiltinModule("module") : undefined;
2
+ const require = __frontmcpModule ? __frontmcpModule.createRequire(import.meta.url || 'file:///') : undefined;
3
+ // __frontmcpCreateRequire
4
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
6
+ }) : x)(function(x) {
7
+ if (typeof require !== "undefined") return require.apply(this, arguments);
8
+ throw Error('Dynamic require of "' + x + '" is not supported');
9
+ });
10
+
11
+ // libs/utils/src/storage/adapters/redis.ts
12
+ import { StorageConfigError, StorageConnectionError } from "../errors.mjs";
13
+ import { attachRedisErrorListener } from "../redis-error-listener.mjs";
14
+ import {
15
+ describeRedisUrlConflicts,
16
+ mergeRedisUrlFields
17
+ } from "../redis-url.mjs";
18
+ import { validateTTL } from "../utils/index.mjs";
19
+ import {
20
+ COMPARE_AND_DELETE_SCRIPT,
21
+ COMPARE_AND_EXPIRE_SCRIPT,
22
+ COMPARE_AND_SET_SCRIPT
23
+ } from "../utils/compare-and-delete.mjs";
24
+ import { BaseStorageAdapter } from "./base.mjs";
25
+ function getRedisClass() {
26
+ try {
27
+ return __require("ioredis").default || __require("ioredis");
28
+ } catch (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ if (msg.includes("Dynamic require") || msg.includes("require is not defined")) {
31
+ throw new Error(
32
+ `Failed to load ioredis: ${msg}. This typically happens with ESM bundlers (esbuild, Vite). Ensure your bundler externalizes ioredis or use CJS mode.`,
33
+ { cause: error }
34
+ );
35
+ }
36
+ throw new Error("ioredis is required for Redis storage adapter. Install it with: npm install ioredis", {
37
+ cause: error
38
+ });
39
+ }
40
+ }
41
+ var RedisStorageAdapter = class extends BaseStorageAdapter {
42
+ backendName = "redis";
43
+ client;
44
+ subscriber;
45
+ options;
46
+ ownsClient;
47
+ detachErrorListener;
48
+ connecting;
49
+ keyPrefix;
50
+ subscriptionHandlers = /* @__PURE__ */ new Map();
51
+ reconnectListeners = /* @__PURE__ */ new Set();
52
+ connectionUrl;
53
+ /** The connection options of a URL this adapter reads itself; undefined for one only ioredis reads. */
54
+ urlOptions;
55
+ constructor(options = {}) {
56
+ super();
57
+ const hasClient = options.client !== void 0;
58
+ const hasConfig = options.config !== void 0 || options.url !== void 0;
59
+ if (hasClient && hasConfig) {
60
+ throw new StorageConfigError("redis", 'Cannot specify both "client" and "config"/"url". Use one or the other.');
61
+ }
62
+ if (!hasClient && !hasConfig) {
63
+ const envUrl = process.env["REDIS_URL"] || process.env["REDIS_HOST"];
64
+ if (envUrl) {
65
+ options = { ...options, url: envUrl };
66
+ } else {
67
+ throw new StorageConfigError(
68
+ "redis",
69
+ 'Either "client", "config", "url", or REDIS_URL environment variable must be provided.'
70
+ );
71
+ }
72
+ }
73
+ this.options = options;
74
+ this.ownsClient = !hasClient;
75
+ this.keyPrefix = options.keyPrefix ?? "";
76
+ const configUrl = options.config?.url;
77
+ this.connectionUrl = options.url ?? (typeof configUrl === "string" ? configUrl : void 0);
78
+ this.urlOptions = this.connectionUrl ? resolveUrlOptions(this.connectionUrl, options) : void 0;
79
+ }
80
+ // ============================================
81
+ // Connection Lifecycle
82
+ // ============================================
83
+ async connect() {
84
+ if (this.connected) return;
85
+ this.connecting ??= this.openConnection().finally(() => {
86
+ this.connecting = void 0;
87
+ });
88
+ return this.connecting;
89
+ }
90
+ async openConnection() {
91
+ let socketError;
92
+ const recordSocketError = (error) => {
93
+ socketError = error;
94
+ };
95
+ let client;
96
+ try {
97
+ if (this.options.client) {
98
+ client = this.options.client;
99
+ } else {
100
+ client = this.newClient();
101
+ client.on("error", recordSocketError);
102
+ }
103
+ await client.ping();
104
+ this.client = client;
105
+ this.connected = true;
106
+ if (this.ownsClient) {
107
+ client.removeListener("error", recordSocketError);
108
+ this.detachErrorListener = attachRedisErrorListener(client, { label: "RedisStorage" });
109
+ }
110
+ } catch (e) {
111
+ if (this.ownsClient && client) {
112
+ try {
113
+ client.disconnect();
114
+ } catch {
115
+ }
116
+ }
117
+ const cause = socketError ?? (e instanceof Error ? e : void 0);
118
+ throw new StorageConnectionError("Failed to connect to Redis", cause, "redis");
119
+ }
120
+ }
121
+ async disconnect() {
122
+ if (!this.connected) return;
123
+ if (this.subscriber) {
124
+ await this.subscriber.quit();
125
+ this.subscriber = void 0;
126
+ }
127
+ if (this.ownsClient && this.client) {
128
+ await this.client.quit();
129
+ }
130
+ this.detachErrorListener?.();
131
+ this.detachErrorListener = void 0;
132
+ this.client = void 0;
133
+ this.connected = false;
134
+ this.subscriptionHandlers.clear();
135
+ }
136
+ async ping() {
137
+ if (!this.client) return false;
138
+ try {
139
+ const result = await this.client.ping();
140
+ return result === "PONG";
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ // ============================================
146
+ // Connection Helpers
147
+ // ============================================
148
+ /**
149
+ * Get the connected Redis client, throwing if not connected.
150
+ */
151
+ getConnectedClient() {
152
+ this.ensureConnected();
153
+ if (!this.client) {
154
+ throw new StorageConnectionError("Redis client not connected", void 0, "redis");
155
+ }
156
+ return this.client;
157
+ }
158
+ /**
159
+ * Get the connected Redis subscriber, throwing if not created.
160
+ */
161
+ getConnectedSubscriber() {
162
+ if (!this.subscriber) {
163
+ throw new StorageConnectionError("Redis subscriber not created", void 0, "redis");
164
+ }
165
+ return this.subscriber;
166
+ }
167
+ // ============================================
168
+ // Core Operations
169
+ // ============================================
170
+ async get(key) {
171
+ return this.getConnectedClient().get(this.prefixKey(key));
172
+ }
173
+ async doSet(key, value, options) {
174
+ const client = this.getConnectedClient();
175
+ const prefixedKey = this.prefixKey(key);
176
+ if (options?.ttlSeconds) {
177
+ if (options.ifNotExists) {
178
+ await client.set(prefixedKey, value, "EX", options.ttlSeconds, "NX");
179
+ } else if (options.ifExists) {
180
+ await client.set(prefixedKey, value, "EX", options.ttlSeconds, "XX");
181
+ } else {
182
+ await client.set(prefixedKey, value, "EX", options.ttlSeconds);
183
+ }
184
+ } else if (options?.ifNotExists) {
185
+ await client.set(prefixedKey, value, "NX");
186
+ } else if (options?.ifExists) {
187
+ await client.set(prefixedKey, value, "XX");
188
+ } else {
189
+ await client.set(prefixedKey, value);
190
+ }
191
+ }
192
+ async delete(key) {
193
+ const result = await this.getConnectedClient().del(this.prefixKey(key));
194
+ return result > 0;
195
+ }
196
+ async deleteIfEquals(key, expectedValue) {
197
+ const deleted = await this.getConnectedClient().eval(
198
+ COMPARE_AND_DELETE_SCRIPT,
199
+ 1,
200
+ this.prefixKey(key),
201
+ expectedValue
202
+ );
203
+ return deleted === 1;
204
+ }
205
+ async expireIfEquals(key, expectedValue, ttlSeconds) {
206
+ validateTTL(ttlSeconds);
207
+ const extended = await this.getConnectedClient().eval(
208
+ COMPARE_AND_EXPIRE_SCRIPT,
209
+ 1,
210
+ this.prefixKey(key),
211
+ expectedValue,
212
+ String(ttlSeconds)
213
+ );
214
+ return extended === 1;
215
+ }
216
+ async setIfEquals(key, expectedValue, value) {
217
+ const stored = await this.getConnectedClient().eval(
218
+ COMPARE_AND_SET_SCRIPT,
219
+ 1,
220
+ this.prefixKey(key),
221
+ expectedValue,
222
+ value
223
+ );
224
+ return stored === 1;
225
+ }
226
+ async exists(key) {
227
+ const result = await this.getConnectedClient().exists(this.prefixKey(key));
228
+ return result > 0;
229
+ }
230
+ // ============================================
231
+ // Batch Operations (pipelined)
232
+ // ============================================
233
+ async mget(keys) {
234
+ if (keys.length === 0) return [];
235
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
236
+ return this.getConnectedClient().mget(...prefixedKeys);
237
+ }
238
+ async mset(entries) {
239
+ if (entries.length === 0) return;
240
+ for (const entry of entries) {
241
+ this.validateSetOptions(entry.options);
242
+ }
243
+ const pipeline = this.getConnectedClient().pipeline();
244
+ for (const entry of entries) {
245
+ const prefixedKey = this.prefixKey(entry.key);
246
+ if (entry.options?.ttlSeconds) {
247
+ if (entry.options.ifNotExists) {
248
+ pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds, "NX");
249
+ } else if (entry.options.ifExists) {
250
+ pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds, "XX");
251
+ } else {
252
+ pipeline.set(prefixedKey, entry.value, "EX", entry.options.ttlSeconds);
253
+ }
254
+ } else if (entry.options?.ifNotExists) {
255
+ pipeline.set(prefixedKey, entry.value, "NX");
256
+ } else if (entry.options?.ifExists) {
257
+ pipeline.set(prefixedKey, entry.value, "XX");
258
+ } else {
259
+ pipeline.set(prefixedKey, entry.value);
260
+ }
261
+ }
262
+ await pipeline.exec();
263
+ }
264
+ async mdelete(keys) {
265
+ if (keys.length === 0) return 0;
266
+ const prefixedKeys = keys.map((k) => this.prefixKey(k));
267
+ return this.getConnectedClient().del(...prefixedKeys);
268
+ }
269
+ // ============================================
270
+ // TTL Operations
271
+ // ============================================
272
+ async expire(key, ttlSeconds) {
273
+ validateTTL(ttlSeconds);
274
+ const result = await this.getConnectedClient().expire(this.prefixKey(key), ttlSeconds);
275
+ return result === 1;
276
+ }
277
+ async ttl(key) {
278
+ const result = await this.getConnectedClient().ttl(this.prefixKey(key));
279
+ if (result === -2) return null;
280
+ return result;
281
+ }
282
+ // ============================================
283
+ // Key Enumeration (SCAN)
284
+ // ============================================
285
+ async keys(pattern = "*") {
286
+ const client = this.getConnectedClient();
287
+ const prefixedPattern = this.prefixKey(pattern);
288
+ const result = [];
289
+ let cursor = "0";
290
+ do {
291
+ const [nextCursor, keys] = await client.scan(cursor, "MATCH", prefixedPattern, "COUNT", 100);
292
+ cursor = nextCursor;
293
+ for (const key of keys) {
294
+ result.push(this.unprefixKey(key));
295
+ }
296
+ } while (cursor !== "0");
297
+ return result;
298
+ }
299
+ // ============================================
300
+ // Atomic Operations
301
+ // ============================================
302
+ async incr(key) {
303
+ return this.getConnectedClient().incr(this.prefixKey(key));
304
+ }
305
+ async decr(key) {
306
+ return this.getConnectedClient().decr(this.prefixKey(key));
307
+ }
308
+ async incrBy(key, amount) {
309
+ return this.getConnectedClient().incrby(this.prefixKey(key), amount);
310
+ }
311
+ // ============================================
312
+ // Pub/Sub
313
+ // ============================================
314
+ supportsPubSub() {
315
+ return true;
316
+ }
317
+ async publish(channel, message) {
318
+ const prefixedChannel = this.prefixKey(channel);
319
+ return this.getConnectedClient().publish(prefixedChannel, message);
320
+ }
321
+ async subscribe(channel, handler) {
322
+ this.ensureConnected();
323
+ const prefixedChannel = this.prefixKey(channel);
324
+ if (!this.subscriber) {
325
+ await this.createSubscriber();
326
+ }
327
+ const subscriber = this.getConnectedSubscriber();
328
+ if (!this.subscriptionHandlers.has(prefixedChannel)) {
329
+ this.subscriptionHandlers.set(prefixedChannel, /* @__PURE__ */ new Set());
330
+ await subscriber.subscribe(prefixedChannel);
331
+ }
332
+ const handlers = this.subscriptionHandlers.get(prefixedChannel);
333
+ if (handlers) {
334
+ handlers.add(handler);
335
+ }
336
+ return async () => {
337
+ const handlers2 = this.subscriptionHandlers.get(prefixedChannel);
338
+ if (handlers2) {
339
+ handlers2.delete(handler);
340
+ if (handlers2.size === 0) {
341
+ this.subscriptionHandlers.delete(prefixedChannel);
342
+ await this.subscriber?.unsubscribe(prefixedChannel);
343
+ }
344
+ }
345
+ };
346
+ }
347
+ onPubSubReconnect(listener) {
348
+ this.reconnectListeners.add(listener);
349
+ return () => {
350
+ this.reconnectListeners.delete(listener);
351
+ };
352
+ }
353
+ // ============================================
354
+ // Internal Helpers
355
+ // ============================================
356
+ /**
357
+ * Build Redis options from config.
358
+ */
359
+ buildRedisOptions() {
360
+ if (this.connectionUrl) {
361
+ return { lazyConnect: false, maxRetriesPerRequest: 3, ...this.urlOptions };
362
+ }
363
+ const config = this.options.config;
364
+ if (!config || typeof config.url === "string" || config.host === void 0) {
365
+ throw new StorageConfigError("redis", "Redis config is required when URL is not provided");
366
+ }
367
+ return {
368
+ host: config.host,
369
+ port: config.port ?? 6379,
370
+ password: config.password,
371
+ db: config.db ?? 0,
372
+ tls: config.tls ? {} : void 0,
373
+ lazyConnect: false,
374
+ maxRetriesPerRequest: 3
375
+ };
376
+ }
377
+ /**
378
+ * Create a client this adapter owns. A URL only ioredis reads goes to it as is.
379
+ */
380
+ newClient() {
381
+ const RedisClass = getRedisClass();
382
+ if (this.connectionUrl && !this.urlOptions) {
383
+ return new RedisClass(this.connectionUrl, this.buildRedisOptions());
384
+ }
385
+ return new RedisClass(this.buildRedisOptions());
386
+ }
387
+ /**
388
+ * Create subscriber connection.
389
+ */
390
+ async createSubscriber() {
391
+ let subscriber;
392
+ if (this.connectionUrl || this.options.config) {
393
+ subscriber = this.newClient();
394
+ } else if (this.options.client) {
395
+ subscriber = this.options.client.duplicate();
396
+ } else {
397
+ throw new StorageConfigError("redis", "Cannot create subscriber without url, config, or client");
398
+ }
399
+ attachRedisErrorListener(subscriber, { label: "RedisStorage:subscriber" });
400
+ subscriber.on("message", (channel, message) => {
401
+ const handlers = this.subscriptionHandlers.get(channel);
402
+ if (handlers) {
403
+ const unprefixedChannel = this.unprefixKey(channel);
404
+ for (const handler of handlers) {
405
+ try {
406
+ handler(message, unprefixedChannel);
407
+ } catch {
408
+ }
409
+ }
410
+ }
411
+ });
412
+ let readyBefore = false;
413
+ subscriber.on("ready", () => {
414
+ if (readyBefore) {
415
+ for (const listener of [...this.reconnectListeners]) {
416
+ try {
417
+ listener();
418
+ } catch {
419
+ }
420
+ }
421
+ }
422
+ readyBefore = true;
423
+ });
424
+ this.subscriber = subscriber;
425
+ }
426
+ /**
427
+ * Add prefix to a key.
428
+ */
429
+ prefixKey(key) {
430
+ return this.keyPrefix + key;
431
+ }
432
+ /**
433
+ * Remove prefix from a key.
434
+ */
435
+ unprefixKey(key) {
436
+ if (this.keyPrefix && key.startsWith(this.keyPrefix)) {
437
+ return key.slice(this.keyPrefix.length);
438
+ }
439
+ return key;
440
+ }
441
+ /**
442
+ * Get the underlying Redis client (for advanced use).
443
+ */
444
+ getClient() {
445
+ return this.client;
446
+ }
447
+ };
448
+ function resolveUrlOptions(connectionUrl, options) {
449
+ const { url: configUrl, ...siblingFields } = { url: void 0, ...options.config };
450
+ if (options.url && configUrl && configUrl !== options.url) {
451
+ throw new StorageConfigError("redis", "redis.url and redis.config.url name different servers; set one of them.");
452
+ }
453
+ return urlConnectionOptions(connectionUrl, siblingFields);
454
+ }
455
+ function urlConnectionOptions(url, fields) {
456
+ const merge = mergeRedisUrlFields(url, fields);
457
+ if (!merge) return void 0;
458
+ if (merge.conflicts.length > 0) {
459
+ throw new StorageConfigError("redis", describeRedisUrlConflicts(merge.conflicts));
460
+ }
461
+ const { queryOptions, tls, ...stated } = merge.connection;
462
+ return { ...queryOptions, ...stated, ...toFillInOptions(merge.fillIns), ...tls ? { tls: {} } : {} };
463
+ }
464
+ function toFillInOptions({ port, password, db, tls }) {
465
+ return {
466
+ ...port !== void 0 ? { port } : {},
467
+ ...password !== void 0 ? { password } : {},
468
+ ...db !== void 0 ? { db } : {},
469
+ ...tls ? { tls: {} } : {}
470
+ };
471
+ }
472
+ function createRedisClient(options) {
473
+ const urlOptions = options.url ? urlConnectionOptions(options.url, options) : void 0;
474
+ const RedisClass = getRedisClass();
475
+ const baseOptions = { lazyConnect: false, maxRetriesPerRequest: 3 };
476
+ let client;
477
+ if (urlOptions) {
478
+ client = new RedisClass({ ...baseOptions, ...urlOptions });
479
+ } else if (options.url) {
480
+ client = new RedisClass(options.url, baseOptions);
481
+ } else {
482
+ client = new RedisClass({
483
+ ...baseOptions,
484
+ host: options.host ?? "localhost",
485
+ port: options.port ?? 6379,
486
+ password: options.password,
487
+ db: options.db ?? 0,
488
+ tls: options.tls ? {} : void 0
489
+ });
490
+ }
491
+ attachRedisErrorListener(client, { label: options.label ?? "redis", logger: options.logger });
492
+ return client;
493
+ }
494
+ export {
495
+ RedisStorageAdapter,
496
+ createRedisClient
497
+ };