@frontmcp/utils 1.9.1 → 1.9.3

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.
@@ -39,6 +39,9 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
39
39
  private connecting?;
40
40
  private readonly keyPrefix;
41
41
  private readonly subscriptionHandlers;
42
+ private readonly connectionUrl?;
43
+ /** The connection options of a URL this adapter reads itself; undefined for one only ioredis reads. */
44
+ private readonly urlOptions?;
42
45
  constructor(options?: RedisAdapterOptions);
43
46
  connect(): Promise<void>;
44
47
  private openConnection;
@@ -73,6 +76,10 @@ export declare class RedisStorageAdapter extends BaseStorageAdapter {
73
76
  * Build Redis options from config.
74
77
  */
75
78
  private buildRedisOptions;
79
+ /**
80
+ * Create a client this adapter owns. A URL only ioredis reads goes to it as is.
81
+ */
82
+ private newClient;
76
83
  /**
77
84
  * Create subscriber connection.
78
85
  */
@@ -104,6 +111,9 @@ export interface CreateRedisClientOptions {
104
111
  /**
105
112
  * Create a plain ioredis client that reconnects on its own and never emits an unhandled
106
113
  * 'error' event. The caller owns the client and must `quit()`/`disconnect()` it.
114
+ *
115
+ * With a `url`, the other connection fields fill in only what the URL leaves out (port,
116
+ * password, db, tls), and one that contradicts it throws a `StorageConfigError`.
107
117
  */
108
118
  export declare function createRedisClient(options: CreateRedisClientOptions): Redis;
109
119
  export {};
@@ -6,6 +6,46 @@
6
6
  */
7
7
  import type { SetOptions, VercelKvAdapterOptions } from '../types';
8
8
  import { BaseStorageAdapter } from './base';
9
+ type VercelKvClient = {
10
+ get: (key: string) => Promise<string | null>;
11
+ set: (key: string, value: string, options?: {
12
+ ex?: number;
13
+ nx?: boolean;
14
+ xx?: boolean;
15
+ }) => Promise<string | null>;
16
+ del: (...keys: string[]) => Promise<number>;
17
+ exists: (...keys: string[]) => Promise<number>;
18
+ mget: (...keys: string[]) => Promise<(string | null)[]>;
19
+ expire: (key: string, seconds: number) => Promise<number>;
20
+ ttl: (key: string) => Promise<number>;
21
+ incr: (key: string) => Promise<number>;
22
+ decr: (key: string) => Promise<number>;
23
+ incrby: (key: string, increment: number) => Promise<number>;
24
+ eval: (script: string, keys: string[], args: string[]) => Promise<unknown>;
25
+ scan: (cursor: number, options?: {
26
+ match?: string;
27
+ count?: number;
28
+ }) => Promise<[string, string[]]>;
29
+ keys: (pattern: string) => Promise<string[]>;
30
+ };
31
+ /** Where a Vercel KV client connects; both default to the `KV_REST_API_URL` / `KV_REST_API_TOKEN` variables. */
32
+ export interface VercelKvConnection {
33
+ url?: string;
34
+ token?: string;
35
+ }
36
+ /**
37
+ * Build a `@vercel/kv` REST client, the one loader every FrontMCP Vercel KV store shares (#711).
38
+ *
39
+ * `@vercel/kv` sets `cache: 'default'` on every request, which Cloudflare Workers reject ("The
40
+ * 'cache' field on 'RequestInitializerDict' is not implemented" / "Unsupported cache mode:
41
+ * default"), and the module's `kv` singleton cannot take another value. So each store gets a client
42
+ * of its own with the fetch cache mode unset (the value `@vercel/kv` itself calls equivalent), and
43
+ * values are read back exactly as stored instead of being JSON-parsed.
44
+ *
45
+ * @param connection - URL and token; each falls back to its environment variable
46
+ * @returns The client, typed as the subset of it the caller uses
47
+ */
48
+ export declare function createVercelKvClient<TClient = VercelKvClient>(connection?: VercelKvConnection): Promise<TClient>;
9
49
  /**
10
50
  * Vercel KV storage adapter.
11
51
  *
@@ -68,3 +108,4 @@ export declare class VercelKvStorageAdapter extends BaseStorageAdapter {
68
108
  */
69
109
  private unprefixKey;
70
110
  }
111
+ export {};
@@ -4,17 +4,19 @@
4
4
  * Unified storage abstraction with pluggable backends.
5
5
  * Supports Memory (dev), Redis (prod), Vercel KV (edge), and Upstash (edge + pub/sub).
6
6
  */
7
- export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, } from './types';
7
+ export type { StorageAdapter, NamespacedStorage, RootStorage, SetOptions, SetEntry, MessageHandler, Unsubscribe, MemoryAdapterOptions, RedisAdapterOptions, RedisHostConnectionConfig, RedisUrlConnectionConfig, RedisUrlFillInConfig, VercelKvAdapterOptions, UpstashAdapterOptions, StorageType, StorageConfig, } from './types';
8
8
  export { createStorage, createMemoryStorage, getDetectedStorageType } from './factory';
9
9
  export { attachRedisErrorListener, DEFAULT_REDIS_ERROR_LOG_INTERVAL_MS } from './redis-error-listener';
10
10
  export type { RedisErrorListenerOptions, ErrorEmitterClient } from './redis-error-listener';
11
+ export { mergeRedisUrlFields, describeRedisUrlConflicts } from './redis-url';
12
+ export type { RedisUrlConnection, RedisUrlMerge, RedisUrlSiblingFields } from './redis-url';
11
13
  export { NamespacedStorageImpl, createRootStorage, createNamespacedStorage, buildPrefix, NAMESPACE_SEPARATOR, } from './namespace';
12
14
  export { StorageError, StorageConnectionError, StorageOperationError, StorageNotSupportedError, StorageConfigError, StorageTTLError, StoragePatternError, StorageNotConnectedError, EncryptedStorageError, } from './errors';
13
15
  export { TypedStorage } from './typed-storage';
14
16
  export type { TypedStorageOptions, TypedSetOptions, TypedSetEntry } from './typed-storage.types';
15
17
  export { EncryptedTypedStorage } from './encrypted-typed-storage';
16
18
  export type { EncryptedTypedStorageOptions, EncryptedSetOptions, EncryptedSetEntry, EncryptionKey, StoredEncryptedBlob, ClientKeyBinding, } from './encrypted-typed-storage.types';
17
- export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, createRedisClient, VercelKvStorageAdapter, UpstashStorageAdapter, FileSystemStorageAdapter, LocalStorageAdapter, IndexedDBStorageAdapter, } from './adapters';
18
- export type { CreateRedisClientOptions, FileSystemAdapterOptions, LocalStorageAdapterOptions, IndexedDBAdapterOptions, } from './adapters';
19
+ export { BaseStorageAdapter, MemoryStorageAdapter, RedisStorageAdapter, createRedisClient, VercelKvStorageAdapter, createVercelKvClient, UpstashStorageAdapter, FileSystemStorageAdapter, LocalStorageAdapter, IndexedDBStorageAdapter, } from './adapters';
20
+ export type { CreateRedisClientOptions, VercelKvConnection, FileSystemAdapterOptions, LocalStorageAdapterOptions, IndexedDBAdapterOptions, } from './adapters';
19
21
  export { globToRegex, matchesPattern, validatePattern, escapeGlob } from './utils/pattern';
20
22
  export { MAX_TTL_SECONDS, validateTTL, validateOptionalTTL, ttlToExpiresAt, expiresAtToTTL, isExpired, normalizeTTL, } from './utils/ttl';
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Connection fields that may be written beside a Redis `url`.
3
+ */
4
+ export interface RedisUrlSiblingFields {
5
+ host?: string;
6
+ port?: number;
7
+ password?: string;
8
+ db?: number;
9
+ tls?: boolean;
10
+ }
11
+ /**
12
+ * What a `redis://` or `rediss://` URL states, read as ioredis reads it: the userinfo, port and path
13
+ * first, then the same names in its query. An empty username or password counts as left out.
14
+ */
15
+ export interface RedisUrlConnection {
16
+ host?: string;
17
+ port?: number;
18
+ username?: string;
19
+ password?: string;
20
+ db?: number;
21
+ tls: boolean;
22
+ /** The query's other options, which ioredis takes as they are (`family` as a number). */
23
+ queryOptions: Record<string, string | number>;
24
+ }
25
+ /**
26
+ * Outcome of combining a Redis URL with the fields written beside it.
27
+ */
28
+ export interface RedisUrlMerge {
29
+ /** What the URL states. */
30
+ connection: RedisUrlConnection;
31
+ /** Fields the URL leaves out that the sibling fields supply. */
32
+ fillIns: Pick<RedisUrlSiblingFields, 'port' | 'password' | 'db' | 'tls'>;
33
+ /** Sibling fields whose value contradicts the URL. */
34
+ conflicts: Array<keyof RedisUrlSiblingFields>;
35
+ }
36
+ /**
37
+ * Read a `redis://` or `rediss://` URL. Returns `undefined` for an unparseable URL or another
38
+ * scheme (such as a socket path), which only ioredis reads.
39
+ */
40
+ export declare function readRedisUrl(rawUrl: string): RedisUrlConnection | undefined;
41
+ /**
42
+ * The URL is the base: fields beside it fill in only what it leaves out (a
43
+ * port, a password, a database, TLS), and a field that contradicts what it
44
+ * states, in its userinfo, path or query, is a conflict the caller must reject
45
+ * (#768). Returns `undefined` for a URL {@link readRedisUrl} cannot read, which
46
+ * the caller reports on its own terms.
47
+ */
48
+ export declare function mergeRedisUrlFields(rawUrl: string, fields: RedisUrlSiblingFields): RedisUrlMerge | undefined;
49
+ /** Human-readable reason for {@link RedisUrlMerge.conflicts}. */
50
+ export declare function describeRedisUrlConflicts(conflicts: RedisUrlMerge['conflicts']): string;
@@ -309,6 +309,31 @@ export interface MemoryAdapterOptions {
309
309
  */
310
310
  maxEntries?: number;
311
311
  }
312
+ /**
313
+ * Redis connection by host.
314
+ */
315
+ export interface RedisHostConnectionConfig {
316
+ host: string;
317
+ port?: number;
318
+ password?: string;
319
+ db?: number;
320
+ tls?: boolean;
321
+ /** Unset for a host config (a config built from optional settings may carry `url: undefined`). */
322
+ url?: undefined;
323
+ }
324
+ /**
325
+ * Redis connection by URL (`redis://` / `rediss://`).
326
+ */
327
+ export interface RedisUrlConnectionConfig extends Partial<Omit<RedisHostConnectionConfig, 'url'>> {
328
+ url: string;
329
+ }
330
+ /**
331
+ * Fields beside a top-level `url` (`{ url, config: { password } }`) that fill in what the URL
332
+ * leaves out. Without a `url` the adapter needs a `host` and rejects this shape.
333
+ */
334
+ export interface RedisUrlFillInConfig extends Partial<Omit<RedisHostConnectionConfig, 'url'>> {
335
+ url?: undefined;
336
+ }
312
337
  /**
313
338
  * Options for Redis storage adapter.
314
339
  */
@@ -319,19 +344,16 @@ export interface RedisAdapterOptions {
319
344
  */
320
345
  client?: unknown;
321
346
  /**
322
- * Redis connection configuration.
347
+ * Redis connection configuration — `host`, or a `url` like the top-level
348
+ * `redis` option. Beside a URL, the other fields only fill in what the URL
349
+ * leaves out; a contradicting field is rejected.
323
350
  * Mutually exclusive with `client`.
324
351
  */
325
- config?: {
326
- host: string;
327
- port?: number;
328
- password?: string;
329
- db?: number;
330
- tls?: boolean;
331
- };
352
+ config?: RedisHostConnectionConfig | RedisUrlConnectionConfig | RedisUrlFillInConfig;
332
353
  /**
333
354
  * Redis connection URI.
334
- * e.g., "redis://user:pass@host:6379/0"
355
+ * e.g., "redis://user:pass@host:6379/0". Fields in `config` fill in what it
356
+ * leaves out (port, password, db, tls).
335
357
  * Mutually exclusive with `client`.
336
358
  */
337
359
  url?: string;