@beignet/provider-cache-redis 0.0.32
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 +162 -0
- package/LICENSE +21 -0
- package/README.md +256 -0
- package/dist/index.d.ts +176 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +414 -0
- package/dist/index.js.map +1 -0
- package/package.json +104 -0
- package/src/index.ts +536 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# @beignet/provider-redis
|
|
2
|
+
|
|
3
|
+
## 0.0.32
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f759611: Provider factory options now resolve through one framework-owned rule:
|
|
8
|
+
`ProviderConfigDef` gains an `overrides` map that merges defined values over
|
|
9
|
+
env-derived input before validation, and every first-party provider factory
|
|
10
|
+
uses it instead of hand-rolled schema defaults and setup-time fallbacks. The
|
|
11
|
+
precedence is now uniform — defined factory options win over environment
|
|
12
|
+
variables in all thirteen providers. This flips the previous env-wins
|
|
13
|
+
behavior of `provider-cache-redis`, `provider-locks-redis`, and the storage,
|
|
14
|
+
search, and blob providers; set the env var (or drop the option) if you
|
|
15
|
+
relied on env winning. `installProviderForTest(...)` gains an `env` option
|
|
16
|
+
that resolves config through the same loader, and unreachable missing-config
|
|
17
|
+
guards in the pino and inngest factories are gone.
|
|
18
|
+
- eb680ef: Provider naming now follows one implementation-first order matching the
|
|
19
|
+
factories. `@beignet/provider-redis` is renamed `@beignet/provider-cache-redis`
|
|
20
|
+
(the package fills the cache port; the raw client stays as the
|
|
21
|
+
`ctx.ports.redis` escape hatch) with `redisCacheProvider`,
|
|
22
|
+
`createRedisCacheProvider`, and provider name `cache-redis` matching
|
|
23
|
+
`locks-redis`. Default consts flip to implementation-first:
|
|
24
|
+
`loggerPinoProvider` is now `pinoLoggerProvider`, `mailResendProvider` is now
|
|
25
|
+
`resendMailProvider`, and `mailSmtpProvider` is now `smtpMailProvider`.
|
|
26
|
+
CLI starter templates and the `redis-cache` provider preset wire the new
|
|
27
|
+
names. No aliases are kept.
|
|
28
|
+
|
|
29
|
+
## 0.0.31
|
|
30
|
+
|
|
31
|
+
## 0.0.30
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 38d1770: Every published package now rebuilds in `prepack`, so publishing can never ship a stale `dist/` again (the 0.0.28 packaging incident). No runtime behavior changes.
|
|
36
|
+
|
|
37
|
+
## 0.0.29
|
|
38
|
+
|
|
39
|
+
## 0.0.28
|
|
40
|
+
|
|
41
|
+
## 0.0.27
|
|
42
|
+
|
|
43
|
+
### Patch Changes
|
|
44
|
+
|
|
45
|
+
- f2461a9: Add lazy Next route server loaders and update generated route files and package docs to avoid booting providers during production build imports.
|
|
46
|
+
|
|
47
|
+
## 0.0.26
|
|
48
|
+
|
|
49
|
+
## 0.0.25
|
|
50
|
+
|
|
51
|
+
## 0.0.24
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- d07a420: Redact Redis credentials from provider startup errors and show a production
|
|
56
|
+
hardening checklist when doctor reports deployment-readiness diagnostics.
|
|
57
|
+
|
|
58
|
+
## 0.0.23
|
|
59
|
+
|
|
60
|
+
## 0.0.22
|
|
61
|
+
|
|
62
|
+
### Patch Changes
|
|
63
|
+
|
|
64
|
+
- Add app-owned readiness helpers, first-party provider health checks, generated `/api/ready` routes, and doctor readiness hints.
|
|
65
|
+
- Normalize provider README setup guidance across first-party provider packages.
|
|
66
|
+
|
|
67
|
+
## 0.0.21
|
|
68
|
+
|
|
69
|
+
## 0.0.20
|
|
70
|
+
|
|
71
|
+
## 0.0.19
|
|
72
|
+
|
|
73
|
+
## 0.0.18
|
|
74
|
+
|
|
75
|
+
## 0.0.17
|
|
76
|
+
|
|
77
|
+
## 0.0.16
|
|
78
|
+
|
|
79
|
+
## 0.0.15
|
|
80
|
+
|
|
81
|
+
## 0.0.14
|
|
82
|
+
|
|
83
|
+
## 0.0.13
|
|
84
|
+
|
|
85
|
+
## 0.0.12
|
|
86
|
+
|
|
87
|
+
## 0.0.11
|
|
88
|
+
|
|
89
|
+
## 0.0.10
|
|
90
|
+
|
|
91
|
+
## 0.0.9
|
|
92
|
+
|
|
93
|
+
## 0.0.8
|
|
94
|
+
|
|
95
|
+
## 0.0.7
|
|
96
|
+
|
|
97
|
+
## 0.0.6
|
|
98
|
+
|
|
99
|
+
## 0.0.5
|
|
100
|
+
|
|
101
|
+
## 0.0.4
|
|
102
|
+
|
|
103
|
+
### Patch Changes
|
|
104
|
+
|
|
105
|
+
- 8bcb31f: Mark package READMEs with Beignet's experimental alpha status and 0.0.x stability expectations.
|
|
106
|
+
- d137044: Declare `@beignet/core` as a peer dependency with a lockstep version range in
|
|
107
|
+
every integration and provider package instead of a regular `"*"` dependency.
|
|
108
|
+
Installs now always resolve a single shared copy of core, so `instanceof`
|
|
109
|
+
checks such as `isContractError` and upload error identity keep working, and
|
|
110
|
+
mixed Beignet versions fail loudly at install time instead of at runtime.
|
|
111
|
+
|
|
112
|
+
If your package manager does not install peer dependencies automatically, add
|
|
113
|
+
`@beignet/core` to your app alongside these packages. `@beignet/nuqs` now also
|
|
114
|
+
declares `@beignet/react-query` as a peer dependency, and
|
|
115
|
+
`@beignet/provider-storage-s3` now expects you to install
|
|
116
|
+
`@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` yourself, matching
|
|
117
|
+
how other providers treat their SDKs.
|
|
118
|
+
|
|
119
|
+
- 603478f: Align package documentation with the canonical route registry and AppContext conventions.
|
|
120
|
+
- 1a79090: Emit Node-compatible ESM: all relative imports in published packages now carry explicit .js extensions, fixing ERR_MODULE_NOT_FOUND when running the CLI or importing package dist files under plain Node.
|
|
121
|
+
- 44f1192: Move first-party provider diagnostics to package-owned `beignet.provider`
|
|
122
|
+
manifest metadata and have doctor read installed provider package manifests.
|
|
123
|
+
- 2aa77ca: Add static provider metadata and provider wiring diagnostics for generated apps.
|
|
124
|
+
- 89390fe: Add `createRedisProvider(options)` factory with connection robustness controls
|
|
125
|
+
and move the raw client escape hatch to a separate port.
|
|
126
|
+
|
|
127
|
+
- New config: `REDIS_CONNECT_TIMEOUT_MS` (default 5000),
|
|
128
|
+
`REDIS_MAX_RETRIES_PER_REQUEST` (default 2), and
|
|
129
|
+
`REDIS_CONNECT_MAX_ATTEMPTS` (default 3), with matching factory options
|
|
130
|
+
`connectTimeoutMs`, `maxRetriesPerRequest`, `retryStrategy`, and `db`.
|
|
131
|
+
- Startup now fails fast with a clear error when Redis is unreachable instead
|
|
132
|
+
of retrying forever. After a successful connection, reconnects use capped
|
|
133
|
+
exponential backoff (max 2s).
|
|
134
|
+
- The provider now contributes `{ cache: CachePort, redis: { client } }`.
|
|
135
|
+
`RedisCachePort` is replaced by the exported `RedisProviderPorts` interface;
|
|
136
|
+
access the raw ioredis client through `ctx.ports.redis.client` instead of
|
|
137
|
+
casting `ctx.ports.cache`.
|
|
138
|
+
|
|
139
|
+
## 0.0.3
|
|
140
|
+
|
|
141
|
+
### Patch Changes
|
|
142
|
+
|
|
143
|
+
- Updated dependencies [3160184]
|
|
144
|
+
- Updated dependencies [254ef6d]
|
|
145
|
+
- Updated dependencies [4cb1784]
|
|
146
|
+
- Updated dependencies [8bd9085]
|
|
147
|
+
- @beignet/core@0.0.3
|
|
148
|
+
|
|
149
|
+
## 0.0.2
|
|
150
|
+
|
|
151
|
+
### Patch Changes
|
|
152
|
+
|
|
153
|
+
- Updated dependencies [90b29ad]
|
|
154
|
+
- Updated dependencies [07fa19c]
|
|
155
|
+
- Updated dependencies [08bae67]
|
|
156
|
+
- Updated dependencies [730a818]
|
|
157
|
+
- Updated dependencies [a79f60c]
|
|
158
|
+
- @beignet/core@0.0.2
|
|
159
|
+
|
|
160
|
+
## 0.0.1
|
|
161
|
+
|
|
162
|
+
- Initial Beignet release under the `@beignet` npm scope.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Taylor Bryant
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# @beignet/provider-cache-redis
|
|
2
|
+
|
|
3
|
+
> [!CAUTION]
|
|
4
|
+
> Beignet is experimental alpha software. The `0.0.x` package line is for early
|
|
5
|
+
> evaluation, and APIs may change between releases while the framework settles.
|
|
6
|
+
|
|
7
|
+
Redis-backed `CachePort` provider for Beignet applications.
|
|
8
|
+
|
|
9
|
+
The provider installs `ctx.ports.cache` using
|
|
10
|
+
[ioredis](https://github.com/redis/ioredis) and exposes the raw Redis client
|
|
11
|
+
separately as `ctx.ports.redis` for Redis-specific features.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @beignet/provider-cache-redis @beignet/core ioredis
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { createNextServer, createNextServerLoader } from "@beignet/next";
|
|
23
|
+
import { definePorts } from "@beignet/core/ports";
|
|
24
|
+
import { redisCacheProvider } from "@beignet/provider-cache-redis";
|
|
25
|
+
import { routes } from "@/server/routes";
|
|
26
|
+
|
|
27
|
+
// Set environment variables:
|
|
28
|
+
// REDIS_URL=redis://localhost:6379
|
|
29
|
+
// REDIS_DB=0 (optional)
|
|
30
|
+
|
|
31
|
+
const appPorts = definePorts({});
|
|
32
|
+
|
|
33
|
+
export const getServer = createNextServerLoader(() =>
|
|
34
|
+
createNextServer({
|
|
35
|
+
ports: appPorts,
|
|
36
|
+
providers: [redisCacheProvider],
|
|
37
|
+
context: ({ ports }) => ({
|
|
38
|
+
ports,
|
|
39
|
+
}),
|
|
40
|
+
routes,
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`beignet doctor --strict` checks that installed Redis cache providers are
|
|
46
|
+
registered in `server/providers.ts` and that `REDIS_URL` is present in app env
|
|
47
|
+
examples or config when the env-backed provider is used.
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
Once the provider is registered, your ports will include a `cache` property:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// In your use case
|
|
55
|
+
async function getUserProfile(ctx: AppContext) {
|
|
56
|
+
const userId = ctx.actor.type === "user" ? ctx.actor.id : undefined;
|
|
57
|
+
if (!userId) throw new Error("User actor required.");
|
|
58
|
+
|
|
59
|
+
const cacheKey = `user:${userId}:profile`;
|
|
60
|
+
|
|
61
|
+
// Try to get from cache
|
|
62
|
+
const cached = await ctx.ports.cache.get(cacheKey);
|
|
63
|
+
if (cached) {
|
|
64
|
+
return JSON.parse(cached);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Fetch from database
|
|
68
|
+
const profile = await ctx.ports.db.users.findById(userId);
|
|
69
|
+
|
|
70
|
+
// Store in cache for 1 hour
|
|
71
|
+
await ctx.ports.cache.set(
|
|
72
|
+
cacheKey,
|
|
73
|
+
JSON.stringify(profile),
|
|
74
|
+
{ ttlSeconds: 3600 }
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
return profile;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Configuration
|
|
82
|
+
|
|
83
|
+
The Redis provider reads configuration from environment variables with the `REDIS_` prefix:
|
|
84
|
+
|
|
85
|
+
| Variable | Required | Description | Example |
|
|
86
|
+
|----------|----------|-------------|---------|
|
|
87
|
+
| `REDIS_URL` | Yes | Redis connection URL | `redis://localhost:6379` |
|
|
88
|
+
| `REDIS_DB` | No | Redis database number (default: 0) | `0` |
|
|
89
|
+
| `REDIS_CONNECT_TIMEOUT_MS` | No | Initial connection timeout in milliseconds (default: 5000) | `2000` |
|
|
90
|
+
| `REDIS_MAX_RETRIES_PER_REQUEST` | No | Per-command retry limit before the command rejects (default: 2) | `1` |
|
|
91
|
+
| `REDIS_CONNECT_MAX_ATTEMPTS` | No | Connection attempts before startup fails (default: 3) | `5` |
|
|
92
|
+
|
|
93
|
+
### Factory options
|
|
94
|
+
|
|
95
|
+
Use `createRedisCacheProvider(options)` when the app should own connection
|
|
96
|
+
defaults. Defined options override matching `REDIS_*` environment variables:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { createRedisCacheProvider } from "@beignet/provider-cache-redis";
|
|
100
|
+
|
|
101
|
+
const redisCacheProvider = createRedisCacheProvider({
|
|
102
|
+
connectTimeoutMs: 2000,
|
|
103
|
+
maxRetriesPerRequest: 1,
|
|
104
|
+
db: 1,
|
|
105
|
+
// Optional: replace the default retry strategy entirely.
|
|
106
|
+
retryStrategy: (times) => Math.min(times * 100, 1000),
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The default `redisCacheProvider` export is `createRedisCacheProvider()` with no options.
|
|
111
|
+
|
|
112
|
+
### Connection behavior
|
|
113
|
+
|
|
114
|
+
During startup the provider connects eagerly and fails fast: the initial
|
|
115
|
+
`connect()` stops retrying after `REDIS_CONNECT_MAX_ATTEMPTS` attempts (or
|
|
116
|
+
after `REDIS_CONNECT_TIMEOUT_MS` per attempt) and throws a clear error instead
|
|
117
|
+
of hanging against an unreachable host. After a successful connection, lost
|
|
118
|
+
connections are retried forever with capped exponential backoff (maximum 2
|
|
119
|
+
seconds between attempts).
|
|
120
|
+
|
|
121
|
+
## Cache port API
|
|
122
|
+
|
|
123
|
+
The provider extends your ports with the following cache interface:
|
|
124
|
+
|
|
125
|
+
### `get(key: string): Promise<string | null>`
|
|
126
|
+
|
|
127
|
+
Get a value from the cache.
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const value = await ctx.ports.cache.get("my-key");
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `set(key: string, value: string, options?: { ttlSeconds?: number }): Promise<void>`
|
|
134
|
+
|
|
135
|
+
Set a value in the cache with optional TTL (time-to-live) in seconds.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Without TTL (persists forever)
|
|
139
|
+
await ctx.ports.cache.set("key", "value");
|
|
140
|
+
|
|
141
|
+
// With TTL (expires after 1 hour)
|
|
142
|
+
await ctx.ports.cache.set("key", "value", { ttlSeconds: 3600 });
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### `delete(key: string): Promise<boolean>`
|
|
146
|
+
|
|
147
|
+
Delete a key from the cache. Returns `true` when a key was deleted.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const deleted = await ctx.ports.cache.delete("my-key");
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `has(key: string): Promise<boolean>`
|
|
154
|
+
|
|
155
|
+
Check if a key exists in the cache.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
const exists = await ctx.ports.cache.has("my-key");
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `remember(key: string, factory: () => Promise<string>, options?: { ttlSeconds?: number }): Promise<string>`
|
|
162
|
+
|
|
163
|
+
Return the cached value when present. On a miss, compute, store, and return the
|
|
164
|
+
factory value.
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const value = await ctx.ports.cache.remember(
|
|
168
|
+
"my-key",
|
|
169
|
+
async () => JSON.stringify(await loadExpensiveData()),
|
|
170
|
+
{ ttlSeconds: 300 },
|
|
171
|
+
);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Escape hatch
|
|
175
|
+
|
|
176
|
+
The provider also contributes `ctx.ports.redis` with the raw ioredis client
|
|
177
|
+
for operations the stable cache port does not model:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// Use ioredis methods directly
|
|
181
|
+
await ctx.ports.redis.client.expire("key", 300);
|
|
182
|
+
await ctx.ports.redis.client.incr("counter");
|
|
183
|
+
|
|
184
|
+
// Use from app-owned readiness endpoints
|
|
185
|
+
const health = await ctx.ports.redis.checkHealth();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Use the stable `CachePort` for normal application behavior. Use the raw client
|
|
189
|
+
only when the Redis-specific operation is intentional. `checkHealth()` sends a
|
|
190
|
+
cheap Redis `PING` and returns a structured result instead of throwing.
|
|
191
|
+
|
|
192
|
+
## Failure behavior
|
|
193
|
+
|
|
194
|
+
The env-backed provider throws during startup when `REDIS_URL` is missing or
|
|
195
|
+
Redis cannot be reached within the configured connection attempts. Runtime cache
|
|
196
|
+
operations throw Redis errors so callers can decide whether to fail, retry, or
|
|
197
|
+
fall back to the source of truth.
|
|
198
|
+
|
|
199
|
+
## Devtools
|
|
200
|
+
|
|
201
|
+
When `@beignet/devtools` is installed before this provider, Redis cache
|
|
202
|
+
operations appear under the dashboard's Cache watcher.
|
|
203
|
+
|
|
204
|
+
The provider records `cache.get`, `cache.set`, `cache.delete`, `cache.has`, and
|
|
205
|
+
`cache.remember` events with the cache key, hit/miss or deleted status, TTL, and
|
|
206
|
+
duration. Cached values are not recorded.
|
|
207
|
+
|
|
208
|
+
## TypeScript support
|
|
209
|
+
|
|
210
|
+
To get proper type inference for the contributed ports, extend your ports
|
|
211
|
+
type with `RedisCacheProviderPorts`:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import type { RedisCacheProviderPorts } from "@beignet/provider-cache-redis";
|
|
215
|
+
|
|
216
|
+
// Your base ports, if any
|
|
217
|
+
const basePorts = definePorts({});
|
|
218
|
+
|
|
219
|
+
// After using redisCacheProvider, your ports will have this shape:
|
|
220
|
+
type AppPorts = typeof basePorts & RedisCacheProviderPorts;
|
|
221
|
+
// { cache: CachePort; redis: { client: Redis } }
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Lifecycle
|
|
225
|
+
|
|
226
|
+
The Redis provider:
|
|
227
|
+
|
|
228
|
+
1. **During `setup`**: Connects to Redis and returns the `cache` and `redis`
|
|
229
|
+
ports
|
|
230
|
+
2. **During `stop`**: Gracefully closes the Redis connection
|
|
231
|
+
|
|
232
|
+
## Error handling
|
|
233
|
+
|
|
234
|
+
The provider will throw errors in these cases:
|
|
235
|
+
|
|
236
|
+
- Missing `REDIS_URL` environment variable
|
|
237
|
+
- Failed connection to Redis server after `REDIS_CONNECT_MAX_ATTEMPTS`
|
|
238
|
+
attempts
|
|
239
|
+
|
|
240
|
+
Make sure to handle these during application startup.
|
|
241
|
+
|
|
242
|
+
## Local and tests
|
|
243
|
+
|
|
244
|
+
Use an app-owned fake or memory `CachePort` in use-case tests. For local
|
|
245
|
+
development, either run Redis locally or omit this provider until cache behavior
|
|
246
|
+
is part of the workflow being tested.
|
|
247
|
+
|
|
248
|
+
## Deployment notes
|
|
249
|
+
|
|
250
|
+
Set `REDIS_DB` or a key prefix strategy per app/environment if Redis is shared.
|
|
251
|
+
Cache values are never the source of truth; design misses and Redis outages so
|
|
252
|
+
the app can rebuild from durable state when appropriate.
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @beignet/provider-cache-redis
|
|
3
|
+
*
|
|
4
|
+
* Redis provider that extends ports with cache capabilities using ioredis.
|
|
5
|
+
*
|
|
6
|
+
* Configuration:
|
|
7
|
+
* - REDIS_URL: Redis connection URL (required)
|
|
8
|
+
* - REDIS_DB: Redis database number (optional, default: 0)
|
|
9
|
+
* - REDIS_CONNECT_TIMEOUT_MS: Initial connection timeout in milliseconds (optional, default: 5000)
|
|
10
|
+
* - REDIS_MAX_RETRIES_PER_REQUEST: Per-command retry limit (optional, default: 2)
|
|
11
|
+
* - REDIS_CONNECT_MAX_ATTEMPTS: Connection attempts before startup fails (optional, default: 3)
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { createNextServer } from "@beignet/next";
|
|
16
|
+
* import { redisCacheProvider } from "@beignet/provider-cache-redis";
|
|
17
|
+
*
|
|
18
|
+
* const server = await createNextServer({
|
|
19
|
+
* ports: basePorts,
|
|
20
|
+
* providers: [redisCacheProvider],
|
|
21
|
+
* // ...
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // In your use cases:
|
|
25
|
+
* async function myUseCase(ctx: AppCtx) {
|
|
26
|
+
* const cached = await ctx.ports.cache.get("key");
|
|
27
|
+
* if (!cached) {
|
|
28
|
+
* const value = await fetchData();
|
|
29
|
+
* await ctx.ports.cache.set("key", value, { ttlSeconds: 3600 });
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
import type { CachePort } from "@beignet/core/ports";
|
|
35
|
+
import { Redis } from "ioredis";
|
|
36
|
+
import { z } from "zod";
|
|
37
|
+
/**
|
|
38
|
+
* Configuration schema for the Redis provider.
|
|
39
|
+
* Validates environment variables with REDIS_ prefix.
|
|
40
|
+
*/
|
|
41
|
+
declare const RedisConfigSchema: z.ZodObject<{
|
|
42
|
+
URL: z.ZodString;
|
|
43
|
+
DB: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
44
|
+
CONNECT_TIMEOUT_MS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
45
|
+
MAX_RETRIES_PER_REQUEST: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
46
|
+
CONNECT_MAX_ATTEMPTS: z.ZodDefault<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>>;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
/**
|
|
49
|
+
* Inferred configuration type for Redis provider.
|
|
50
|
+
*/
|
|
51
|
+
export type RedisConfig = z.infer<typeof RedisConfigSchema>;
|
|
52
|
+
/**
|
|
53
|
+
* Options for {@link createRedisCacheProvider}.
|
|
54
|
+
*
|
|
55
|
+
* Numeric options become schema defaults, so matching `REDIS_*` environment
|
|
56
|
+
* variables still win when both are set.
|
|
57
|
+
*/
|
|
58
|
+
export interface RedisCacheProviderOptions {
|
|
59
|
+
/**
|
|
60
|
+
* Timeout for the initial connection attempt, in milliseconds.
|
|
61
|
+
*
|
|
62
|
+
* @default 5000
|
|
63
|
+
*/
|
|
64
|
+
connectTimeoutMs?: number;
|
|
65
|
+
/**
|
|
66
|
+
* How many times a single command is retried before its promise rejects.
|
|
67
|
+
*
|
|
68
|
+
* @default 2
|
|
69
|
+
*/
|
|
70
|
+
maxRetriesPerRequest?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Custom ioredis retry strategy. Replaces the default strategy, which stops
|
|
73
|
+
* retrying after `REDIS_CONNECT_MAX_ATTEMPTS` during the initial connect and
|
|
74
|
+
* reconnects with capped exponential backoff afterwards.
|
|
75
|
+
*/
|
|
76
|
+
retryStrategy?: (times: number) => number | null;
|
|
77
|
+
/**
|
|
78
|
+
* Redis database number.
|
|
79
|
+
*
|
|
80
|
+
* @default 0
|
|
81
|
+
*/
|
|
82
|
+
db?: number;
|
|
83
|
+
}
|
|
84
|
+
export interface RedisHealth {
|
|
85
|
+
ok: boolean;
|
|
86
|
+
message?: string;
|
|
87
|
+
metadata: {
|
|
88
|
+
adapter: "redis";
|
|
89
|
+
response?: string;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Escape hatch for apps that need the raw ioredis client.
|
|
94
|
+
*/
|
|
95
|
+
export interface RedisEscapeHatch {
|
|
96
|
+
/**
|
|
97
|
+
* Raw ioredis client.
|
|
98
|
+
* Use this for Redis operations the stable cache port does not model.
|
|
99
|
+
*/
|
|
100
|
+
client: Redis;
|
|
101
|
+
/**
|
|
102
|
+
* Check whether Redis responds to a cheap PING command.
|
|
103
|
+
*/
|
|
104
|
+
checkHealth(): Promise<RedisHealth>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Ports contributed by the Redis provider.
|
|
108
|
+
*/
|
|
109
|
+
export interface RedisCacheProviderPorts {
|
|
110
|
+
/**
|
|
111
|
+
* Beignet cache port backed by Redis.
|
|
112
|
+
*/
|
|
113
|
+
cache: CachePort;
|
|
114
|
+
/**
|
|
115
|
+
* Raw ioredis client escape hatch.
|
|
116
|
+
*/
|
|
117
|
+
redis: RedisEscapeHatch;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Create an env-backed Redis cache provider.
|
|
121
|
+
*
|
|
122
|
+
* Reads `REDIS_*` env vars, contributes `ports.cache`, and exposes
|
|
123
|
+
* `ports.redis` for raw ioredis client access.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```ts
|
|
127
|
+
* const provider = createRedisCacheProvider({ connectTimeoutMs: 2000 });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export declare function createRedisCacheProvider(options?: RedisCacheProviderOptions): import("@beignet/core/providers").ServiceProvider<unknown, z.ZodObject<{
|
|
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 {};
|
|
176
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;AAKrD,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAYxB;;;GAGG;AACH,QAAA,MAAM,iBAAiB;;;;;;iBAgCrB,CAAC;AAEH;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D;;;;;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;AAuCD;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,yBAA8B;;;;;;;;;;;;kBAwSxC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;iBAA6B,CAAC"}
|