dfx 0.77.3 → 0.79.0
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/Cache/prelude.d.ts +3 -3
- package/DiscordConfig.d.ts +8 -10
- package/DiscordConfig.d.ts.map +1 -1
- package/DiscordConfig.js +1 -3
- package/DiscordConfig.js.map +1 -1
- package/DiscordGateway/DiscordWS.d.ts +12 -11
- package/DiscordGateway/DiscordWS.d.ts.map +1 -1
- package/DiscordGateway/DiscordWS.js +8 -5
- package/DiscordGateway/DiscordWS.js.map +1 -1
- package/DiscordGateway/Messaging.d.ts +28 -0
- package/DiscordGateway/Messaging.d.ts.map +1 -0
- package/DiscordGateway/Messaging.js +41 -0
- package/DiscordGateway/Messaging.js.map +1 -0
- package/DiscordGateway/Shard.d.ts +14 -11
- package/DiscordGateway/Shard.d.ts.map +1 -1
- package/DiscordGateway/Shard.js +22 -19
- package/DiscordGateway/Shard.js.map +1 -1
- package/DiscordGateway/ShardStore.d.ts +5 -2
- package/DiscordGateway/ShardStore.d.ts.map +1 -1
- package/DiscordGateway/ShardStore.js +1 -1
- package/DiscordGateway/ShardStore.js.map +1 -1
- package/DiscordGateway/Sharder.d.ts +9 -18
- package/DiscordGateway/Sharder.d.ts.map +1 -1
- package/DiscordGateway/Sharder.js +36 -45
- package/DiscordGateway/Sharder.js.map +1 -1
- package/DiscordGateway/WS.d.ts +14 -10
- package/DiscordGateway/WS.d.ts.map +1 -1
- package/DiscordGateway/WS.js +31 -21
- package/DiscordGateway/WS.js.map +1 -1
- package/DiscordGateway.d.ts +10 -7
- package/DiscordGateway.d.ts.map +1 -1
- package/DiscordGateway.js +13 -31
- package/DiscordGateway.js.map +1 -1
- package/DiscordREST.d.ts +6 -4
- package/DiscordREST.d.ts.map +1 -1
- package/DiscordREST.js +9 -8
- package/DiscordREST.js.map +1 -1
- package/Interactions/context.d.ts +36 -18
- package/Interactions/context.d.ts.map +1 -1
- package/Interactions/context.js +6 -6
- package/Interactions/context.js.map +1 -1
- package/Interactions/definitions.d.ts +11 -11
- package/Interactions/definitions.d.ts.map +1 -1
- package/Interactions/definitions.js.map +1 -1
- package/Interactions/gateway.d.ts +7 -6
- package/Interactions/gateway.d.ts.map +1 -1
- package/Interactions/gateway.js +1 -1
- package/Interactions/gateway.js.map +1 -1
- package/Interactions/handlers.d.ts +2 -1
- package/Interactions/handlers.d.ts.map +1 -1
- package/Interactions/webhook.d.ts +7 -8
- package/Interactions/webhook.d.ts.map +1 -1
- package/Interactions/webhook.js +3 -3
- package/Interactions/webhook.js.map +1 -1
- package/README.md +37 -29
- package/RateLimit/memory.d.ts +2 -2
- package/RateLimit/memory.d.ts.map +1 -1
- package/RateLimit/memory.js.map +1 -1
- package/RateLimit.d.ts +10 -9
- package/RateLimit.d.ts.map +1 -1
- package/RateLimit.js +4 -5
- package/RateLimit.js.map +1 -1
- package/gateway.d.ts +2 -2
- package/gateway.d.ts.map +1 -1
- package/gateway.js +1 -2
- package/gateway.js.map +1 -1
- package/index.d.ts +1 -2
- package/index.d.ts.map +1 -1
- package/index.js +1 -3
- package/index.js.map +1 -1
- package/mjs/DiscordConfig.mjs +1 -3
- package/mjs/DiscordConfig.mjs.map +1 -1
- package/mjs/DiscordGateway/DiscordWS.mjs +8 -5
- package/mjs/DiscordGateway/DiscordWS.mjs.map +1 -1
- package/mjs/DiscordGateway/Messaging.mjs +33 -0
- package/mjs/DiscordGateway/Messaging.mjs.map +1 -0
- package/mjs/DiscordGateway/Shard.mjs +22 -19
- package/mjs/DiscordGateway/Shard.mjs.map +1 -1
- package/mjs/DiscordGateway/ShardStore.mjs +1 -1
- package/mjs/DiscordGateway/ShardStore.mjs.map +1 -1
- package/mjs/DiscordGateway/Sharder.mjs +35 -44
- package/mjs/DiscordGateway/Sharder.mjs.map +1 -1
- package/mjs/DiscordGateway/WS.mjs +32 -22
- package/mjs/DiscordGateway/WS.mjs.map +1 -1
- package/mjs/DiscordGateway.mjs +12 -30
- package/mjs/DiscordGateway.mjs.map +1 -1
- package/mjs/DiscordREST.mjs +9 -8
- package/mjs/DiscordREST.mjs.map +1 -1
- package/mjs/Interactions/context.mjs +6 -6
- package/mjs/Interactions/context.mjs.map +1 -1
- package/mjs/Interactions/definitions.mjs.map +1 -1
- package/mjs/Interactions/gateway.mjs +1 -1
- package/mjs/Interactions/gateway.mjs.map +1 -1
- package/mjs/Interactions/webhook.mjs +3 -3
- package/mjs/Interactions/webhook.mjs.map +1 -1
- package/mjs/RateLimit/memory.mjs.map +1 -1
- package/mjs/RateLimit.mjs +4 -5
- package/mjs/RateLimit.mjs.map +1 -1
- package/mjs/gateway.mjs +1 -2
- package/mjs/gateway.mjs.map +1 -1
- package/mjs/index.mjs +1 -2
- package/mjs/index.mjs.map +1 -1
- package/mjs/version.mjs +1 -1
- package/mjs/webhooks.mjs +1 -2
- package/mjs/webhooks.mjs.map +1 -1
- package/package.json +4 -4
- package/src/DiscordConfig.ts +10 -12
- package/src/DiscordGateway/DiscordWS.ts +23 -13
- package/src/DiscordGateway/Messaging.ts +72 -0
- package/src/DiscordGateway/Shard.ts +53 -35
- package/src/DiscordGateway/ShardStore.ts +8 -3
- package/src/DiscordGateway/Sharder.ts +72 -97
- package/src/DiscordGateway/WS.ts +64 -32
- package/src/DiscordGateway.ts +22 -71
- package/src/DiscordREST.ts +42 -29
- package/src/Interactions/context.ts +47 -10
- package/src/Interactions/definitions.ts +22 -20
- package/src/Interactions/gateway.ts +14 -6
- package/src/Interactions/handlers.ts +1 -1
- package/src/Interactions/webhook.ts +21 -7
- package/src/RateLimit/memory.ts +2 -2
- package/src/RateLimit.ts +17 -8
- package/src/gateway.ts +0 -2
- package/src/index.ts +0 -2
- package/src/version.ts +1 -1
- package/src/webhooks.ts +1 -2
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/webhooks.d.ts +1 -2
- package/webhooks.d.ts.map +1 -1
- package/webhooks.js +1 -2
- package/webhooks.js.map +1 -1
- package/Log.d.ts +0 -14
- package/Log.d.ts.map +0 -1
- package/Log.js +0 -23
- package/Log.js.map +0 -1
- package/mjs/Log.mjs +0 -15
- package/mjs/Log.mjs.map +0 -1
- package/src/Log.ts +0 -24
|
@@ -3,7 +3,7 @@ import { Tag } from "effect/Context"
|
|
|
3
3
|
import * as Duration from "effect/Duration"
|
|
4
4
|
import { pipe } from "effect/Function"
|
|
5
5
|
import * as Option from "effect/Option"
|
|
6
|
-
import * as
|
|
6
|
+
import * as Secret from "effect/Secret"
|
|
7
7
|
import * as Effect from "effect/Effect"
|
|
8
8
|
import * as PubSub from "effect/PubSub"
|
|
9
9
|
import * as Layer from "effect/Layer"
|
|
@@ -17,9 +17,9 @@ import * as Identify from "dfx/DiscordGateway/Shard/identify"
|
|
|
17
17
|
import * as InvalidSession from "dfx/DiscordGateway/Shard/invalidSession"
|
|
18
18
|
import * as Utils from "dfx/DiscordGateway/Shard/utils"
|
|
19
19
|
import { Reconnect } from "dfx/DiscordGateway/WS"
|
|
20
|
-
import { Log } from "dfx/Log"
|
|
21
20
|
import { RateLimiterLive, RateLimiter } from "dfx/RateLimit"
|
|
22
21
|
import * as Discord from "dfx/types"
|
|
22
|
+
import { Messaging, MesssagingLive } from "dfx/DiscordGateway/Messaging"
|
|
23
23
|
|
|
24
24
|
const enum Phase {
|
|
25
25
|
Connecting,
|
|
@@ -31,19 +31,18 @@ export const make = Effect.gen(function* (_) {
|
|
|
31
31
|
const { gateway, token } = yield* _(DiscordConfig)
|
|
32
32
|
const limiter = yield* _(RateLimiter)
|
|
33
33
|
const dws = yield* _(DiscordWS)
|
|
34
|
-
const
|
|
34
|
+
const { hub, sendQueue } = yield* _(Messaging)
|
|
35
35
|
|
|
36
|
-
const connect = (
|
|
37
|
-
shard: [id: number, count: number],
|
|
38
|
-
hub: PubSub.PubSub<Discord.GatewayPayload<Discord.ReceiveEvent>>,
|
|
39
|
-
sendQueue: Queue.Dequeue<Discord.GatewayPayload<Discord.SendEvent>>,
|
|
40
|
-
) =>
|
|
36
|
+
const connect = (shard: [id: number, count: number]) =>
|
|
41
37
|
Effect.gen(function* (_) {
|
|
42
38
|
const outboundQueue = yield* _(Queue.unbounded<Message>())
|
|
43
39
|
const pendingQueue = yield* _(Queue.unbounded<Message>())
|
|
44
40
|
const phase = yield* _(Ref.make(Phase.Connecting))
|
|
45
41
|
const setPhase = (p: Phase) =>
|
|
46
|
-
Effect.zipLeft(
|
|
42
|
+
Effect.zipLeft(
|
|
43
|
+
Ref.set(phase, p),
|
|
44
|
+
Effect.annotateLogs(Effect.logTrace("phase transition"), "phase", p),
|
|
45
|
+
)
|
|
47
46
|
const outbound = Effect.zipLeft(
|
|
48
47
|
Queue.take(outboundQueue),
|
|
49
48
|
limiter.maybeWait("dfx.shard.send", Duration.minutes(1), 120),
|
|
@@ -115,21 +114,29 @@ export const make = Effect.gen(function* (_) {
|
|
|
115
114
|
},
|
|
116
115
|
)
|
|
117
116
|
|
|
118
|
-
const hellos = yield* _(
|
|
119
|
-
|
|
117
|
+
const hellos = yield* _(
|
|
118
|
+
Effect.acquireRelease(
|
|
119
|
+
Queue.unbounded<Discord.GatewayPayload>(),
|
|
120
|
+
Queue.shutdown,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
const acks = yield* _(
|
|
124
|
+
Effect.acquireRelease(
|
|
125
|
+
Queue.unbounded<Discord.GatewayPayload>(),
|
|
126
|
+
Queue.shutdown,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
120
129
|
|
|
121
130
|
// heartbeats
|
|
122
|
-
|
|
123
|
-
hellos,
|
|
124
|
-
|
|
125
|
-
latestSequence,
|
|
126
|
-
heartbeatSend,
|
|
131
|
+
yield* _(
|
|
132
|
+
Heartbeats.send(hellos, acks, latestSequence, heartbeatSend),
|
|
133
|
+
Effect.forkScoped,
|
|
127
134
|
)
|
|
128
135
|
|
|
129
136
|
// identify
|
|
130
137
|
const identify = Identify.identifyOrResume(
|
|
131
138
|
{
|
|
132
|
-
token:
|
|
139
|
+
token: Secret.value(token),
|
|
133
140
|
shard,
|
|
134
141
|
intents: gateway.intents,
|
|
135
142
|
presence: gateway.presence,
|
|
@@ -169,32 +176,43 @@ export const make = Effect.gen(function* (_) {
|
|
|
169
176
|
}),
|
|
170
177
|
)
|
|
171
178
|
|
|
172
|
-
|
|
173
|
-
|
|
179
|
+
yield* _(
|
|
180
|
+
Queue.take(sendQueue),
|
|
181
|
+
Effect.tap(send),
|
|
182
|
+
Effect.forever,
|
|
183
|
+
Effect.forkScoped,
|
|
174
184
|
)
|
|
175
185
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
socket.run,
|
|
182
|
-
],
|
|
183
|
-
{ discard: true, concurrency: "unbounded" },
|
|
186
|
+
yield* _(
|
|
187
|
+
socket.take,
|
|
188
|
+
Effect.flatMap(onPayload),
|
|
189
|
+
Effect.forever,
|
|
190
|
+
Effect.forkScoped,
|
|
184
191
|
)
|
|
185
192
|
|
|
186
|
-
return { id: shard, send
|
|
187
|
-
})
|
|
193
|
+
return { id: shard, send } as const
|
|
194
|
+
}).pipe(
|
|
195
|
+
Effect.annotateLogs({
|
|
196
|
+
package: "dfx",
|
|
197
|
+
module: "DiscordGateway/Shard",
|
|
198
|
+
shard,
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
188
201
|
|
|
189
202
|
return { connect } as const
|
|
190
203
|
})
|
|
191
204
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
export
|
|
195
|
-
|
|
196
|
-
|
|
205
|
+
type ShardService = Effect.Effect.Success<typeof make>
|
|
206
|
+
|
|
207
|
+
export interface Shard {
|
|
208
|
+
readonly _: unique symbol
|
|
209
|
+
}
|
|
210
|
+
export const Shard = Tag<Shard, ShardService>("dfx/DiscordGateway/Shard")
|
|
211
|
+
export const ShardLive = Layer.effect(Shard, make).pipe(
|
|
212
|
+
Layer.provide(DiscordWSLive),
|
|
213
|
+
Layer.provide(MesssagingLive),
|
|
214
|
+
Layer.provide(RateLimiterLive),
|
|
197
215
|
)
|
|
198
216
|
|
|
199
217
|
export interface RunningShard
|
|
200
|
-
extends Effect.Effect.Success<ReturnType<
|
|
218
|
+
extends Effect.Effect.Success<ReturnType<ShardService["connect"]>> {}
|
|
@@ -8,17 +8,22 @@ export interface ClaimIdContext {
|
|
|
8
8
|
totalCount: number
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export interface
|
|
11
|
+
export interface ShardStoreService {
|
|
12
12
|
claimId: (
|
|
13
13
|
ctx: ClaimIdContext,
|
|
14
14
|
) => Effect.Effect<never, never, Option.Option<number>>
|
|
15
15
|
allClaimed: (totalCount: number) => Effect.Effect<never, never, boolean>
|
|
16
16
|
heartbeat?: (shardId: number) => Effect.Effect<never, never, void>
|
|
17
17
|
}
|
|
18
|
-
export
|
|
18
|
+
export interface ShardStore {
|
|
19
|
+
readonly _: unique symbol
|
|
20
|
+
}
|
|
21
|
+
export const ShardStore = Tag<ShardStore, ShardStoreService>(
|
|
22
|
+
"dfx/DiscordGateway/ShardStore",
|
|
23
|
+
)
|
|
19
24
|
|
|
20
25
|
// Very basic shard id store, that does no health checks
|
|
21
|
-
const memoryStore = ():
|
|
26
|
+
const memoryStore = (): ShardStoreService => {
|
|
22
27
|
let currentId = 0
|
|
23
28
|
|
|
24
29
|
return {
|
|
@@ -1,24 +1,19 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { DiscordConfig } from "dfx/DiscordConfig"
|
|
2
|
+
import type { RunningShard } from "dfx/DiscordGateway/Shard"
|
|
3
|
+
import { Shard, ShardLive } from "dfx/DiscordGateway/Shard"
|
|
4
|
+
import { ShardStore } from "dfx/DiscordGateway/ShardStore"
|
|
5
|
+
import { DiscordREST, DiscordRESTLive } from "dfx/DiscordREST"
|
|
6
|
+
import { RateLimiter, RateLimiterLive } from "dfx/RateLimit"
|
|
7
|
+
import type * as Discord from "dfx/types"
|
|
2
8
|
import { Tag } from "effect/Context"
|
|
3
9
|
import * as Duration from "effect/Duration"
|
|
10
|
+
import * as Effect from "effect/Effect"
|
|
4
11
|
import { pipe } from "effect/Function"
|
|
5
12
|
import * as HashSet from "effect/HashSet"
|
|
6
|
-
import type * as Option from "effect/Option"
|
|
7
|
-
import * as Deferred from "effect/Deferred"
|
|
8
|
-
import * as Effect from "effect/Effect"
|
|
9
|
-
import type * as PubSub from "effect/PubSub"
|
|
10
13
|
import * as Layer from "effect/Layer"
|
|
11
|
-
import type * as
|
|
14
|
+
import type * as Option from "effect/Option"
|
|
12
15
|
import * as Ref from "effect/Ref"
|
|
13
16
|
import * as Schedule from "effect/Schedule"
|
|
14
|
-
import { DiscordConfig } from "dfx/DiscordConfig"
|
|
15
|
-
import type { RunningShard } from "dfx/DiscordGateway/Shard"
|
|
16
|
-
import { ShardLive, Shard } from "dfx/DiscordGateway/Shard"
|
|
17
|
-
import { ShardStore } from "dfx/DiscordGateway/ShardStore"
|
|
18
|
-
import type { WebSocketCloseError, WebSocketError } from "dfx/DiscordGateway/WS"
|
|
19
|
-
import { DiscordREST } from "dfx/DiscordREST"
|
|
20
|
-
import { RateLimiterLive, RateLimiter } from "dfx/RateLimit"
|
|
21
|
-
import type * as Discord from "dfx/types"
|
|
22
17
|
|
|
23
18
|
const claimRepeatPolicy = Schedule.spaced("3 minutes").pipe(
|
|
24
19
|
Schedule.whileInput((_: Option.Option<number>) => _._tag === "None"),
|
|
@@ -33,29 +28,6 @@ const make = Effect.gen(function* (_) {
|
|
|
33
28
|
const shard = yield* _(Shard)
|
|
34
29
|
const currentShards = yield* _(Ref.make(HashSet.empty<RunningShard>()))
|
|
35
30
|
|
|
36
|
-
const takeConfig = (totalCount: number) =>
|
|
37
|
-
Effect.gen(function* (_) {
|
|
38
|
-
const currentCount = yield* _(Ref.make(0))
|
|
39
|
-
|
|
40
|
-
const claimId = (
|
|
41
|
-
sharderCount: number,
|
|
42
|
-
): Effect.Effect<never, never, number> =>
|
|
43
|
-
pipe(
|
|
44
|
-
store.claimId({
|
|
45
|
-
totalCount,
|
|
46
|
-
sharderCount,
|
|
47
|
-
}),
|
|
48
|
-
Effect.repeat(claimRepeatPolicy),
|
|
49
|
-
Effect.map(_ => _.value),
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
return pipe(
|
|
53
|
-
Ref.getAndUpdate(currentCount, _ => _ + 1),
|
|
54
|
-
Effect.flatMap(claimId),
|
|
55
|
-
Effect.map(id => ({ id, totalCount }) as const),
|
|
56
|
-
)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
31
|
const gateway = yield* _(
|
|
60
32
|
rest.getGatewayBot(),
|
|
61
33
|
Effect.flatMap(r => r.json),
|
|
@@ -73,68 +45,71 @@ const make = Effect.gen(function* (_) {
|
|
|
73
45
|
),
|
|
74
46
|
)
|
|
75
47
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
})),
|
|
93
|
-
Effect.tap(({ concurrency, id }) =>
|
|
94
|
-
limiter.maybeWait(
|
|
95
|
-
`dfx.sharder.${id % concurrency}`,
|
|
96
|
-
Duration.millis(config.identifyRateLimit[0]),
|
|
97
|
-
config.identifyRateLimit[1],
|
|
98
|
-
),
|
|
99
|
-
),
|
|
100
|
-
Effect.flatMap(c =>
|
|
101
|
-
shard.connect([c.id, c.totalCount], hub, sendQueue),
|
|
102
|
-
),
|
|
103
|
-
Effect.flatMap(shard =>
|
|
104
|
-
Effect.acquireUseRelease(
|
|
105
|
-
Ref.update(currentShards, HashSet.add(shard)),
|
|
106
|
-
() => shard.run,
|
|
107
|
-
() => Ref.update(currentShards, HashSet.remove(shard)),
|
|
108
|
-
).pipe(
|
|
109
|
-
Effect.catchAllCause(_ => Deferred.failCause(deferred, _)),
|
|
110
|
-
Effect.fork,
|
|
111
|
-
),
|
|
112
|
-
),
|
|
113
|
-
Effect.forever,
|
|
114
|
-
)
|
|
48
|
+
const totalCount = config.shardCount ?? gateway.shards
|
|
49
|
+
const currentCount = yield* _(Ref.make(0))
|
|
50
|
+
const claimId = (sharderCount: number): Effect.Effect<never, never, number> =>
|
|
51
|
+
pipe(
|
|
52
|
+
store.claimId({
|
|
53
|
+
totalCount,
|
|
54
|
+
sharderCount,
|
|
55
|
+
}),
|
|
56
|
+
Effect.repeat(claimRepeatPolicy),
|
|
57
|
+
Effect.map(_ => _.value),
|
|
58
|
+
)
|
|
59
|
+
const takeConfig = pipe(
|
|
60
|
+
Ref.getAndUpdate(currentCount, _ => _ + 1),
|
|
61
|
+
Effect.flatMap(claimId),
|
|
62
|
+
Effect.map(id => ({ id, totalCount }) as const),
|
|
63
|
+
)
|
|
115
64
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
65
|
+
const spawner = pipe(
|
|
66
|
+
takeConfig,
|
|
67
|
+
Effect.map(config => ({
|
|
68
|
+
...config,
|
|
69
|
+
url: gateway.url,
|
|
70
|
+
concurrency: gateway.session_start_limit.max_concurrency,
|
|
71
|
+
})),
|
|
72
|
+
Effect.tap(({ concurrency, id }) =>
|
|
73
|
+
limiter.maybeWait(
|
|
74
|
+
`dfx.sharder.${id % concurrency}`,
|
|
75
|
+
Duration.millis(config.identifyRateLimit[0]),
|
|
76
|
+
config.identifyRateLimit[1],
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
Effect.flatMap(c => shard.connect([c.id, c.totalCount])),
|
|
80
|
+
Effect.flatMap(shard => Ref.update(currentShards, HashSet.add(shard))),
|
|
81
|
+
Effect.forever,
|
|
82
|
+
)
|
|
120
83
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
84
|
+
yield* _(
|
|
85
|
+
Effect.replicateEffect(
|
|
86
|
+
spawner,
|
|
87
|
+
gateway.session_start_limit.max_concurrency,
|
|
88
|
+
{ concurrency: "unbounded", discard: true },
|
|
89
|
+
),
|
|
90
|
+
Effect.scoped,
|
|
91
|
+
Effect.catchAllCause(Effect.logError),
|
|
92
|
+
Effect.ensuring(Ref.set(currentShards, HashSet.empty())),
|
|
93
|
+
Effect.forever,
|
|
94
|
+
Effect.forkScoped,
|
|
95
|
+
)
|
|
131
96
|
|
|
132
|
-
return { shards: Ref.get(currentShards)
|
|
133
|
-
})
|
|
97
|
+
return { shards: Ref.get(currentShards) } as const
|
|
98
|
+
}).pipe(
|
|
99
|
+
Effect.annotateLogs({
|
|
100
|
+
package: "dfx",
|
|
101
|
+
module: "DiscordGateway/Sharder",
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
134
104
|
|
|
135
|
-
export interface Sharder
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
105
|
+
export interface Sharder {
|
|
106
|
+
readonly _: unique symbol
|
|
107
|
+
}
|
|
108
|
+
export const Sharder = Tag<Sharder, Effect.Effect.Success<typeof make>>(
|
|
109
|
+
"dfx/DiscordGateway/Sharder",
|
|
110
|
+
)
|
|
111
|
+
export const SharderLive = Layer.scoped(Sharder, make).pipe(
|
|
112
|
+
Layer.provide(DiscordRESTLive),
|
|
113
|
+
Layer.provide(RateLimiterLive),
|
|
114
|
+
Layer.provide(ShardLive),
|
|
140
115
|
)
|
package/src/DiscordGateway/WS.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Tag } from "effect/Context"
|
|
2
|
-
import * as Duration from "effect/Duration"
|
|
3
|
-
import { pipe } from "effect/Function"
|
|
2
|
+
import type * as Duration from "effect/Duration"
|
|
3
|
+
import { identity, pipe } from "effect/Function"
|
|
4
4
|
import * as Effect from "effect/Effect"
|
|
5
5
|
import * as Layer from "effect/Layer"
|
|
6
6
|
import * as Queue from "effect/Queue"
|
|
7
7
|
import * as Ref from "effect/Ref"
|
|
8
|
-
import { Log } from "dfx/Log"
|
|
9
8
|
import WebSocket from "isomorphic-ws"
|
|
9
|
+
import type { Predicate } from "effect/Predicate"
|
|
10
|
+
import * as Schedule from "effect/Schedule"
|
|
10
11
|
|
|
11
12
|
export const Reconnect = Symbol()
|
|
12
13
|
export type Reconnect = typeof Reconnect
|
|
@@ -49,16 +50,10 @@ const socket = (urlRef: Ref.Ref<string>) =>
|
|
|
49
50
|
const offer = (
|
|
50
51
|
ws: globalThis.WebSocket,
|
|
51
52
|
queue: Queue.Enqueue<WebSocket.Data>,
|
|
52
|
-
log: Log,
|
|
53
53
|
) =>
|
|
54
54
|
Effect.async<never, WebSocketError | WebSocketCloseError, never>(resume => {
|
|
55
55
|
ws.addEventListener("message", message => {
|
|
56
|
-
|
|
57
|
-
Effect.zipRight(
|
|
58
|
-
log.debug("WS", "receive", message.data),
|
|
59
|
-
Queue.offer(queue, message.data),
|
|
60
|
-
),
|
|
61
|
-
)
|
|
56
|
+
Queue.unsafeOffer(queue, message.data)
|
|
62
57
|
})
|
|
63
58
|
|
|
64
59
|
ws.addEventListener("error", cause => {
|
|
@@ -70,7 +65,10 @@ const offer = (
|
|
|
70
65
|
})
|
|
71
66
|
})
|
|
72
67
|
|
|
73
|
-
const waitForOpen = (
|
|
68
|
+
const waitForOpen = (
|
|
69
|
+
ws: globalThis.WebSocket,
|
|
70
|
+
timeout: Duration.DurationInput,
|
|
71
|
+
) =>
|
|
74
72
|
Effect.timeoutFail(
|
|
75
73
|
Effect.suspend(() => {
|
|
76
74
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -92,11 +90,10 @@ const waitForOpen = (ws: globalThis.WebSocket, timeout: Duration.Duration) =>
|
|
|
92
90
|
const send = (
|
|
93
91
|
ws: globalThis.WebSocket,
|
|
94
92
|
take: Effect.Effect<never, never, Message>,
|
|
95
|
-
log: Log,
|
|
96
93
|
) =>
|
|
97
94
|
pipe(
|
|
98
95
|
take,
|
|
99
|
-
Effect.tap(data =>
|
|
96
|
+
Effect.tap(data => Effect.logTrace(data)),
|
|
100
97
|
Effect.tap(data => {
|
|
101
98
|
if (data === Reconnect) {
|
|
102
99
|
return Effect.failSync(() => {
|
|
@@ -110,30 +107,50 @@ const send = (
|
|
|
110
107
|
})
|
|
111
108
|
}),
|
|
112
109
|
Effect.forever,
|
|
110
|
+
Effect.annotateLogs("method", "send"),
|
|
113
111
|
)
|
|
114
112
|
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const connect = (
|
|
119
|
-
url: Ref.Ref<string>,
|
|
120
|
-
takeOutbound: Effect.Effect<never, never, Message>,
|
|
113
|
+
const wsImpl = {
|
|
114
|
+
connect: ({
|
|
121
115
|
onConnecting = Effect.unit,
|
|
122
|
-
openTimeout =
|
|
123
|
-
|
|
116
|
+
openTimeout = "3 seconds",
|
|
117
|
+
reconnectWhen,
|
|
118
|
+
takeOutbound,
|
|
119
|
+
urlRef,
|
|
120
|
+
}: {
|
|
121
|
+
readonly urlRef: Ref.Ref<string>
|
|
122
|
+
readonly takeOutbound: Effect.Effect<never, never, Message>
|
|
123
|
+
readonly onConnecting?: Effect.Effect<never, never, void>
|
|
124
|
+
readonly openTimeout?: Duration.DurationInput
|
|
125
|
+
readonly reconnectWhen?: Predicate<WebSocketError | WebSocketCloseError>
|
|
126
|
+
}) =>
|
|
124
127
|
Effect.gen(function* (_) {
|
|
125
|
-
const
|
|
128
|
+
const scope = yield* _(Effect.scope)
|
|
129
|
+
const queue = yield* _(
|
|
130
|
+
Effect.acquireRelease(
|
|
131
|
+
Queue.unbounded<WebSocket.Data>(),
|
|
132
|
+
Queue.shutdown,
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
const take = Effect.annotateLogs(
|
|
136
|
+
Effect.tap(Queue.take(queue), data => Effect.logTrace(data)),
|
|
137
|
+
{
|
|
138
|
+
package: "dfx",
|
|
139
|
+
module: "DiscordGateway/WS",
|
|
140
|
+
method: "take",
|
|
141
|
+
},
|
|
142
|
+
)
|
|
126
143
|
|
|
127
144
|
const run = pipe(
|
|
128
145
|
onConnecting,
|
|
129
|
-
Effect.zipRight(socket(
|
|
146
|
+
Effect.zipRight(socket(urlRef)),
|
|
130
147
|
Effect.flatMap(ws =>
|
|
131
148
|
Effect.all(
|
|
132
149
|
[
|
|
133
|
-
offer(ws, queue
|
|
150
|
+
offer(ws, queue),
|
|
134
151
|
Effect.zipRight(
|
|
135
152
|
waitForOpen(ws, openTimeout),
|
|
136
|
-
send(ws, takeOutbound
|
|
153
|
+
send(ws, takeOutbound),
|
|
137
154
|
),
|
|
138
155
|
],
|
|
139
156
|
{ concurrency: "unbounded", discard: true },
|
|
@@ -141,14 +158,29 @@ const make = Effect.gen(function* (_) {
|
|
|
141
158
|
),
|
|
142
159
|
Effect.scoped,
|
|
143
160
|
Effect.retryWhile(isReconnect),
|
|
161
|
+
reconnectWhen ? Effect.retryWhile(reconnectWhen) : identity,
|
|
162
|
+
Effect.catchAllCause(Effect.logError),
|
|
163
|
+
Effect.repeat(
|
|
164
|
+
Schedule.exponential("500 millis").pipe(
|
|
165
|
+
Schedule.union(Schedule.spaced("30 seconds")),
|
|
166
|
+
),
|
|
167
|
+
),
|
|
168
|
+
Effect.forkIn(scope),
|
|
144
169
|
)
|
|
145
170
|
|
|
146
|
-
|
|
147
|
-
})
|
|
171
|
+
yield* _(run)
|
|
148
172
|
|
|
149
|
-
|
|
150
|
-
})
|
|
173
|
+
return { take } as const
|
|
174
|
+
}).pipe(
|
|
175
|
+
Effect.annotateLogs({
|
|
176
|
+
package: "dfx",
|
|
177
|
+
module: "DiscordGateway/WS",
|
|
178
|
+
}),
|
|
179
|
+
),
|
|
180
|
+
} as const
|
|
151
181
|
|
|
152
|
-
export interface WS
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
export interface WS {
|
|
183
|
+
readonly _: unique symbol
|
|
184
|
+
}
|
|
185
|
+
export const WS = Tag<WS, typeof wsImpl>("dfx/DiscordGateway/WS")
|
|
186
|
+
export const WSLive = Layer.succeed(WS, wsImpl)
|
package/src/DiscordGateway.ts
CHANGED
|
@@ -1,42 +1,19 @@
|
|
|
1
|
+
import { Messaging, MesssagingLive } from "dfx/DiscordGateway/Messaging"
|
|
2
|
+
import type { RunningShard } from "dfx/DiscordGateway/Shard"
|
|
3
|
+
import { Sharder, SharderLive } from "dfx/DiscordGateway/Sharder"
|
|
4
|
+
import type * as Discord from "dfx/types"
|
|
1
5
|
import { Tag } from "effect/Context"
|
|
2
|
-
import type * as HashSet from "effect/HashSet"
|
|
3
6
|
import * as Effect from "effect/Effect"
|
|
4
|
-
import * as
|
|
7
|
+
import type * as HashSet from "effect/HashSet"
|
|
5
8
|
import * as Layer from "effect/Layer"
|
|
6
|
-
import * as
|
|
7
|
-
import * as Stream from "effect/Stream"
|
|
8
|
-
import type { RunningShard } from "dfx/DiscordGateway/Shard"
|
|
9
|
-
import { SharedLive, Sharder } from "dfx/DiscordGateway/Sharder"
|
|
10
|
-
import type * as Discord from "dfx/types"
|
|
11
|
-
import * as EffectUtils from "dfx/utils/Effect"
|
|
12
|
-
import * as Schedule from "effect/Schedule"
|
|
13
|
-
|
|
14
|
-
const fromDispatchFactory =
|
|
15
|
-
<R, E>(
|
|
16
|
-
source: Stream.Stream<R, E, Discord.GatewayPayload<Discord.ReceiveEvent>>,
|
|
17
|
-
) =>
|
|
18
|
-
<K extends keyof Discord.ReceiveEvents>(
|
|
19
|
-
event: K,
|
|
20
|
-
): Stream.Stream<R, E, Discord.ReceiveEvents[K]> =>
|
|
21
|
-
Stream.map(
|
|
22
|
-
Stream.filter(source, p => p.t === event),
|
|
23
|
-
p => p.d! as any,
|
|
24
|
-
)
|
|
9
|
+
import type * as Stream from "effect/Stream"
|
|
25
10
|
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
<K extends keyof Discord.ReceiveEvents, R, E, A>(
|
|
29
|
-
event: K,
|
|
30
|
-
handle: (event: Discord.ReceiveEvents[K]) => Effect.Effect<R, E, A>,
|
|
31
|
-
): Effect.Effect<R, E, never> =>
|
|
32
|
-
EffectUtils.subscribeForEachPar(hub, _ => {
|
|
33
|
-
if (_.t === event) {
|
|
34
|
-
return handle(_.d as any)
|
|
35
|
-
}
|
|
36
|
-
return Effect.unit as any
|
|
37
|
-
})
|
|
11
|
+
export const TypeId = Symbol.for("dfx/DiscordGateway")
|
|
12
|
+
export type TypeId = typeof TypeId
|
|
38
13
|
|
|
39
14
|
export interface DiscordGateway {
|
|
15
|
+
readonly [TypeId]: TypeId
|
|
16
|
+
|
|
40
17
|
readonly dispatch: Stream.Stream<
|
|
41
18
|
never,
|
|
42
19
|
never,
|
|
@@ -54,50 +31,24 @@ export interface DiscordGateway {
|
|
|
54
31
|
) => Effect.Effect<never, never, boolean>
|
|
55
32
|
readonly shards: Effect.Effect<never, never, HashSet.HashSet<RunningShard>>
|
|
56
33
|
}
|
|
57
|
-
|
|
34
|
+
|
|
35
|
+
export const DiscordGateway = Tag<DiscordGateway>(TypeId)
|
|
58
36
|
|
|
59
37
|
export const make = Effect.gen(function* (_) {
|
|
60
38
|
const sharder = yield* _(Sharder)
|
|
61
|
-
const
|
|
62
|
-
PubSub.unbounded<Discord.GatewayPayload<Discord.ReceiveEvent>>(),
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
const sendQueue = yield* _(
|
|
66
|
-
Queue.unbounded<Discord.GatewayPayload<Discord.SendEvent>>(),
|
|
67
|
-
)
|
|
68
|
-
const send = (payload: Discord.GatewayPayload<Discord.SendEvent>) =>
|
|
69
|
-
sendQueue.offer(payload)
|
|
70
|
-
|
|
71
|
-
const dispatch = Stream.fromPubSub(hub)
|
|
72
|
-
const fromDispatch = fromDispatchFactory(dispatch)
|
|
73
|
-
const handleDispatch = handleDispatchFactory(hub)
|
|
74
|
-
|
|
75
|
-
yield* _(
|
|
76
|
-
sharder.run(hub, sendQueue),
|
|
77
|
-
Effect.tapErrorCause(_ => Effect.logError("fatal error, restarting", _)),
|
|
78
|
-
Effect.retry(
|
|
79
|
-
Schedule.exponential("1 seconds").pipe(
|
|
80
|
-
Schedule.union(Schedule.spaced("30 seconds")),
|
|
81
|
-
),
|
|
82
|
-
),
|
|
83
|
-
Effect.forkScoped,
|
|
84
|
-
)
|
|
39
|
+
const messaging = yield* _(Messaging)
|
|
85
40
|
|
|
86
41
|
return DiscordGateway.of({
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
42
|
+
[TypeId]: TypeId,
|
|
43
|
+
dispatch: messaging.dispatch,
|
|
44
|
+
fromDispatch: messaging.fromDispatch,
|
|
45
|
+
handleDispatch: messaging.handleDispatch,
|
|
46
|
+
send: messaging.send,
|
|
91
47
|
shards: sharder.shards,
|
|
92
48
|
})
|
|
93
|
-
})
|
|
94
|
-
Effect.annotateLogs({
|
|
95
|
-
package: "dfx",
|
|
96
|
-
service: "DiscordGateway",
|
|
97
|
-
}),
|
|
98
|
-
)
|
|
49
|
+
})
|
|
99
50
|
|
|
100
|
-
export const DiscordGatewayLive = Layer.
|
|
101
|
-
Layer.
|
|
102
|
-
|
|
51
|
+
export const DiscordGatewayLive = Layer.effect(DiscordGateway, make).pipe(
|
|
52
|
+
Layer.provide(MesssagingLive),
|
|
53
|
+
Layer.provide(SharderLive),
|
|
103
54
|
)
|