mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-12 22:58:27 +00:00
Co-authored-by: Brendan Allan <14191578+Brendonovich@users.noreply.github.com> Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: opencode-agent[bot] <opencode-agent[bot]@users.noreply.github.com> Co-authored-by: Affan Ali <93028901+affanali2k3@users.noreply.github.com> Co-authored-by: affanali2k3 <affanalikhanxx@gmail.com> Co-authored-by: Frank <frank@anoma.ly> Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com> Co-authored-by: 𝓛𝓲𝓽𝓽𝓵𝓮 𝓕𝓻𝓪𝓷𝓴 <little-frank@opencord.local> Co-authored-by: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Co-authored-by: Jay V <air@live.ca> Co-authored-by: Dax Raad <d@ironbay.co> Co-authored-by: Aarav Sareen <96787824+arvsrn@users.noreply.github.com> Co-authored-by: OpeOginni <107570612+OpeOginni@users.noreply.github.com> Co-authored-by: Luke Parker <10430890+Hona@users.noreply.github.com> Co-authored-by: Ben Guthrie <benjee.012@gmail.com> Co-authored-by: Dax <mail@thdxr.com> Co-authored-by: Filip <34747899+neriousy@users.noreply.github.com> Co-authored-by: Max Anderson <max.a.anderson95@gmail.com> Co-authored-by: Brendan Allan <git@brendonovich.dev> Co-authored-by: Jack <jack@anoma.ly> Co-authored-by: Shoubhit Dash <shoubhit2005@gmail.com> Co-authored-by: Dustin Deus <deusdustin@gmail.com> Co-authored-by: starptech <starptech@starptechs-MBP.fritz.box> Co-authored-by: Aiden Cline <aidenpcline@gmail.com> Co-authored-by: usrnk1 <7547651+usrnk1@users.noreply.github.com> Co-authored-by: Jay <53023+jayair@users.noreply.github.com> Co-authored-by: runvip <164729189+runvip@users.noreply.github.com> Co-authored-by: opencode <opencode@sst.dev> Co-authored-by: Julian Coy <julian@ex-machina.co> Co-authored-by: Vladimir Glafirov <vglafirov@gitlab.com>
261 lines
10 KiB
TypeScript
261 lines
10 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import { Context, Effect, Layer } from "effect"
|
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
|
|
|
class Value extends Context.Service<Value, { readonly value: string }>()("test/LayerNodeValue") {}
|
|
class Greeting extends Context.Service<Greeting, { readonly value: string }>()("test/LayerNodeGreeting") {}
|
|
class Left extends Context.Service<Left, { readonly value: string }>()("test/LayerNodeLeft") {}
|
|
class Right extends Context.Service<Right, { readonly value: string }>()("test/LayerNodeRight") {}
|
|
class Database extends Context.Service<Database, { readonly name: string }>()("test/GraphDatabase") {}
|
|
class Users extends Context.Service<Users, { readonly list: Effect.Effect<string[]> }>()("test/GraphUsers") {}
|
|
class App extends Context.Service<App, { readonly run: Effect.Effect<string[]> }>()("test/GraphApp") {}
|
|
|
|
const tags = LayerNode.tags({ app: [] })
|
|
const make = tags.make("app")
|
|
const build = <A, E>(root: LayerNode.Node<A, E, any>, replacements?: readonly LayerNode.Replacement[]) =>
|
|
LayerNode.compile(root, replacements) as Layer.Layer<A, E>
|
|
const valueLayer = Layer.succeed(Value, Value.of({ value: "production" }))
|
|
const greetingLayer = Layer.effect(
|
|
Greeting,
|
|
Effect.map(Value, (value) => Greeting.of({ value: `hello ${value.value}` })),
|
|
)
|
|
const value = make({ service: Value, layer: valueLayer, deps: [] })
|
|
const greeting = make({ service: Greeting, layer: greetingLayer, deps: [value] })
|
|
|
|
describe("layer node", () => {
|
|
test("builds an untagged graph", async () => {
|
|
const value = LayerNode.make({ service: Value, layer: valueLayer, deps: [] })
|
|
const greeting = LayerNode.make({ service: Greeting, layer: greetingLayer, deps: [value] })
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
|
Effect.provide(LayerNode.compile(LayerNode.group([greeting]))),
|
|
)
|
|
expect(await Effect.runPromise(program)).toBe("hello production")
|
|
})
|
|
|
|
test("builds a dependency graph", async () => {
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(build(LayerNode.group([greeting]))))
|
|
expect(await Effect.runPromise(program)).toBe("hello production")
|
|
})
|
|
|
|
test("exposes roots but hides transitive dependencies", () => {
|
|
const layer = build(LayerNode.group([greeting]))
|
|
const check: Layer.Layer<Greeting> = layer
|
|
void check
|
|
})
|
|
|
|
test("preserves branch-specific implementations across roots", async () => {
|
|
const firstValue = make({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "first" })), deps: [] })
|
|
const secondValue = make({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "second" })), deps: [] })
|
|
const leftLayer = Layer.effect(
|
|
Left,
|
|
Effect.map(Value, (item) => Left.of({ value: item.value })),
|
|
)
|
|
const rightLayer = Layer.effect(
|
|
Right,
|
|
Effect.map(Value, (item) => Right.of({ value: item.value })),
|
|
)
|
|
const left = make({ service: Left, layer: leftLayer, deps: [firstValue] })
|
|
const right = make({ service: Right, layer: rightLayer, deps: [secondValue] })
|
|
const layer = build(LayerNode.group([left, right]))
|
|
const program = Effect.gen(function* () {
|
|
return [(yield* Left).value, (yield* Right).value]
|
|
}).pipe(Effect.provide(layer))
|
|
expect(await Effect.runPromise(program)).toEqual(["first", "second"])
|
|
})
|
|
|
|
test("requires unbound nodes to be replaced before compilation", async () => {
|
|
const unbound = LayerNode.unbound(Value, tags.values.app)
|
|
const greeting = make({ service: Greeting, layer: greetingLayer, deps: [unbound] })
|
|
const tree = LayerNode.group([greeting])
|
|
expect(() => LayerNode.compile(tree)).toThrow("Unbound layer node: test/LayerNodeValue")
|
|
const layer = LayerNode.compile(tree, [[unbound, value]]) as Layer.Layer<Greeting>
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(layer))
|
|
expect(await Effect.runPromise(program)).toBe("hello production")
|
|
})
|
|
|
|
test("replaces a node with a closed layer", async () => {
|
|
const replacement = Layer.succeed(Value, Value.of({ value: "simulation" }))
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
|
Effect.provide(build(LayerNode.group([greeting]), [[value, replacement]])),
|
|
)
|
|
expect(await Effect.runPromise(program)).toBe("hello simulation")
|
|
})
|
|
|
|
test("replaces every use of the same layer", async () => {
|
|
const leftLayer = Layer.effect(
|
|
Left,
|
|
Effect.map(Value, (item) => Left.of({ value: item.value })),
|
|
)
|
|
const rightLayer = Layer.effect(
|
|
Right,
|
|
Effect.map(Value, (item) => Right.of({ value: item.value })),
|
|
)
|
|
const left = make({ service: Left, layer: leftLayer, deps: [value] })
|
|
const right = make({ service: Right, layer: rightLayer, deps: [value] })
|
|
const replacement = Layer.succeed(Value, Value.of({ value: "replaced" }))
|
|
const layer = build(LayerNode.group([left, right]), [[value, replacement]])
|
|
const program = Effect.gen(function* () {
|
|
return [(yield* Left).value, (yield* Right).value]
|
|
}).pipe(Effect.provide(layer))
|
|
expect(await Effect.runPromise(program)).toEqual(["replaced", "replaced"])
|
|
})
|
|
|
|
test("does not acquire an unused replacement", async () => {
|
|
let acquisitions = 0
|
|
const other = make({ service: Left, layer: Layer.succeed(Left, Left.of({ value: "other" })), deps: [] })
|
|
const replacement = Layer.effect(
|
|
Left,
|
|
Effect.sync(() => {
|
|
acquisitions++
|
|
return Left.of({ value: "replacement" })
|
|
}),
|
|
)
|
|
await Effect.runPromise(
|
|
Effect.map(Greeting, (item) => item.value).pipe(
|
|
Effect.provide(build(LayerNode.group([greeting]), [[other, replacement]])),
|
|
),
|
|
)
|
|
expect(acquisitions).toBe(0)
|
|
})
|
|
|
|
test("replaces a node without acquiring its dependencies", async () => {
|
|
let acquisitions = 0
|
|
const dependencyLayer = Layer.effect(
|
|
Value,
|
|
Effect.sync(() => {
|
|
acquisitions++
|
|
return Value.of({ value: "dependency" })
|
|
}),
|
|
)
|
|
const dependency = make({ service: Value, layer: dependencyLayer, deps: [] })
|
|
const original = make({ service: Greeting, layer: greetingLayer, deps: [dependency] })
|
|
const replacement = make({
|
|
service: Greeting,
|
|
layer: Layer.succeed(Greeting, Greeting.of({ value: "replacement" })),
|
|
deps: [],
|
|
})
|
|
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
|
Effect.provide(build(LayerNode.group([original]), [[original, replacement]])),
|
|
)
|
|
|
|
expect(await Effect.runPromise(program)).toBe("replacement")
|
|
expect(acquisitions).toBe(0)
|
|
})
|
|
|
|
test("applies later replacements inside earlier replacement nodes", async () => {
|
|
const original = make({ service: Greeting, layer: greetingLayer, deps: [value] })
|
|
const replacement = make({ service: Greeting, layer: greetingLayer, deps: [value] })
|
|
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
|
Effect.provide(
|
|
build(LayerNode.group([original]), [
|
|
[original, replacement],
|
|
[value, Layer.succeed(Value, Value.of({ value: "replacement dependency" }))],
|
|
]),
|
|
),
|
|
)
|
|
|
|
expect(await Effect.runPromise(program)).toBe("hello replacement dependency")
|
|
})
|
|
|
|
test("hoists and compiles tagged graphs", async () => {
|
|
const tags = LayerNode.tags({ location: ["global"], global: [] })
|
|
const global = tags.make("global")
|
|
const location = tags.make("location")
|
|
const database = global({
|
|
service: Database,
|
|
layer: Layer.succeed(Database, Database.of({ name: "Alice" })),
|
|
deps: [],
|
|
})
|
|
const users = location({
|
|
service: Users,
|
|
layer: Layer.effect(
|
|
Users,
|
|
Effect.gen(function* () {
|
|
const db = yield* Database
|
|
return Users.of({ list: Effect.succeed([db.name]) })
|
|
}),
|
|
),
|
|
deps: [database],
|
|
})
|
|
const app = location({
|
|
service: App,
|
|
layer: Layer.effect(
|
|
App,
|
|
Effect.gen(function* () {
|
|
const service = yield* Users
|
|
return App.of({ run: service.list })
|
|
}),
|
|
),
|
|
deps: [users],
|
|
})
|
|
|
|
const result = LayerNode.hoist(LayerNode.group([app]), tags.values.global)
|
|
expect(result.node.dependencies[0]?.dependencies[0]?.dependencies[0]).toMatchObject({
|
|
kind: "group",
|
|
dependencies: [],
|
|
})
|
|
expect(result.hoisted.dependencies).toEqual([database])
|
|
|
|
const layer = LayerNode.compile(result.node).pipe(
|
|
Layer.provide(LayerNode.compile(result.hoisted)),
|
|
) as unknown as Layer.Layer<App>
|
|
const program = Effect.gen(function* () {
|
|
return yield* (yield* App).run
|
|
}).pipe(Effect.provide(layer))
|
|
|
|
expect(await Effect.runPromise(program)).toEqual(["Alice"])
|
|
})
|
|
|
|
test("rejects conflicting hoisted implementations", () => {
|
|
const tags = LayerNode.tags({ location: ["global"], global: [] })
|
|
const global = tags.make("global")
|
|
const location = tags.make("location")
|
|
const first = global({
|
|
service: Database,
|
|
layer: Layer.succeed(Database, Database.of({ name: "first" })),
|
|
deps: [],
|
|
})
|
|
const second = global({
|
|
service: Database,
|
|
layer: Layer.succeed(Database, Database.of({ name: "second" })),
|
|
deps: [],
|
|
})
|
|
const left = location({
|
|
service: Users,
|
|
layer: Layer.effect(Users, Effect.as(Database, Users.of({ list: Effect.succeed([]) }))),
|
|
deps: [first],
|
|
})
|
|
const right = location({
|
|
service: App,
|
|
layer: Layer.effect(App, Effect.as(Database, App.of({ run: Effect.succeed([]) }))),
|
|
deps: [second],
|
|
})
|
|
|
|
expect(() => LayerNode.hoist(LayerNode.group([left, right]), tags.values.global)).toThrow(
|
|
"Tag global has conflicting implementations for test/GraphDatabase",
|
|
)
|
|
})
|
|
|
|
test("treats dependency groups as transparent while hoisting", () => {
|
|
const tags = LayerNode.tags({ location: ["global"], global: [] })
|
|
const global = tags.make("global")
|
|
const location = tags.make("location")
|
|
const database = global({
|
|
service: Database,
|
|
layer: Layer.succeed(Database, Database.of({ name: "Alice" })),
|
|
deps: [],
|
|
})
|
|
const users = location({
|
|
service: Users,
|
|
layer: Layer.effect(Users, Effect.as(Database, Users.of({ list: Effect.succeed([]) }))),
|
|
deps: [LayerNode.group([database])],
|
|
})
|
|
const result = LayerNode.hoist(LayerNode.group([users]), tags.values.global)
|
|
|
|
expect(result.node.dependencies[0]?.dependencies[0]?.dependencies[0]).toMatchObject({
|
|
kind: "group",
|
|
dependencies: [],
|
|
})
|
|
})
|
|
})
|