Skip to content

Commit 9052e8a

Browse files
authored
test: cover HttpApi workspace routing middleware (#25027)
1 parent de78ded commit 9052e8a

4 files changed

Lines changed: 480 additions & 2 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Context, Effect } from "effect"
2+
3+
type EffectMethod = (...args: ReadonlyArray<never>) => Effect.Effect<unknown, unknown, unknown>
4+
5+
type ServiceUse<Identifier, Shape> = {
6+
readonly [Key in keyof Shape as Shape[Key] extends EffectMethod ? Key : never]: Shape[Key] extends (
7+
...args: infer Args
8+
) => infer Return
9+
? Args extends ReadonlyArray<unknown>
10+
? Return extends Effect.Effect<infer A, infer E, infer R>
11+
? (...args: Args) => Effect.Effect<A, E, R | Identifier>
12+
: never
13+
: never
14+
: never
15+
}
16+
17+
export const serviceUse = <Identifier, Shape>(tag: Context.Service<Identifier, Shape>) => {
18+
// This is the only dynamic boundary: TypeScript knows the accessor shape,
19+
// but Proxy property names are runtime values.
20+
const access = new Proxy(
21+
{},
22+
{
23+
get: (_, key) => {
24+
if (typeof key !== "string") return undefined
25+
return (...args: unknown[]) =>
26+
tag.use((service) => {
27+
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy keys are checked at runtime.
28+
const method = service[key as keyof Shape]
29+
if (typeof method !== "function") return Effect.die(new Error(`Service method not found: ${key}`))
30+
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- ServiceUse exposes only Effect-returning methods.
31+
return (method as (...args: unknown[]) => Effect.Effect<unknown, unknown, unknown>)(...args)
32+
})
33+
},
34+
},
35+
)
36+
// oxlint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- Proxy implements the mapped accessor surface lazily.
37+
return access as ServiceUse<Identifier, Shape>
38+
}

packages/opencode/src/project/project.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { AppFileSystem } from "@opencode-ai/core/filesystem"
1717
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
1818
import { zod } from "@/util/effect-zod"
1919
import { NonNegativeInt, withStatics } from "@/util/schema"
20+
import { serviceUse } from "@/effect/service-use"
2021

2122
const log = Log.create({ service: "project" })
2223

@@ -178,7 +179,7 @@ export const layer: Layer.Layer<
178179
const readCachedProjectId = Effect.fnUntraced(function* (dir: string) {
179180
return yield* fs.readFileString(pathSvc.join(dir, "opencode")).pipe(
180181
Effect.map((x) => x.trim()),
181-
Effect.map(ProjectID.make),
182+
Effect.map((x) => ProjectID.make(x)),
182183
Effect.catch(() => Effect.void),
183184
)
184185
})
@@ -485,6 +486,8 @@ export const defaultLayer = layer.pipe(
485486
Layer.provide(NodePath.layer),
486487
)
487488

489+
export const use = serviceUse(Service)
490+
488491
export function list() {
489492
return Database.use((db) =>
490493
db

0 commit comments

Comments
 (0)