@effect/platform-node 4.0.0-rc.108 → 4.0.0-rc.110
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/AGENTS.md +14 -0
- package/CLAUDE.md +14 -0
- package/README.md +1 -1
- package/ai-docs/src/40_sql/10_basics.ts +175 -0
- package/ai-docs/src/40_sql/index.md +6 -0
- package/ai-docs/src/51_http-server/10_basics.ts +3 -1
- package/ai-docs/src/51_http-server/20_testing.ts +100 -0
- package/ai-docs/src/51_http-server/fixtures/api/Users.ts +30 -14
- package/ai-docs/src/51_http-server/fixtures/domain/User.ts +15 -6
- package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +11 -10
- package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +41 -32
- package/ai-docs/src/51_http-server/fixtures/server/Users.ts +141 -20
- package/ai-docs/src/70_cli/10_basics.ts +31 -5
- package/dist/NodeRedis.d.ts +40 -15
- package/dist/NodeRedis.d.ts.map +1 -1
- package/dist/NodeRedis.js +66 -16
- package/dist/NodeRedis.js.map +1 -1
- package/package.json +6 -5
- package/src/NodeRedis.ts +86 -26
package/src/NodeRedis.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Node.js Redis integration backed by `
|
|
2
|
+
* Node.js Redis integration backed by `redis` (node-redis).
|
|
3
3
|
*
|
|
4
|
-
* This module creates a scoped `
|
|
4
|
+
* This module creates a scoped `node-redis` client and exposes it in two forms:
|
|
5
5
|
* the generic `Redis` service and the {@link NodeRedis} service for direct
|
|
6
|
-
* access to the underlying client. `layer` accepts
|
|
7
|
-
* while `layerConfig` reads them from Effect config.
|
|
8
|
-
*
|
|
6
|
+
* access to the underlying client. `layer` accepts node-redis client options
|
|
7
|
+
* directly, while `layerConfig` reads them from Effect config. `node-redis`
|
|
8
|
+
* connects explicitly, so layer construction can fail with a `RedisError`.
|
|
9
|
+
* Both layers close the client when the layer scope ends.
|
|
9
10
|
*
|
|
10
11
|
* @since 4.0.0
|
|
11
12
|
*/
|
|
@@ -14,31 +15,68 @@ import * as Context from "effect/Context"
|
|
|
14
15
|
import * as Effect from "effect/Effect"
|
|
15
16
|
import * as Fn from "effect/Function"
|
|
16
17
|
import * as Layer from "effect/Layer"
|
|
17
|
-
import * as Scope from "effect/Scope"
|
|
18
18
|
import * as Redis from "effect/unstable/persistence/Redis"
|
|
19
|
-
import
|
|
19
|
+
import { createClient, SocketTimeoutError } from "redis"
|
|
20
|
+
|
|
21
|
+
type NodeRedisClient = ReturnType<typeof createClient>
|
|
22
|
+
type NodeRedisClientOptions = NonNullable<Parameters<typeof createClient>[0]>
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* Service tag for the Node Redis integration, exposing the underlying
|
|
23
|
-
* `
|
|
26
|
+
* `node-redis` client and a `use` helper that maps client failures to
|
|
24
27
|
* `RedisError`.
|
|
25
28
|
*
|
|
26
29
|
* @category services
|
|
27
30
|
* @since 4.0.0
|
|
28
31
|
*/
|
|
29
32
|
export class NodeRedis extends Context.Service<NodeRedis, {
|
|
30
|
-
readonly client:
|
|
31
|
-
readonly use: <A>(f: (client:
|
|
33
|
+
readonly client: NodeRedisClient
|
|
34
|
+
readonly use: <A>(f: (client: NodeRedisClient) => Promise<A>) => Effect.Effect<A, Redis.RedisError>
|
|
32
35
|
}>()("@effect/platform-node/NodeRedis") {}
|
|
33
36
|
|
|
34
37
|
const make = Effect.fnUntraced(function*(
|
|
35
|
-
options?:
|
|
38
|
+
options?: NodeRedisClientOptions
|
|
36
39
|
) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const client =
|
|
40
|
+
let ready = false
|
|
41
|
+
const socket = options?.socket
|
|
42
|
+
const client = yield* Effect.acquireRelease(
|
|
43
|
+
Effect.sync((): NodeRedisClient =>
|
|
44
|
+
createClient({
|
|
45
|
+
...options,
|
|
46
|
+
socket: socket?.reconnectStrategy === undefined
|
|
47
|
+
? {
|
|
48
|
+
...socket,
|
|
49
|
+
reconnectStrategy: (retries, cause) => {
|
|
50
|
+
if (!ready) return cause
|
|
51
|
+
if (cause instanceof SocketTimeoutError) return false
|
|
52
|
+
const jitter = Math.floor(Math.random() * 200)
|
|
53
|
+
const delay = Math.min(2 ** retries * 50, 2000)
|
|
54
|
+
return delay + jitter
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
: socket
|
|
58
|
+
})
|
|
59
|
+
),
|
|
60
|
+
(client) => Effect.ignoreCause(Effect.promise(() => client.close()))
|
|
61
|
+
)
|
|
62
|
+
client.once("ready", () => {
|
|
63
|
+
ready = true
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// node-redis rethrows `error` events that have no listener, which would crash
|
|
67
|
+
// the process on a transient socket failure. Command failures are still
|
|
68
|
+
// reported as `RedisError`.
|
|
69
|
+
const runSync = Effect.runSyncWith(yield* Effect.context<never>())
|
|
70
|
+
client.on("error", (cause) => {
|
|
71
|
+
runSync(Effect.logWarning("NodeRedis client error", cause))
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
yield* Effect.tryPromise({
|
|
75
|
+
try: () => client.connect(),
|
|
76
|
+
catch: (cause) => new Redis.RedisError({ cause })
|
|
77
|
+
})
|
|
40
78
|
|
|
41
|
-
const use = <A>(f: (client:
|
|
79
|
+
const use = <A>(f: (client: NodeRedisClient) => Promise<A>) =>
|
|
42
80
|
Effect.tryPromise({
|
|
43
81
|
try: () => f(client),
|
|
44
82
|
catch: (cause) => new Redis.RedisError({ cause })
|
|
@@ -47,7 +85,7 @@ const make = Effect.fnUntraced(function*(
|
|
|
47
85
|
const redis = yield* Redis.make({
|
|
48
86
|
send: <A = unknown>(command: string, ...args: ReadonlyArray<string>) =>
|
|
49
87
|
Effect.tryPromise({
|
|
50
|
-
try: () => client.
|
|
88
|
+
try: () => client.sendCommand([command, ...args]) as Promise<A>,
|
|
51
89
|
catch: (cause) => new Redis.RedisError({ cause })
|
|
52
90
|
})
|
|
53
91
|
})
|
|
@@ -63,28 +101,50 @@ const make = Effect.fnUntraced(function*(
|
|
|
63
101
|
})
|
|
64
102
|
|
|
65
103
|
/**
|
|
66
|
-
* Provides `Redis` and `NodeRedis` services backed by
|
|
67
|
-
* created with the supplied options
|
|
104
|
+
* Provides `Redis` and `NodeRedis` services backed by a `node-redis` client
|
|
105
|
+
* created with the supplied options, connected when the layer is built and
|
|
106
|
+
* closed when the layer scope ends.
|
|
107
|
+
*
|
|
108
|
+
* **Details**
|
|
109
|
+
*
|
|
110
|
+
* By default, the initial connection fails on its first connection error. A
|
|
111
|
+
* caller-supplied `socket.reconnectStrategy` is used instead when present. Once
|
|
112
|
+
* the client has emitted `ready`, the default reconnect strategy uses
|
|
113
|
+
* node-redis' exponential backoff and stops on socket timeouts.
|
|
114
|
+
*
|
|
115
|
+
* Scope finalization calls `close()`, which waits for in-flight commands,
|
|
116
|
+
* including blocking commands, and can therefore delay scope closure.
|
|
68
117
|
*
|
|
69
118
|
* @category layers
|
|
70
119
|
* @since 4.0.0
|
|
71
120
|
*/
|
|
72
121
|
export const layer = (
|
|
73
|
-
options?:
|
|
74
|
-
): Layer.Layer<Redis.Redis | NodeRedis> => Layer.effectContext(make(options))
|
|
122
|
+
options?: NodeRedisClientOptions | undefined
|
|
123
|
+
): Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError> => Layer.effectContext(make(options))
|
|
75
124
|
|
|
76
125
|
/**
|
|
77
|
-
* Provides `Redis` and `NodeRedis` services from `Config`-backed
|
|
78
|
-
* options,
|
|
126
|
+
* Provides `Redis` and `NodeRedis` services from `Config`-backed node-redis
|
|
127
|
+
* client options, connecting the client when the layer is built and closing it
|
|
128
|
+
* when the layer scope ends.
|
|
129
|
+
*
|
|
130
|
+
* **Details**
|
|
131
|
+
*
|
|
132
|
+
* By default, the initial connection fails on its first connection error. A
|
|
133
|
+
* caller-supplied `socket.reconnectStrategy` is used instead when present. Once
|
|
134
|
+
* the client has emitted `ready`, the default reconnect strategy uses
|
|
135
|
+
* node-redis' exponential backoff and stops on socket timeouts.
|
|
136
|
+
*
|
|
137
|
+
* Scope finalization calls `close()`, which waits for in-flight commands,
|
|
138
|
+
* including blocking commands, and can therefore delay scope closure.
|
|
79
139
|
*
|
|
80
140
|
* @category layers
|
|
81
141
|
* @since 4.0.0
|
|
82
142
|
*/
|
|
83
143
|
export const layerConfig: (
|
|
84
|
-
options: Config.Wrap<
|
|
85
|
-
) => Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> = (
|
|
86
|
-
options: Config.Wrap<
|
|
87
|
-
): Layer.Layer<Redis.Redis | NodeRedis, Config.ConfigError> =>
|
|
144
|
+
options: Config.Wrap<NodeRedisClientOptions>
|
|
145
|
+
) => Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError | Config.ConfigError> = (
|
|
146
|
+
options: Config.Wrap<NodeRedisClientOptions>
|
|
147
|
+
): Layer.Layer<Redis.Redis | NodeRedis, Redis.RedisError | Config.ConfigError> =>
|
|
88
148
|
Layer.effectContext(
|
|
89
149
|
Config.unwrap(options).pipe(
|
|
90
150
|
Effect.flatMap(make)
|