1/**
2 * @title Creating Layers from configuration and/or Effects
3 *
4 * Build a layer dynamically from an Effect / Config with `Layer.unwrap`.
5 */
6import { Config, Context, Effect, Layer, Schema } from "effect"
7
8export class MessageStoreError extends Schema.TaggedError<MessageStoreError>()("MessageStoreError", {
9 cause: Schema.Defect()
10}) {}
11
12export class MessageStore extends Context.Service<MessageStore, {
13 append(message: string): Effect.Effect<void>
14 readonly all: Effect.Effect<ReadonlyArray<string>>
15}>()("myapp/MessageStore") {
16 static readonly layerInMemory = Layer.effect(
17 MessageStore,
18 Effect.sync(() => {
19 const messages: Array<string> = []
20
21 return MessageStore.of({
22 append: (message) =>
23 Effect.sync(() => {
24 messages.push(message)
25 }),
26 all: Effect.sync(() => [...messages])
27 })
28 })
29 )
30
31 static readonly layerRemote = (url: URL) =>
32 Layer.effect(
33 MessageStore,
34 Effect.try({
35 try: () => {
36 // In a real app this is where you would open a network connection.
37 const messages: Array<string> = []
38
39 return MessageStore.of({
40 append: (message) =>
41 Effect.sync(() => {
42 messages.push(`[${url.host}] ${message}`)
43 }),
44 all: Effect.sync(() => [...messages])
45 })
46 },
47 catch: (cause) => new MessageStoreError({ cause })
48 })
49 )
50
51 static readonly layer = Layer.unwrap(
52 Effect.gen(function*() {
53 // Read config inside an Effect, then choose which concrete layer to use.
54 const useInMemory = yield* Config.Boolean("MESSAGE_STORE_IN_MEMORY").pipe(
55 Config.withDefault(false)
56 )
57
58 if (useInMemory) {
59 return MessageStore.layerInMemory
60 }
61
62 const remoteUrl = yield* Config.URL("MESSAGE_STORE_URL")
63 return MessageStore.layerRemote(remoteUrl)
64 })
65 )
66}