refactor(server): inject database options
This commit is contained in:
parent
61b7caa0b5
commit
f971aa0719
14 changed files with 93 additions and 52 deletions
|
|
@ -65,6 +65,9 @@ const processEffect = Effect.fnUntraced(function* (options: Options) {
|
||||||
port: Option.fromNullishOr(port),
|
port: Option.fromNullishOr(port),
|
||||||
password,
|
password,
|
||||||
instanceID,
|
instanceID,
|
||||||
|
database: {
|
||||||
|
path: process.env.OPENCODE_DB,
|
||||||
|
},
|
||||||
service:
|
service:
|
||||||
serviceOptions === undefined
|
serviceOptions === undefined
|
||||||
? undefined
|
? undefined
|
||||||
|
|
|
||||||
|
|
@ -511,7 +511,7 @@ test("a failed service stays registered and owns the selected port until stopped
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
|
||||||
function withDatabase<A, E>(file: string, effect: Effect.Effect<A, E, Database.Service>) {
|
function withDatabase<A, E>(file: string, effect: Effect.Effect<A, E, Database.Service>) {
|
||||||
return Effect.runPromise(effect.pipe(Effect.provide(Database.layerFromPath(file)), Effect.scoped))
|
return Effect.runPromise(effect.pipe(Effect.provide(Database.layer({ path: file })), Effect.scoped))
|
||||||
}
|
}
|
||||||
|
|
||||||
function waitForExecutionStart(file: string, sessionID: SessionV2.ID) {
|
function waitForExecutionStart(file: string, sessionID: SessionV2.ID) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
export * as Database from "./database"
|
export * as Database from "./database"
|
||||||
|
|
||||||
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
|
import { EffectDrizzleSqlite } from "@opencode-ai/effect-drizzle-sqlite"
|
||||||
import { layer } from "#sqlite"
|
import { sqliteLayer } from "#sqlite"
|
||||||
import { Context, Effect, Layer } from "effect"
|
import { Context, Effect, Layer } from "effect"
|
||||||
import { Global } from "../global"
|
import { Global } from "../global"
|
||||||
import { Flag } from "../flag/flag"
|
|
||||||
import { isAbsolute, join } from "path"
|
import { isAbsolute, join } from "path"
|
||||||
import { DatabaseMigration } from "./migration"
|
import { DatabaseMigration } from "./migration"
|
||||||
import { InstallationChannel } from "../installation/version"
|
import { InstallationChannel } from "../installation/version"
|
||||||
|
|
@ -17,6 +16,10 @@ export interface Interface {
|
||||||
db: DatabaseShape
|
db: DatabaseShape
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
readonly path?: string
|
||||||
|
}
|
||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/v2/storage/Database") {}
|
||||||
|
|
||||||
const databaseLayer = Layer.effect(
|
const databaseLayer = Layer.effect(
|
||||||
|
|
@ -36,28 +39,25 @@ const databaseLayer = Layer.effect(
|
||||||
}).pipe(Effect.orDie),
|
}).pipe(Effect.orDie),
|
||||||
)
|
)
|
||||||
|
|
||||||
export function layerFromPath(filename: string) {
|
export function layer(options?: Options) {
|
||||||
return databaseLayer.pipe(Layer.provide(layer({ filename })))
|
return Layer.suspend(() => {
|
||||||
|
const provide = (filename: string) => databaseLayer.pipe(Layer.provide(sqliteLayer({ filename })))
|
||||||
|
if (options?.path === ":memory:" || (options?.path && isAbsolute(options.path))) return provide(options.path)
|
||||||
|
if (options?.path) return provide(join(Global.Path.data, options.path))
|
||||||
|
if (
|
||||||
|
["latest", "beta", "prod"].includes(InstallationChannel) ||
|
||||||
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
|
||||||
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
|
||||||
|
)
|
||||||
|
return provide(join(Global.Path.data, "opencode.db"))
|
||||||
|
return provide(
|
||||||
|
join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`),
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function path() {
|
|
||||||
if (Flag.OPENCODE_DB) {
|
|
||||||
if (Flag.OPENCODE_DB === ":memory:" || isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
|
|
||||||
return join(Global.Path.data, Flag.OPENCODE_DB)
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
["latest", "beta", "prod"].includes(InstallationChannel) ||
|
|
||||||
process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
|
|
||||||
process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
|
|
||||||
)
|
|
||||||
return join(Global.Path.data, "opencode.db")
|
|
||||||
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the database path lazily so tests and embedders that set
|
|
||||||
// Flag.OPENCODE_DB after module evaluation still control the storage target.
|
|
||||||
export const node = makeGlobalNode({
|
export const node = makeGlobalNode({
|
||||||
service: Service,
|
service: Service,
|
||||||
layer: Layer.suspend(() => layerFromPath(path())),
|
layer: layer(),
|
||||||
deps: [],
|
deps: [],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ const nativeLayer = (config: Config) =>
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const sqliteLayer = (config: Config) => Layer.effect(SqlClient.SqlClient, make(config))
|
const clientLayer = (config: Config) => Layer.effect(SqlClient.SqlClient, make(config))
|
||||||
|
|
||||||
const drizzleLayer = Layer.effect(
|
const drizzleLayer = Layer.effect(
|
||||||
Sqlite.Drizzle,
|
Sqlite.Drizzle,
|
||||||
|
|
@ -171,9 +171,9 @@ const drizzleLayer = Layer.effect(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const layer = (config: Config) => {
|
export const sqliteLayer = (config: Config) => {
|
||||||
const native = nativeLayer(config)
|
const native = nativeLayer(config)
|
||||||
return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
|
return Layer.merge(native, Layer.merge(clientLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
|
||||||
Layer.provide(Reactivity.layer),
|
Layer.provide(Reactivity.layer),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ const nativeLayer = (config: Config) =>
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const sqliteLayer = (config: Config) => Layer.effect(SqlClient.SqlClient, make(config))
|
const clientLayer = (config: Config) => Layer.effect(SqlClient.SqlClient, make(config))
|
||||||
|
|
||||||
const drizzleLayer = Layer.effect(
|
const drizzleLayer = Layer.effect(
|
||||||
Sqlite.Drizzle,
|
Sqlite.Drizzle,
|
||||||
|
|
@ -166,9 +166,9 @@ const drizzleLayer = Layer.effect(
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
export const layer = (config: Config) => {
|
export const sqliteLayer = (config: Config) => {
|
||||||
const native = nativeLayer(config)
|
const native = nativeLayer(config)
|
||||||
return Layer.merge(native, Layer.merge(sqliteLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
|
return Layer.merge(native, Layer.merge(clientLayer(config), drizzleLayer).pipe(Layer.provide(native))).pipe(
|
||||||
Layer.provide(Reactivity.layer),
|
Layer.provide(Reactivity.layer),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -324,7 +324,7 @@ describe("DatabaseMigration", () => {
|
||||||
test("serializes concurrent embedded initialization for one database path", async () => {
|
test("serializes concurrent embedded initialization for one database path", async () => {
|
||||||
await using tmp = await tmpdir()
|
await using tmp = await tmpdir()
|
||||||
const filename = path.join(tmp.path, "embedded.sqlite")
|
const filename = path.join(tmp.path, "embedded.sqlite")
|
||||||
const layers = [Database.layerFromPath(filename), Database.layerFromPath(filename)]
|
const layers = [Database.layer({ path: filename }), Database.layer({ path: filename })]
|
||||||
|
|
||||||
await Effect.runPromise(
|
await Effect.runPromise(
|
||||||
Effect.all(
|
Effect.all(
|
||||||
|
|
|
||||||
|
|
@ -460,7 +460,7 @@ describe("SessionV2.create", () => {
|
||||||
Effect.promise(() => tmpdir()),
|
Effect.promise(() => tmpdir()),
|
||||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||||
)
|
)
|
||||||
const targetDatabase = Database.layerFromPath(path.join(tmp.path, "target.sqlite"))
|
const targetDatabase = Database.layer({ path: path.join(tmp.path, "target.sqlite") })
|
||||||
const targetLayer = AppNodeBuilder.build(
|
const targetLayer = AppNodeBuilder.build(
|
||||||
LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node]),
|
LayerNode.group([Database.node, EventV2.node, SessionProjector.node, SessionStore.node]),
|
||||||
[[Database.node, targetDatabase]],
|
[[Database.node, targetDatabase]],
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Argv } from "yargs"
|
import type { Argv } from "yargs"
|
||||||
import { spawn } from "child_process"
|
import { spawn } from "child_process"
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
|
import { InstallationDatabase } from "@/installation/database"
|
||||||
import { Effect } from "effect"
|
import { Effect } from "effect"
|
||||||
import { sql } from "drizzle-orm"
|
import { sql } from "drizzle-orm"
|
||||||
import { effectCmd } from "../effect-cmd"
|
import { effectCmd } from "../effect-cmd"
|
||||||
|
|
@ -35,7 +36,7 @@ const QueryCommand = effectCmd({
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const child = spawn("sqlite3", [Database.path()], {
|
const child = spawn("sqlite3", [InstallationDatabase.path()], {
|
||||||
stdio: "inherit",
|
stdio: "inherit",
|
||||||
})
|
})
|
||||||
yield* Effect.promise(() => new Promise((resolve) => child.on("close", resolve)))
|
yield* Effect.promise(() => new Promise((resolve) => child.on("close", resolve)))
|
||||||
|
|
@ -47,7 +48,7 @@ const PathCommand = effectCmd({
|
||||||
describe: "print the database path",
|
describe: "print the database path",
|
||||||
instance: false,
|
instance: false,
|
||||||
handler: Effect.fn("Cli.db.path")(function* () {
|
handler: Effect.fn("Cli.db.path")(function* () {
|
||||||
console.log(Database.path())
|
console.log(InstallationDatabase.path())
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
20
packages/opencode/src/installation/database.ts
Normal file
20
packages/opencode/src/installation/database.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export * as InstallationDatabase from "./database"
|
||||||
|
|
||||||
|
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||||
|
import { Global } from "@opencode-ai/core/global"
|
||||||
|
import { InstallationChannel } from "@opencode-ai/core/installation/version"
|
||||||
|
import { isAbsolute, join } from "node:path"
|
||||||
|
|
||||||
|
export function path() {
|
||||||
|
if (Flag.OPENCODE_DB) {
|
||||||
|
if (Flag.OPENCODE_DB === ":memory:" || isAbsolute(Flag.OPENCODE_DB)) return Flag.OPENCODE_DB
|
||||||
|
return join(Global.Path.data, Flag.OPENCODE_DB)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
["latest", "beta", "prod"].includes(InstallationChannel) ||
|
||||||
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "1" ||
|
||||||
|
process.env.OPENCODE_DISABLE_CHANNEL_DB === "true"
|
||||||
|
)
|
||||||
|
return join(Global.Path.data, "opencode.db")
|
||||||
|
return join(Global.Path.data, `opencode-${InstallationChannel.replace(/[^a-zA-Z0-9._-]/g, "-")}.db`)
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { rm } from "fs/promises"
|
import { rm } from "fs/promises"
|
||||||
import { Database } from "@opencode-ai/core/database/database"
|
import { InstallationDatabase } from "@/installation/database"
|
||||||
import { disposeAllInstances } from "./fixture"
|
import { disposeAllInstances } from "./fixture"
|
||||||
|
|
||||||
export async function resetDatabase() {
|
export async function resetDatabase() {
|
||||||
await disposeAllInstances().catch(() => undefined)
|
await disposeAllInstances().catch(() => undefined)
|
||||||
const dbPath = Database.path()
|
const dbPath = InstallationDatabase.path()
|
||||||
await rm(dbPath, { force: true }).catch(() => undefined)
|
await rm(dbPath, { force: true }).catch(() => undefined)
|
||||||
await rm(`${dbPath}-wal`, { force: true }).catch(() => undefined)
|
await rm(`${dbPath}-wal`, { force: true }).catch(() => undefined)
|
||||||
await rm(`${dbPath}-shm`, { force: true }).catch(() => undefined)
|
await rm(`${dbPath}-shm`, { force: true }).catch(() => undefined)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
import { OpenCode } from "@opencode-ai/client/effect"
|
import { OpenCode } from "@opencode-ai/client/effect"
|
||||||
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk"
|
import { SdkPlugins } from "@opencode-ai/core/plugin/sdk"
|
||||||
import { createEmbeddedRoutes } from "@opencode-ai/server/routes"
|
import { createEmbeddedRoutes } from "@opencode-ai/server/routes"
|
||||||
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
import { Context, Effect, Layer, ManagedRuntime } from "effect"
|
||||||
import { FetchHttpClient, HttpEffect, HttpRouter, HttpServer } from "effect/unstable/http"
|
import { FetchHttpClient, HttpEffect, HttpRouter, HttpServer } from "effect/unstable/http"
|
||||||
|
|
||||||
export const create = Effect.fn("OpenCode.create")(function* () {
|
export const create = Effect.fn("OpenCode.create")(function* (options: { readonly database?: Database.Options } = {}) {
|
||||||
const runtime = yield* Effect.acquireRelease(
|
const runtime = yield* Effect.acquireRelease(
|
||||||
Effect.sync(() => ManagedRuntime.make(createEmbeddedRoutes().pipe(Layer.provide(HttpServer.layerServices)))),
|
Effect.sync(() =>
|
||||||
|
ManagedRuntime.make(
|
||||||
|
createEmbeddedRoutes({ database: { path: ":memory:", ...options.database } }).pipe(
|
||||||
|
Layer.provide(HttpServer.layerServices),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
(runtime) => runtime.disposeEffect,
|
(runtime) => runtime.disposeEffect,
|
||||||
)
|
)
|
||||||
const context = yield* runtime.contextEffect
|
const context = yield* runtime.contextEffect
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import fs from "fs/promises"
|
import fs from "fs/promises"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { expect } from "bun:test"
|
import { expect } from "bun:test"
|
||||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
|
||||||
import { Deferred, Effect, Latch, Layer, Option, Ref, Schema, Stream } from "effect"
|
import { Deferred, Effect, Latch, Layer, Option, Ref, Schema, Stream } from "effect"
|
||||||
import { testEffect } from "../../core/test/lib/effect"
|
import { testEffect } from "../../core/test/lib/effect"
|
||||||
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
import { tmpdir } from "../../core/test/fixture/tmpdir"
|
||||||
import type { OpenCodeEvent } from "../src"
|
import type { OpenCodeEvent } from "../src"
|
||||||
|
|
||||||
Flag.OPENCODE_DB = ":memory:"
|
|
||||||
|
|
||||||
const it = testEffect(Layer.empty)
|
const it = testEffect(Layer.empty)
|
||||||
type Sdk = typeof import("../src")
|
type Sdk = typeof import("../src")
|
||||||
type Fixture = { readonly directory: string; readonly sdk: Sdk }
|
type Fixture = { readonly directory: string; readonly sdk: Sdk }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export * as ServerProcess from "./process"
|
export * as ServerProcess from "./process"
|
||||||
|
|
||||||
import { NodeHttpServer, NodeHttpServerRequest } from "@effect/platform-node"
|
import { NodeHttpServer, NodeHttpServerRequest } from "@effect/platform-node"
|
||||||
|
import { Database } from "@opencode-ai/core/database/database"
|
||||||
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
import { InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||||
import { SessionRestart } from "@opencode-ai/core/session/execution/restart"
|
import { SessionRestart } from "@opencode-ai/core/session/execution/restart"
|
||||||
import { ServiceStatus } from "@opencode-ai/protocol/groups/health"
|
import { ServiceStatus } from "@opencode-ai/protocol/groups/health"
|
||||||
|
|
@ -20,6 +21,7 @@ export type Options<E = never, R = never> = {
|
||||||
readonly port: Option.Option<number>
|
readonly port: Option.Option<number>
|
||||||
readonly password: string
|
readonly password: string
|
||||||
readonly instanceID: string
|
readonly instanceID: string
|
||||||
|
readonly database?: Database.Options
|
||||||
readonly service?: {
|
readonly service?: {
|
||||||
readonly onListen: (
|
readonly onListen: (
|
||||||
address: HttpServer.Address,
|
address: HttpServer.Address,
|
||||||
|
|
@ -66,11 +68,15 @@ export const start = Effect.fn("ServerProcess.start")(function* <E, R>(options:
|
||||||
|
|
||||||
const boot = Effect.gen(function* () {
|
const boot = Effect.gen(function* () {
|
||||||
const context = yield* Layer.buildWithScope(
|
const context = yield* Layer.buildWithScope(
|
||||||
createRoutes(options.password, () => {
|
createRoutes({
|
||||||
const address = bound.server.address()
|
password: options.password,
|
||||||
if (address === null || typeof address === "string") return []
|
serviceURLs: () => {
|
||||||
const host = address.family === "IPv6" ? `[${address.address}]` : address.address
|
const address = bound.server.address()
|
||||||
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, options.hostname)
|
if (address === null || typeof address === "string") return []
|
||||||
|
const host = address.family === "IPv6" ? `[${address.address}]` : address.address
|
||||||
|
return ServerInfo.connectionURLs(`http://${host}:${address.port}`, options.hostname)
|
||||||
|
},
|
||||||
|
database: options.database,
|
||||||
}).pipe(Layer.provide(NodeHttpServer.layerHttpServices)),
|
}).pipe(Layer.provide(NodeHttpServer.layerHttpServices)),
|
||||||
applicationScope,
|
applicationScope,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -51,25 +51,32 @@ const applicationServices = LayerNode.group([
|
||||||
SessionRestart.node,
|
SessionRestart.node,
|
||||||
])
|
])
|
||||||
|
|
||||||
export function createRoutes(password?: string, serviceURLs: () => ReadonlyArray<string> = () => []) {
|
export interface Options {
|
||||||
|
readonly password?: string
|
||||||
|
readonly serviceURLs?: () => ReadonlyArray<string>
|
||||||
|
readonly database?: Database.Options
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRoutes(options: Options = {}) {
|
||||||
return makeRoutes(
|
return makeRoutes(
|
||||||
password
|
options.password
|
||||||
? ServerAuth.Config.configLayer({ username: "opencode", password: Option.some(password) })
|
? ServerAuth.Config.configLayer({ username: "opencode", password: Option.some(options.password) })
|
||||||
: ServerAuth.Config.layer,
|
: ServerAuth.Config.layer,
|
||||||
serviceURLs,
|
options,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createEmbeddedRoutes() {
|
export function createEmbeddedRoutes(options: Pick<Options, "database"> = {}) {
|
||||||
return makeRoutes(ServerAuth.Config.configLayer({ username: "opencode", password: Option.none() }))
|
return makeRoutes(ServerAuth.Config.configLayer({ username: "opencode", password: Option.none() }), options)
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeRoutes<AuthError, AuthServices>(
|
function makeRoutes<AuthError, AuthServices>(
|
||||||
auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>,
|
auth: Layer.Layer<ServerAuth.Config, AuthError, AuthServices>,
|
||||||
serviceURLs: () => ReadonlyArray<string> = () => [],
|
options: Pick<Options, "database" | "serviceURLs">,
|
||||||
) {
|
) {
|
||||||
const pluginRuntimeCell = PluginRuntime.makeCell()
|
const pluginRuntimeCell = PluginRuntime.makeCell()
|
||||||
const replacements: LayerNode.Replacements = [
|
const replacements: LayerNode.Replacements = [
|
||||||
|
[Database.node, Database.layer(options.database)],
|
||||||
[PluginRuntime.node, PluginRuntime.layerWithCell(pluginRuntimeCell)],
|
[PluginRuntime.node, PluginRuntime.layerWithCell(pluginRuntimeCell)],
|
||||||
[PluginRuntime.providerNode, PluginRuntime.providerNodeWithCell(pluginRuntimeCell)],
|
[PluginRuntime.providerNode, PluginRuntime.providerNodeWithCell(pluginRuntimeCell)],
|
||||||
]
|
]
|
||||||
|
|
@ -88,7 +95,7 @@ function makeRoutes<AuthError, AuthServices>(
|
||||||
const services = Layer.succeedContext(context)
|
const services = Layer.succeedContext(context)
|
||||||
const requestServices = Layer.merge(
|
const requestServices = Layer.merge(
|
||||||
Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service, WellKnown.Service)(context)),
|
Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service, WellKnown.Service)(context)),
|
||||||
ServerInfo.layer(serviceURLs),
|
ServerInfo.layer(options.serviceURLs ?? (() => [])),
|
||||||
)
|
)
|
||||||
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
|
return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe(
|
||||||
Layer.provide(handlers.pipe(Layer.provide(services))),
|
Layer.provide(handlers.pipe(Layer.provide(services))),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue