layer-node.test.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import { describe, expect, test } from "bun:test"
  2. import { Context, Effect, Layer } from "effect"
  3. import { LayerNode } from "@opencode-ai/core/effect/layer-node"
  4. class Value extends Context.Service<Value, { readonly value: string }>()("test/LayerNodeValue") {}
  5. class Greeting extends Context.Service<Greeting, { readonly value: string }>()("test/LayerNodeGreeting") {}
  6. class Left extends Context.Service<Left, { readonly value: string }>()("test/LayerNodeLeft") {}
  7. class Right extends Context.Service<Right, { readonly value: string }>()("test/LayerNodeRight") {}
  8. const tiers = LayerNode.tiers(["app"])
  9. const make = tiers.make("app")
  10. const valueLayer = Layer.succeed(Value, Value.of({ value: "production" }))
  11. const greetingLayer = Layer.effect(
  12. Greeting,
  13. Effect.map(Value, (value) => Greeting.of({ value: `hello ${value.value}` })),
  14. )
  15. const value = make({ service: Value, layer: valueLayer, deps: [] })
  16. const greeting = make({ service: Greeting, layer: greetingLayer, deps: [value] })
  17. describe("layer node", () => {
  18. test("builds an untiered graph", async () => {
  19. const value = LayerNode.make({ service: Value, layer: valueLayer, deps: [] })
  20. const greeting = LayerNode.make({ service: Greeting, layer: greetingLayer, deps: [value] })
  21. const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(LayerNode.buildLayer(greeting)))
  22. expect(await Effect.runPromise(program)).toBe("hello production")
  23. })
  24. test("builds a dependency graph", async () => {
  25. const program = Effect.map(Greeting, (item) => item.value).pipe(
  26. Effect.provide(LayerNode.buildLayer(greeting, { tiers })),
  27. )
  28. expect(await Effect.runPromise(program)).toBe("hello production")
  29. })
  30. test("replaces a layer by identity", async () => {
  31. const replacement = Layer.succeed(Value, Value.of({ value: "simulation" }))
  32. const program = Effect.map(Greeting, (item) => item.value).pipe(
  33. Effect.provide(LayerNode.buildLayer(greeting, { tiers, replacements: [LayerNode.replace(valueLayer, replacement)] })),
  34. )
  35. expect(await Effect.runPromise(program)).toBe("hello simulation")
  36. })
  37. test("replaces every use of the same layer", async () => {
  38. const leftLayer = Layer.effect(
  39. Left,
  40. Effect.map(Value, (item) => Left.of({ value: item.value })),
  41. )
  42. const rightLayer = Layer.effect(
  43. Right,
  44. Effect.map(Value, (item) => Right.of({ value: item.value })),
  45. )
  46. const left = make({ service: Left, layer: leftLayer, deps: [value] })
  47. const right = make({ service: Right, layer: rightLayer, deps: [value] })
  48. const replacement = Layer.succeed(Value, Value.of({ value: "replaced" }))
  49. const layer = LayerNode.buildLayer(LayerNode.group([left, right]), {
  50. tiers,
  51. replacements: [LayerNode.replace(valueLayer, replacement)],
  52. })
  53. const program = Effect.gen(function* () {
  54. return [(yield* Left).value, (yield* Right).value]
  55. }).pipe(Effect.provide(layer))
  56. expect(await Effect.runPromise(program)).toEqual(["replaced", "replaced"])
  57. })
  58. test("does not acquire an unused replacement", async () => {
  59. let acquisitions = 0
  60. const other = Layer.succeed(Value, Value.of({ value: "other" }))
  61. const replacement = Layer.effect(
  62. Value,
  63. Effect.sync(() => {
  64. acquisitions++
  65. return Value.of({ value: "replacement" })
  66. }),
  67. )
  68. await Effect.runPromise(
  69. Effect.map(Greeting, (item) => item.value).pipe(
  70. Effect.provide(LayerNode.buildLayer(greeting, { tiers, replacements: [LayerNode.replace(other, replacement)] })),
  71. ),
  72. )
  73. expect(acquisitions).toBe(0)
  74. })
  75. })