| 1 | import { assert, describe, it } from "@effect/vitest" |
| 2 | import { Context, Effect, Layer } from "effect" |
| 3 | import * as Exit from "effect/Exit" |
| 4 | import * as LayerRef from "effect/LayerRef" |
| 5 | import * as Scope from "effect/Scope" |
| 6 | |
| 7 | class Resource extends Context.Service<Resource, { readonly id: number }>()("LayerRefTest/Resource") {} |
| 8 | |
| 9 | const 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 | |
| 24 | describe("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 | }) |