From 7aeb5ec6f1f16382383d5d4baa329086f00cf0f1 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 1 Dec 2023 23:59:23 +1300 Subject: [PATCH] refactor: layer structure --- examples/interactions.ts | 12 ++-- examples/registry.ts | 9 +-- src/DiscordConfig.ts | 4 +- src/DiscordGateway.ts | 6 +- src/DiscordGateway/DiscordWS.ts | 6 +- src/DiscordGateway/Shard.ts | 8 +-- src/DiscordGateway/ShardStore.ts | 2 +- src/DiscordGateway/Sharder.ts | 8 +-- src/DiscordGateway/WS.ts | 2 +- src/DiscordREST.ts | 6 +- src/Interactions/gateway.ts | 94 ++++++++++++++++---------------- src/Interactions/webhook.ts | 4 +- src/Log.ts | 7 ++- src/RateLimit.ts | 2 +- src/gateway.ts | 71 ++++++++---------------- src/index.ts | 7 ++- src/webhooks.ts | 67 +++++------------------ 17 files changed, 132 insertions(+), 183 deletions(-) diff --git a/examples/interactions.ts b/examples/interactions.ts index 8bb6503..13c2820 100644 --- a/examples/interactions.ts +++ b/examples/interactions.ts @@ -1,12 +1,12 @@ -import { Discord, Ix } from "dfx" -import { gatewayLayer, runIx } from "dfx/gateway" +import { Discord, DiscordConfig, Ix } from "dfx" +import { DiscordLive, runIx } from "dfx/gateway" import Dotenv from "dotenv" -import { Cause, Config, Effect, Option, pipe } from "effect" +import { Cause, Config, Effect, Layer, Option, pipe } from "effect" Dotenv.config() // Create the dependencies layer -const DiscordLive = gatewayLayer({ +const DiscordConfigLive = DiscordConfig.layerConfig({ token: Config.secret("DISCORD_BOT_TOKEN"), }) @@ -78,9 +78,11 @@ const program = Effect.gen(function* (_) { yield* _(interactions) }) +const EnvLive = DiscordLive.pipe(Layer.provide(DiscordConfigLive)) + // Run it program.pipe( - Effect.provide(DiscordLive), + Effect.provide(EnvLive), Effect.tapErrorCause(_ => Effect.sync(() => { console.error(Cause.squash(_)) diff --git a/examples/registry.ts b/examples/registry.ts index c26d20c..1ac19ef 100644 --- a/examples/registry.ts +++ b/examples/registry.ts @@ -1,6 +1,6 @@ -import { Discord, Ix } from "dfx" +import { Discord, DiscordConfig, Ix } from "dfx" import { - gatewayLayer, + DiscordLive, InteractionsRegistry, InteractionsRegistryLive, } from "dfx/gateway" @@ -53,9 +53,10 @@ const GreetLive = Layer.effectDiscard(makeGreetService) // Main layer const MainLive = GreetLive.pipe( - Layer.provide(InteractionsRegistryLive()), + Layer.provide(InteractionsRegistryLive), + Layer.provide(DiscordLive), Layer.provide( - gatewayLayer({ + DiscordConfig.layerConfig({ token: Config.secret("DISCORD_BOT_TOKEN"), debug: Config.withDefault(Config.boolean("DEBUG"), false), }), diff --git a/src/DiscordConfig.ts b/src/DiscordConfig.ts index 2bb76f0..84a6398 100644 --- a/src/DiscordConfig.ts +++ b/src/DiscordConfig.ts @@ -60,12 +60,12 @@ export const make = ({ }, }) -export const makeLayer = ( +export const layer = ( opts: MakeOpts, ): Layer.Layer => Layer.succeed(DiscordConfig, make(opts)) -export const makeFromConfig = ( +export const layerConfig = ( _: Config.Config.Wrap, ): Layer.Layer => Layer.effect(DiscordConfig, Effect.map(Effect.config(Config.unwrap(_)), make)) diff --git a/src/DiscordGateway.ts b/src/DiscordGateway.ts index 453b31a..585bf85 100644 --- a/src/DiscordGateway.ts +++ b/src/DiscordGateway.ts @@ -6,7 +6,7 @@ import * as Layer from "effect/Layer" import * as Queue from "effect/Queue" import * as Stream from "effect/Stream" import type { RunningShard } from "dfx/DiscordGateway/Shard" -import { LiveSharder, Sharder } from "dfx/DiscordGateway/Sharder" +import { SharedLive, Sharder } from "dfx/DiscordGateway/Sharder" import type * as Discord from "dfx/types" import * as EffectUtils from "dfx/utils/Effect" import * as Schedule from "effect/Schedule" @@ -97,7 +97,7 @@ export const make = Effect.gen(function* (_) { }), ) -export const LiveDiscordGateway = Layer.provide( +export const DiscordGatewayLive = Layer.provide( Layer.scoped(DiscordGateway, make), - LiveSharder, + SharedLive, ) diff --git a/src/DiscordGateway/DiscordWS.ts b/src/DiscordGateway/DiscordWS.ts index e423b2c..614d8a9 100644 --- a/src/DiscordGateway/DiscordWS.ts +++ b/src/DiscordGateway/DiscordWS.ts @@ -2,7 +2,7 @@ import { Tag } from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" import * as Ref from "effect/Ref" -import { LiveWS, Reconnect, WS } from "dfx/DiscordGateway/WS" +import { WSLive, Reconnect, WS } from "dfx/DiscordGateway/WS" import type * as Discord from "dfx/types" import type WebSocket from "isomorphic-ws" @@ -69,7 +69,7 @@ const make = Effect.gen(function* (_) { export interface DiscordWS extends Effect.Effect.Success {} export const DiscordWS = Tag() -export const LiveDiscordWS = Layer.provide( +export const DiscordWSLive = Layer.provide( Layer.effect(DiscordWS, make), - LiveWS, + WSLive, ) diff --git a/src/DiscordGateway/Shard.ts b/src/DiscordGateway/Shard.ts index 1b9267d..2b7b8b9 100644 --- a/src/DiscordGateway/Shard.ts +++ b/src/DiscordGateway/Shard.ts @@ -11,14 +11,14 @@ import * as Queue from "effect/Queue" import * as Ref from "effect/Ref" import { DiscordConfig } from "dfx/DiscordConfig" import type { Message } from "dfx/DiscordGateway/DiscordWS" -import { DiscordWS, LiveDiscordWS } from "dfx/DiscordGateway/DiscordWS" +import { DiscordWS, DiscordWSLive } from "dfx/DiscordGateway/DiscordWS" import * as Heartbeats from "dfx/DiscordGateway/Shard/heartbeats" import * as Identify from "dfx/DiscordGateway/Shard/identify" import * as InvalidSession from "dfx/DiscordGateway/Shard/invalidSession" import * as Utils from "dfx/DiscordGateway/Shard/utils" import { Reconnect } from "dfx/DiscordGateway/WS" import { Log } from "dfx/Log" -import { LiveRateLimiter, RateLimiter } from "dfx/RateLimit" +import { RateLimiterLive, RateLimiter } from "dfx/RateLimit" import * as Discord from "dfx/types" const enum Phase { @@ -191,9 +191,9 @@ export const make = Effect.gen(function* (_) { export interface Shard extends Effect.Effect.Success {} export const Shard = Tag() -export const LiveShard = Layer.provide( +export const ShardLive = Layer.provide( Layer.effect(Shard, make), - Layer.merge(LiveDiscordWS, LiveRateLimiter), + Layer.merge(DiscordWSLive, RateLimiterLive), ) export interface RunningShard diff --git a/src/DiscordGateway/ShardStore.ts b/src/DiscordGateway/ShardStore.ts index c6afcab..9fef50b 100644 --- a/src/DiscordGateway/ShardStore.ts +++ b/src/DiscordGateway/ShardStore.ts @@ -37,4 +37,4 @@ const memoryStore = (): ShardStore => { } } -export const LiveMemoryShardStore = Layer.sync(ShardStore, memoryStore) +export const MemoryShardStoreLive = Layer.sync(ShardStore, memoryStore) diff --git a/src/DiscordGateway/Sharder.ts b/src/DiscordGateway/Sharder.ts index fd27136..b9bde51 100644 --- a/src/DiscordGateway/Sharder.ts +++ b/src/DiscordGateway/Sharder.ts @@ -13,11 +13,11 @@ import * as Ref from "effect/Ref" import * as Schedule from "effect/Schedule" import { DiscordConfig } from "dfx/DiscordConfig" import type { RunningShard } from "dfx/DiscordGateway/Shard" -import { LiveShard, Shard } from "dfx/DiscordGateway/Shard" +import { ShardLive, Shard } from "dfx/DiscordGateway/Shard" import { ShardStore } from "dfx/DiscordGateway/ShardStore" import type { WebSocketCloseError, WebSocketError } from "dfx/DiscordGateway/WS" import { DiscordREST } from "dfx/DiscordREST" -import { LiveRateLimiter, RateLimiter } from "dfx/RateLimit" +import { RateLimiterLive, RateLimiter } from "dfx/RateLimit" import type * as Discord from "dfx/types" const claimRepeatPolicy = Schedule.spaced("3 minutes").pipe( @@ -134,7 +134,7 @@ const make = Effect.gen(function* (_) { export interface Sharder extends Effect.Effect.Success {} export const Sharder = Tag() -export const LiveSharder = Layer.provide( +export const SharedLive = Layer.provide( Layer.effect(Sharder, make), - Layer.merge(LiveRateLimiter, LiveShard), + Layer.merge(RateLimiterLive, ShardLive), ) diff --git a/src/DiscordGateway/WS.ts b/src/DiscordGateway/WS.ts index c475fcb..010554d 100644 --- a/src/DiscordGateway/WS.ts +++ b/src/DiscordGateway/WS.ts @@ -151,4 +151,4 @@ const make = Effect.gen(function* (_) { export interface WS extends Effect.Effect.Success {} export const WS = Tag() -export const LiveWS = Layer.effect(WS, make) +export const WSLive = Layer.effect(WS, make) diff --git a/src/DiscordREST.ts b/src/DiscordREST.ts index 5d91606..043253a 100644 --- a/src/DiscordREST.ts +++ b/src/DiscordREST.ts @@ -17,7 +17,7 @@ import { routeFromConfig, } from "dfx/DiscordREST/utils" import { Log } from "dfx/Log" -import { LiveRateLimiter, RateLimiter, RateLimitStore } from "dfx/RateLimit" +import { RateLimiterLive, RateLimiter, RateLimitStore } from "dfx/RateLimit" import * as Discord from "dfx/types" import { LIB_VERSION } from "dfx/version" @@ -254,7 +254,7 @@ export interface DiscordREST } export const DiscordREST = Tag() -export const LiveDiscordREST = Layer.effect(DiscordREST, make).pipe( - Layer.provide(LiveRateLimiter), +export const DiscordRESTLive = Layer.effect(DiscordREST, make).pipe( + Layer.provide(RateLimiterLive), Layer.provide(Http.client.layer), ) diff --git a/src/Interactions/gateway.ts b/src/Interactions/gateway.ts index e0f120b..22395a6 100644 --- a/src/Interactions/gateway.ts +++ b/src/Interactions/gateway.ts @@ -21,14 +21,17 @@ import { builder, Interaction } from "dfx/Interactions/index" import type * as Discord from "dfx/types" import * as EffectUtils from "dfx/utils/Effect" import * as Schedule from "effect/Schedule" +import { globalValue } from "effect/GlobalValue" +import * as FiberRef from "effect/FiberRef" -export interface RunOpts { - sync?: boolean -} +export const interactionsSync: FiberRef.FiberRef = globalValue( + "dfx/Interactions/sync", + () => FiberRef.unsafeMake(true), +) + +export const setInteractionsSync = (enabled: boolean) => + Layer.locally(interactionsSync, enabled) -/** - * @tsplus pipeable dfx/InteractionBuilder runGateway - */ export const run = ( postHandler: ( @@ -38,7 +41,6 @@ export const run = void >, ) => Effect.Effect, - { sync = true }: RunOpts = {}, ) => ( ix: InteractionBuilder, @@ -100,6 +102,8 @@ export const run = Effect.provideService(postHandler(handle[i.type](i)), Interaction, i), ) + const sync = yield* _(FiberRef.get(interactionsSync)) + return yield* _( sync ? Effect.forever( @@ -112,49 +116,45 @@ export const run = ) }) -const makeRegistry = (options?: RunOpts) => - Effect.gen(function* (_) { - const ref = yield* _( - Ref.make(builder as InteractionBuilder), - ) - const queue = yield* _( - Queue.sliding>(1), - ) +const makeRegistry = Effect.gen(function* (_) { + const ref = yield* _( + Ref.make(builder as InteractionBuilder), + ) + const queue = yield* _( + Queue.sliding>(1), + ) - const register = (ix: InteractionBuilder) => - Effect.flatMap( - Ref.updateAndGet(ref, _ => _.concat(ix as any)), - _ => Queue.offer(queue, _), - ) + const register = (ix: InteractionBuilder) => + Effect.flatMap( + Ref.updateAndGet(ref, _ => _.concat(ix as any)), + _ => Queue.offer(queue, _), + ) - yield* _( - EffectUtils.foreverSwitch(Queue.take(queue), ix => - pipe( - ix, - run( - Effect.catchAllCause(_ => Effect.logError("unhandled error", _)), - options, - ), - Effect.delay(Duration.seconds(0.1)), - ), + yield* _( + EffectUtils.foreverSwitch(Queue.take(queue), ix => + pipe( + ix, + run(Effect.catchAllCause(_ => Effect.logError("unhandled error", _))), + Effect.delay(Duration.seconds(0.1)), ), - Effect.tapErrorCause(_ => Effect.logError("registry error", _)), - Effect.retry( - Schedule.exponential("1 seconds").pipe( - Schedule.union(Schedule.spaced("20 seconds")), - ), + ), + Effect.tapErrorCause(_ => Effect.logError("registry error", _)), + Effect.retry( + Schedule.exponential("1 seconds").pipe( + Schedule.union(Schedule.spaced("20 seconds")), ), - Effect.forkScoped, - ) - - return { register } as const - }).pipe( - Effect.annotateLogs({ - package: "dfx", - service: "InteractionsRegistry", - }), + ), + Effect.forkScoped, ) + return { register } as const +}).pipe( + Effect.annotateLogs({ + package: "dfx", + service: "InteractionsRegistry", + }), +) + export interface InteractionsRegistry { readonly register: ( ix: InteractionBuilder, @@ -162,5 +162,7 @@ export interface InteractionsRegistry { } export const InteractionsRegistry = Tag() -export const InteractionsRegistryLive = (options?: RunOpts) => - Layer.scoped(InteractionsRegistry, makeRegistry(options)) +export const InteractionsRegistryLive = Layer.scoped( + InteractionsRegistry, + makeRegistry, +) diff --git a/src/Interactions/webhook.ts b/src/Interactions/webhook.ts index 4fe741a..83fdc88 100644 --- a/src/Interactions/webhook.ts +++ b/src/Interactions/webhook.ts @@ -71,10 +71,10 @@ const makeConfig = ({ export interface WebhookConfig extends ReturnType {} export const WebhookConfig = Tag() -export const makeConfigLayer = (opts: MakeConfigOpts) => +export const layer = (opts: MakeConfigOpts) => Layer.succeed(WebhookConfig, makeConfig(opts)) -export const makeFromConfig: ( +export const layerConfig: ( a: Config.Config, ) => Layer.Layer = ( a: Config.Config, diff --git a/src/Log.ts b/src/Log.ts index 8cde654..585dc8f 100644 --- a/src/Log.ts +++ b/src/Log.ts @@ -1,3 +1,4 @@ +import { DiscordConfig } from "dfx/DiscordConfig" import { Tag } from "effect/Context" import * as Effect from "effect/Effect" import * as Layer from "effect/Layer" @@ -17,5 +18,7 @@ const make = (debug = false) => ({ export interface Log extends ReturnType {} export const Log = Tag() -export const LiveLog = Layer.succeed(Log, make(false)) -export const LiveLogDebug = Layer.succeed(Log, make(true)) +export const LogLive = Layer.effect( + Log, + Effect.map(DiscordConfig, config => make(config.debug)), +) diff --git a/src/RateLimit.ts b/src/RateLimit.ts index 78a2c42..4aa8e5d 100644 --- a/src/RateLimit.ts +++ b/src/RateLimit.ts @@ -79,4 +79,4 @@ const makeLimiter = Effect.gen(function* (_) { export interface RateLimiter extends Effect.Effect.Success {} export const RateLimiter = Tag() -export const LiveRateLimiter = Layer.effect(RateLimiter, makeLimiter) +export const RateLimiterLive = Layer.effect(RateLimiter, makeLimiter) diff --git a/src/gateway.ts b/src/gateway.ts index d33340d..0e043ef 100644 --- a/src/gateway.ts +++ b/src/gateway.ts @@ -1,65 +1,42 @@ -import * as Config from "effect/Config" -import type * as ConfigError from "effect/ConfigError" -import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" -import type { DiscordREST } from "dfx" -import { DiscordConfig, LiveDiscordREST, Log } from "dfx" +import { LiveDiscordREST as DiscordRESTLive } from "dfx" import * as CachePrelude from "dfx/Cache/prelude" -import type { DiscordGateway } from "dfx/DiscordGateway" -import { LiveDiscordGateway } from "dfx/DiscordGateway" +import { DiscordGatewayLive } from "dfx/DiscordGateway" import * as DiscordWS from "dfx/DiscordGateway/DiscordWS" -import { LiveJsonDiscordWSCodec } from "dfx/DiscordGateway/DiscordWS" +import { LiveJsonDiscordWSCodec as JsonDiscordWSCodecLive } from "dfx/DiscordGateway/DiscordWS" import * as Shard from "dfx/DiscordGateway/Shard" import * as SendEvent from "dfx/DiscordGateway/Shard/sendEvents" import * as ShardStore from "dfx/DiscordGateway/ShardStore" -import { LiveMemoryShardStore } from "dfx/DiscordGateway/ShardStore" +import { MemoryShardStoreLive as MemoryShardStoreLive } from "dfx/DiscordGateway/ShardStore" import * as WS from "dfx/DiscordGateway/WS" -import type { RateLimiter } from "dfx/RateLimit" -import { LiveMemoryRateLimitStore, LiveRateLimiter } from "dfx/RateLimit" +import { LogLive } from "dfx/Log" +import { + LiveMemoryRateLimitStore as MemoryRateLimitStoreLive, + RateLimiterLive as RateLimiterLive, +} from "dfx/RateLimit" +import * as Layer from "effect/Layer" -export { DiscordGateway, LiveDiscordGateway } from "dfx/DiscordGateway" +export { + DiscordGateway, + DiscordGatewayLive as LiveDiscordGateway, +} from "dfx/DiscordGateway" export { InteractionsRegistry, InteractionsRegistryLive, + interactionsSync, run as runIx, + setInteractionsSync, } from "dfx/Interactions/gateway" export { CachePrelude, DiscordWS, SendEvent, Shard, ShardStore, WS } -export const MemoryRateLimit = Layer.provide( - LiveRateLimiter, - LiveMemoryRateLimitStore, -) - -export const MemoryBot = Layer.mergeAll( - MemoryRateLimit, - LiveDiscordGateway, +export const DiscordLive = Layer.mergeAll( + RateLimiterLive, + DiscordGatewayLive, ).pipe( - Layer.provideMerge(LiveDiscordREST), - Layer.provide(LiveJsonDiscordWSCodec), - Layer.provide(LiveMemoryRateLimitStore), - Layer.provide(LiveMemoryShardStore), + Layer.provideMerge(DiscordRESTLive), + Layer.provide(JsonDiscordWSCodecLive), + Layer.provide(MemoryRateLimitStoreLive), + Layer.provide(MemoryShardStoreLive), + Layer.provideMerge(LogLive), ) - -export const gatewayLayer = ( - config: Config.Config.Wrap, -): Layer.Layer< - never, - ConfigError.ConfigError, - | RateLimiter - | Log.Log - | DiscordREST - | DiscordGateway - | DiscordConfig.DiscordConfig -> => - Layer.unwrapEffect( - Effect.config(Config.unwrap(config)).pipe( - Effect.map(DiscordConfig.make), - Effect.map(config => { - const LiveLog = config.debug ? Log.LiveLogDebug : Log.LiveLog - const LiveConfig = Layer.succeed(DiscordConfig.DiscordConfig, config) - return Layer.provideMerge(MemoryBot, Layer.merge(LiveLog, LiveConfig)) - }), - ), - ) diff --git a/src/index.ts b/src/index.ts index 461c132..3b95e8a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,12 +10,15 @@ import * as Ix from "dfx/Interactions/index" import * as Log from "dfx/Log" import * as Discord from "dfx/types" -export { DiscordREST, LiveDiscordREST } from "dfx/DiscordREST" +export { + DiscordREST, + DiscordRESTLive as LiveDiscordREST, +} from "dfx/DiscordREST" export { BucketDetails, LiveMemoryRateLimitStore, - LiveRateLimiter, + RateLimiterLive as LiveRateLimiter, RateLimiter, RateLimitStore, } from "dfx/RateLimit" diff --git a/src/webhooks.ts b/src/webhooks.ts index e5c8f91..709a06d 100644 --- a/src/webhooks.ts +++ b/src/webhooks.ts @@ -1,61 +1,22 @@ -import * as Config from "effect/Config" -import type * as ConfigError from "effect/ConfigError" -import * as Effect from "effect/Effect" -import * as Layer from "effect/Layer" -import * as DiscordConfig from "dfx/DiscordConfig" -import type { DiscordREST } from "dfx/DiscordREST" -import { LiveDiscordREST } from "dfx/DiscordREST" -import type { MakeConfigOpts, WebhookConfig } from "dfx/Interactions/webhook" -import { makeConfigLayer } from "dfx/Interactions/webhook" +import { DiscordRESTLive } from "dfx/DiscordREST" import * as Log from "dfx/Log" -import type { RateLimiter } from "dfx/RateLimit" -import { LiveMemoryRateLimitStore, LiveRateLimiter } from "dfx/RateLimit" +import { + LiveMemoryRateLimitStore as MemoryRateLimitStoreLive, + RateLimiterLive as RateLimiterLive, +} from "dfx/RateLimit" +import * as Layer from "effect/Layer" export { BadWebhookSignature, - makeConfigLayer, - makeHandler, - makeSimpleHandler, WebhookConfig, WebhookParseError, + makeHandler, + makeSimpleHandler, + layer as webhookLayer, + layerConfig as webhookLayerConfig, } from "dfx/Interactions/webhook" -export const MemoryRateLimit = Layer.provide( - LiveRateLimiter, - LiveMemoryRateLimitStore, -) - -export const MemoryREST = Layer.provide( - LiveDiscordREST, - LiveMemoryRateLimitStore, -) - -export const webhookLayer = ( - options: DiscordConfig.MakeOpts & MakeConfigOpts, -): Layer.Layer< - never, - ConfigError.ConfigError, - RateLimiter | DiscordREST | WebhookConfig -> => { - const config = DiscordConfig.make(options) - const LiveConfig = Layer.succeed(DiscordConfig.DiscordConfig, config) - const LiveWebhook = makeConfigLayer(options) - const LiveLog = config.debug ? Log.LiveLogDebug : Log.LiveLog - const LiveEnv = Layer.provide( - Layer.mergeAll(MemoryREST, LiveWebhook, MemoryRateLimit), - Layer.merge(LiveLog, LiveConfig), - ) - - return LiveEnv -} - -export const webhookLayerConfig = ( - config: Config.Config.Wrap, -): Layer.Layer< - never, - ConfigError.ConfigError, - RateLimiter | DiscordREST | WebhookConfig -> => - Layer.unwrapEffect( - Effect.map(Effect.config(Config.unwrap(config)), webhookLayer), - ) +export const DiscordLive = Layer.mergeAll( + DiscordRESTLive, + RateLimiterLive, +).pipe(Layer.provide(MemoryRateLimitStoreLive), Layer.provideMerge(Log.LogLive))