1import { assert, describe, it } from "@effect/vitest"
2import { Context, Effect, Layer } from "effect"
3import * as Exit from "effect/Exit"
4import * as LayerRef from "effect/LayerRef"
5import * as Scope from "effect/Scope"
6
7class Resource extends Context.Service<Resource, { readonly id: number }>()("LayerRefTest/Resource") {}
8
9const makeLayer = (acquired: Array<number>, released: Array<number>) =>
10 Layer.effect(Resource)(
11 Effect.acquireRelease(
12 Effect.sync(() => {
13 const id = acquired.length + 1
14 acquired.push(id)
15 return { id }
16 }),
17 (resource) =>
18 Effect.sync(() => {
19 released.push(resource.id)
20 })
21 )
22 )
23
24describe("LayerRef", () => {
25 it.effect("make lazily acquires and releases resources", () =>
26 Effect.gen(function*() {
27 const acquired: Array<number> = []
28 const released: Array<number> = []
29 const refScope = yield* Scope.make()
30 const ref = yield* LayerRef.make(makeLayer(acquired, released)).pipe(Scope.provide(refScope))
31
32 assert.deepStrictEqual(acquired, [])
33 assert.deepStrictEqual(released, [])
34
35 const context = yield* Effect.scoped(ref.contextEffect)
36 assert.strictEqual(Context.get(context, Resource).id, 1)
37 assert.deepStrictEqual(acquired, [1])
38 assert.deepStrictEqual(released, [1])
39
40 const contextFromLayer = yield* Effect.scoped(Layer.build(ref.get))
41 assert.strictEqual(Context.get(contextFromLayer, Resource).id, 2)
42 assert.deepStrictEqual(acquired, [1, 2])
43 assert.deepStrictEqual(released, [1, 2])
44
45 yield* Scope.close(refScope, Exit.void)
46 assert.deepStrictEqual(released, [1, 2])
47 }))
48
49 it.effect("invalidate releases an idle resource and acquires a fresh one", () =>
50 Effect.gen(function*() {
51 const acquired: Array<number> = []
52 const released: Array<number> = []
53 const refScope = yield* Scope.make()
54 const ref = yield* LayerRef.make(makeLayer(acquired, released), {
55 idleTimeToLive: "1 minute"
56 }).pipe(Scope.provide(refScope))
57
58 const context = yield* Effect.scoped(ref.contextEffect)
59 assert.strictEqual(Context.get(context, Resource).id, 1)
60 assert.deepStrictEqual(acquired, [1])
61 assert.deepStrictEqual(released, [])
62
63 const cachedContext = yield* Effect.scoped(ref.contextEffect)
64 assert.strictEqual(Context.get(cachedContext, Resource).id, 1)
65 assert.deepStrictEqual(acquired, [1])
66 assert.deepStrictEqual(released, [])
67
68 yield* ref.invalidate
69 assert.deepStrictEqual(released, [1])
70
71 const freshContext = yield* Effect.scoped(ref.contextEffect)
72 assert.strictEqual(Context.get(freshContext, Resource).id, 2)
73 assert.deepStrictEqual(acquired, [1, 2])
74 assert.deepStrictEqual(released, [1])
75
76 yield* Scope.close(refScope, Exit.void)
77 assert.deepStrictEqual(released, [1, 2])
78 }))
79
80 it.effect("preload acquires the resource when the LayerRef is created", () =>
81 Effect.gen(function*() {
82 const acquired: Array<number> = []
83 const released: Array<number> = []
84 const refScope = yield* Scope.make()
85 const ref = yield* LayerRef.make(makeLayer(acquired, released), {
86 idleTimeToLive: "1 minute",
87 preload: true
88 }).pipe(Scope.provide(refScope))
89
90 assert.deepStrictEqual(acquired, [1])
91 assert.deepStrictEqual(released, [])
92
93 const context = yield* Effect.scoped(ref.contextEffect)
94 assert.strictEqual(Context.get(context, Resource).id, 1)
95 assert.deepStrictEqual(acquired, [1])
96 assert.deepStrictEqual(released, [])
97
98 yield* Scope.close(refScope, Exit.void)
99 assert.deepStrictEqual(released, [1])
100 }))
101
102 it.effect("Service exposes layer, get, contextEffect, and invalidate", () =>
103 Effect.gen(function*() {
104 const acquired: Array<number> = []
105 const released: Array<number> = []
106
107 class ResourceRef extends LayerRef.Service<ResourceRef>()("LayerRefTest/ResourceRef", {
108 layer: makeLayer(acquired, released),
109 idleTimeToLive: "1 minute"
110 }) {}
111
112 yield* Effect.gen(function*() {
113 const context = yield* Effect.scoped(ResourceRef.contextEffect)
114 assert.strictEqual(Context.get(context, Resource).id, 1)
115
116 const contextFromLayer = yield* Effect.scoped(Layer.build(ResourceRef.get))
117 assert.strictEqual(Context.get(contextFromLayer, Resource).id, 1)
118 assert.deepStrictEqual(acquired, [1])
119 assert.deepStrictEqual(released, [])
120
121 yield* ResourceRef.invalidate
122 assert.deepStrictEqual(released, [1])
123
124 const freshContext = yield* Effect.scoped(ResourceRef.contextEffect)
125 assert.strictEqual(Context.get(freshContext, Resource).id, 2)
126 assert.deepStrictEqual(acquired, [1, 2])
127 }).pipe(Effect.provide(ResourceRef.layer))
128
129 assert.deepStrictEqual(released, [1, 2])
130 }))
131})