@beignet/provider-cache-redis 0.0.33 → 0.0.35
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/CHANGELOG.md +13 -0
- package/README.md +30 -5
- package/dist/index.d.ts +37 -62
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +197 -211
- package/dist/index.js.map +1 -1
- package/package.json +1 -2
- package/src/index.ts +249 -223
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @beignet/provider-redis
|
|
2
2
|
|
|
3
|
+
## 0.0.35
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 397f106: Standardize framework-owned operational errors, degrade opaque OpenAPI path parameters safely, and add direct Redis, Pino, and Upstash adapters.
|
|
8
|
+
- 7d0c795: Expose stable named provider types while keeping private config schemas out of package declarations and preserving validated config and contributed-port inference. Drizzle's Postgres and MySQL config schema constants are now internal; use the exported config interfaces for validated shapes.
|
|
9
|
+
|
|
10
|
+
## 0.0.34
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- 9345cc9: Stabilize the pre-1.0 public surface around factory-only providers, canonical server and testing imports, consistent memory naming, and the removal of deprecated aliases.
|
|
15
|
+
|
|
3
16
|
## 0.0.33
|
|
4
17
|
|
|
5
18
|
## 0.0.32
|
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@ The provider installs `ctx.ports.cache` using
|
|
|
10
10
|
[ioredis](https://github.com/redis/ioredis) and exposes the raw Redis client
|
|
11
11
|
separately as `ctx.ports.redis` for Redis-specific features.
|
|
12
12
|
|
|
13
|
+
`createRedisCacheProvider(...)` returns the stable `RedisCacheProvider` type.
|
|
14
|
+
`RedisConfig` describes its validated config; the Zod schema remains internal.
|
|
15
|
+
|
|
13
16
|
## Install
|
|
14
17
|
|
|
15
18
|
```bash
|
|
@@ -21,7 +24,7 @@ bun add @beignet/provider-cache-redis @beignet/core ioredis
|
|
|
21
24
|
```typescript
|
|
22
25
|
import { createNextServer, createNextServerLoader } from "@beignet/next";
|
|
23
26
|
import { definePorts } from "@beignet/core/ports";
|
|
24
|
-
import {
|
|
27
|
+
import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
25
28
|
import { routes } from "@/server/routes";
|
|
26
29
|
|
|
27
30
|
// Set environment variables:
|
|
@@ -33,7 +36,7 @@ const appPorts = definePorts({});
|
|
|
33
36
|
export const getServer = createNextServerLoader(() =>
|
|
34
37
|
createNextServer({
|
|
35
38
|
ports: appPorts,
|
|
36
|
-
providers: [
|
|
39
|
+
providers: [createRedisCacheProvider()],
|
|
37
40
|
context: ({ ports }) => ({
|
|
38
41
|
ports,
|
|
39
42
|
}),
|
|
@@ -98,7 +101,7 @@ defaults. Defined options override matching `REDIS_*` environment variables:
|
|
|
98
101
|
```typescript
|
|
99
102
|
import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
100
103
|
|
|
101
|
-
const
|
|
104
|
+
const provider = createRedisCacheProvider({
|
|
102
105
|
connectTimeoutMs: 2000,
|
|
103
106
|
maxRetriesPerRequest: 1,
|
|
104
107
|
db: 1,
|
|
@@ -107,7 +110,26 @@ const redisCacheProvider = createRedisCacheProvider({
|
|
|
107
110
|
});
|
|
108
111
|
```
|
|
109
112
|
|
|
110
|
-
|
|
113
|
+
Calling `createRedisCacheProvider()` with no options uses the env-backed
|
|
114
|
+
defaults above.
|
|
115
|
+
|
|
116
|
+
### Direct adapter
|
|
117
|
+
|
|
118
|
+
Use `createRedisCache(...)` when the app already owns an ioredis client. The
|
|
119
|
+
factory adapts the client to `CachePort` without connecting, disconnecting, or
|
|
120
|
+
reading environment variables:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { createRedisCache } from "@beignet/provider-cache-redis";
|
|
124
|
+
import { Redis } from "ioredis";
|
|
125
|
+
|
|
126
|
+
const client = new Redis(process.env.APP_REDIS_URL!);
|
|
127
|
+
const cache = createRedisCache({ client });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Pass `instrumentation` when direct cache operations should appear in Beignet
|
|
131
|
+
provider instrumentation. The caller remains responsible for client startup,
|
|
132
|
+
retry policy, health checks, and shutdown.
|
|
111
133
|
|
|
112
134
|
### Connection behavior
|
|
113
135
|
|
|
@@ -216,11 +238,14 @@ import type { RedisCacheProviderPorts } from "@beignet/provider-cache-redis";
|
|
|
216
238
|
// Your base ports, if any
|
|
217
239
|
const basePorts = definePorts({});
|
|
218
240
|
|
|
219
|
-
// After using
|
|
241
|
+
// After using createRedisCacheProvider(), your ports will have this shape:
|
|
220
242
|
type AppPorts = typeof basePorts & RedisCacheProviderPorts;
|
|
221
243
|
// { cache: CachePort; redis: { client: Redis } }
|
|
222
244
|
```
|
|
223
245
|
|
|
246
|
+
Direct wiring can use the exported `RedisCacheClient` and
|
|
247
|
+
`CreateRedisCacheOptions` types without adopting the lifecycle provider ports.
|
|
248
|
+
|
|
224
249
|
## Lifecycle
|
|
225
250
|
|
|
226
251
|
The Redis provider:
|
package/dist/index.d.ts
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
15
15
|
* import { createNextServer } from "@beignet/next";
|
|
16
|
-
* import {
|
|
16
|
+
* import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
17
17
|
*
|
|
18
18
|
* const server = await createNextServer({
|
|
19
19
|
* ports: basePorts,
|
|
20
|
-
* providers: [
|
|
20
|
+
* providers: [createRedisCacheProvider()],
|
|
21
21
|
* // ...
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
@@ -32,23 +32,18 @@
|
|
|
32
32
|
* ```
|
|
33
33
|
*/
|
|
34
34
|
import type { CachePort } from "@beignet/core/ports";
|
|
35
|
+
import { type AnyProviderConfigSchema, type ProviderInstrumentationTarget, type ServiceProvider } from "@beignet/core/providers";
|
|
35
36
|
import { Redis } from "ioredis";
|
|
36
|
-
import { z } from "zod";
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
39
|
-
* Validates environment variables with REDIS_ prefix.
|
|
38
|
+
* Validated configuration for the Redis provider.
|
|
40
39
|
*/
|
|
41
|
-
|
|
42
|
-
URL:
|
|
43
|
-
DB
|
|
44
|
-
CONNECT_TIMEOUT_MS:
|
|
45
|
-
MAX_RETRIES_PER_REQUEST:
|
|
46
|
-
CONNECT_MAX_ATTEMPTS:
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Inferred configuration type for Redis provider.
|
|
50
|
-
*/
|
|
51
|
-
export type RedisConfig = z.infer<typeof RedisConfigSchema>;
|
|
40
|
+
export interface RedisConfig {
|
|
41
|
+
URL: string;
|
|
42
|
+
DB?: number;
|
|
43
|
+
CONNECT_TIMEOUT_MS: number;
|
|
44
|
+
MAX_RETRIES_PER_REQUEST: number;
|
|
45
|
+
CONNECT_MAX_ATTEMPTS: number;
|
|
46
|
+
}
|
|
52
47
|
/**
|
|
53
48
|
* Options for {@link createRedisCacheProvider}.
|
|
54
49
|
*
|
|
@@ -116,6 +111,31 @@ export interface RedisCacheProviderPorts {
|
|
|
116
111
|
*/
|
|
117
112
|
redis: RedisEscapeHatch;
|
|
118
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Redis cache lifecycle provider with stable contributed-port typing.
|
|
116
|
+
*/
|
|
117
|
+
export type RedisCacheProvider = ServiceProvider<unknown, AnyProviderConfigSchema<RedisConfig>, Pick<RedisCacheProviderPorts, keyof RedisCacheProviderPorts>>;
|
|
118
|
+
/**
|
|
119
|
+
* Minimal ioredis client shape required by the direct cache adapter.
|
|
120
|
+
*/
|
|
121
|
+
export type RedisCacheClient = Pick<Redis, "get" | "set" | "del" | "exists">;
|
|
122
|
+
/**
|
|
123
|
+
* Options for adapting an app-owned ioredis client directly.
|
|
124
|
+
*/
|
|
125
|
+
export interface CreateRedisCacheOptions {
|
|
126
|
+
/**
|
|
127
|
+
* Connected ioredis client. The caller owns connection and shutdown.
|
|
128
|
+
*/
|
|
129
|
+
client: RedisCacheClient;
|
|
130
|
+
/**
|
|
131
|
+
* Optional provider instrumentation target.
|
|
132
|
+
*/
|
|
133
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Adapt an app-owned ioredis client to Beignet's stable CachePort.
|
|
137
|
+
*/
|
|
138
|
+
export declare function createRedisCache(options: CreateRedisCacheOptions): CachePort;
|
|
119
139
|
/**
|
|
120
140
|
* Create an env-backed Redis cache provider.
|
|
121
141
|
*
|
|
@@ -127,50 +147,5 @@ export interface RedisCacheProviderPorts {
|
|
|
127
147
|
* const provider = createRedisCacheProvider({ connectTimeoutMs: 2000 });
|
|
128
148
|
* ```
|
|
129
149
|
*/
|
|
130
|
-
export declare function createRedisCacheProvider(options?: RedisCacheProviderOptions):
|
|
131
|
-
URL: z.ZodString;
|
|
132
|
-
CONNECT_MAX_ATTEMPTS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
133
|
-
DB: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>> | z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
134
|
-
CONNECT_TIMEOUT_MS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
135
|
-
MAX_RETRIES_PER_REQUEST: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
136
|
-
}, z.core.$strip>, {
|
|
137
|
-
cache: CachePort;
|
|
138
|
-
redis: {
|
|
139
|
-
client: Redis;
|
|
140
|
-
checkHealth: () => Promise<RedisHealth>;
|
|
141
|
-
};
|
|
142
|
-
}, unknown, void>;
|
|
143
|
-
/**
|
|
144
|
-
* Default env-backed Redis provider.
|
|
145
|
-
*
|
|
146
|
-
* Configuration via environment variables:
|
|
147
|
-
* - REDIS_URL: Redis connection URL (required)
|
|
148
|
-
* - REDIS_DB: Redis database number (optional)
|
|
149
|
-
* - REDIS_CONNECT_TIMEOUT_MS: Initial connection timeout in ms (optional)
|
|
150
|
-
* - REDIS_MAX_RETRIES_PER_REQUEST: Per-command retry limit (optional)
|
|
151
|
-
* - REDIS_CONNECT_MAX_ATTEMPTS: Connection attempts before startup fails (optional)
|
|
152
|
-
*
|
|
153
|
-
* @example
|
|
154
|
-
* ```ts
|
|
155
|
-
* const server = await createNextServer({
|
|
156
|
-
* ports: basePorts,
|
|
157
|
-
* providers: [redisCacheProvider],
|
|
158
|
-
* // ...
|
|
159
|
-
* });
|
|
160
|
-
* ```
|
|
161
|
-
*/
|
|
162
|
-
export declare const redisCacheProvider: import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
|
|
163
|
-
URL: z.ZodString;
|
|
164
|
-
CONNECT_MAX_ATTEMPTS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
165
|
-
DB: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>> | z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
166
|
-
CONNECT_TIMEOUT_MS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
167
|
-
MAX_RETRIES_PER_REQUEST: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
168
|
-
}, z.core.$strip>, {
|
|
169
|
-
cache: CachePort;
|
|
170
|
-
redis: {
|
|
171
|
-
client: Redis;
|
|
172
|
-
checkHealth: () => Promise<RedisHealth>;
|
|
173
|
-
};
|
|
174
|
-
}, unknown, void>;
|
|
175
|
-
export {};
|
|
150
|
+
export declare function createRedisCacheProvider(options?: RedisCacheProviderOptions): RedisCacheProvider;
|
|
176
151
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EACL,KAAK,uBAAuB,EAG5B,KAAK,6BAA6B,EAClC,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAmDhC;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAEjD;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,MAAM,EAAE,KAAK,CAAC;IACd;;OAEG;IACH,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;OAEG;IACH,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAC9C,OAAO,EACP,uBAAuB,CAAC,WAAW,CAAC,EACpC,IAAI,CAAC,uBAAuB,EAAE,MAAM,uBAAuB,CAAC,CAC7D,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,CAAC,CAAC;AAE7E;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB;;OAEG;IACH,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AAuCD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,SAAS,CAsM5E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B,GACtC,kBAAkB,CAsGpB"}
|
package/dist/index.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
15
15
|
* import { createNextServer } from "@beignet/next";
|
|
16
|
-
* import {
|
|
16
|
+
* import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
17
17
|
*
|
|
18
18
|
* const server = await createNextServer({
|
|
19
19
|
* ports: basePorts,
|
|
20
|
-
* providers: [
|
|
20
|
+
* providers: [createRedisCacheProvider()],
|
|
21
21
|
* // ...
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
@@ -111,6 +111,198 @@ async function checkRedisHealth(client) {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* Adapt an app-owned ioredis client to Beignet's stable CachePort.
|
|
116
|
+
*/
|
|
117
|
+
export function createRedisCache(options) {
|
|
118
|
+
const client = options.client;
|
|
119
|
+
const instrumentation = createProviderInstrumentation(options.instrumentation, {
|
|
120
|
+
providerName: "cache-redis",
|
|
121
|
+
watcher: "cache",
|
|
122
|
+
});
|
|
123
|
+
const recordCacheEvent = (event) => {
|
|
124
|
+
instrumentation.custom({
|
|
125
|
+
name: `cache.${event.operation}`,
|
|
126
|
+
label: `Cache ${event.operation}`,
|
|
127
|
+
summary: event.summary,
|
|
128
|
+
details: {
|
|
129
|
+
operation: event.operation,
|
|
130
|
+
key: event.key,
|
|
131
|
+
...event.details,
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
return {
|
|
136
|
+
get: async (key) => {
|
|
137
|
+
const startedAt = Date.now();
|
|
138
|
+
try {
|
|
139
|
+
const value = await client.get(key);
|
|
140
|
+
recordCacheEvent({
|
|
141
|
+
operation: "get",
|
|
142
|
+
key,
|
|
143
|
+
summary: value == null ? "Cache miss" : "Cache hit",
|
|
144
|
+
details: {
|
|
145
|
+
hit: value != null,
|
|
146
|
+
durationMs: Date.now() - startedAt,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return value;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
recordCacheEvent({
|
|
153
|
+
operation: "get.failed",
|
|
154
|
+
key,
|
|
155
|
+
summary: "Cache get failed",
|
|
156
|
+
details: {
|
|
157
|
+
durationMs: Date.now() - startedAt,
|
|
158
|
+
error: errorMessage(error),
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
set: async (key, value, cacheOptions) => {
|
|
165
|
+
const startedAt = Date.now();
|
|
166
|
+
try {
|
|
167
|
+
if (cacheOptions?.ttlSeconds != null && cacheOptions.ttlSeconds > 0) {
|
|
168
|
+
await client.set(key, value, "EX", cacheOptions.ttlSeconds);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
await client.set(key, value);
|
|
172
|
+
}
|
|
173
|
+
recordCacheEvent({
|
|
174
|
+
operation: "set",
|
|
175
|
+
key,
|
|
176
|
+
summary: "Cache set",
|
|
177
|
+
details: {
|
|
178
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
179
|
+
durationMs: Date.now() - startedAt,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
recordCacheEvent({
|
|
185
|
+
operation: "set.failed",
|
|
186
|
+
key,
|
|
187
|
+
summary: "Cache set failed",
|
|
188
|
+
details: {
|
|
189
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
190
|
+
durationMs: Date.now() - startedAt,
|
|
191
|
+
error: errorMessage(error),
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
delete: async (key) => {
|
|
198
|
+
const startedAt = Date.now();
|
|
199
|
+
try {
|
|
200
|
+
const deleted = (await client.del(key)) > 0;
|
|
201
|
+
recordCacheEvent({
|
|
202
|
+
operation: "delete",
|
|
203
|
+
key,
|
|
204
|
+
summary: deleted ? "Cache key deleted" : "Cache key missing",
|
|
205
|
+
details: {
|
|
206
|
+
deleted,
|
|
207
|
+
durationMs: Date.now() - startedAt,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
return deleted;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
recordCacheEvent({
|
|
214
|
+
operation: "delete.failed",
|
|
215
|
+
key,
|
|
216
|
+
summary: "Cache delete failed",
|
|
217
|
+
details: {
|
|
218
|
+
durationMs: Date.now() - startedAt,
|
|
219
|
+
error: errorMessage(error),
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
has: async (key) => {
|
|
226
|
+
const startedAt = Date.now();
|
|
227
|
+
try {
|
|
228
|
+
const result = await client.exists(key);
|
|
229
|
+
const exists = result === 1;
|
|
230
|
+
recordCacheEvent({
|
|
231
|
+
operation: "has",
|
|
232
|
+
key,
|
|
233
|
+
summary: exists ? "Cache key exists" : "Cache key missing",
|
|
234
|
+
details: {
|
|
235
|
+
exists,
|
|
236
|
+
durationMs: Date.now() - startedAt,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
return exists;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
recordCacheEvent({
|
|
243
|
+
operation: "has.failed",
|
|
244
|
+
key,
|
|
245
|
+
summary: "Cache has failed",
|
|
246
|
+
details: {
|
|
247
|
+
durationMs: Date.now() - startedAt,
|
|
248
|
+
error: errorMessage(error),
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
throw error;
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
remember: async (key, factory, cacheOptions) => {
|
|
255
|
+
const startedAt = Date.now();
|
|
256
|
+
try {
|
|
257
|
+
const cached = await client.get(key);
|
|
258
|
+
if (cached != null) {
|
|
259
|
+
recordCacheEvent({
|
|
260
|
+
operation: "remember",
|
|
261
|
+
key,
|
|
262
|
+
summary: "Cache remember hit",
|
|
263
|
+
details: {
|
|
264
|
+
hit: true,
|
|
265
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
266
|
+
durationMs: Date.now() - startedAt,
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
return cached;
|
|
270
|
+
}
|
|
271
|
+
const value = await factory();
|
|
272
|
+
if (cacheOptions?.ttlSeconds != null && cacheOptions.ttlSeconds > 0) {
|
|
273
|
+
await client.set(key, value, "EX", cacheOptions.ttlSeconds);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
await client.set(key, value);
|
|
277
|
+
}
|
|
278
|
+
recordCacheEvent({
|
|
279
|
+
operation: "remember",
|
|
280
|
+
key,
|
|
281
|
+
summary: "Cache remember miss",
|
|
282
|
+
details: {
|
|
283
|
+
hit: false,
|
|
284
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
285
|
+
durationMs: Date.now() - startedAt,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return value;
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
recordCacheEvent({
|
|
292
|
+
operation: "remember.failed",
|
|
293
|
+
key,
|
|
294
|
+
summary: "Cache remember failed",
|
|
295
|
+
details: {
|
|
296
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
297
|
+
durationMs: Date.now() - startedAt,
|
|
298
|
+
error: errorMessage(error),
|
|
299
|
+
},
|
|
300
|
+
});
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
114
306
|
/**
|
|
115
307
|
* Create an env-backed Redis cache provider.
|
|
116
308
|
*
|
|
@@ -142,7 +334,7 @@ export function createRedisCacheProvider(options = {}) {
|
|
|
142
334
|
},
|
|
143
335
|
async setup({ ports, config }) {
|
|
144
336
|
if (!config) {
|
|
145
|
-
throw new Error("[
|
|
337
|
+
throw new Error("[createRedisCacheProvider] Missing Redis config. " +
|
|
146
338
|
"Please set REDIS_URL environment variable.");
|
|
147
339
|
}
|
|
148
340
|
const connectMaxAttempts = config.CONNECT_MAX_ATTEMPTS ?? DEFAULT_CONNECT_MAX_ATTEMPTS;
|
|
@@ -182,195 +374,9 @@ export function createRedisCacheProvider(options = {}) {
|
|
|
182
374
|
}
|
|
183
375
|
catch (error) {
|
|
184
376
|
client.disconnect();
|
|
185
|
-
throw new Error(`[
|
|
377
|
+
throw new Error(`[createRedisCacheProvider] Failed to connect to Redis at ${redactConnectionUrl(config.URL)}: ${errorMessage(error)}`);
|
|
186
378
|
}
|
|
187
|
-
const
|
|
188
|
-
providerName: "cache-redis",
|
|
189
|
-
watcher: "cache",
|
|
190
|
-
});
|
|
191
|
-
const recordCacheEvent = (event) => {
|
|
192
|
-
instrumentation.custom({
|
|
193
|
-
name: `cache.${event.operation}`,
|
|
194
|
-
label: `Cache ${event.operation}`,
|
|
195
|
-
summary: event.summary,
|
|
196
|
-
details: {
|
|
197
|
-
operation: event.operation,
|
|
198
|
-
key: event.key,
|
|
199
|
-
...event.details,
|
|
200
|
-
},
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
// Extend ports with cache API
|
|
204
|
-
const cache = {
|
|
205
|
-
get: async (key) => {
|
|
206
|
-
const startedAt = Date.now();
|
|
207
|
-
try {
|
|
208
|
-
const value = await client.get(key);
|
|
209
|
-
recordCacheEvent({
|
|
210
|
-
operation: "get",
|
|
211
|
-
key,
|
|
212
|
-
summary: value == null ? "Cache miss" : "Cache hit",
|
|
213
|
-
details: {
|
|
214
|
-
hit: value != null,
|
|
215
|
-
durationMs: Date.now() - startedAt,
|
|
216
|
-
},
|
|
217
|
-
});
|
|
218
|
-
return value;
|
|
219
|
-
}
|
|
220
|
-
catch (error) {
|
|
221
|
-
recordCacheEvent({
|
|
222
|
-
operation: "get.failed",
|
|
223
|
-
key,
|
|
224
|
-
summary: "Cache get failed",
|
|
225
|
-
details: {
|
|
226
|
-
durationMs: Date.now() - startedAt,
|
|
227
|
-
error: errorMessage(error),
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
throw error;
|
|
231
|
-
}
|
|
232
|
-
},
|
|
233
|
-
set: async (key, value, options) => {
|
|
234
|
-
const startedAt = Date.now();
|
|
235
|
-
try {
|
|
236
|
-
if (options?.ttlSeconds != null && options.ttlSeconds > 0) {
|
|
237
|
-
await client.set(key, value, "EX", options.ttlSeconds);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
await client.set(key, value);
|
|
241
|
-
}
|
|
242
|
-
recordCacheEvent({
|
|
243
|
-
operation: "set",
|
|
244
|
-
key,
|
|
245
|
-
summary: "Cache set",
|
|
246
|
-
details: {
|
|
247
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
248
|
-
durationMs: Date.now() - startedAt,
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
recordCacheEvent({
|
|
254
|
-
operation: "set.failed",
|
|
255
|
-
key,
|
|
256
|
-
summary: "Cache set failed",
|
|
257
|
-
details: {
|
|
258
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
259
|
-
durationMs: Date.now() - startedAt,
|
|
260
|
-
error: errorMessage(error),
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
throw error;
|
|
264
|
-
}
|
|
265
|
-
},
|
|
266
|
-
delete: async (key) => {
|
|
267
|
-
const startedAt = Date.now();
|
|
268
|
-
try {
|
|
269
|
-
const deleted = (await client.del(key)) > 0;
|
|
270
|
-
recordCacheEvent({
|
|
271
|
-
operation: "delete",
|
|
272
|
-
key,
|
|
273
|
-
summary: deleted ? "Cache key deleted" : "Cache key missing",
|
|
274
|
-
details: {
|
|
275
|
-
deleted,
|
|
276
|
-
durationMs: Date.now() - startedAt,
|
|
277
|
-
},
|
|
278
|
-
});
|
|
279
|
-
return deleted;
|
|
280
|
-
}
|
|
281
|
-
catch (error) {
|
|
282
|
-
recordCacheEvent({
|
|
283
|
-
operation: "delete.failed",
|
|
284
|
-
key,
|
|
285
|
-
summary: "Cache delete failed",
|
|
286
|
-
details: {
|
|
287
|
-
durationMs: Date.now() - startedAt,
|
|
288
|
-
error: errorMessage(error),
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
has: async (key) => {
|
|
295
|
-
const startedAt = Date.now();
|
|
296
|
-
try {
|
|
297
|
-
const result = await client.exists(key);
|
|
298
|
-
const exists = result === 1;
|
|
299
|
-
recordCacheEvent({
|
|
300
|
-
operation: "has",
|
|
301
|
-
key,
|
|
302
|
-
summary: exists ? "Cache key exists" : "Cache key missing",
|
|
303
|
-
details: {
|
|
304
|
-
exists,
|
|
305
|
-
durationMs: Date.now() - startedAt,
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
return exists;
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
recordCacheEvent({
|
|
312
|
-
operation: "has.failed",
|
|
313
|
-
key,
|
|
314
|
-
summary: "Cache has failed",
|
|
315
|
-
details: {
|
|
316
|
-
durationMs: Date.now() - startedAt,
|
|
317
|
-
error: errorMessage(error),
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
throw error;
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
remember: async (key, factory, options) => {
|
|
324
|
-
const startedAt = Date.now();
|
|
325
|
-
try {
|
|
326
|
-
const cached = await client.get(key);
|
|
327
|
-
if (cached != null) {
|
|
328
|
-
recordCacheEvent({
|
|
329
|
-
operation: "remember",
|
|
330
|
-
key,
|
|
331
|
-
summary: "Cache remember hit",
|
|
332
|
-
details: {
|
|
333
|
-
hit: true,
|
|
334
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
335
|
-
durationMs: Date.now() - startedAt,
|
|
336
|
-
},
|
|
337
|
-
});
|
|
338
|
-
return cached;
|
|
339
|
-
}
|
|
340
|
-
const value = await factory();
|
|
341
|
-
if (options?.ttlSeconds != null && options.ttlSeconds > 0) {
|
|
342
|
-
await client.set(key, value, "EX", options.ttlSeconds);
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
await client.set(key, value);
|
|
346
|
-
}
|
|
347
|
-
recordCacheEvent({
|
|
348
|
-
operation: "remember",
|
|
349
|
-
key,
|
|
350
|
-
summary: "Cache remember miss",
|
|
351
|
-
details: {
|
|
352
|
-
hit: false,
|
|
353
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
354
|
-
durationMs: Date.now() - startedAt,
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
return value;
|
|
358
|
-
}
|
|
359
|
-
catch (error) {
|
|
360
|
-
recordCacheEvent({
|
|
361
|
-
operation: "remember.failed",
|
|
362
|
-
key,
|
|
363
|
-
summary: "Cache remember failed",
|
|
364
|
-
details: {
|
|
365
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
366
|
-
durationMs: Date.now() - startedAt,
|
|
367
|
-
error: errorMessage(error),
|
|
368
|
-
},
|
|
369
|
-
});
|
|
370
|
-
throw error;
|
|
371
|
-
}
|
|
372
|
-
},
|
|
373
|
-
};
|
|
379
|
+
const cache = createRedisCache({ client, instrumentation: ports });
|
|
374
380
|
return {
|
|
375
381
|
ports: {
|
|
376
382
|
cache,
|
|
@@ -391,24 +397,4 @@ export function createRedisCacheProvider(options = {}) {
|
|
|
391
397
|
},
|
|
392
398
|
});
|
|
393
399
|
}
|
|
394
|
-
/**
|
|
395
|
-
* Default env-backed Redis provider.
|
|
396
|
-
*
|
|
397
|
-
* Configuration via environment variables:
|
|
398
|
-
* - REDIS_URL: Redis connection URL (required)
|
|
399
|
-
* - REDIS_DB: Redis database number (optional)
|
|
400
|
-
* - REDIS_CONNECT_TIMEOUT_MS: Initial connection timeout in ms (optional)
|
|
401
|
-
* - REDIS_MAX_RETRIES_PER_REQUEST: Per-command retry limit (optional)
|
|
402
|
-
* - REDIS_CONNECT_MAX_ATTEMPTS: Connection attempts before startup fails (optional)
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* const server = await createNextServer({
|
|
407
|
-
* ports: basePorts,
|
|
408
|
-
* providers: [redisCacheProvider],
|
|
409
|
-
* // ...
|
|
410
|
-
* });
|
|
411
|
-
* ```
|
|
412
|
-
*/
|
|
413
|
-
export const redisCacheProvider = createRedisCacheProvider();
|
|
414
400
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAGH,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beignet/provider-cache-redis",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.35",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Redis provider for Beignet - adds cache port using ioredis",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -95,7 +95,6 @@
|
|
|
95
95
|
"registration": {
|
|
96
96
|
"required": true,
|
|
97
97
|
"tokens": [
|
|
98
|
-
"redisCacheProvider",
|
|
99
98
|
"createRedisCacheProvider"
|
|
100
99
|
]
|
|
101
100
|
}
|
package/src/index.ts
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* @example
|
|
14
14
|
* ```ts
|
|
15
15
|
* import { createNextServer } from "@beignet/next";
|
|
16
|
-
* import {
|
|
16
|
+
* import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
17
17
|
*
|
|
18
18
|
* const server = await createNextServer({
|
|
19
19
|
* ports: basePorts,
|
|
20
|
-
* providers: [
|
|
20
|
+
* providers: [createRedisCacheProvider()],
|
|
21
21
|
* // ...
|
|
22
22
|
* });
|
|
23
23
|
*
|
|
@@ -34,8 +34,11 @@
|
|
|
34
34
|
|
|
35
35
|
import type { CachePort } from "@beignet/core/ports";
|
|
36
36
|
import {
|
|
37
|
+
type AnyProviderConfigSchema,
|
|
37
38
|
createProvider,
|
|
38
39
|
createProviderInstrumentation,
|
|
40
|
+
type ProviderInstrumentationTarget,
|
|
41
|
+
type ServiceProvider,
|
|
39
42
|
} from "@beignet/core/providers";
|
|
40
43
|
import { Redis } from "ioredis";
|
|
41
44
|
import { z } from "zod";
|
|
@@ -86,12 +89,18 @@ const RedisConfigSchema = z.object({
|
|
|
86
89
|
* Defaults to 3.
|
|
87
90
|
*/
|
|
88
91
|
CONNECT_MAX_ATTEMPTS: NumberString.default(DEFAULT_CONNECT_MAX_ATTEMPTS),
|
|
89
|
-
})
|
|
92
|
+
}) satisfies z.ZodType<RedisConfig>;
|
|
90
93
|
|
|
91
94
|
/**
|
|
92
|
-
*
|
|
95
|
+
* Validated configuration for the Redis provider.
|
|
93
96
|
*/
|
|
94
|
-
export
|
|
97
|
+
export interface RedisConfig {
|
|
98
|
+
URL: string;
|
|
99
|
+
DB?: number;
|
|
100
|
+
CONNECT_TIMEOUT_MS: number;
|
|
101
|
+
MAX_RETRIES_PER_REQUEST: number;
|
|
102
|
+
CONNECT_MAX_ATTEMPTS: number;
|
|
103
|
+
}
|
|
95
104
|
|
|
96
105
|
/**
|
|
97
106
|
* Options for {@link createRedisCacheProvider}.
|
|
@@ -167,6 +176,34 @@ export interface RedisCacheProviderPorts {
|
|
|
167
176
|
redis: RedisEscapeHatch;
|
|
168
177
|
}
|
|
169
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Redis cache lifecycle provider with stable contributed-port typing.
|
|
181
|
+
*/
|
|
182
|
+
export type RedisCacheProvider = ServiceProvider<
|
|
183
|
+
unknown,
|
|
184
|
+
AnyProviderConfigSchema<RedisConfig>,
|
|
185
|
+
Pick<RedisCacheProviderPorts, keyof RedisCacheProviderPorts>
|
|
186
|
+
>;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Minimal ioredis client shape required by the direct cache adapter.
|
|
190
|
+
*/
|
|
191
|
+
export type RedisCacheClient = Pick<Redis, "get" | "set" | "del" | "exists">;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Options for adapting an app-owned ioredis client directly.
|
|
195
|
+
*/
|
|
196
|
+
export interface CreateRedisCacheOptions {
|
|
197
|
+
/**
|
|
198
|
+
* Connected ioredis client. The caller owns connection and shutdown.
|
|
199
|
+
*/
|
|
200
|
+
client: RedisCacheClient;
|
|
201
|
+
/**
|
|
202
|
+
* Optional provider instrumentation target.
|
|
203
|
+
*/
|
|
204
|
+
instrumentation?: ProviderInstrumentationTarget;
|
|
205
|
+
}
|
|
206
|
+
|
|
170
207
|
function errorMessage(error: unknown): string {
|
|
171
208
|
return error instanceof Error ? error.message : String(error);
|
|
172
209
|
}
|
|
@@ -204,6 +241,209 @@ async function checkRedisHealth(client: Redis): Promise<RedisHealth> {
|
|
|
204
241
|
}
|
|
205
242
|
}
|
|
206
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Adapt an app-owned ioredis client to Beignet's stable CachePort.
|
|
246
|
+
*/
|
|
247
|
+
export function createRedisCache(options: CreateRedisCacheOptions): CachePort {
|
|
248
|
+
const client = options.client;
|
|
249
|
+
const instrumentation = createProviderInstrumentation(
|
|
250
|
+
options.instrumentation,
|
|
251
|
+
{
|
|
252
|
+
providerName: "cache-redis",
|
|
253
|
+
watcher: "cache",
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const recordCacheEvent = (event: {
|
|
258
|
+
operation: string;
|
|
259
|
+
key: string;
|
|
260
|
+
summary: string;
|
|
261
|
+
details?: Record<string, unknown>;
|
|
262
|
+
}) => {
|
|
263
|
+
instrumentation.custom({
|
|
264
|
+
name: `cache.${event.operation}`,
|
|
265
|
+
label: `Cache ${event.operation}`,
|
|
266
|
+
summary: event.summary,
|
|
267
|
+
details: {
|
|
268
|
+
operation: event.operation,
|
|
269
|
+
key: event.key,
|
|
270
|
+
...event.details,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
get: async (key: string) => {
|
|
277
|
+
const startedAt = Date.now();
|
|
278
|
+
try {
|
|
279
|
+
const value = await client.get(key);
|
|
280
|
+
recordCacheEvent({
|
|
281
|
+
operation: "get",
|
|
282
|
+
key,
|
|
283
|
+
summary: value == null ? "Cache miss" : "Cache hit",
|
|
284
|
+
details: {
|
|
285
|
+
hit: value != null,
|
|
286
|
+
durationMs: Date.now() - startedAt,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
return value;
|
|
290
|
+
} catch (error) {
|
|
291
|
+
recordCacheEvent({
|
|
292
|
+
operation: "get.failed",
|
|
293
|
+
key,
|
|
294
|
+
summary: "Cache get failed",
|
|
295
|
+
details: {
|
|
296
|
+
durationMs: Date.now() - startedAt,
|
|
297
|
+
error: errorMessage(error),
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
throw error;
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
set: async (key, value, cacheOptions) => {
|
|
305
|
+
const startedAt = Date.now();
|
|
306
|
+
try {
|
|
307
|
+
if (cacheOptions?.ttlSeconds != null && cacheOptions.ttlSeconds > 0) {
|
|
308
|
+
await client.set(key, value, "EX", cacheOptions.ttlSeconds);
|
|
309
|
+
} else {
|
|
310
|
+
await client.set(key, value);
|
|
311
|
+
}
|
|
312
|
+
recordCacheEvent({
|
|
313
|
+
operation: "set",
|
|
314
|
+
key,
|
|
315
|
+
summary: "Cache set",
|
|
316
|
+
details: {
|
|
317
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
318
|
+
durationMs: Date.now() - startedAt,
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
recordCacheEvent({
|
|
323
|
+
operation: "set.failed",
|
|
324
|
+
key,
|
|
325
|
+
summary: "Cache set failed",
|
|
326
|
+
details: {
|
|
327
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
328
|
+
durationMs: Date.now() - startedAt,
|
|
329
|
+
error: errorMessage(error),
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
throw error;
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
|
|
336
|
+
delete: async (key) => {
|
|
337
|
+
const startedAt = Date.now();
|
|
338
|
+
try {
|
|
339
|
+
const deleted = (await client.del(key)) > 0;
|
|
340
|
+
recordCacheEvent({
|
|
341
|
+
operation: "delete",
|
|
342
|
+
key,
|
|
343
|
+
summary: deleted ? "Cache key deleted" : "Cache key missing",
|
|
344
|
+
details: {
|
|
345
|
+
deleted,
|
|
346
|
+
durationMs: Date.now() - startedAt,
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
return deleted;
|
|
350
|
+
} catch (error) {
|
|
351
|
+
recordCacheEvent({
|
|
352
|
+
operation: "delete.failed",
|
|
353
|
+
key,
|
|
354
|
+
summary: "Cache delete failed",
|
|
355
|
+
details: {
|
|
356
|
+
durationMs: Date.now() - startedAt,
|
|
357
|
+
error: errorMessage(error),
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
|
|
364
|
+
has: async (key) => {
|
|
365
|
+
const startedAt = Date.now();
|
|
366
|
+
try {
|
|
367
|
+
const result = await client.exists(key);
|
|
368
|
+
const exists = result === 1;
|
|
369
|
+
recordCacheEvent({
|
|
370
|
+
operation: "has",
|
|
371
|
+
key,
|
|
372
|
+
summary: exists ? "Cache key exists" : "Cache key missing",
|
|
373
|
+
details: {
|
|
374
|
+
exists,
|
|
375
|
+
durationMs: Date.now() - startedAt,
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
return exists;
|
|
379
|
+
} catch (error) {
|
|
380
|
+
recordCacheEvent({
|
|
381
|
+
operation: "has.failed",
|
|
382
|
+
key,
|
|
383
|
+
summary: "Cache has failed",
|
|
384
|
+
details: {
|
|
385
|
+
durationMs: Date.now() - startedAt,
|
|
386
|
+
error: errorMessage(error),
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
remember: async (key, factory, cacheOptions) => {
|
|
394
|
+
const startedAt = Date.now();
|
|
395
|
+
try {
|
|
396
|
+
const cached = await client.get(key);
|
|
397
|
+
if (cached != null) {
|
|
398
|
+
recordCacheEvent({
|
|
399
|
+
operation: "remember",
|
|
400
|
+
key,
|
|
401
|
+
summary: "Cache remember hit",
|
|
402
|
+
details: {
|
|
403
|
+
hit: true,
|
|
404
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
405
|
+
durationMs: Date.now() - startedAt,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
return cached;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const value = await factory();
|
|
412
|
+
if (cacheOptions?.ttlSeconds != null && cacheOptions.ttlSeconds > 0) {
|
|
413
|
+
await client.set(key, value, "EX", cacheOptions.ttlSeconds);
|
|
414
|
+
} else {
|
|
415
|
+
await client.set(key, value);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
recordCacheEvent({
|
|
419
|
+
operation: "remember",
|
|
420
|
+
key,
|
|
421
|
+
summary: "Cache remember miss",
|
|
422
|
+
details: {
|
|
423
|
+
hit: false,
|
|
424
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
425
|
+
durationMs: Date.now() - startedAt,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return value;
|
|
430
|
+
} catch (error) {
|
|
431
|
+
recordCacheEvent({
|
|
432
|
+
operation: "remember.failed",
|
|
433
|
+
key,
|
|
434
|
+
summary: "Cache remember failed",
|
|
435
|
+
details: {
|
|
436
|
+
ttlSeconds: cacheOptions?.ttlSeconds ?? null,
|
|
437
|
+
durationMs: Date.now() - startedAt,
|
|
438
|
+
error: errorMessage(error),
|
|
439
|
+
},
|
|
440
|
+
});
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
207
447
|
/**
|
|
208
448
|
* Create an env-backed Redis cache provider.
|
|
209
449
|
*
|
|
@@ -217,7 +457,7 @@ async function checkRedisHealth(client: Redis): Promise<RedisHealth> {
|
|
|
217
457
|
*/
|
|
218
458
|
export function createRedisCacheProvider(
|
|
219
459
|
options: RedisCacheProviderOptions = {},
|
|
220
|
-
) {
|
|
460
|
+
): RedisCacheProvider {
|
|
221
461
|
const ConfigSchema = RedisConfigSchema.extend({
|
|
222
462
|
DB:
|
|
223
463
|
options.db !== undefined
|
|
@@ -244,7 +484,7 @@ export function createRedisCacheProvider(
|
|
|
244
484
|
async setup({ ports, config }) {
|
|
245
485
|
if (!config) {
|
|
246
486
|
throw new Error(
|
|
247
|
-
"[
|
|
487
|
+
"[createRedisCacheProvider] Missing Redis config. " +
|
|
248
488
|
"Please set REDIS_URL environment variable.",
|
|
249
489
|
);
|
|
250
490
|
}
|
|
@@ -293,206 +533,13 @@ export function createRedisCacheProvider(
|
|
|
293
533
|
} catch (error) {
|
|
294
534
|
client.disconnect();
|
|
295
535
|
throw new Error(
|
|
296
|
-
`[
|
|
536
|
+
`[createRedisCacheProvider] Failed to connect to Redis at ${redactConnectionUrl(
|
|
297
537
|
config.URL,
|
|
298
538
|
)}: ${errorMessage(error)}`,
|
|
299
539
|
);
|
|
300
540
|
}
|
|
301
541
|
|
|
302
|
-
const
|
|
303
|
-
providerName: "cache-redis",
|
|
304
|
-
watcher: "cache",
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
const recordCacheEvent = (event: {
|
|
308
|
-
operation: string;
|
|
309
|
-
key: string;
|
|
310
|
-
summary: string;
|
|
311
|
-
details?: Record<string, unknown>;
|
|
312
|
-
}) => {
|
|
313
|
-
instrumentation.custom({
|
|
314
|
-
name: `cache.${event.operation}`,
|
|
315
|
-
label: `Cache ${event.operation}`,
|
|
316
|
-
summary: event.summary,
|
|
317
|
-
details: {
|
|
318
|
-
operation: event.operation,
|
|
319
|
-
key: event.key,
|
|
320
|
-
...event.details,
|
|
321
|
-
},
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
|
|
325
|
-
// Extend ports with cache API
|
|
326
|
-
const cache: CachePort = {
|
|
327
|
-
get: async (key: string) => {
|
|
328
|
-
const startedAt = Date.now();
|
|
329
|
-
try {
|
|
330
|
-
const value = await client.get(key);
|
|
331
|
-
recordCacheEvent({
|
|
332
|
-
operation: "get",
|
|
333
|
-
key,
|
|
334
|
-
summary: value == null ? "Cache miss" : "Cache hit",
|
|
335
|
-
details: {
|
|
336
|
-
hit: value != null,
|
|
337
|
-
durationMs: Date.now() - startedAt,
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
return value;
|
|
341
|
-
} catch (error) {
|
|
342
|
-
recordCacheEvent({
|
|
343
|
-
operation: "get.failed",
|
|
344
|
-
key,
|
|
345
|
-
summary: "Cache get failed",
|
|
346
|
-
details: {
|
|
347
|
-
durationMs: Date.now() - startedAt,
|
|
348
|
-
error: errorMessage(error),
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
throw error;
|
|
352
|
-
}
|
|
353
|
-
},
|
|
354
|
-
|
|
355
|
-
set: async (key, value, options) => {
|
|
356
|
-
const startedAt = Date.now();
|
|
357
|
-
try {
|
|
358
|
-
if (options?.ttlSeconds != null && options.ttlSeconds > 0) {
|
|
359
|
-
await client.set(key, value, "EX", options.ttlSeconds);
|
|
360
|
-
} else {
|
|
361
|
-
await client.set(key, value);
|
|
362
|
-
}
|
|
363
|
-
recordCacheEvent({
|
|
364
|
-
operation: "set",
|
|
365
|
-
key,
|
|
366
|
-
summary: "Cache set",
|
|
367
|
-
details: {
|
|
368
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
369
|
-
durationMs: Date.now() - startedAt,
|
|
370
|
-
},
|
|
371
|
-
});
|
|
372
|
-
} catch (error) {
|
|
373
|
-
recordCacheEvent({
|
|
374
|
-
operation: "set.failed",
|
|
375
|
-
key,
|
|
376
|
-
summary: "Cache set failed",
|
|
377
|
-
details: {
|
|
378
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
379
|
-
durationMs: Date.now() - startedAt,
|
|
380
|
-
error: errorMessage(error),
|
|
381
|
-
},
|
|
382
|
-
});
|
|
383
|
-
throw error;
|
|
384
|
-
}
|
|
385
|
-
},
|
|
386
|
-
|
|
387
|
-
delete: async (key) => {
|
|
388
|
-
const startedAt = Date.now();
|
|
389
|
-
try {
|
|
390
|
-
const deleted = (await client.del(key)) > 0;
|
|
391
|
-
recordCacheEvent({
|
|
392
|
-
operation: "delete",
|
|
393
|
-
key,
|
|
394
|
-
summary: deleted ? "Cache key deleted" : "Cache key missing",
|
|
395
|
-
details: {
|
|
396
|
-
deleted,
|
|
397
|
-
durationMs: Date.now() - startedAt,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
400
|
-
return deleted;
|
|
401
|
-
} catch (error) {
|
|
402
|
-
recordCacheEvent({
|
|
403
|
-
operation: "delete.failed",
|
|
404
|
-
key,
|
|
405
|
-
summary: "Cache delete failed",
|
|
406
|
-
details: {
|
|
407
|
-
durationMs: Date.now() - startedAt,
|
|
408
|
-
error: errorMessage(error),
|
|
409
|
-
},
|
|
410
|
-
});
|
|
411
|
-
throw error;
|
|
412
|
-
}
|
|
413
|
-
},
|
|
414
|
-
|
|
415
|
-
has: async (key) => {
|
|
416
|
-
const startedAt = Date.now();
|
|
417
|
-
try {
|
|
418
|
-
const result = await client.exists(key);
|
|
419
|
-
const exists = result === 1;
|
|
420
|
-
recordCacheEvent({
|
|
421
|
-
operation: "has",
|
|
422
|
-
key,
|
|
423
|
-
summary: exists ? "Cache key exists" : "Cache key missing",
|
|
424
|
-
details: {
|
|
425
|
-
exists,
|
|
426
|
-
durationMs: Date.now() - startedAt,
|
|
427
|
-
},
|
|
428
|
-
});
|
|
429
|
-
return exists;
|
|
430
|
-
} catch (error) {
|
|
431
|
-
recordCacheEvent({
|
|
432
|
-
operation: "has.failed",
|
|
433
|
-
key,
|
|
434
|
-
summary: "Cache has failed",
|
|
435
|
-
details: {
|
|
436
|
-
durationMs: Date.now() - startedAt,
|
|
437
|
-
error: errorMessage(error),
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
throw error;
|
|
441
|
-
}
|
|
442
|
-
},
|
|
443
|
-
|
|
444
|
-
remember: async (key, factory, options) => {
|
|
445
|
-
const startedAt = Date.now();
|
|
446
|
-
try {
|
|
447
|
-
const cached = await client.get(key);
|
|
448
|
-
if (cached != null) {
|
|
449
|
-
recordCacheEvent({
|
|
450
|
-
operation: "remember",
|
|
451
|
-
key,
|
|
452
|
-
summary: "Cache remember hit",
|
|
453
|
-
details: {
|
|
454
|
-
hit: true,
|
|
455
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
456
|
-
durationMs: Date.now() - startedAt,
|
|
457
|
-
},
|
|
458
|
-
});
|
|
459
|
-
return cached;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
const value = await factory();
|
|
463
|
-
if (options?.ttlSeconds != null && options.ttlSeconds > 0) {
|
|
464
|
-
await client.set(key, value, "EX", options.ttlSeconds);
|
|
465
|
-
} else {
|
|
466
|
-
await client.set(key, value);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
recordCacheEvent({
|
|
470
|
-
operation: "remember",
|
|
471
|
-
key,
|
|
472
|
-
summary: "Cache remember miss",
|
|
473
|
-
details: {
|
|
474
|
-
hit: false,
|
|
475
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
476
|
-
durationMs: Date.now() - startedAt,
|
|
477
|
-
},
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
return value;
|
|
481
|
-
} catch (error) {
|
|
482
|
-
recordCacheEvent({
|
|
483
|
-
operation: "remember.failed",
|
|
484
|
-
key,
|
|
485
|
-
summary: "Cache remember failed",
|
|
486
|
-
details: {
|
|
487
|
-
ttlSeconds: options?.ttlSeconds ?? null,
|
|
488
|
-
durationMs: Date.now() - startedAt,
|
|
489
|
-
error: errorMessage(error),
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
throw error;
|
|
493
|
-
}
|
|
494
|
-
},
|
|
495
|
-
};
|
|
542
|
+
const cache = createRedisCache({ client, instrumentation: ports });
|
|
496
543
|
|
|
497
544
|
return {
|
|
498
545
|
ports: {
|
|
@@ -513,24 +560,3 @@ export function createRedisCacheProvider(
|
|
|
513
560
|
},
|
|
514
561
|
});
|
|
515
562
|
}
|
|
516
|
-
|
|
517
|
-
/**
|
|
518
|
-
* Default env-backed Redis provider.
|
|
519
|
-
*
|
|
520
|
-
* Configuration via environment variables:
|
|
521
|
-
* - REDIS_URL: Redis connection URL (required)
|
|
522
|
-
* - REDIS_DB: Redis database number (optional)
|
|
523
|
-
* - REDIS_CONNECT_TIMEOUT_MS: Initial connection timeout in ms (optional)
|
|
524
|
-
* - REDIS_MAX_RETRIES_PER_REQUEST: Per-command retry limit (optional)
|
|
525
|
-
* - REDIS_CONNECT_MAX_ATTEMPTS: Connection attempts before startup fails (optional)
|
|
526
|
-
*
|
|
527
|
-
* @example
|
|
528
|
-
* ```ts
|
|
529
|
-
* const server = await createNextServer({
|
|
530
|
-
* ports: basePorts,
|
|
531
|
-
* providers: [redisCacheProvider],
|
|
532
|
-
* // ...
|
|
533
|
-
* });
|
|
534
|
-
* ```
|
|
535
|
-
*/
|
|
536
|
-
export const redisCacheProvider = createRedisCacheProvider();
|