dfx 0.74.0 → 0.75.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.
@@ -1,4 +1,5 @@
1
1
  import * as Effect from "effect/Effect"
2
+ import type * as Scope from "effect/Scope"
2
3
  import * as Stream from "effect/Stream"
3
4
  import type { DiscordRESTError } from "dfx/DiscordREST"
4
5
  import { DiscordREST } from "dfx/DiscordREST"
@@ -138,41 +139,42 @@ export const ops = <E, T>({
138
139
  export const guilds = <RM, EM, E>(
139
140
  makeDriver: Effect.Effect<RM, EM, CacheDriver<E, Discord.Guild>>,
140
141
  ): Effect.Effect<
141
- RM | DiscordGateway | DiscordREST,
142
+ RM | DiscordGateway | DiscordREST | Scope.Scope,
142
143
  EM,
143
- Cache<never, E, ResponseError | DiscordRESTError, Discord.Guild>
144
+ Cache<E, ResponseError | DiscordRESTError, Discord.Guild>
144
145
  > =>
145
146
  Effect.gen(function* (_) {
146
147
  const driver = yield* _(makeDriver)
147
148
  const gateway = yield* _(DiscordGateway)
148
149
  const rest = yield* _(DiscordREST)
149
150
 
150
- return make({
151
- driver,
152
- id: _ => _.id,
153
- ops: ops({
154
- id: (g: Discord.Guild) => g.id,
155
- create: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => ({
156
- ...g,
157
- channels: [],
158
- roles: [],
159
- emojis: [],
160
- members: [],
161
- })),
162
- update: gateway.fromDispatch("GUILD_UPDATE"),
163
- remove: Stream.map(gateway.fromDispatch("GUILD_DELETE"), a => a.id),
151
+ return yield* _(
152
+ make({
153
+ driver,
154
+ id: _ => _.id,
155
+ ops: ops({
156
+ id: (g: Discord.Guild) => g.id,
157
+ create: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => ({
158
+ ...g,
159
+ channels: [],
160
+ roles: [],
161
+ emojis: [],
162
+ members: [],
163
+ })),
164
+ update: gateway.fromDispatch("GUILD_UPDATE"),
165
+ remove: Stream.map(gateway.fromDispatch("GUILD_DELETE"), a => a.id),
166
+ }),
167
+ onMiss: id => Effect.flatMap(rest.getGuild(id), r => r.json),
164
168
  }),
165
- onMiss: id => Effect.flatMap(rest.getGuild(id), r => r.json),
166
- })
169
+ )
167
170
  })
168
171
 
169
172
  export const channels = <RM, EM, E>(
170
173
  makeDriver: Effect.Effect<RM, EM, ParentCacheDriver<E, Discord.Channel>>,
171
174
  ): Effect.Effect<
172
- DiscordGateway | DiscordREST | RM,
175
+ DiscordGateway | DiscordREST | RM | Scope.Scope,
173
176
  EM,
174
177
  ParentCache<
175
- never,
176
178
  E,
177
179
  ResponseError | DiscordRESTError,
178
180
  ResponseError | DiscordRESTError,
@@ -184,90 +186,88 @@ export const channels = <RM, EM, E>(
184
186
  const gateway = yield* _(DiscordGateway)
185
187
  const rest = yield* _(DiscordREST)
186
188
 
187
- return makeWithParent({
188
- driver,
189
- id: _ => Effect.succeed([_.guild_id!, _.id]),
190
- ops: opsWithParent({
191
- id: (a: Discord.Channel) => a.id,
192
- fromParent: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => [
193
- g.id,
194
- g.channels.concat(g.threads),
195
- ]),
196
- create: Stream.merge(
197
- gateway.fromDispatch("CHANNEL_CREATE"),
198
- gateway.fromDispatch("THREAD_CREATE"),
199
- ).pipe(Stream.map(c => [c.guild_id!, c])),
200
- update: Stream.merge(
201
- gateway.fromDispatch("CHANNEL_UPDATE"),
202
- gateway.fromDispatch("THREAD_UPDATE"),
203
- ).pipe(Stream.map(c => [c.guild_id!, c])),
204
- remove: Stream.merge(
205
- gateway.fromDispatch("CHANNEL_DELETE"),
206
- gateway.fromDispatch("THREAD_DELETE"),
207
- ).pipe(Stream.map(a => [a.guild_id!, a.id])),
208
- parentRemove: Stream.map(
209
- gateway.fromDispatch("GUILD_DELETE"),
210
- g => g.id,
211
- ),
189
+ return yield* _(
190
+ makeWithParent({
191
+ driver,
192
+ id: _ => Effect.succeed([_.guild_id!, _.id]),
193
+ ops: opsWithParent({
194
+ id: (a: Discord.Channel) => a.id,
195
+ fromParent: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => [
196
+ g.id,
197
+ g.channels.concat(g.threads),
198
+ ]),
199
+ create: Stream.merge(
200
+ gateway.fromDispatch("CHANNEL_CREATE"),
201
+ gateway.fromDispatch("THREAD_CREATE"),
202
+ ).pipe(Stream.map(c => [c.guild_id!, c])),
203
+ update: Stream.merge(
204
+ gateway.fromDispatch("CHANNEL_UPDATE"),
205
+ gateway.fromDispatch("THREAD_UPDATE"),
206
+ ).pipe(Stream.map(c => [c.guild_id!, c])),
207
+ remove: Stream.merge(
208
+ gateway.fromDispatch("CHANNEL_DELETE"),
209
+ gateway.fromDispatch("THREAD_DELETE"),
210
+ ).pipe(Stream.map(a => [a.guild_id!, a.id])),
211
+ parentRemove: Stream.map(
212
+ gateway.fromDispatch("GUILD_DELETE"),
213
+ g => g.id,
214
+ ),
215
+ }),
216
+ onMiss: (_, id) => Effect.flatMap(rest.getChannel(id), r => r.json),
217
+ onParentMiss: guildId =>
218
+ rest.getGuildChannels(guildId).pipe(
219
+ Effect.flatMap(r => r.json),
220
+ Effect.map(a => a.map(a => [a.id, a])),
221
+ ),
212
222
  }),
213
- onMiss: (_, id) => Effect.flatMap(rest.getChannel(id), r => r.json),
214
- onParentMiss: guildId =>
215
- rest.getGuildChannels(guildId).pipe(
216
- Effect.flatMap(r => r.json),
217
- Effect.map(a => a.map(a => [a.id, a])),
218
- ),
219
- })
223
+ )
220
224
  })
221
225
 
222
226
  export const roles = <RM, EM, E>(
223
227
  makeDriver: Effect.Effect<RM, EM, ParentCacheDriver<E, Discord.Role>>,
224
228
  ): Effect.Effect<
225
- DiscordGateway | DiscordREST | RM,
229
+ DiscordGateway | DiscordREST | RM | Scope.Scope,
226
230
  EM,
227
- ParentCache<
228
- never,
229
- E,
230
- CacheMissError,
231
- ResponseError | DiscordRESTError,
232
- Discord.Role
233
- >
231
+ ParentCache<E, CacheMissError, ResponseError | DiscordRESTError, Discord.Role>
234
232
  > =>
235
233
  Effect.gen(function* (_) {
236
234
  const driver = yield* _(makeDriver)
237
235
  const gateway = yield* _(DiscordGateway)
238
236
  const rest = yield* _(DiscordREST)
239
237
 
240
- return makeWithParent({
241
- driver,
242
- id: _ => Effect.fail(new CacheMissError("RolesCache/id", _.id)),
243
- ops: opsWithParent({
244
- id: (a: Discord.Role) => a.id,
245
- fromParent: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => [
246
- g.id,
247
- g.roles,
248
- ]),
249
- create: Stream.map(gateway.fromDispatch("GUILD_ROLE_CREATE"), r => [
250
- r.guild_id,
251
- r.role,
252
- ]),
253
- update: Stream.map(gateway.fromDispatch("GUILD_ROLE_UPDATE"), r => [
254
- r.guild_id,
255
- r.role,
256
- ]),
257
- remove: Stream.map(gateway.fromDispatch("GUILD_ROLE_DELETE"), r => [
258
- r.guild_id,
259
- r.role_id,
260
- ]),
261
- parentRemove: Stream.map(
262
- gateway.fromDispatch("GUILD_DELETE"),
263
- g => g.id,
264
- ),
238
+ return yield* _(
239
+ makeWithParent({
240
+ driver,
241
+ id: _ => Effect.fail(new CacheMissError("RolesCache/id", _.id)),
242
+ ops: opsWithParent({
243
+ id: (a: Discord.Role) => a.id,
244
+ fromParent: Stream.map(gateway.fromDispatch("GUILD_CREATE"), g => [
245
+ g.id,
246
+ g.roles,
247
+ ]),
248
+ create: Stream.map(gateway.fromDispatch("GUILD_ROLE_CREATE"), r => [
249
+ r.guild_id,
250
+ r.role,
251
+ ]),
252
+ update: Stream.map(gateway.fromDispatch("GUILD_ROLE_UPDATE"), r => [
253
+ r.guild_id,
254
+ r.role,
255
+ ]),
256
+ remove: Stream.map(gateway.fromDispatch("GUILD_ROLE_DELETE"), r => [
257
+ r.guild_id,
258
+ r.role_id,
259
+ ]),
260
+ parentRemove: Stream.map(
261
+ gateway.fromDispatch("GUILD_DELETE"),
262
+ g => g.id,
263
+ ),
264
+ }),
265
+ onMiss: (_, id) => Effect.fail(new CacheMissError("RolesCache", id)),
266
+ onParentMiss: guildId =>
267
+ rest.getGuildRoles(guildId).pipe(
268
+ Effect.flatMap(r => r.json),
269
+ Effect.map(_ => _.map(role => [role.id, role])),
270
+ ),
265
271
  }),
266
- onMiss: (_, id) => Effect.fail(new CacheMissError("RolesCache", id)),
267
- onParentMiss: guildId =>
268
- rest.getGuildRoles(guildId).pipe(
269
- Effect.flatMap(r => r.json),
270
- Effect.map(_ => _.map(role => [role.id, role])),
271
- ),
272
- })
272
+ )
273
273
  })
package/src/Cache.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import * as Option from "effect/Option"
2
+ import * as Schedule from "effect/Schedule"
3
+ import type * as Scope from "effect/Scope"
2
4
  import * as Effect from "effect/Effect"
3
5
  import * as Stream from "effect/Stream"
4
6
  import type { CacheDriver, ParentCacheDriver } from "dfx/Cache/driver"
@@ -24,7 +26,11 @@ export type CacheOp<T> =
24
26
  | { op: "update"; resourceId: string; resource: T }
25
27
  | { op: "delete"; resourceId: string }
26
28
 
27
- export interface ParentCache<EOps, EDriver, EMiss, EPMiss, A> {
29
+ const retryPolicy = Schedule.exponential("500 millis").pipe(
30
+ Schedule.union(Schedule.spaced("10 seconds")),
31
+ )
32
+
33
+ export interface ParentCache<EDriver, EMiss, EPMiss, A> {
28
34
  readonly get: (
29
35
  parentId: string,
30
36
  id: string,
@@ -38,7 +44,6 @@ export interface ParentCache<EOps, EDriver, EMiss, EPMiss, A> {
38
44
  readonly getForParent: (
39
45
  parentId: string,
40
46
  ) => Effect.Effect<never, EDriver | EPMiss, ReadonlyMap<string, A>>
41
- readonly run: Effect.Effect<never, EOps | EDriver, void>
42
47
  readonly size: Effect.Effect<never, EDriver, number>
43
48
  readonly sizeForParent: (
44
49
  parentId: string,
@@ -77,86 +82,101 @@ export const makeWithParent = <EOps, EDriver, EMiss, EPMiss, A>({
77
82
  onParentMiss: (
78
83
  parentId: string,
79
84
  ) => Effect.Effect<never, EPMiss, Array<[id: string, resource: A]>>
80
- }): ParentCache<EOps, EDriver, EMiss, EPMiss, A> => {
81
- const sync = Stream.runDrain(
82
- Stream.tap(ops, (op): Effect.Effect<never, EDriver, void> => {
83
- switch (op.op) {
84
- case "create":
85
- case "update":
86
- return driver.set(op.parentId, op.resourceId, op.resource)
87
-
88
- case "delete":
89
- return driver.delete(op.parentId, op.resourceId)
90
-
91
- case "parentDelete":
92
- return driver.parentDelete(op.parentId)
93
- }
94
- }),
95
- )
85
+ }): Effect.Effect<Scope.Scope, never, ParentCache<EDriver, EMiss, EPMiss, A>> =>
86
+ Effect.gen(function* (_) {
87
+ yield* _(
88
+ Stream.runDrain(
89
+ Stream.tap(ops, (op): Effect.Effect<never, EDriver, void> => {
90
+ switch (op.op) {
91
+ case "create":
92
+ case "update":
93
+ return driver.set(op.parentId, op.resourceId, op.resource)
96
94
 
97
- const get = (parentId: string, id: string) =>
98
- Effect.flatMap(
99
- driver.get(parentId, id),
100
- Option.match({
101
- onNone: () =>
102
- Effect.tap(onMiss(parentId, id), a => driver.set(parentId, id, a)),
103
- onSome: Effect.succeed,
104
- }),
105
- )
106
-
107
- const put = (_: A) =>
108
- Effect.flatMap(id(_), ([parentId, id]) => driver.set(parentId, id, _))
95
+ case "delete":
96
+ return driver.delete(op.parentId, op.resourceId)
109
97
 
110
- const update = <R, E>(
111
- parentId: string,
112
- id: string,
113
- f: (_: A) => Effect.Effect<R, E, A>,
114
- ) =>
115
- get(parentId, id).pipe(
116
- Effect.flatMap(f),
117
- Effect.tap(_ => driver.set(parentId, id, _)),
98
+ case "parentDelete":
99
+ return driver.parentDelete(op.parentId)
100
+ }
101
+ }),
102
+ ),
103
+ Effect.tapErrorCause(_ => Effect.logError("ops error, restarting", _)),
104
+ Effect.retry(retryPolicy),
105
+ Effect.forkScoped,
106
+ )
107
+ yield* _(
108
+ driver.run,
109
+ Effect.tapErrorCause(_ =>
110
+ Effect.logError("cache driver error, restarting", _),
111
+ ),
112
+ Effect.retry(retryPolicy),
113
+ Effect.forkScoped,
118
114
  )
119
115
 
120
- return {
121
- ...driver,
122
-
123
- get,
124
- put,
125
- update,
126
-
127
- getForParent: (parentId: string) =>
116
+ const get = (parentId: string, id: string) =>
128
117
  Effect.flatMap(
129
- driver.getForParent(parentId),
118
+ driver.get(parentId, id),
130
119
  Option.match({
131
120
  onNone: () =>
132
- onParentMiss(parentId).pipe(
133
- Effect.tap(entries =>
134
- Effect.all(
135
- entries.map(([id, a]) => driver.set(parentId, id, a)),
136
- { concurrency: "unbounded" },
137
- ),
138
- ),
139
- Effect.map(entries => new Map(entries) as ReadonlyMap<string, A>),
140
- ),
121
+ Effect.tap(onMiss(parentId, id), a => driver.set(parentId, id, a)),
141
122
  onSome: Effect.succeed,
142
123
  }),
143
- ),
124
+ )
125
+
126
+ const put = (_: A) =>
127
+ Effect.flatMap(id(_), ([parentId, id]) => driver.set(parentId, id, _))
128
+
129
+ const update = <R, E>(
130
+ parentId: string,
131
+ id: string,
132
+ f: (_: A) => Effect.Effect<R, E, A>,
133
+ ) =>
134
+ get(parentId, id).pipe(
135
+ Effect.flatMap(f),
136
+ Effect.tap(_ => driver.set(parentId, id, _)),
137
+ )
138
+
139
+ return {
140
+ ...driver,
141
+
142
+ get,
143
+ put,
144
+ update,
144
145
 
145
- run: Effect.all([sync, driver.run], {
146
- concurrency: "unbounded",
147
- discard: true,
146
+ getForParent: (parentId: string) =>
147
+ Effect.flatMap(
148
+ driver.getForParent(parentId),
149
+ Option.match({
150
+ onNone: () =>
151
+ onParentMiss(parentId).pipe(
152
+ Effect.tap(entries =>
153
+ Effect.all(
154
+ entries.map(([id, a]) => driver.set(parentId, id, a)),
155
+ { concurrency: "unbounded" },
156
+ ),
157
+ ),
158
+ Effect.map(
159
+ entries => new Map(entries) as ReadonlyMap<string, A>,
160
+ ),
161
+ ),
162
+ onSome: Effect.succeed,
163
+ }),
164
+ ),
165
+ } as const
166
+ }).pipe(
167
+ Effect.annotateLogs({
168
+ package: "dfx",
169
+ service: "Cache",
148
170
  }),
149
- } as const
150
- }
171
+ )
151
172
 
152
- export interface Cache<EOps, EDriver, EMiss, A> {
173
+ export interface Cache<EDriver, EMiss, A> {
153
174
  readonly get: (id: string) => Effect.Effect<never, EDriver | EMiss, A>
154
175
  readonly put: (_: A) => Effect.Effect<never, EDriver, void>
155
176
  readonly update: <R, E>(
156
177
  id: string,
157
178
  f: (_: A) => Effect.Effect<R, E, A>,
158
179
  ) => Effect.Effect<R, EDriver | EMiss | E, A>
159
- readonly run: Effect.Effect<never, EOps | EDriver, void>
160
180
  readonly size: Effect.Effect<never, EDriver, number>
161
181
  readonly set: (
162
182
  resourceId: string,
@@ -178,48 +198,64 @@ export const make = <EOps, EDriver, EMiss, A>({
178
198
  ops?: Stream.Stream<never, EOps, CacheOp<A>>
179
199
  id: (_: A) => string
180
200
  onMiss: (id: string) => Effect.Effect<never, EMiss, A>
181
- }): Cache<EOps, EDriver, EMiss, A> => {
182
- const sync = Stream.runDrain(
183
- Stream.tap(ops, (op): Effect.Effect<never, EDriver, void> => {
184
- switch (op.op) {
185
- case "create":
186
- case "update":
187
- return driver.set(op.resourceId, op.resource)
188
-
189
- case "delete":
190
- return driver.delete(op.resourceId)
191
- }
192
- }),
193
- )
201
+ }): Effect.Effect<Scope.Scope, never, Cache<EDriver, EMiss, A>> =>
202
+ Effect.gen(function* (_) {
203
+ yield* _(
204
+ Stream.runDrain(
205
+ Stream.tap(ops, (op): Effect.Effect<never, EDriver, void> => {
206
+ switch (op.op) {
207
+ case "create":
208
+ case "update":
209
+ return driver.set(op.resourceId, op.resource)
194
210
 
195
- const get = (id: string) =>
196
- Effect.flatMap(
197
- driver.get(id),
198
- Option.match({
199
- onNone: () => Effect.tap(onMiss(id), a => driver.set(id, a)),
200
- onSome: Effect.succeed,
201
- }),
211
+ case "delete":
212
+ return driver.delete(op.resourceId)
213
+ }
214
+ }),
215
+ ),
216
+ Effect.tapErrorCause(_ => Effect.logError("ops error, restarting", _)),
217
+ Effect.retry(retryPolicy),
218
+ Effect.forkScoped,
202
219
  )
203
220
 
204
- const put = (_: A) => driver.set(id(_), _)
205
-
206
- const update = <R, E>(id: string, f: (_: A) => Effect.Effect<R, E, A>) =>
207
- get(id).pipe(
208
- Effect.flatMap(f),
209
- Effect.tap(_ => driver.set(id, _)),
221
+ yield* _(
222
+ driver.run,
223
+ Effect.tapErrorCause(_ =>
224
+ Effect.logError("cache driver error, restarting", _),
225
+ ),
226
+ Effect.retry(retryPolicy),
227
+ Effect.forkScoped,
210
228
  )
211
229
 
212
- return {
213
- ...driver,
214
- get,
215
- put,
216
- update,
217
- run: Effect.all([sync, driver.run], {
218
- concurrency: "unbounded",
219
- discard: true,
230
+ const get = (id: string) =>
231
+ Effect.flatMap(
232
+ driver.get(id),
233
+ Option.match({
234
+ onNone: () => Effect.tap(onMiss(id), a => driver.set(id, a)),
235
+ onSome: Effect.succeed,
236
+ }),
237
+ )
238
+
239
+ const put = (_: A) => driver.set(id(_), _)
240
+
241
+ const update = <R, E>(id: string, f: (_: A) => Effect.Effect<R, E, A>) =>
242
+ get(id).pipe(
243
+ Effect.flatMap(f),
244
+ Effect.tap(_ => driver.set(id, _)),
245
+ )
246
+
247
+ return {
248
+ ...driver,
249
+ get,
250
+ put,
251
+ update,
252
+ } as const
253
+ }).pipe(
254
+ Effect.annotateLogs({
255
+ package: "dfx",
256
+ service: "Cache",
220
257
  }),
221
- } as const
222
- }
258
+ )
223
259
 
224
260
  export class CacheMissError {
225
261
  readonly _tag = "CacheMissError"
@@ -7,9 +7,9 @@ import * as Queue from "effect/Queue"
7
7
  import * as Stream from "effect/Stream"
8
8
  import type { RunningShard } from "dfx/DiscordGateway/Shard"
9
9
  import { LiveSharder, Sharder } from "dfx/DiscordGateway/Sharder"
10
- import type { WebSocketCloseError, WebSocketError } from "dfx/DiscordGateway/WS"
11
10
  import type * as Discord from "dfx/types"
12
11
  import * as EffectUtils from "dfx/utils/Effect"
12
+ import * as Schedule from "effect/Schedule"
13
13
 
14
14
  const fromDispatchFactory =
15
15
  <R, E>(
@@ -33,15 +33,10 @@ const handleDispatchFactory =
33
33
  if (_.t === event) {
34
34
  return handle(_.d as any)
35
35
  }
36
- return Effect.unit
36
+ return Effect.unit as any
37
37
  })
38
38
 
39
39
  export interface DiscordGateway {
40
- readonly run: Effect.Effect<
41
- never,
42
- WebSocketError | WebSocketCloseError,
43
- never
44
- >
45
40
  readonly dispatch: Stream.Stream<
46
41
  never,
47
42
  never,
@@ -77,19 +72,32 @@ export const make = Effect.gen(function* (_) {
77
72
  const fromDispatch = fromDispatchFactory(dispatch)
78
73
  const handleDispatch = handleDispatchFactory(hub)
79
74
 
80
- const run = sharder.run(hub, sendQueue)
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
+ )
81
85
 
82
86
  return DiscordGateway.of({
83
- run,
84
87
  dispatch,
85
88
  fromDispatch,
86
89
  handleDispatch,
87
90
  send,
88
91
  shards: sharder.shards,
89
92
  })
90
- })
93
+ }).pipe(
94
+ Effect.annotateLogs({
95
+ package: "dfx",
96
+ service: "DiscordGateway",
97
+ }),
98
+ )
91
99
 
92
100
  export const LiveDiscordGateway = Layer.provide(
93
101
  LiveSharder,
94
- Layer.effect(DiscordGateway, make),
102
+ Layer.scoped(DiscordGateway, make),
95
103
  )