refactor: use Effect services instead of async facades in provider, auth, and file (#20480)
parent
a9c85b7c27
commit
2f405daa98
10
bun.lock
10
bun.lock
|
|
@ -612,7 +612,7 @@
|
||||||
},
|
},
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@cloudflare/workers-types": "4.20251008.0",
|
"@cloudflare/workers-types": "4.20251008.0",
|
||||||
"@effect/platform-node": "4.0.0-beta.42",
|
"@effect/platform-node": "4.0.0-beta.43",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
"@kobalte/core": "0.13.11",
|
"@kobalte/core": "0.13.11",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
|
|
@ -636,7 +636,7 @@
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||||
"effect": "4.0.0-beta.42",
|
"effect": "4.0.0-beta.43",
|
||||||
"fuzzysort": "3.1.0",
|
"fuzzysort": "3.1.0",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
|
|
@ -995,9 +995,9 @@
|
||||||
|
|
||||||
"@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="],
|
"@effect/language-service": ["@effect/language-service@0.79.0", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-DEmIOsg1GjjP6s9HXH1oJrW+gDmzkhVv9WOZl6to5eNyyCrjz1S2PDqQ7aYrW/HuifhfwI5Bik1pK4pj7Z+lrg=="],
|
||||||
|
|
||||||
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.42", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.42", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.42", "ioredis": "^5.7.0" } }, "sha512-kbdRML2FBa4q8U8rZQcnmLKZ5zN/z1bAA7t5D1/UsBHZqJgnfRgu1CP6kaEfb1Nie6YyaWshxTktZQryjvW/Yg=="],
|
"@effect/platform-node": ["@effect/platform-node@4.0.0-beta.43", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.43", "mime": "^4.1.0", "undici": "^7.24.0" }, "peerDependencies": { "effect": "^4.0.0-beta.43", "ioredis": "^5.7.0" } }, "sha512-Uq6E1rjaIpjHauzjwoB2HzAg3battYt2Boy8XO50GoHiWCXKE6WapYZ0/AnaBx5v5qg2sOfqpuiLsUf9ZgxOkA=="],
|
||||||
|
|
||||||
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.42", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.42" } }, "sha512-PC+lxLsrwob3+nBChAPrQq32olCeyApgXBvs1NrRsoArLViNT76T/68CttuCAksCZj5e1bZ1ZibLPel3vUmx2g=="],
|
"@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.43", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.43" } }, "sha512-A9q0GEb61pYcQ06Dr6gXj1nKlDI3KHsar1sk3qb1ZY+kVSR64tBAylI8zGon23KY+NPtTUj/sEIToB7jc3Qt5w=="],
|
||||||
|
|
||||||
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
"@electron/asar": ["@electron/asar@3.4.1", "", { "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" } }, "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA=="],
|
||||||
|
|
||||||
|
|
@ -2771,7 +2771,7 @@
|
||||||
|
|
||||||
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
"effect": ["effect@4.0.0-beta.42", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-c1UrRP+tLzyHb4Fepl8XBDJlLQLkrcMXrRBba441GQRxMbeQ/aIOSFcBwSda1iMJ5l9F0lYc3Bhe33/whrmavQ=="],
|
"effect": ["effect@4.0.0-beta.43", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-AJYyDimIwJOn87uUz/JzmgDc5GfjxJbXvEbTvNzMa+M3Uer344bLo/O5mMRkqc1vBleA+Ygs4+dbE3QsqOkKTQ=="],
|
||||||
|
|
||||||
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
"packages/slack"
|
"packages/slack"
|
||||||
],
|
],
|
||||||
"catalog": {
|
"catalog": {
|
||||||
"@effect/platform-node": "4.0.0-beta.42",
|
"@effect/platform-node": "4.0.0-beta.43",
|
||||||
"@types/bun": "1.3.11",
|
"@types/bun": "1.3.11",
|
||||||
"@octokit/rest": "22.0.0",
|
"@octokit/rest": "22.0.0",
|
||||||
"@hono/zod-validator": "0.4.2",
|
"@hono/zod-validator": "0.4.2",
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
"drizzle-kit": "1.0.0-beta.19-d95b7a4",
|
||||||
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
"drizzle-orm": "1.0.0-beta.19-d95b7a4",
|
||||||
"effect": "4.0.0-beta.42",
|
"effect": "4.0.0-beta.43",
|
||||||
"ai": "6.0.138",
|
"ai": "6.0.138",
|
||||||
"hono": "4.10.7",
|
"hono": "4.10.7",
|
||||||
"hono-openapi": "1.1.2",
|
"hono-openapi": "1.1.2",
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,9 @@ export namespace InstanceState {
|
||||||
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
|
return ((...args: any[]) => Instance.restore(ctx, () => fn(...args))) as F
|
||||||
}
|
}
|
||||||
|
|
||||||
export const context = Effect.fnUntraced(function* () {
|
export const context = Effect.gen(function* () {
|
||||||
return (yield* InstanceRef) ?? Instance.current
|
return (yield* InstanceRef) ?? Instance.current
|
||||||
})()
|
})
|
||||||
|
|
||||||
export const directory = Effect.map(context, (ctx) => ctx.directory)
|
export const directory = Effect.map(context, (ctx) => ctx.directory)
|
||||||
|
|
||||||
|
|
@ -37,9 +37,9 @@ export namespace InstanceState {
|
||||||
const cache = yield* ScopedCache.make<string, A, E, R>({
|
const cache = yield* ScopedCache.make<string, A, E, R>({
|
||||||
capacity: Number.POSITIVE_INFINITY,
|
capacity: Number.POSITIVE_INFINITY,
|
||||||
lookup: () =>
|
lookup: () =>
|
||||||
Effect.fnUntraced(function* () {
|
Effect.gen(function* () {
|
||||||
return yield* init(yield* context)
|
return yield* init(yield* context)
|
||||||
})(),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const off = registerDisposer((directory) => Effect.runPromise(ScopedCache.invalidate(cache, directory)))
|
const off = registerDisposer((directory) => Effect.runPromise(ScopedCache.invalidate(cache, directory)))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { AppFileSystem } from "@/filesystem"
|
||||||
import { git } from "@/util/git"
|
import { git } from "@/util/git"
|
||||||
import { Effect, Layer, ServiceMap } from "effect"
|
import { Effect, Layer, ServiceMap } from "effect"
|
||||||
import { formatPatch, structuredPatch } from "diff"
|
import { formatPatch, structuredPatch } from "diff"
|
||||||
import fs from "fs"
|
|
||||||
import fuzzysort from "fuzzysort"
|
import fuzzysort from "fuzzysort"
|
||||||
import ignore from "ignore"
|
import ignore from "ignore"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
|
|
@ -359,49 +358,46 @@ export namespace File {
|
||||||
const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
|
const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
|
||||||
const next: Entry = { files: [], dirs: [] }
|
const next: Entry = { files: [], dirs: [] }
|
||||||
|
|
||||||
yield* Effect.promise(async () => {
|
if (isGlobalHome) {
|
||||||
if (isGlobalHome) {
|
const dirs = new Set<string>()
|
||||||
const dirs = new Set<string>()
|
const protectedNames = Protected.names()
|
||||||
const protectedNames = Protected.names()
|
const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
|
||||||
const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
|
const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
|
||||||
const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
|
const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
|
||||||
const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
|
const top = yield* appFs.readDirectoryEntries(Instance.directory).pipe(Effect.orElseSucceed(() => []))
|
||||||
const top = await fs.promises
|
|
||||||
.readdir(Instance.directory, { withFileTypes: true })
|
|
||||||
.catch(() => [] as fs.Dirent[])
|
|
||||||
|
|
||||||
for (const entry of top) {
|
for (const entry of top) {
|
||||||
if (!entry.isDirectory()) continue
|
if (entry.type !== "directory") continue
|
||||||
if (shouldIgnoreName(entry.name)) continue
|
if (shouldIgnoreName(entry.name)) continue
|
||||||
dirs.add(entry.name + "/")
|
dirs.add(entry.name + "/")
|
||||||
|
|
||||||
const base = path.join(Instance.directory, entry.name)
|
const base = path.join(Instance.directory, entry.name)
|
||||||
const children = await fs.promises.readdir(base, { withFileTypes: true }).catch(() => [] as fs.Dirent[])
|
const children = yield* appFs.readDirectoryEntries(base).pipe(Effect.orElseSucceed(() => []))
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (!child.isDirectory()) continue
|
if (child.type !== "directory") continue
|
||||||
if (shouldIgnoreNested(child.name)) continue
|
if (shouldIgnoreNested(child.name)) continue
|
||||||
dirs.add(entry.name + "/" + child.name + "/")
|
dirs.add(entry.name + "/" + child.name + "/")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next.dirs = Array.from(dirs).toSorted()
|
|
||||||
} else {
|
|
||||||
const seen = new Set<string>()
|
|
||||||
for await (const file of Ripgrep.files({ cwd: Instance.directory })) {
|
|
||||||
next.files.push(file)
|
|
||||||
let current = file
|
|
||||||
while (true) {
|
|
||||||
const dir = path.dirname(current)
|
|
||||||
if (dir === ".") break
|
|
||||||
if (dir === current) break
|
|
||||||
current = dir
|
|
||||||
if (seen.has(dir)) continue
|
|
||||||
seen.add(dir)
|
|
||||||
next.dirs.push(dir + "/")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
next.dirs = Array.from(dirs).toSorted()
|
||||||
|
} else {
|
||||||
|
const files = yield* Effect.promise(() => Array.fromAsync(Ripgrep.files({ cwd: Instance.directory })))
|
||||||
|
const seen = new Set<string>()
|
||||||
|
for (const file of files) {
|
||||||
|
next.files.push(file)
|
||||||
|
let current = file
|
||||||
|
while (true) {
|
||||||
|
const dir = path.dirname(current)
|
||||||
|
if (dir === ".") break
|
||||||
|
if (dir === current) break
|
||||||
|
current = dir
|
||||||
|
if (seen.has(dir)) continue
|
||||||
|
seen.add(dir)
|
||||||
|
next.dirs.push(dir + "/")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const s = yield* InstanceState.get(state)
|
const s = yield* InstanceState.get(state)
|
||||||
s.cache = next
|
s.cache = next
|
||||||
|
|
@ -636,30 +632,27 @@ export namespace File {
|
||||||
yield* ensure()
|
yield* ensure()
|
||||||
const { cache } = yield* InstanceState.get(state)
|
const { cache } = yield* InstanceState.get(state)
|
||||||
|
|
||||||
return yield* Effect.promise(async () => {
|
const query = input.query.trim()
|
||||||
const query = input.query.trim()
|
const limit = input.limit ?? 100
|
||||||
const limit = input.limit ?? 100
|
const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
||||||
const kind = input.type ?? (input.dirs === false ? "file" : "all")
|
log.info("search", { query, kind })
|
||||||
log.info("search", { query, kind })
|
|
||||||
|
|
||||||
const result = cache
|
const preferHidden = query.startsWith(".") || query.includes("/.")
|
||||||
const preferHidden = query.startsWith(".") || query.includes("/.")
|
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
if (kind === "file") return result.files.slice(0, limit)
|
if (kind === "file") return cache.files.slice(0, limit)
|
||||||
return sortHiddenLast(result.dirs.toSorted(), preferHidden).slice(0, limit)
|
return sortHiddenLast(cache.dirs.toSorted(), preferHidden).slice(0, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
const items =
|
const items =
|
||||||
kind === "file" ? result.files : kind === "directory" ? result.dirs : [...result.files, ...result.dirs]
|
kind === "file" ? cache.files : kind === "directory" ? cache.dirs : [...cache.files, ...cache.dirs]
|
||||||
|
|
||||||
const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
|
const searchLimit = kind === "directory" && !preferHidden ? limit * 20 : limit
|
||||||
const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target)
|
const sorted = fuzzysort.go(query, items, { limit: searchLimit }).map((item) => item.target)
|
||||||
const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted
|
const output = kind === "directory" ? sortHiddenLast(sorted, preferHidden).slice(0, limit) : sorted
|
||||||
|
|
||||||
log.info("search", { query, kind, results: output.length })
|
log.info("search", { query, kind, results: output.length })
|
||||||
return output
|
return output
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
log.info("init")
|
log.info("init")
|
||||||
|
|
|
||||||
|
|
@ -111,26 +111,25 @@ export namespace ProviderAuth {
|
||||||
|
|
||||||
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/ProviderAuth") {}
|
export class Service extends ServiceMap.Service<Service, Interface>()("@opencode/ProviderAuth") {}
|
||||||
|
|
||||||
export const layer = Layer.effect(
|
export const layer: Layer.Layer<Service, never, Auth.Service | Plugin.Service> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const auth = yield* Auth.Service
|
const auth = yield* Auth.Service
|
||||||
|
const plugin = yield* Plugin.Service
|
||||||
const state = yield* InstanceState.make<State>(
|
const state = yield* InstanceState.make<State>(
|
||||||
Effect.fn("ProviderAuth.state")(() =>
|
Effect.fn("ProviderAuth.state")(function* () {
|
||||||
Effect.promise(async () => {
|
const plugins = yield* plugin.list()
|
||||||
const plugins = await Plugin.list()
|
return {
|
||||||
return {
|
hooks: Record.fromEntries(
|
||||||
hooks: Record.fromEntries(
|
Arr.filterMap(plugins, (x) =>
|
||||||
Arr.filterMap(plugins, (x) =>
|
x.auth?.provider !== undefined
|
||||||
x.auth?.provider !== undefined
|
? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const)
|
||||||
? Result.succeed([ProviderID.make(x.auth.provider), x.auth] as const)
|
: Result.failVoid,
|
||||||
: Result.failVoid,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
pending: new Map<ProviderID, AuthOAuthResult>(),
|
),
|
||||||
}
|
pending: new Map<ProviderID, AuthOAuthResult>(),
|
||||||
}),
|
}
|
||||||
),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const methods = Effect.fn("ProviderAuth.methods")(function* () {
|
const methods = Effect.fn("ProviderAuth.methods")(function* () {
|
||||||
|
|
@ -230,7 +229,9 @@ export namespace ProviderAuth {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(Layer.provide(Auth.defaultLayer))
|
export const defaultLayer = Layer.suspend(() =>
|
||||||
|
layer.pipe(Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
|
||||||
|
)
|
||||||
|
|
||||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -961,11 +961,12 @@ export namespace Provider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const layer: Layer.Layer<Service, never, Config.Service | Auth.Service> = Layer.effect(
|
const layer: Layer.Layer<Service, never, Config.Service | Auth.Service | Plugin.Service> = Layer.effect(
|
||||||
Service,
|
Service,
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const config = yield* Config.Service
|
const config = yield* Config.Service
|
||||||
const auth = yield* Auth.Service
|
const auth = yield* Auth.Service
|
||||||
|
const plugin = yield* Plugin.Service
|
||||||
|
|
||||||
const state = yield* InstanceState.make<State>(() =>
|
const state = yield* InstanceState.make<State>(() =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
|
|
@ -1128,7 +1129,7 @@ export namespace Provider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const plugins = yield* Effect.promise(() => Plugin.list())
|
const plugins = yield* plugin.list()
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
if (!plugin.auth) continue
|
if (!plugin.auth) continue
|
||||||
const providerID = ProviderID.make(plugin.auth.provider)
|
const providerID = ProviderID.make(plugin.auth.provider)
|
||||||
|
|
@ -1541,7 +1542,9 @@ export namespace Provider {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const defaultLayer = layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer))
|
export const defaultLayer = Layer.suspend(() =>
|
||||||
|
layer.pipe(Layer.provide(Config.defaultLayer), Layer.provide(Auth.defaultLayer), Layer.provide(Plugin.defaultLayer)),
|
||||||
|
)
|
||||||
|
|
||||||
const { runPromise } = makeRuntime(Service, defaultLayer)
|
const { runPromise } = makeRuntime(Service, defaultLayer)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue