fix(core): defer cyclic layer dependencies

This commit is contained in:
Dax Raad
2026-07-17 20:25:21 -04:00
parent 22c23cb44c
commit c4830c147d
4 changed files with 16 additions and 3 deletions
+4 -2
View File
@@ -74,7 +74,7 @@ type MakeInput<
T extends Tag | undefined = undefined,
> = NodeIdentity & {
readonly layer: Implementation
readonly deps: Items & CheckDependencies<Implementation, NoInfer<Items>>
readonly deps: (Items | (() => Items)) & CheckDependencies<Implementation, NoInfer<Items>>
readonly tag?: T
}
@@ -90,7 +90,9 @@ export function make<
name: input.service !== undefined ? input.service.key : input.name,
service: input.service,
implementation: input.layer,
dependencies: input.deps,
get dependencies() {
return typeof input.deps === "function" ? input.deps() : input.deps
},
tag: input.tag,
}
}
+2 -1
View File
@@ -1033,7 +1033,8 @@ const SHELL_MAX_CAPTURE_BYTES = 1024 * 1024
export const node = makeGlobalNode({
service: Service,
layer: layer.pipe(Layer.orDie),
deps: [
// Defer the execution node across the Session/runner module cycle until the graph is compiled.
deps: () => [
Job.node,
Database.node,
EventV2.node,
@@ -32,6 +32,7 @@ const b = make({ service: B, layer: bLayer, deps: [a] })
const c = make({ service: C, layer: cLayer, deps: [a, b] })
const failing = make({ service: A, layer: failingA, deps: [] })
const dependent = make({ service: B, layer: bLayer, deps: [failing] })
make({ service: B, layer: bLayer, deps: () => [a] })
const inputA = LayerNode.unbound(A, tags.values.app)
const inputDependent = make({ service: B, layer: bLayer, deps: [inputA] })
@@ -46,6 +47,9 @@ make({ service: A, name: "a", layer: aLayer, deps: [] })
// @ts-expect-error B requires A
make({ service: B, layer: bLayer, deps: [] })
// @ts-expect-error Lazy dependencies must still provide A
make({ service: B, layer: bLayer, deps: () => [] })
// @ts-expect-error C requires A and B
make({ service: C, layer: cLayer, deps: [a] })
@@ -37,6 +37,12 @@ describe("layer node", () => {
expect(await Effect.runPromise(program)).toBe("hello production")
})
test("resolves lazy dependencies when compiling", async () => {
const greeting = make({ service: Greeting, layer: greetingLayer, deps: () => [value] })
const program = Effect.map(Greeting, (item) => item.value).pipe(Effect.provide(build(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