refactor(core): support tiered layer nodes (#33937)
This commit is contained in:
parent
21b78c49fc
commit
0e731c2a1a
80 changed files with 1132 additions and 601 deletions
|
|
@ -458,6 +458,6 @@ export const layer: Layer.Layer<Service, never, AccountRepo.Service | HttpClient
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AccountRepo.defaultLayer), Layer.provide(FetchHttpClient.layer))
|
||||
|
||||
export const node = LayerNode.make(layer, [AccountRepo.node, httpClient])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AccountRepo.node, httpClient] })
|
||||
|
||||
export * as Account from "./account"
|
||||
|
|
|
|||
|
|
@ -168,6 +168,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [Database.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Database.node] })
|
||||
|
||||
export * as AccountRepo from "./repo"
|
||||
|
|
|
|||
|
|
@ -447,15 +447,12 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(LocationServiceMap.layer),
|
||||
)
|
||||
|
||||
const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, [])
|
||||
const locationServiceMapNode = LayerNode.make({ service: Service, layer: LocationServiceMap.layer, deps: [] })
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Config.node,
|
||||
Auth.node,
|
||||
Plugin.node,
|
||||
Skill.node,
|
||||
Provider.node,
|
||||
locationServiceMapNode,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, Auth.node, Plugin.node, Skill.node, Provider.node, locationServiceMapNode],
|
||||
})
|
||||
|
||||
export * as Agent from "./agent"
|
||||
|
|
|
|||
|
|
@ -94,6 +94,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node] })
|
||||
|
||||
export * as Auth from "."
|
||||
|
|
|
|||
|
|
@ -34,6 +34,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const node = LayerNode.make(layer, [])
|
||||
export const node = LayerNode.make({ service: CoreBackgroundJob.Service, layer, deps: [] })
|
||||
|
||||
export * as BackgroundJob from "./job"
|
||||
|
|
|
|||
|
|
@ -170,6 +170,6 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(Skill.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, MCP.node, Skill.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Config.node, MCP.node, Skill.node] })
|
||||
|
||||
export * as Command from "."
|
||||
|
|
|
|||
|
|
@ -681,6 +681,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(FetchHttpClient.layer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, Auth.node, Account.node, Env.node, Npm.node, httpClient])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [FSUtil.node, Auth.node, Account.node, Env.node, Npm.node, httpClient],
|
||||
})
|
||||
|
||||
export * as Config from "./config"
|
||||
|
|
|
|||
|
|
@ -958,16 +958,20 @@ function route(url: string | URL, path: string) {
|
|||
return next
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Auth.node,
|
||||
Session.node,
|
||||
SessionPrompt.node,
|
||||
httpClient,
|
||||
EventV2Bridge.node,
|
||||
Vcs.node,
|
||||
RuntimeFlags.node,
|
||||
FSUtil.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
Auth.node,
|
||||
Session.node,
|
||||
SessionPrompt.node,
|
||||
httpClient,
|
||||
EventV2Bridge.node,
|
||||
Vcs.node,
|
||||
RuntimeFlags.node,
|
||||
FSUtil.node,
|
||||
Database.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as Workspace from "./workspace"
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ export const layer = (overrides: Partial<Info> = {}) =>
|
|||
|
||||
export const defaultLayer = Service.defaultLayer.pipe(Layer.orDie)
|
||||
|
||||
export const node = LayerNode.make(defaultLayer, [])
|
||||
export const node = LayerNode.make({ service: Service, layer: defaultLayer, deps: [] })
|
||||
|
||||
export * as RuntimeFlags from "./runtime-flags"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
|
|
|||
2
packages/opencode/src/env/index.ts
vendored
2
packages/opencode/src/env/index.ts
vendored
|
|
@ -38,6 +38,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer
|
||||
|
||||
export const node = LayerNode.make(layer, [])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [] })
|
||||
|
||||
export * as Env from "."
|
||||
|
|
|
|||
|
|
@ -68,6 +68,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2.node] })
|
||||
|
||||
export * as EventV2Bridge from "./event-v2-bridge"
|
||||
|
|
|
|||
|
|
@ -200,6 +200,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, AppProcess.node, RuntimeFlags.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, AppProcess.node, RuntimeFlags.node],
|
||||
})
|
||||
|
||||
export * as Format from "."
|
||||
|
|
|
|||
|
|
@ -345,6 +345,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(AppProcess.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [AppProcess.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [AppProcess.node] })
|
||||
|
||||
export * as Git from "."
|
||||
|
|
|
|||
|
|
@ -169,6 +169,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Config.node] })
|
||||
|
||||
export * as Image from "./image"
|
||||
|
|
|
|||
|
|
@ -332,6 +332,6 @@ export const latest = (...args: Parameters<Interface["latest"]>) => runPromise((
|
|||
export const method = () => runPromise((s) => s.method())
|
||||
export const upgrade = (...args: Parameters<Interface["upgrade"]>) => runPromise((s) => s.upgrade(...args))
|
||||
|
||||
export const node = LayerNode.make(layer, [httpClient, AppProcess.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [httpClient, AppProcess.node] })
|
||||
|
||||
export * as Installation from "."
|
||||
|
|
|
|||
|
|
@ -504,6 +504,10 @@ export const defaultLayer = layer.pipe(
|
|||
|
||||
export * as Diagnostic from "./diagnostic"
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, RuntimeFlags.node, FSUtil.node, EventV2Bridge.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, RuntimeFlags.node, FSUtil.node, EventV2Bridge.node],
|
||||
})
|
||||
|
||||
export * as LSP from "./lsp"
|
||||
|
|
|
|||
|
|
@ -169,6 +169,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EffectFlock.defaultLayer), Layer.provide(FSUtil.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, EffectFlock.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, EffectFlock.node] })
|
||||
|
||||
export * as McpAuth from "./auth"
|
||||
|
|
|
|||
|
|
@ -1005,6 +1005,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(FSUtil.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [CrossSpawnSpawner.node, McpAuth.node, EventV2Bridge.node, Config.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [CrossSpawnSpawner.node, McpAuth.node, EventV2Bridge.node, Config.node],
|
||||
})
|
||||
|
||||
export * as MCP from "."
|
||||
|
|
|
|||
|
|
@ -215,6 +215,6 @@ export function disabled(tools: string[], ruleset: PermissionV1.Ruleset): Set<st
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as Permission from "."
|
||||
|
|
|
|||
|
|
@ -311,6 +311,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node, Config.node, RuntimeFlags.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [EventV2Bridge.node, Config.node, RuntimeFlags.node],
|
||||
})
|
||||
|
||||
export * as Plugin from "."
|
||||
|
|
|
|||
|
|
@ -62,15 +62,10 @@ export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
|||
]),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Config.node,
|
||||
Format.node,
|
||||
LSP.node,
|
||||
Plugin.node,
|
||||
Project.node,
|
||||
ShareNext.node,
|
||||
Snapshot.node,
|
||||
Vcs.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, Format.node, LSP.node, Plugin.node, Project.node, ShareNext.node, Snapshot.node, Vcs.node],
|
||||
})
|
||||
|
||||
export * as InstanceBootstrap from "./bootstrap"
|
||||
|
|
|
|||
|
|
@ -204,6 +204,10 @@ export const layer: Layer.Layer<Service, never, Project.Service | InstanceBootst
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Project.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [Project.node, InstanceBootstrapGraph.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Project.node, InstanceBootstrapGraph.node],
|
||||
})
|
||||
|
||||
export * as InstanceStore from "./instance-store"
|
||||
|
|
|
|||
|
|
@ -476,15 +476,19 @@ export const defaultLayer = layer.pipe(
|
|||
|
||||
export const use = serviceUse(Service)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
FSUtil.node,
|
||||
AppProcess.node,
|
||||
CrossSpawnSpawner.node,
|
||||
ProjectV2.node,
|
||||
ProjectDirectories.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
FSUtil.node,
|
||||
AppProcess.node,
|
||||
CrossSpawnSpawner.node,
|
||||
ProjectV2.node,
|
||||
ProjectDirectories.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as Project from "./project"
|
||||
|
|
|
|||
|
|
@ -420,6 +420,6 @@ export const layer: Layer.Layer<Service, never, Git.Service | EventV2Bridge.Serv
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(Git.defaultLayer), Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [Git.node, EventV2Bridge.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Git.node, EventV2Bridge.node] })
|
||||
|
||||
export * as Vcs from "./vcs"
|
||||
|
|
|
|||
|
|
@ -228,6 +228,6 @@ export const defaultLayer = Layer.suspend(() =>
|
|||
layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Auth.node, Plugin.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [Auth.node, Plugin.node] })
|
||||
|
||||
export * as ProviderAuth from "./auth"
|
||||
|
|
|
|||
|
|
@ -1979,14 +1979,10 @@ export function parseModel(model: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
FSUtil.node,
|
||||
Config.node,
|
||||
Auth.node,
|
||||
Env.node,
|
||||
Plugin.node,
|
||||
ModelsDev.node,
|
||||
RuntimeFlags.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [FSUtil.node, Config.node, Auth.node, Env.node, Plugin.node, ModelsDev.node, RuntimeFlags.node],
|
||||
})
|
||||
|
||||
export * as Provider from "./provider"
|
||||
|
|
|
|||
|
|
@ -158,6 +158,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as Question from "."
|
||||
|
|
|
|||
|
|
@ -599,15 +599,19 @@ export const defaultLayer = Layer.suspend(() =>
|
|||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Config.node,
|
||||
Session.node,
|
||||
Agent.node,
|
||||
Plugin.node,
|
||||
SessionProcessor.node,
|
||||
Provider.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
Config.node,
|
||||
Session.node,
|
||||
Agent.node,
|
||||
Plugin.node,
|
||||
SessionProcessor.node,
|
||||
Provider.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as SessionCompaction from "./compaction"
|
||||
|
|
|
|||
|
|
@ -236,6 +236,10 @@ export function loaded(messages: SessionV1.WithParts[]) {
|
|||
return extract(messages)
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, FSUtil.node, Global.node, RuntimeFlags.node, httpClient])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, FSUtil.node, Global.node, RuntimeFlags.node, httpClient],
|
||||
})
|
||||
|
||||
export * as Instruction from "./instruction"
|
||||
|
|
|
|||
|
|
@ -401,15 +401,19 @@ export const defaultLayer = Layer.suspend(() =>
|
|||
|
||||
export const hasToolCalls = LLMRequestPrep.hasToolCalls
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Auth.node,
|
||||
Config.node,
|
||||
Provider.node,
|
||||
Plugin.node,
|
||||
Permission.node,
|
||||
EventV2Bridge.node,
|
||||
llmClient,
|
||||
RuntimeFlags.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
Auth.node,
|
||||
Config.node,
|
||||
Provider.node,
|
||||
Plugin.node,
|
||||
Permission.node,
|
||||
EventV2Bridge.node,
|
||||
llmClient,
|
||||
RuntimeFlags.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as LLM from "./llm"
|
||||
|
|
|
|||
|
|
@ -1065,20 +1065,24 @@ export const defaultLayer = Layer.suspend(() =>
|
|||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Session.node,
|
||||
Config.node,
|
||||
Snapshot.node,
|
||||
Agent.node,
|
||||
LLM.node,
|
||||
Permission.node,
|
||||
Plugin.node,
|
||||
SessionSummary.node,
|
||||
SessionStatus.node,
|
||||
Image.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
Session.node,
|
||||
Config.node,
|
||||
Snapshot.node,
|
||||
Agent.node,
|
||||
LLM.node,
|
||||
Permission.node,
|
||||
Plugin.node,
|
||||
SessionSummary.node,
|
||||
SessionStatus.node,
|
||||
Image.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as SessionProcessor from "./processor"
|
||||
|
|
|
|||
|
|
@ -1731,33 +1731,37 @@ const argsRegex = /(?:\[Image\s+\d+\]|"[^"]*"|'[^']*'|[^\s"']+)/gi
|
|||
const placeholderRegex = /\$(\d+)/g
|
||||
const quoteTrimRegex = /^["']|["']$/g
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
SessionStatus.node,
|
||||
Session.node,
|
||||
Agent.node,
|
||||
Provider.node,
|
||||
SessionProcessor.node,
|
||||
SessionCompaction.node,
|
||||
Plugin.node,
|
||||
Command.node,
|
||||
Config.node,
|
||||
Permission.node,
|
||||
FSUtil.node,
|
||||
MCP.node,
|
||||
LSP.node,
|
||||
ToolRegistry.node,
|
||||
Truncate.node,
|
||||
Image.node,
|
||||
CrossSpawnSpawner.node,
|
||||
Instruction.node,
|
||||
SessionRunState.node,
|
||||
SessionRevert.node,
|
||||
SessionSummary.node,
|
||||
SystemPrompt.node,
|
||||
LLM.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [
|
||||
SessionStatus.node,
|
||||
Session.node,
|
||||
Agent.node,
|
||||
Provider.node,
|
||||
SessionProcessor.node,
|
||||
SessionCompaction.node,
|
||||
Plugin.node,
|
||||
Command.node,
|
||||
Config.node,
|
||||
Permission.node,
|
||||
FSUtil.node,
|
||||
MCP.node,
|
||||
LSP.node,
|
||||
ToolRegistry.node,
|
||||
Truncate.node,
|
||||
Image.node,
|
||||
CrossSpawnSpawner.node,
|
||||
Instruction.node,
|
||||
SessionRunState.node,
|
||||
SessionRevert.node,
|
||||
SessionSummary.node,
|
||||
SystemPrompt.node,
|
||||
LLM.node,
|
||||
EventV2Bridge.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as SessionPrompt from "./prompt"
|
||||
|
|
|
|||
|
|
@ -148,13 +148,10 @@ export const defaultLayer = Layer.suspend(() =>
|
|||
),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Session.node,
|
||||
Snapshot.node,
|
||||
Storage.node,
|
||||
EventV2Bridge.node,
|
||||
SessionSummary.node,
|
||||
SessionRunState.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Session.node, Snapshot.node, Storage.node, EventV2Bridge.node, SessionSummary.node, SessionRunState.node],
|
||||
})
|
||||
|
||||
export * as SessionRevert from "./revert"
|
||||
|
|
|
|||
|
|
@ -151,6 +151,6 @@ function busyError(sessionID: SessionID) {
|
|||
return new Session.BusyError({ sessionID })
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [BackgroundJob.node, SessionStatus.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [BackgroundJob.node, SessionStatus.node] })
|
||||
|
||||
export * as SessionRunState from "./run-state"
|
||||
|
|
|
|||
|
|
@ -1070,6 +1070,10 @@ export function* listGlobal(input?: {
|
|||
}
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [BackgroundJob.node, RuntimeFlags.node, Database.node, EventV2Bridge.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [BackgroundJob.node, RuntimeFlags.node, Database.node, EventV2Bridge.node],
|
||||
})
|
||||
|
||||
export * as Session from "./session"
|
||||
|
|
|
|||
|
|
@ -53,6 +53,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node] })
|
||||
|
||||
export * as SessionStatus from "./status"
|
||||
|
|
|
|||
|
|
@ -160,6 +160,10 @@ export const DiffInput = Schema.Struct({
|
|||
})
|
||||
export type DiffInput = Schema.Schema.Type<typeof DiffInput>
|
||||
|
||||
export const node = LayerNode.make(layer, [Session.node, Snapshot.node, EventV2Bridge.node, Config.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Session.node, Snapshot.node, EventV2Bridge.node, Config.node],
|
||||
})
|
||||
|
||||
export * as SessionSummary from "./summary"
|
||||
|
|
|
|||
|
|
@ -134,8 +134,12 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(LocationServiceMap.layer),
|
||||
)
|
||||
|
||||
const locationServiceMapNode = LayerNode.make(LocationServiceMap.layer, [])
|
||||
const locationServiceMapNode = LayerNode.make({ service: Service, layer: LocationServiceMap.layer, deps: [] })
|
||||
|
||||
export const node = LayerNode.make(layer, [Skill.node, MCP.node, locationServiceMapNode])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Skill.node, MCP.node, locationServiceMapNode],
|
||||
})
|
||||
|
||||
export * as SystemPrompt from "./system"
|
||||
|
|
|
|||
|
|
@ -71,6 +71,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(EventV2Bridge.defaultLayer), Layer.provide(Database.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [EventV2Bridge.node, Database.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [EventV2Bridge.node, Database.node] })
|
||||
|
||||
export * as Todo from "./todo"
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [Config.node, Session.node, ShareNext.node, RuntimeFlags.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Config.node, Session.node, ShareNext.node, RuntimeFlags.node],
|
||||
})
|
||||
|
||||
export * as SessionShare from "./session"
|
||||
|
|
|
|||
|
|
@ -372,14 +372,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(Session.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Account.node,
|
||||
EventV2Bridge.node,
|
||||
Config.node,
|
||||
Database.node,
|
||||
httpClient,
|
||||
Provider.node,
|
||||
Session.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Account.node, EventV2Bridge.node, Config.node, Database.node, httpClient, Provider.node, Session.node],
|
||||
})
|
||||
|
||||
export * as ShareNext from "./share-next"
|
||||
|
|
|
|||
|
|
@ -104,6 +104,6 @@ export const defaultLayer: Layer.Layer<Service> = layer.pipe(
|
|||
Layer.provide(NodePath.layer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, path, httpClient])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, path, httpClient] })
|
||||
|
||||
export * as Discovery from "./discovery"
|
||||
|
|
|
|||
|
|
@ -354,13 +354,10 @@ export function fmt(list: Info[], opts: { verbose: boolean }) {
|
|||
].join("\n")
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
Discovery.node,
|
||||
Config.node,
|
||||
EventV2Bridge.node,
|
||||
FSUtil.node,
|
||||
Global.node,
|
||||
RuntimeFlags.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [Discovery.node, Config.node, EventV2Bridge.node, FSUtil.node, Global.node, RuntimeFlags.node],
|
||||
})
|
||||
|
||||
export * as Skill from "."
|
||||
|
|
|
|||
|
|
@ -804,6 +804,10 @@ export const defaultLayer = layer.pipe(
|
|||
Layer.provide(Config.defaultLayer),
|
||||
)
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, AppProcess.node, Config.node])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [FSUtil.node, AppProcess.node, Config.node],
|
||||
})
|
||||
|
||||
export * as Snapshot from "."
|
||||
|
|
|
|||
|
|
@ -324,6 +324,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(Git.defaultLayer))
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node, Git.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node, Git.node] })
|
||||
|
||||
export * as Storage from "./storage"
|
||||
|
|
|
|||
|
|
@ -415,26 +415,30 @@ function isJsonSchemaObject(value: unknown): value is Record<string, unknown> {
|
|||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export const node = LayerNode.make(layer.pipe(Layer.provide(Ripgrep.defaultLayer)), [
|
||||
Config.node,
|
||||
Plugin.node,
|
||||
Question.node,
|
||||
Todo.node,
|
||||
Agent.node,
|
||||
Skill.node,
|
||||
Session.node,
|
||||
BackgroundJob.node,
|
||||
Provider.node,
|
||||
LSP.node,
|
||||
Instruction.node,
|
||||
FSUtil.node,
|
||||
EventV2Bridge.node,
|
||||
httpClient,
|
||||
CrossSpawnSpawner.node,
|
||||
Format.node,
|
||||
Truncate.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer.pipe(Layer.provide(Ripgrep.defaultLayer)),
|
||||
deps: [
|
||||
Config.node,
|
||||
Plugin.node,
|
||||
Question.node,
|
||||
Todo.node,
|
||||
Agent.node,
|
||||
Skill.node,
|
||||
Session.node,
|
||||
BackgroundJob.node,
|
||||
Provider.node,
|
||||
LSP.node,
|
||||
Instruction.node,
|
||||
FSUtil.node,
|
||||
EventV2Bridge.node,
|
||||
httpClient,
|
||||
CrossSpawnSpawner.node,
|
||||
Format.node,
|
||||
Truncate.node,
|
||||
RuntimeFlags.node,
|
||||
Database.node,
|
||||
],
|
||||
})
|
||||
|
||||
export * as ToolRegistry from "./registry"
|
||||
|
|
|
|||
|
|
@ -153,6 +153,6 @@ export const layer = Layer.effect(
|
|||
|
||||
export const defaultLayer = layer.pipe(Layer.provide(FSUtil.defaultLayer), Layer.provide(NodePath.layer))
|
||||
|
||||
export const node = LayerNode.make(layer, [FSUtil.node])
|
||||
export const node = LayerNode.make({ service: Service, layer: layer, deps: [FSUtil.node] })
|
||||
|
||||
export * as Truncate from "./truncate"
|
||||
|
|
|
|||
|
|
@ -627,14 +627,10 @@ export const appLayer = layer.pipe(
|
|||
|
||||
export const defaultLayer = appLayer.pipe(Layer.provide(InstanceLayer.layer))
|
||||
|
||||
export const node = LayerNode.make(layer, [
|
||||
FSUtil.node,
|
||||
path,
|
||||
AppProcess.node,
|
||||
Git.node,
|
||||
Project.node,
|
||||
InstanceStore.node,
|
||||
Database.node,
|
||||
])
|
||||
export const node = LayerNode.make({
|
||||
service: Service,
|
||||
layer: layer,
|
||||
deps: [FSUtil.node, path, AppProcess.node, Git.node, Project.node, InstanceStore.node, Database.node],
|
||||
})
|
||||
|
||||
export * as Worktree from "."
|
||||
|
|
|
|||
|
|
@ -1,108 +0,0 @@
|
|||
import { test } from "bun:test"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
class A extends Context.Service<A, { readonly value: "a" }>()("test/A") {}
|
||||
class B extends Context.Service<B, { readonly value: "b" }>()("test/B") {}
|
||||
class C extends Context.Service<C, { readonly value: "c" }>()("test/C") {}
|
||||
class LayerError {
|
||||
readonly _tag = "LayerError"
|
||||
}
|
||||
class NotFoundError {
|
||||
readonly _tag = "NotFoundError"
|
||||
}
|
||||
class DiskError {
|
||||
readonly _tag = "DiskError"
|
||||
}
|
||||
class NetworkError {
|
||||
readonly _tag = "NetworkError"
|
||||
}
|
||||
|
||||
const aImplementation = Layer.succeed(A, A.of({ value: "a" }))
|
||||
const bImplementation = Layer.effect(
|
||||
B,
|
||||
Effect.gen(function* () {
|
||||
yield* A
|
||||
return B.of({ value: "b" })
|
||||
}),
|
||||
)
|
||||
const cImplementation = Layer.effect(
|
||||
C,
|
||||
Effect.gen(function* () {
|
||||
yield* A
|
||||
yield* B
|
||||
return C.of({ value: "c" })
|
||||
}),
|
||||
)
|
||||
const failingAImplementation = Layer.effect(A, Effect.fail(new LayerError()))
|
||||
const notFoundAImplementation = Layer.effect(A, Effect.fail(new NotFoundError()))
|
||||
const diskAImplementation = Layer.effect(A, Effect.fail(new DiskError()))
|
||||
const networkAImplementation = Layer.effect(A, Effect.fail(new NetworkError()))
|
||||
const notFoundOrDiskAImplementation = Layer.effect(A, Effect.fail(new NotFoundError() as NotFoundError | DiskError))
|
||||
|
||||
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false
|
||||
type Assert<T extends true> = T
|
||||
|
||||
type AProvides = Assert<Equal<Layer.Success<typeof aImplementation>, A>>
|
||||
type ARequires = Assert<Equal<Layer.Services<typeof aImplementation>, never>>
|
||||
type BProvides = Assert<Equal<Layer.Success<typeof bImplementation>, B>>
|
||||
type BRequires = Assert<Equal<Layer.Services<typeof bImplementation>, A>>
|
||||
type CRequires = Assert<Equal<Layer.Services<typeof cImplementation>, A | B>>
|
||||
void (0 as unknown as AProvides)
|
||||
void (0 as unknown as ARequires)
|
||||
void (0 as unknown as BProvides)
|
||||
void (0 as unknown as BRequires)
|
||||
void (0 as unknown as CRequires)
|
||||
|
||||
const a = LayerNode.make(aImplementation, [])
|
||||
const b = LayerNode.make(bImplementation, [a])
|
||||
const c = LayerNode.make(cImplementation, [a, b])
|
||||
const failingA = LayerNode.make(failingAImplementation, [])
|
||||
const bWithFailingA = LayerNode.make(bImplementation, [failingA])
|
||||
const notFoundA = LayerNode.make(notFoundAImplementation, [])
|
||||
const diskA = LayerNode.make(diskAImplementation, [])
|
||||
const networkA = LayerNode.make(networkAImplementation, [])
|
||||
const notFoundOrDiskA = LayerNode.make(notFoundOrDiskAImplementation, [])
|
||||
|
||||
// @ts-expect-error B requires A
|
||||
LayerNode.make(bImplementation, [])
|
||||
|
||||
// @ts-expect-error C requires both A and B
|
||||
LayerNode.make(cImplementation, [a])
|
||||
|
||||
type ANodeProvides = Assert<Equal<typeof a, LayerNode.Node<A, never>>>
|
||||
type BNodeProvides = Assert<Equal<typeof b, LayerNode.Node<B, never>>>
|
||||
type CNodeProvides = Assert<Equal<typeof c, LayerNode.Node<C, never>>>
|
||||
type FailingANodeError = Assert<Equal<typeof failingA, LayerNode.Node<A, LayerError>>>
|
||||
type DependentNodeError = Assert<Equal<typeof bWithFailingA, LayerNode.Node<B, LayerError>>>
|
||||
void (0 as unknown as ANodeProvides)
|
||||
void (0 as unknown as BNodeProvides)
|
||||
void (0 as unknown as CNodeProvides)
|
||||
void (0 as unknown as FailingANodeError)
|
||||
void (0 as unknown as DependentNodeError)
|
||||
|
||||
const closed = LayerNode.buildLayer(c)
|
||||
const closedWithError = LayerNode.buildLayer(bWithFailingA)
|
||||
type ClosedProvides = Assert<Equal<Layer.Success<typeof closed>, C>>
|
||||
type ClosedRequires = Assert<Equal<Layer.Services<typeof closed>, never>>
|
||||
type ClosedError = Assert<Equal<Layer.Error<typeof closedWithError>, LayerError>>
|
||||
void (0 as unknown as ClosedProvides)
|
||||
void (0 as unknown as ClosedRequires)
|
||||
void (0 as unknown as ClosedError)
|
||||
|
||||
const replacement = LayerNode.make(Layer.succeed(A, A.of({ value: "a" })), [])
|
||||
LayerNode.replace(a, Layer.succeed(A, A.of({ value: "a" })))
|
||||
LayerNode.replace(notFoundOrDiskA, notFoundAImplementation)
|
||||
LayerNode.replace(notFoundOrDiskA, diskAImplementation)
|
||||
LayerNode.replaceWithNode(a, replacement)
|
||||
|
||||
// @ts-expect-error An override for A must still provide A
|
||||
LayerNode.replaceWithNode(a, b)
|
||||
|
||||
// @ts-expect-error A replacement cannot introduce NetworkError
|
||||
LayerNode.replace(notFoundOrDiskA, networkAImplementation)
|
||||
|
||||
// @ts-expect-error A replacement layer must not have unresolved dependencies
|
||||
LayerNode.replace(b, bImplementation)
|
||||
|
||||
test("type exploration compiles", () => {})
|
||||
|
|
@ -1,204 +0,0 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { Cause, Context, Effect, Exit, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
const { buildLayer: build, group, replace, replaceWithNode } = LayerNode
|
||||
const node = LayerNode.make
|
||||
|
||||
class Value extends Context.Service<Value, { readonly value: string }>()("test/Value") {}
|
||||
class Greeting extends Context.Service<Greeting, { readonly text: string }>()("test/Greeting") {}
|
||||
|
||||
const value = LayerNode.make(Layer.succeed(Value, Value.of({ value: "production" })), [])
|
||||
const greetingImplementation = Layer.effect(
|
||||
Greeting,
|
||||
Effect.gen(function* () {
|
||||
return Greeting.of({ text: `hello ${(yield* Value).value}` })
|
||||
}),
|
||||
)
|
||||
const greeting = LayerNode.make(greetingImplementation, [value])
|
||||
|
||||
// @ts-expect-error Greeting requires Value
|
||||
LayerNode.make(greetingImplementation, [])
|
||||
|
||||
describe("app graph", () => {
|
||||
test("creates any selected dependency layer", async () => {
|
||||
const result = Effect.gen(function* () {
|
||||
return (yield* Greeting).text
|
||||
}).pipe(Effect.provide(build(greeting)))
|
||||
|
||||
expect(await Effect.runPromise(result)).toBe("hello production")
|
||||
})
|
||||
|
||||
test("applies overrides before dependency materialization", async () => {
|
||||
const replacement = Layer.succeed(Value, Value.of({ value: "simulation" }))
|
||||
const graph = build(greeting, { replacements: [replace(value, replacement)] })
|
||||
const result = Effect.gen(function* () {
|
||||
return (yield* Greeting).text
|
||||
}).pipe(Effect.provide(graph))
|
||||
|
||||
expect(await Effect.runPromise(result)).toBe("hello simulation")
|
||||
})
|
||||
|
||||
test("acquires a shared dependency once", async () => {
|
||||
class Shared extends Context.Service<Shared, { readonly value: string }>()("test/Shared") {}
|
||||
class Left extends Context.Service<Left, { readonly value: string }>()("test/Left") {}
|
||||
class Right extends Context.Service<Right, { readonly value: string }>()("test/Right") {}
|
||||
let acquisitions = 0
|
||||
const shared = node(
|
||||
Layer.effect(
|
||||
Shared,
|
||||
Effect.sync(() => {
|
||||
acquisitions++
|
||||
return Shared.of({ value: "shared" })
|
||||
}),
|
||||
),
|
||||
[],
|
||||
)
|
||||
const left = node(
|
||||
Layer.effect(
|
||||
Left,
|
||||
Effect.gen(function* () {
|
||||
return Left.of({ value: `${(yield* Shared).value}-left` })
|
||||
}),
|
||||
),
|
||||
[shared],
|
||||
)
|
||||
const right = node(
|
||||
Layer.effect(
|
||||
Right,
|
||||
Effect.gen(function* () {
|
||||
return Right.of({ value: `${(yield* Shared).value}-right` })
|
||||
}),
|
||||
),
|
||||
[shared],
|
||||
)
|
||||
|
||||
const result = Effect.gen(function* () {
|
||||
return [(yield* Left).value, (yield* Right).value]
|
||||
}).pipe(Effect.provide(build(group([left, right]))))
|
||||
|
||||
expect(await Effect.runPromise(result)).toEqual(["shared-left", "shared-right"])
|
||||
expect(acquisitions).toBe(1)
|
||||
})
|
||||
|
||||
test("applies a replacement to every transitive consumer", async () => {
|
||||
class Left extends Context.Service<Left, { readonly value: string }>()("test/ReplacementLeft") {}
|
||||
class Right extends Context.Service<Right, { readonly value: string }>()("test/ReplacementRight") {}
|
||||
const left = node(
|
||||
Layer.effect(
|
||||
Left,
|
||||
Effect.gen(function* () {
|
||||
return Left.of({ value: (yield* Value).value })
|
||||
}),
|
||||
),
|
||||
[value],
|
||||
)
|
||||
const right = node(
|
||||
Layer.effect(
|
||||
Right,
|
||||
Effect.gen(function* () {
|
||||
return Right.of({ value: (yield* Value).value })
|
||||
}),
|
||||
),
|
||||
[value],
|
||||
)
|
||||
const replacement = Layer.succeed(Value, Value.of({ value: "simulation" }))
|
||||
const graph = build(group([left, right]), { replacements: [replace(value, replacement)] })
|
||||
|
||||
const result = Effect.gen(function* () {
|
||||
return [(yield* Left).value, (yield* Right).value]
|
||||
}).pipe(Effect.provide(graph))
|
||||
|
||||
expect(await Effect.runPromise(result)).toEqual(["simulation", "simulation"])
|
||||
})
|
||||
|
||||
test("propagates layer acquisition errors", async () => {
|
||||
class AcquisitionError {
|
||||
readonly _tag = "AcquisitionError"
|
||||
}
|
||||
const failing = node(Layer.effect(Value, Effect.fail(new AcquisitionError())), [])
|
||||
const exit = await Effect.runPromiseExit(Effect.provide(Value, build(failing)))
|
||||
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isFailure(exit)) expect(Cause.squash(exit.cause)).toBeInstanceOf(AcquisitionError)
|
||||
})
|
||||
|
||||
test("groups expose every selected service", async () => {
|
||||
class Count extends Context.Service<Count, { readonly value: number }>()("test/Count") {}
|
||||
const count = node(Layer.succeed(Count, Count.of({ value: 3 })), [])
|
||||
const result = Effect.gen(function* () {
|
||||
return { text: (yield* Value).value, count: (yield* Count).value }
|
||||
}).pipe(Effect.provide(build(group([value, count]))))
|
||||
|
||||
expect(await Effect.runPromise(result)).toEqual({ text: "production", count: 3 })
|
||||
})
|
||||
|
||||
test("builds an empty group", async () => {
|
||||
expect(await Effect.runPromise(Effect.succeed("ok").pipe(Effect.provide(build(group([])))))).toBe("ok")
|
||||
})
|
||||
|
||||
test("builds replacements with their own dependencies", async () => {
|
||||
class ReplacementConfig extends Context.Service<ReplacementConfig, { readonly value: string }>()(
|
||||
"test/ReplacementConfig",
|
||||
) {}
|
||||
const replacementConfig = node(Layer.succeed(ReplacementConfig, ReplacementConfig.of({ value: "replacement" })), [])
|
||||
const replacement = node(
|
||||
Layer.effect(
|
||||
Value,
|
||||
Effect.gen(function* () {
|
||||
return Value.of({ value: (yield* ReplacementConfig).value })
|
||||
}),
|
||||
),
|
||||
[replacementConfig],
|
||||
)
|
||||
const result = Effect.gen(function* () {
|
||||
return (yield* Greeting).text
|
||||
}).pipe(Effect.provide(build(greeting, { replacements: [replaceWithNode(value, replacement)] })))
|
||||
|
||||
expect(await Effect.runPromise(result)).toBe("hello replacement")
|
||||
})
|
||||
|
||||
test("does not acquire unreachable replacements", async () => {
|
||||
let acquisitions = 0
|
||||
const unreachable = node(Layer.succeed(Value, Value.of({ value: "unreachable" })), [])
|
||||
const replacement = Layer.effect(
|
||||
Value,
|
||||
Effect.sync(() => {
|
||||
acquisitions++
|
||||
return Value.of({ value: "replacement" })
|
||||
}),
|
||||
)
|
||||
|
||||
await Effect.runPromise(
|
||||
Effect.provide(Greeting, build(greeting, { replacements: [replace(unreachable, replacement)] })),
|
||||
)
|
||||
|
||||
expect(acquisitions).toBe(0)
|
||||
})
|
||||
|
||||
test("rejects a direct cycle", () => {
|
||||
const cyclic = node(Layer.succeed(Value, Value.of({ value: "cyclic" })), [])
|
||||
;(cyclic.dependencies as LayerNode.Node<unknown, unknown>[]).push(cyclic)
|
||||
|
||||
expect(() => build(cyclic)).toThrow("Cycle detected in app graph: layer#1 -> layer#1")
|
||||
})
|
||||
|
||||
test("rejects an indirect cycle", () => {
|
||||
const first = node(Layer.succeed(Value, Value.of({ value: "first" })), [])
|
||||
const second = node(Layer.succeed(Value, Value.of({ value: "second" })), [first])
|
||||
const third = node(Layer.succeed(Value, Value.of({ value: "third" })), [second])
|
||||
;(first.dependencies as LayerNode.Node<unknown, unknown>[]).push(third)
|
||||
|
||||
expect(() => build(first)).toThrow("Cycle detected in app graph: layer#1 -> layer#2 -> layer#3 -> layer#1")
|
||||
})
|
||||
|
||||
test("rejects a cycle introduced by a replacement", () => {
|
||||
const replacement = node(Layer.succeed(Value, Value.of({ value: "replacement" })), [])
|
||||
const consumer = node(greetingImplementation, [value])
|
||||
;(replacement.dependencies as LayerNode.Node<unknown, unknown>[]).push(consumer)
|
||||
|
||||
expect(() => build(consumer, { replacements: [replaceWithNode(value, replacement)] })).toThrow(
|
||||
"Cycle detected in app graph: layer#1 -> layer#2 -> layer#1",
|
||||
)
|
||||
})
|
||||
})
|
||||
19
packages/opencode/test/effect/layer-node-tiers-types.test.ts
Normal file
19
packages/opencode/test/effect/layer-node-tiers-types.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { test } from "bun:test"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
class A extends Context.Service<A, {}>()("test/TierA") {}
|
||||
class B extends Context.Service<B, {}>()("test/TierB") {}
|
||||
|
||||
const tiers = LayerNode.tiers(["request", "global"])
|
||||
const request = tiers.make("request")
|
||||
const global = tiers.make("global")
|
||||
const globalA = global({ service: A, layer: Layer.succeed(A, A.of({})), deps: [] })
|
||||
const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
|
||||
|
||||
request({ service: B, layer: bLayer, deps: [globalA] })
|
||||
|
||||
// @ts-expect-error Global cannot depend on request
|
||||
global({ service: B, layer: bLayer, deps: [request({ service: A, layer: Layer.succeed(A, A.of({})), deps: [] })] })
|
||||
|
||||
test("type exploration compiles", () => {})
|
||||
169
packages/opencode/test/effect/layer-node-tiers.test.ts
Normal file
169
packages/opencode/test/effect/layer-node-tiers.test.ts
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
import { 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/TierValue") {}
|
||||
class Result extends Context.Service<Result, { readonly value: string }>()("test/TierResult") {}
|
||||
class Left extends Context.Service<Left, { readonly value: string }>()("test/TierLeft") {}
|
||||
class Right extends Context.Service<Right, { readonly value: string }>()("test/TierRight") {}
|
||||
class Last extends Context.Service<Last, { readonly value: string }>()("test/TierLast") {}
|
||||
|
||||
test("builds tiers with a custom builder", async () => {
|
||||
let locationBuilds = 0
|
||||
const tiers = LayerNode.tiers(["location", "global"])
|
||||
const global = tiers.make("global")
|
||||
const location = tiers.make("location")
|
||||
const value = global({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "value" })), deps: [] })
|
||||
const result = location({
|
||||
service: Result,
|
||||
layer: Layer.effect(
|
||||
Result,
|
||||
Effect.gen(function* () {
|
||||
return Result.of({ value: (yield* Value).value })
|
||||
}),
|
||||
),
|
||||
deps: [value],
|
||||
})
|
||||
const layer = LayerNode.buildLayer(LayerNode.group([result]), {
|
||||
tiers,
|
||||
buildTier: (tier, layers) => {
|
||||
if (tier !== "location") return LayerNode.combine(layers)
|
||||
locationBuilds++
|
||||
return LayerNode.combine(layers).pipe(Layer.fresh)
|
||||
},
|
||||
})
|
||||
const program = Effect.gen(function* () {
|
||||
return (yield* Result).value
|
||||
}).pipe(Effect.provide(layer))
|
||||
|
||||
expect(await Effect.runPromise(program)).toBe("value")
|
||||
expect(locationBuilds).toBe(1)
|
||||
})
|
||||
|
||||
test("rejects conflicting higher-tier service implementations", () => {
|
||||
const tiers = LayerNode.tiers(["location", "global"])
|
||||
const global = tiers.make("global")
|
||||
const location = tiers.make("location")
|
||||
const first = global({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "first" })), deps: [] })
|
||||
const second = global({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "second" })), deps: [] })
|
||||
const left = location({
|
||||
service: Left,
|
||||
layer: Layer.effect(Left, Effect.as(Value, Left.of({ value: "left" }))),
|
||||
deps: [first],
|
||||
})
|
||||
const right = location({
|
||||
service: Right,
|
||||
layer: Layer.effect(Right, Effect.as(Value, Right.of({ value: "right" }))),
|
||||
deps: [second],
|
||||
})
|
||||
|
||||
expect(() => LayerNode.buildLayer(LayerNode.group([left, right]), { tiers })).toThrow(
|
||||
"conflicting implementations for test/TierValue",
|
||||
)
|
||||
})
|
||||
|
||||
test("validates tier dependencies through groups", () => {
|
||||
const tiers = LayerNode.tiers(["location", "global"])
|
||||
const global = tiers.make("global")
|
||||
const location = tiers.make("location")
|
||||
const local = location({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "local" })), deps: [] })
|
||||
const invalid = global({
|
||||
service: Result,
|
||||
layer: Layer.effect(
|
||||
Result,
|
||||
Effect.map(Value, (value) => Result.of({ value: value.value })),
|
||||
),
|
||||
deps: [LayerNode.group([local])],
|
||||
})
|
||||
|
||||
expect(() => LayerNode.buildLayer(invalid, { tiers })).toThrow("Tier global cannot depend on lower tier location")
|
||||
})
|
||||
|
||||
test("validates shared groups in each consumer tier", () => {
|
||||
const tiers = LayerNode.tiers(["location", "global"])
|
||||
const global = tiers.make("global")
|
||||
const location = tiers.make("location")
|
||||
const local = location({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "local" })), deps: [] })
|
||||
const shared = LayerNode.group([local])
|
||||
const valid = location({
|
||||
service: Left,
|
||||
layer: Layer.effect(
|
||||
Left,
|
||||
Effect.map(Value, (value) => Left.of({ value: value.value })),
|
||||
),
|
||||
deps: [shared],
|
||||
})
|
||||
const invalid = global({
|
||||
service: Result,
|
||||
layer: Layer.effect(
|
||||
Result,
|
||||
Effect.map(Value, (value) => Result.of({ value: value.value })),
|
||||
),
|
||||
deps: [shared],
|
||||
})
|
||||
|
||||
expect(() => LayerNode.buildLayer(LayerNode.group([valid, invalid]), { tiers })).toThrow(
|
||||
"Tier global cannot depend on lower tier location",
|
||||
)
|
||||
})
|
||||
|
||||
test("rejects a service assigned to multiple tiers", () => {
|
||||
const tiers = LayerNode.tiers(["location", "global"])
|
||||
const global = tiers.make("global")
|
||||
const location = tiers.make("location")
|
||||
const local = location({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "local" })), deps: [] })
|
||||
const shared = global({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "global" })), deps: [] })
|
||||
|
||||
expect(() => LayerNode.buildLayer(LayerNode.group([local, shared]), { tiers })).toThrow(
|
||||
"Service test/TierValue belongs to both tier location and tier global",
|
||||
)
|
||||
})
|
||||
|
||||
test("rebinds same-tier providers without reacquiring them", async () => {
|
||||
let firstAcquisitions = 0
|
||||
const tiers = LayerNode.tiers(["global"])
|
||||
const global = tiers.make("global")
|
||||
const first = global({
|
||||
service: Value,
|
||||
layer: Layer.effect(
|
||||
Value,
|
||||
Effect.sync(() => {
|
||||
firstAcquisitions++
|
||||
return Value.of({ value: "first" })
|
||||
}),
|
||||
),
|
||||
deps: [],
|
||||
})
|
||||
const second = global({ service: Value, layer: Layer.succeed(Value, Value.of({ value: "second" })), deps: [] })
|
||||
const left = global({
|
||||
service: Left,
|
||||
layer: Layer.effect(
|
||||
Left,
|
||||
Effect.map(Value, (value) => Left.of({ value: value.value })),
|
||||
),
|
||||
deps: [first],
|
||||
})
|
||||
const right = global({
|
||||
service: Right,
|
||||
layer: Layer.effect(
|
||||
Right,
|
||||
Effect.map(Value, (value) => Right.of({ value: value.value })),
|
||||
),
|
||||
deps: [second],
|
||||
})
|
||||
const last = global({
|
||||
service: Last,
|
||||
layer: Layer.effect(
|
||||
Last,
|
||||
Effect.map(Value, (value) => Last.of({ value: value.value })),
|
||||
),
|
||||
deps: [first],
|
||||
})
|
||||
const layer = LayerNode.buildLayer(LayerNode.group([left, right, last]), { tiers })
|
||||
const values = Effect.gen(function* () {
|
||||
return [(yield* Left).value, (yield* Right).value, (yield* Last).value]
|
||||
}).pipe(Effect.provide(layer))
|
||||
|
||||
expect(await Effect.runPromise(values)).toEqual(["first", "second", "first"])
|
||||
expect(firstAcquisitions).toBe(1)
|
||||
})
|
||||
66
packages/opencode/test/effect/layer-node-types.test.ts
Normal file
66
packages/opencode/test/effect/layer-node-types.test.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import { test } from "bun:test"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
|
||||
class A extends Context.Service<A, {}>()("test/LayerNodeA") {}
|
||||
class B extends Context.Service<B, {}>()("test/LayerNodeB") {}
|
||||
class C extends Context.Service<C, {}>()("test/LayerNodeC") {}
|
||||
class LayerError {
|
||||
readonly _tag = "LayerError"
|
||||
}
|
||||
class OtherError {
|
||||
readonly _tag = "OtherError"
|
||||
}
|
||||
|
||||
const tiers = LayerNode.tiers(["app"])
|
||||
const make = tiers.make("app")
|
||||
const aLayer = Layer.succeed(A, A.of({}))
|
||||
const bLayer = Layer.effect(B, Effect.as(A, B.of({})))
|
||||
const cLayer = Layer.effect(
|
||||
C,
|
||||
Effect.gen(function* () {
|
||||
yield* A
|
||||
yield* B
|
||||
return C.of({})
|
||||
}),
|
||||
)
|
||||
const failingA = Layer.effect(A, Effect.fail(new LayerError()))
|
||||
const a = make({ service: A, layer: aLayer, deps: [] })
|
||||
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({ name: "manual-a", layer: aLayer, deps: [] })
|
||||
|
||||
// @ts-expect-error A node must have a service or name
|
||||
make({ layer: aLayer, deps: [] })
|
||||
|
||||
// @ts-expect-error Service and name are mutually exclusive
|
||||
make({ service: A, name: "a", layer: aLayer, deps: [] })
|
||||
|
||||
// @ts-expect-error B requires A
|
||||
make({ service: B, layer: bLayer, deps: [] })
|
||||
|
||||
// @ts-expect-error C requires A and B
|
||||
make({ service: C, layer: cLayer, deps: [a] })
|
||||
|
||||
const closed = LayerNode.buildLayer(c, { tiers })
|
||||
const closedWithError = LayerNode.buildLayer(dependent, { tiers })
|
||||
const checkClosed: Layer.Layer<C, never, never> = closed
|
||||
const checkError: Layer.Layer<B, LayerError, never> = closedWithError
|
||||
void checkClosed
|
||||
void checkError
|
||||
|
||||
LayerNode.replace(aLayer, Layer.succeed(A, A.of({})))
|
||||
|
||||
// @ts-expect-error Replacement must provide A
|
||||
LayerNode.replace(aLayer, Layer.succeed(B, B.of({})))
|
||||
|
||||
// @ts-expect-error Replacement cannot introduce a new error
|
||||
LayerNode.replace(aLayer, Layer.effect(A, Effect.fail(new OtherError())))
|
||||
|
||||
// @ts-expect-error Replacement must be closed
|
||||
LayerNode.replace(bLayer, bLayer)
|
||||
|
||||
test("type exploration compiles", () => {})
|
||||
82
packages/opencode/test/effect/layer-node.test.ts
Normal file
82
packages/opencode/test/effect/layer-node.test.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
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") {}
|
||||
|
||||
const tiers = LayerNode.tiers(["app"])
|
||||
const make = tiers.make("app")
|
||||
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 untiered 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.buildLayer(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(LayerNode.buildLayer(greeting, { tiers })),
|
||||
)
|
||||
expect(await Effect.runPromise(program)).toBe("hello production")
|
||||
})
|
||||
|
||||
test("replaces a layer by identity", async () => {
|
||||
const replacement = Layer.succeed(Value, Value.of({ value: "simulation" }))
|
||||
const program = Effect.map(Greeting, (item) => item.value).pipe(
|
||||
Effect.provide(LayerNode.buildLayer(greeting, { tiers, replacements: [LayerNode.replace(valueLayer, 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 = LayerNode.buildLayer(LayerNode.group([left, right]), {
|
||||
tiers,
|
||||
replacements: [LayerNode.replace(valueLayer, 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 = Layer.succeed(Value, Value.of({ value: "other" }))
|
||||
const replacement = Layer.effect(
|
||||
Value,
|
||||
Effect.sync(() => {
|
||||
acquisitions++
|
||||
return Value.of({ value: "replacement" })
|
||||
}),
|
||||
)
|
||||
await Effect.runPromise(
|
||||
Effect.map(Greeting, (item) => item.value).pipe(
|
||||
Effect.provide(LayerNode.buildLayer(greeting, { tiers, replacements: [LayerNode.replace(other, replacement)] })),
|
||||
),
|
||||
)
|
||||
expect(acquisitions).toBe(0)
|
||||
})
|
||||
})
|
||||
23
packages/opencode/test/effect/scoped-node-types.test.ts
Normal file
23
packages/opencode/test/effect/scoped-node-types.test.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { test } from "bun:test"
|
||||
import { Context, Effect, Layer } from "effect"
|
||||
import { makeGlobalNode, makeLocationNode } from "@opencode-ai/core/effect/scoped-node"
|
||||
|
||||
class A extends Context.Service<A, {}>()("test/ScopedA") {}
|
||||
class B extends Context.Service<B, {}>()("test/ScopedB") {}
|
||||
|
||||
const a = Layer.succeed(A, A.of({}))
|
||||
const b = Layer.effect(B, Effect.as(A, B.of({})))
|
||||
const globalA = makeGlobalNode({ service: A, layer: a, deps: [] })
|
||||
const locationA = makeLocationNode({ service: A, layer: a, deps: [] })
|
||||
|
||||
makeGlobalNode({ service: B, layer: b, deps: [globalA] })
|
||||
makeLocationNode({ service: B, layer: b, deps: [globalA] })
|
||||
makeLocationNode({ service: B, layer: b, deps: [locationA] })
|
||||
|
||||
// @ts-expect-error Global nodes cannot depend on location nodes
|
||||
makeGlobalNode({ service: B, layer: b, deps: [locationA] })
|
||||
|
||||
// @ts-expect-error B requires A
|
||||
makeLocationNode({ service: B, layer: b, deps: [] })
|
||||
|
||||
test("type exploration compiles", () => {})
|
||||
|
|
@ -177,10 +177,13 @@ const root = LayerNode.group([
|
|||
CrossSpawnSpawner.node,
|
||||
])
|
||||
const replacements = [
|
||||
LayerNode.replace(SessionSummary.node, summary),
|
||||
LayerNode.replace(RuntimeFlags.node, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
LayerNode.replace(SessionSummary.layer, summary),
|
||||
LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
]
|
||||
const env = LayerNode.buildLayer(LayerNode.group([root, LayerNode.make(TestLLMServer.layer, [])]), { replacements })
|
||||
const env = LayerNode.buildLayer(
|
||||
LayerNode.group([root, LayerNode.make({ service: TestLLMServer, layer: TestLLMServer.layer, deps: [] })]),
|
||||
{ replacements },
|
||||
)
|
||||
|
||||
const it = testEffect(env)
|
||||
|
||||
|
|
@ -205,7 +208,7 @@ const providerErrorLLM = Layer.succeed(
|
|||
}),
|
||||
)
|
||||
const providerErrorEnv = LayerNode.buildLayer(root, {
|
||||
replacements: [...replacements, LayerNode.replace(LLM.node, providerErrorLLM)],
|
||||
replacements: [...replacements, LayerNode.replace(LLM.layer, providerErrorLLM)],
|
||||
})
|
||||
const itProviderError = testEffect(providerErrorEnv)
|
||||
|
||||
|
|
@ -224,7 +227,7 @@ const fragmentFailureLLM = Layer.succeed(
|
|||
}),
|
||||
)
|
||||
const fragmentFailureEnv = LayerNode.buildLayer(root, {
|
||||
replacements: [...replacements, LayerNode.replace(LLM.node, fragmentFailureLLM)],
|
||||
replacements: [...replacements, LayerNode.replace(LLM.layer, fragmentFailureLLM)],
|
||||
})
|
||||
const itFragmentFailure = testEffect(fragmentFailureEnv)
|
||||
|
||||
|
|
|
|||
|
|
@ -84,14 +84,14 @@ const root = LayerNode.group([
|
|||
SessionSummary.node,
|
||||
Database.node,
|
||||
CrossSpawnSpawner.node,
|
||||
LayerNode.make(TestLLMServer.layer, []),
|
||||
LayerNode.make({ service: TestLLMServer, layer: TestLLMServer.layer, deps: [] }),
|
||||
])
|
||||
const it = testEffect(
|
||||
LayerNode.buildLayer(root, {
|
||||
replacements: [
|
||||
LayerNode.replace(MCP.node, mcp),
|
||||
LayerNode.replace(LSP.node, lsp),
|
||||
LayerNode.replace(RuntimeFlags.node, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
LayerNode.replace(MCP.layer, mcp),
|
||||
LayerNode.replace(LSP.layer, lsp),
|
||||
LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer({ experimentalEventSystem: true })),
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { beforeEach, describe, expect } from "bun:test"
|
||||
import { Effect, Exit, Layer, Option } from "effect"
|
||||
import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"
|
||||
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
||||
import { httpClient } from "@opencode-ai/core/effect/layer-node-platform"
|
||||
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
|
||||
|
|
@ -35,7 +35,7 @@ const none = HttpClient.make(() => Effect.die("unexpected http call"))
|
|||
|
||||
function requestLayer(client: HttpClient.HttpClient) {
|
||||
return LayerNode.buildLayer(LayerNode.group([ShareNext.node, AccountRepo.node]), {
|
||||
replacements: [LayerNode.replace(httpClient, Layer.succeed(HttpClient.HttpClient, client))],
|
||||
replacements: [LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))],
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -49,9 +49,7 @@ function integrationLayer(client: HttpClient.HttpClient) {
|
|||
AccountRepo.node,
|
||||
Database.node,
|
||||
]),
|
||||
{
|
||||
replacements: [LayerNode.replace(httpClient, Layer.succeed(HttpClient.HttpClient, client))],
|
||||
},
|
||||
{ replacements: [LayerNode.replace(FetchHttpClient.layer, Layer.succeed(HttpClient.HttpClient, client))] },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,14 +50,14 @@ const brokenPluginLayer = Layer.succeed(
|
|||
|
||||
const root = LayerNode.group([ToolRegistry.node, Agent.node])
|
||||
const replacements = [
|
||||
LayerNode.replace(Config.node, configLayer),
|
||||
LayerNode.replace(RuntimeFlags.node, RuntimeFlags.layer()),
|
||||
LayerNode.replace(Config.layer, configLayer),
|
||||
LayerNode.replace(RuntimeFlags.defaultLayer, RuntimeFlags.layer()),
|
||||
]
|
||||
|
||||
const it = testEffect(LayerNode.buildLayer(root, { replacements }))
|
||||
const withBrokenPlugin = testEffect(
|
||||
LayerNode.buildLayer(root, {
|
||||
replacements: [...replacements, LayerNode.replace(Plugin.node, brokenPluginLayer)],
|
||||
replacements: [...replacements, LayerNode.replace(Plugin.layer, brokenPluginLayer)],
|
||||
}),
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue