| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- import { describe, expect, test } from "bun:test"
- import { Context, Effect, Layer, LayerMap, Option } from "effect"
- import { Node } from "@opencode-ai/util/effect/app-node"
- import { AppNodeBuilder } from "@opencode-ai/core/effect/app-node-builder"
- import { LayerNode } from "@opencode-ai/util/effect/layer-node"
- import { Location } from "@opencode-ai/core/location"
- import { LocationServiceMap } from "@opencode-ai/core/location-service-map"
- import type { LocationError, LocationServices } from "@opencode-ai/core/location-services"
- import { Project } from "@opencode-ai/core/project"
- import { AbsolutePath } from "@opencode-ai/core/schema"
- import { tmpdir } from "../../fixture/tmpdir"
- class Value extends Context.Service<Value, { readonly value: string }>()("test/TagValue") {}
- class Result extends Context.Service<Result, { readonly value: string }>()("test/TagResult") {}
- class CycleA extends Context.Service<CycleA, {}>()("test/NodeBuildA") {}
- class CycleB extends Context.Service<CycleB, { readonly directory: AbsolutePath }>()("test/NodeBuildB") {}
- describe("node build", () => {
- test("does not build a location service map when the graph does not require it", async () => {
- const result = Node.makeGlobalNode({
- service: Result,
- layer: Layer.succeed(Result, Result.of({ value: "plain" })),
- deps: [],
- })
- const layer = AppNodeBuilder.build(result)
- const program = Effect.gen(function* () {
- expect(Option.isNone(yield* Effect.serviceOption(LocationServiceMap.Service))).toBe(true)
- return (yield* Result).value
- }).pipe(Effect.provide(layer))
- expect(await Effect.runPromise(program)).toBe("plain")
- })
- test("detects cycles through a replaced location service map", async () => {
- const a = Node.makeGlobalNode({
- service: CycleA,
- layer: Layer.effect(CycleA, Effect.as(LocationServiceMap.Service, CycleA.of({}))),
- deps: [LocationServiceMap.node],
- })
- const b = Node.makeGlobalNode({
- service: CycleB,
- layer: Layer.effect(
- CycleB,
- Effect.map(CycleA, () => CycleB.of({ directory: AbsolutePath.make(process.cwd()) })),
- ),
- deps: [a],
- })
- const mapLayer = Layer.effect(
- LocationServiceMap.Service,
- Effect.gen(function* () {
- const service = yield* CycleB
- return yield* LayerMap.make(
- (ref: Location.Ref) =>
- Layer.succeed(
- Location.Service,
- Location.Service.of({
- directory: ref.directory,
- workspaceID: ref.workspaceID,
- project: { id: Project.ID.global, directory: service.directory },
- }),
- ),
- { idleTimeToLive: "1 minute" },
- )
- }) as unknown as Effect.Effect<LayerMap.LayerMap<Location.Ref, LocationServices, LocationError>, never, CycleB>,
- )
- const map = Node.makeGlobalNode({ service: LocationServiceMap.Service, layer: mapLayer, deps: [b] })
- expect(() => AppNodeBuilder.build(LayerNode.group([a]), [[LocationServiceMap.node, map]])).toThrow(
- "Cycle detected in layer tree",
- )
- })
- test("shares top-level project with location services", async () => {
- await using tmp = await tmpdir()
- let acquisitions = 0
- const projectLayer = Layer.effect(
- Project.Service,
- Effect.sync(() => {
- acquisitions++
- return Project.Service.of({
- list: () => Effect.succeed([]),
- directories: () => Effect.succeed([]),
- resolve: (directory) => Effect.succeed({ id: Project.ID.global, directory }),
- commit: () => Effect.void,
- })
- }),
- )
- const ref = Location.Ref.make({ directory: AbsolutePath.make(tmp.path) })
- const layer = AppNodeBuilder.build(LayerNode.group([Project.node, LocationServiceMap.node]), [
- [Project.node, projectLayer],
- ])
- const program = Effect.gen(function* () {
- yield* Project.Service
- const locations = yield* LocationServiceMap.Service
- expect(Option.isSome(yield* Effect.serviceOption(LocationServiceMap.Service))).toBe(true)
- return yield* Location.Service.pipe(Effect.provide(locations.get(ref)))
- }).pipe(Effect.provide(layer))
- expect((await Effect.runPromise(program)).directory).toBe(ref.directory)
- expect(acquisitions).toBe(1)
- })
- test("returns a composed application layer", async () => {
- const value = Node.makeGlobalNode({
- service: Value,
- layer: Layer.succeed(Value, Value.of({ value: "value" })),
- deps: [],
- })
- const result = Node.makeGlobalNode({
- service: Result,
- layer: Layer.effect(
- Result,
- Effect.gen(function* () {
- return Result.of({ value: (yield* Value).value })
- }),
- ),
- deps: [value],
- })
- const serviceLayer = AppNodeBuilder.build(result)
- const program = Effect.gen(function* () {
- expect(Option.isNone(yield* Effect.serviceOption(LocationServiceMap.Service))).toBe(true)
- return (yield* Result).value
- }).pipe(Effect.provide(serviceLayer))
- expect(await Effect.runPromise(program)).toBe("value")
- })
- })
|