refactor(server): unify instance httpapi middleware routing
Unify declared instance HTTP API endpoints under typed middleware routing, including event streaming and PTY WebSocket connect handling.\n\nPreserve PTY connect compatibility by checking missing PTYs before parsing optional cursor and ticket query fields, with regression coverage.
This commit is contained in:
@@ -14,18 +14,20 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
|
||||
For SSE endpoints, stay in `HttpApiBuilder.group(...)` and return `HttpServerResponse.stream(...)` from the handler. Annotate the endpoint success schema with `HttpApiSchema.asText({ contentType: "text/event-stream" })` so OpenAPI documents the stream content type.
|
||||
|
||||
Use raw `HttpRouter.use(...)` only for routes that do not fit the request/response HttpApi model, such as WebSocket upgrade routes or catch-all fallback routes. Yield stable services at route-layer construction and close over them in `router.add(...)` callbacks.
|
||||
Use `HttpApiBuilder.group(...)` with `handleRaw(...)` for declared endpoints that need the raw request or response, including WebSocket upgrade routes. This keeps endpoint middleware, routing context, and OpenAPI metadata on one typed route tree.
|
||||
|
||||
```ts
|
||||
export const rawRoute = HttpRouter.use((router) =>
|
||||
export const ptyConnectHandlers = HttpApiBuilder.group(PtyConnectApi, "pty-connect", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
|
||||
yield* router.add("GET", PtyPaths.connect, (request) => connectPty(request, pty))
|
||||
return handlers.handleRaw("connect", (ctx) => connectPty(ctx.request, pty))
|
||||
}),
|
||||
)
|
||||
```
|
||||
|
||||
Use raw `HttpRouter.use(...)` only for routes outside the declared API surface, such as a catch-all UI fallback.
|
||||
|
||||
Avoid `Effect.provide(SomeLayer)` inside request handlers or raw route callbacks. Stable layers should be provided once at the application/layer boundary, not rebuilt or scoped per request.
|
||||
|
||||
Avoid `HttpRouter.provideRequest(...)` unless the dependency is intentionally request-level. Prefer `HttpRouter.use(...)` for stable app services.
|
||||
@@ -34,4 +36,4 @@ Use `Effect.provideService(...)` in middleware only for request-derived context,
|
||||
|
||||
Public JSON errors should be explicit `Schema.ErrorClass` contracts declared on each endpoint. Use built-in `HttpApiError.*` classes only when their empty/tagged body is the intended wire shape; for SDK-visible errors with messages, define an API error schema such as `ApiNotFoundError` and fail with that exact declared error. Keep domain and storage services free of HttpApi types, and translate expected domain errors at the handler boundary.
|
||||
|
||||
When adding middleware, compose it at the layer boundary and keep the route tree explicit in `server.ts`. Shared router middleware such as auth, workspace routing, and instance context should stay visible where routes are assembled.
|
||||
When adding middleware, declare endpoint-contract middleware on the owning `HttpApiGroup` and provide its implementation layer at the assembly boundary in `server.ts`. Keep router middleware for truly raw fallback routes or global transport policy.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi"
|
||||
import { WorkspaceRoutingQuery } from "../middleware/workspace-routing"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
import { InstanceContextMiddleware } from "../middleware/instance-context"
|
||||
import { WorkspaceRoutingMiddleware, WorkspaceRoutingQuery } from "../middleware/workspace-routing"
|
||||
|
||||
export const EventPaths = {
|
||||
event: "/event",
|
||||
@@ -20,5 +22,8 @@ export const EventApi = HttpApi.make("event").add(
|
||||
}),
|
||||
),
|
||||
)
|
||||
.middleware(InstanceContextMiddleware)
|
||||
.middleware(WorkspaceRoutingMiddleware)
|
||||
.middleware(Authorization)
|
||||
.annotateMerge(OpenApi.annotations({ title: "event", description: "Instance event stream route." })),
|
||||
)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Pty } from "@/pty"
|
||||
import { PtyTicket } from "@/pty/ticket"
|
||||
import { PtyID } from "@/pty/schema"
|
||||
import { PTY_CONNECT_TICKET_QUERY } from "@/server/shared/pty-ticket"
|
||||
import { Schema } from "effect"
|
||||
import { HttpApi, HttpApiEndpoint, HttpApiError, HttpApiGroup, OpenApi } from "effect/unstable/httpapi"
|
||||
import { Authorization } from "../middleware/authorization"
|
||||
import { Authorization, PtyConnectAuthorization } from "../middleware/authorization"
|
||||
import { InstanceContextMiddleware } from "../middleware/instance-context"
|
||||
import {
|
||||
WorkspaceRoutingMiddleware,
|
||||
@@ -138,9 +139,10 @@ export const PtyApi = HttpApi.make("pty")
|
||||
export const PtyConnectApi = HttpApi.make("pty-connect").add(
|
||||
HttpApiGroup.make("pty-connect")
|
||||
.add(
|
||||
// Decode PTY connection query fields in the raw handler after checking
|
||||
// existence, preserving the established empty-404 response ordering.
|
||||
HttpApiEndpoint.get("connect", PtyPaths.connect, {
|
||||
params: Params,
|
||||
query: WorkspaceRoutingQuery,
|
||||
success: described(Schema.Boolean, "Connected session"),
|
||||
error: [HttpApiError.Forbidden, HttpApiError.NotFound],
|
||||
}).annotateMerge(
|
||||
@@ -149,8 +151,22 @@ export const PtyConnectApi = HttpApi.make("pty-connect").add(
|
||||
summary: "Connect to PTY session",
|
||||
description:
|
||||
"Establish a WebSocket connection to interact with a pseudo-terminal (PTY) session in real-time.",
|
||||
transform: (operation) => ({
|
||||
...operation,
|
||||
parameters: [
|
||||
...(operation.parameters ?? []),
|
||||
...["directory", "workspace", "cursor", PTY_CONNECT_TICKET_QUERY].map((name) => ({
|
||||
in: "query",
|
||||
name,
|
||||
schema: { type: "string" },
|
||||
})),
|
||||
],
|
||||
}),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.annotateMerge(OpenApi.annotations({ title: "pty", description: "PTY websocket route." })),
|
||||
.annotateMerge(OpenApi.annotations({ title: "pty", description: "PTY websocket route." }))
|
||||
.middleware(InstanceContextMiddleware)
|
||||
.middleware(WorkspaceRoutingMiddleware)
|
||||
.middleware(PtyConnectAuthorization),
|
||||
)
|
||||
|
||||
@@ -10,13 +10,13 @@ import {
|
||||
PTY_CONNECT_TOKEN_HEADER,
|
||||
PTY_CONNECT_TOKEN_HEADER_VALUE,
|
||||
} from "@/server/shared/pty-ticket"
|
||||
import { Effect } from "effect"
|
||||
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { Effect, Option, Schema } from "effect"
|
||||
import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiBuilder } from "effect/unstable/httpapi"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
import { InstanceHttpApi } from "../api"
|
||||
import * as ApiError from "../errors"
|
||||
import { CursorQuery, Params, PtyPaths } from "../groups/pty"
|
||||
import { CursorQuery, PtyConnectApi } from "../groups/pty"
|
||||
import { WebSocketTracker } from "../websocket-tracker"
|
||||
|
||||
function validOrigin(request: HttpServerRequest.HttpServerRequest, opts: CorsOptions | undefined) {
|
||||
@@ -121,37 +121,39 @@ export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handler
|
||||
}),
|
||||
)
|
||||
|
||||
export const ptyConnectRoute = HttpRouter.use((router) =>
|
||||
export const ptyConnectHandlers = HttpApiBuilder.group(PtyConnectApi, "pty-connect", (handlers) =>
|
||||
Effect.gen(function* () {
|
||||
const pty = yield* Pty.Service
|
||||
const tickets = yield* PtyTicket.Service
|
||||
const cors = yield* CorsConfig
|
||||
yield* router.add(
|
||||
"GET",
|
||||
PtyPaths.connect,
|
||||
Effect.gen(function* () {
|
||||
const params = yield* HttpRouter.schemaPathParams(Params)
|
||||
const exists = yield* pty.get(params.ptyID).pipe(
|
||||
|
||||
return handlers.handleRaw(
|
||||
"connect",
|
||||
Effect.fn("PtyHttpApi.connect")(function* (ctx: {
|
||||
params: { ptyID: PtyID }
|
||||
request: HttpServerRequest.HttpServerRequest
|
||||
}) {
|
||||
const exists = yield* pty.get(ctx.params.ptyID).pipe(
|
||||
Effect.as(true),
|
||||
Effect.catchTag("Pty.NotFoundError", () => Effect.succeed(false)),
|
||||
)
|
||||
if (!exists) return HttpServerResponse.empty({ status: 404 })
|
||||
|
||||
const query = yield* HttpServerRequest.schemaSearchParams(CursorQuery)
|
||||
const request = yield* HttpServerRequest.HttpServerRequest
|
||||
const ticket = new URL(request.url, "http://localhost").searchParams.get(PTY_CONNECT_TICKET_QUERY)
|
||||
const query = Schema.decodeUnknownOption(CursorQuery)(yield* HttpServerRequest.ParsedSearchParams)
|
||||
if (Option.isNone(query)) return HttpServerResponse.empty({ status: 400 })
|
||||
const ticket = new URL(ctx.request.url, "http://localhost").searchParams.get(PTY_CONNECT_TICKET_QUERY)
|
||||
if (ticket) {
|
||||
const valid = validOrigin(request, cors)
|
||||
? yield* tickets.consume({ ticket, ptyID: params.ptyID, ...(yield* PtyTicket.scope) })
|
||||
const valid = validOrigin(ctx.request, cors)
|
||||
? yield* tickets.consume({ ticket, ptyID: ctx.params.ptyID, ...(yield* PtyTicket.scope) })
|
||||
: false
|
||||
if (!valid) return HttpServerResponse.empty({ status: 403 })
|
||||
}
|
||||
const parsedCursor = query.cursor === undefined ? undefined : Number(query.cursor)
|
||||
const parsedCursor = query.value.cursor === undefined ? undefined : Number(query.value.cursor)
|
||||
const cursor =
|
||||
parsedCursor !== undefined && Number.isSafeInteger(parsedCursor) && parsedCursor >= -1
|
||||
? parsedCursor
|
||||
: undefined
|
||||
const socket = yield* Effect.orDie(request.upgrade)
|
||||
const socket = yield* Effect.orDie(ctx.request.upgrade)
|
||||
const write = yield* socket.writer
|
||||
const closeAccepted = (event: Socket.CloseEvent) =>
|
||||
socket
|
||||
@@ -186,7 +188,7 @@ export const ptyConnectRoute = HttpRouter.use((router) =>
|
||||
},
|
||||
}
|
||||
const handler = yield* pty
|
||||
.connect(params.ptyID, adapter, cursor)
|
||||
.connect(ctx.params.ptyID, adapter, cursor)
|
||||
.pipe(
|
||||
Effect.catchTag("Pty.NotFoundError", () =>
|
||||
closeAccepted(new Socket.CloseEvent(4404, "session not found")).pipe(Effect.as(undefined)),
|
||||
@@ -194,12 +196,8 @@ export const ptyConnectRoute = HttpRouter.use((router) =>
|
||||
)
|
||||
if (!handler) return HttpServerResponse.empty()
|
||||
|
||||
// No `pending[]`-style early-frame buffer (the legacy handler had one).
|
||||
// `request.upgrade` returns a Socket without running the WS handshake; the
|
||||
// handshake fires inside `socket.runRaw` below, AFTER `pty.connect` resolves
|
||||
// and the message callback is registered. The client therefore can't fire
|
||||
// `open` and start sending until the listener is already wired. Don't move
|
||||
// `runRaw` ahead of `pty.connect` without re-introducing a buffer.
|
||||
// The handshake runs inside `socket.runRaw`, after the input callback is
|
||||
// registered, so the client cannot send frames before PTY input is wired.
|
||||
yield* socket
|
||||
.runRaw((message) => handlePtyInput(handler, message))
|
||||
.pipe(
|
||||
|
||||
@@ -27,6 +27,13 @@ export class V2Authorization extends HttpApiMiddleware.Service<V2Authorization>(
|
||||
},
|
||||
) {}
|
||||
|
||||
export class PtyConnectAuthorization extends HttpApiMiddleware.Service<PtyConnectAuthorization>()(
|
||||
"@opencode/ExperimentalHttpApiPtyConnectAuthorization",
|
||||
{
|
||||
error: HttpApiError.UnauthorizedNoContent,
|
||||
},
|
||||
) {}
|
||||
|
||||
function emptyCredential() {
|
||||
return {
|
||||
username: "",
|
||||
@@ -105,7 +112,6 @@ export const authorizationRouterMiddleware = HttpRouter.middleware()(
|
||||
const request = yield* HttpServerRequest.HttpServerRequest
|
||||
const url = new URL(request.url, "http://localhost")
|
||||
if (isPublicUIPath(request.method, url.pathname)) return yield* effect
|
||||
if (hasPtyConnectTicketURL(url)) return yield* effect
|
||||
return yield* credentialFromURL(url, request).pipe(
|
||||
Effect.flatMap((credential) => validateRawCredential(effect, credential, config)),
|
||||
)
|
||||
@@ -129,6 +135,24 @@ export const authorizationLayer = Layer.effect(
|
||||
}),
|
||||
)
|
||||
|
||||
export const ptyConnectAuthorizationLayer = Layer.effect(
|
||||
PtyConnectAuthorization,
|
||||
Effect.gen(function* () {
|
||||
const config = yield* ServerAuth.Config
|
||||
if (!ServerAuth.required(config)) return PtyConnectAuthorization.of((effect) => effect)
|
||||
return PtyConnectAuthorization.of((effect) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* HttpServerRequest.HttpServerRequest
|
||||
const url = new URL(request.url, "http://localhost")
|
||||
if (hasPtyConnectTicketURL(url)) return yield* effect
|
||||
return yield* credentialFromURL(url, request).pipe(
|
||||
Effect.flatMap((credential) => validateCredential(effect, credential, config)),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export const v2AuthorizationLayer = Layer.effect(
|
||||
V2Authorization,
|
||||
Effect.gen(function* () {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { InstanceRef, WorkspaceRef } from "@/effect/instance-ref"
|
||||
import { InstanceStore } from "@/project/instance-store"
|
||||
import { Effect, Layer } from "effect"
|
||||
import { HttpRouter, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import { WorkspaceRouteContext } from "./workspace-routing"
|
||||
|
||||
@@ -41,10 +41,3 @@ export const instanceContextLayer = Layer.effect(
|
||||
return InstanceContextMiddleware.of((effect) => provideInstanceContext(effect, store))
|
||||
}),
|
||||
)
|
||||
|
||||
export const instanceRouterMiddleware = HttpRouter.middleware()(
|
||||
Effect.gen(function* () {
|
||||
const store = yield* InstanceStore.Service
|
||||
return (effect) => provideInstanceContext(effect, store)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getWorkspaceRouteSessionID, isLocalWorkspaceRoute, workspaceProxyURL }
|
||||
import { NotFoundError } from "@/storage/storage"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { Context, Data, Effect, Layer, Option, Schema } from "effect"
|
||||
import { HttpClient, HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpClient, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"
|
||||
import { HttpApiMiddleware } from "effect/unstable/httpapi"
|
||||
import * as Socket from "effect/unstable/socket/Socket"
|
||||
import { InvalidRequestError } from "../errors"
|
||||
@@ -219,7 +219,10 @@ function routeHttpApiWorkspace<E>(
|
||||
const sessionID = getWorkspaceRouteSessionID(requestURL(request))
|
||||
const session = sessionID
|
||||
? yield* Session.Service.use((svc) => svc.get(sessionID)).pipe(
|
||||
Effect.catchIf(NotFoundError.isInstance, () => Effect.succeed(undefined)),
|
||||
Effect.catchIf(
|
||||
(error): error is NotFoundError => NotFoundError.isInstance(error),
|
||||
() => Effect.succeed(undefined),
|
||||
),
|
||||
Effect.catchDefect(() => Effect.succeed(undefined)),
|
||||
)
|
||||
: undefined
|
||||
@@ -242,20 +245,3 @@ export const workspaceRoutingLayer = Layer.effect(
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
export const workspaceRouterMiddleware = HttpRouter.middleware<{ provides: WorkspaceRouteContext }>()(
|
||||
Effect.gen(function* () {
|
||||
const makeWebSocket = yield* Socket.WebSocketConstructor
|
||||
const workspace = yield* Workspace.Service
|
||||
const client = yield* HttpClient.HttpClient
|
||||
return (effect) =>
|
||||
Effect.gen(function* () {
|
||||
const request = yield* HttpServerRequest.HttpServerRequest
|
||||
const plan = yield* planRequest(request)
|
||||
return yield* routeWorkspace(client, effect, plan)
|
||||
}).pipe(
|
||||
Effect.provideService(Socket.WebSocketConstructor, makeWebSocket),
|
||||
Effect.provideService(Workspace.Service, workspace),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -59,8 +59,14 @@ import { serveUIEffect } from "@/server/shared/ui"
|
||||
import { ServerAuth } from "@/server/auth"
|
||||
import { InstanceHttpApi, RootHttpApi } from "./api"
|
||||
import { PublicApi } from "./public"
|
||||
import { authorizationLayer, authorizationRouterMiddleware, v2AuthorizationLayer } from "./middleware/authorization"
|
||||
import {
|
||||
authorizationLayer,
|
||||
authorizationRouterMiddleware,
|
||||
ptyConnectAuthorizationLayer,
|
||||
v2AuthorizationLayer,
|
||||
} from "./middleware/authorization"
|
||||
import { EventApi } from "./groups/event"
|
||||
import { PtyConnectApi } from "./groups/pty"
|
||||
import { eventHandlers } from "./handlers/event"
|
||||
import { configHandlers } from "./handlers/config"
|
||||
import { controlHandlers } from "./handlers/control"
|
||||
@@ -72,15 +78,15 @@ import { mcpHandlers } from "./handlers/mcp"
|
||||
import { permissionHandlers } from "./handlers/permission"
|
||||
import { projectHandlers } from "./handlers/project"
|
||||
import { providerHandlers } from "./handlers/provider"
|
||||
import { ptyConnectRoute, ptyHandlers } from "./handlers/pty"
|
||||
import { ptyConnectHandlers, ptyHandlers } from "./handlers/pty"
|
||||
import { questionHandlers } from "./handlers/question"
|
||||
import { sessionHandlers } from "./handlers/session"
|
||||
import { syncHandlers } from "./handlers/sync"
|
||||
import { tuiHandlers } from "./handlers/tui"
|
||||
import { v2Handlers } from "./handlers/v2"
|
||||
import { workspaceHandlers } from "./handlers/workspace"
|
||||
import { instanceContextLayer, instanceRouterMiddleware } from "./middleware/instance-context"
|
||||
import { workspaceRouterMiddleware, workspaceRoutingLayer } from "./middleware/workspace-routing"
|
||||
import { instanceContextLayer } from "./middleware/instance-context"
|
||||
import { workspaceRoutingLayer } from "./middleware/workspace-routing"
|
||||
import { disposeMiddleware } from "./lifecycle"
|
||||
import { memoMap } from "@opencode-ai/core/effect/memo-map"
|
||||
import { compressionLayer } from "./middleware/compression"
|
||||
@@ -102,24 +108,27 @@ const cors = (corsOptions?: CorsOptions) =>
|
||||
|
||||
// Route tree:
|
||||
// - rootApiRoutes: typed /global/* and control routes; auth is declared by RootHttpApi.
|
||||
// - eventApiRoutes/rawInstanceRoutes: raw instance routes; auth and workspace routing happen as router middleware.
|
||||
// - instanceApiRoutes: schema routes; auth is declared on each group and workspace context is provided below.
|
||||
// - eventApiRoutes: typed SSE route with instance routing context and its existing API contract.
|
||||
// - ptyConnectApiRoutes: typed WebSocket upgrade route with ticket-aware auth.
|
||||
// - instanceApiRoutes: remaining typed instance routes.
|
||||
// - uiRoute: raw catch-all fallback; auth is router middleware so public static assets can bypass it.
|
||||
const authOnlyRouterLayer = authorizationRouterMiddleware.layer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const httpApiAuthLayer = authorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const ptyConnectHttpApiAuthLayer = ptyConnectAuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const v2HttpApiAuthLayer = v2AuthorizationLayer.pipe(Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const workspaceRoutingLive = workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal))
|
||||
const rootApiRoutes = HttpApiBuilder.layer(RootHttpApi).pipe(
|
||||
Layer.provide([controlHandlers, globalHandlers]),
|
||||
Layer.provide(schemaErrorLayer),
|
||||
Layer.provide(httpApiAuthLayer),
|
||||
)
|
||||
const instanceRouterLayer = authorizationRouterMiddleware
|
||||
.combine(instanceRouterMiddleware)
|
||||
.combine(workspaceRouterMiddleware)
|
||||
.layer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal), Layer.provide(ServerAuth.Config.defaultLayer))
|
||||
const eventApiRoutes = HttpApiBuilder.layer(EventApi).pipe(
|
||||
Layer.provide(eventHandlers),
|
||||
Layer.provide(instanceRouterLayer),
|
||||
Layer.provide([httpApiAuthLayer, workspaceRoutingLive, instanceContextLayer]),
|
||||
)
|
||||
const ptyConnectApiRoutes = HttpApiBuilder.layer(PtyConnectApi).pipe(
|
||||
Layer.provide(ptyConnectHandlers),
|
||||
Layer.provide([ptyConnectHttpApiAuthLayer, workspaceRoutingLive, instanceContextLayer]),
|
||||
)
|
||||
const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
||||
Layer.provide([
|
||||
@@ -141,15 +150,8 @@ const instanceApiRoutes = HttpApiBuilder.layer(InstanceHttpApi).pipe(
|
||||
]),
|
||||
)
|
||||
|
||||
const rawInstanceRoutes = Layer.mergeAll(ptyConnectRoute).pipe(Layer.provide(instanceRouterLayer))
|
||||
const instanceRoutes = Layer.mergeAll(rawInstanceRoutes, instanceApiRoutes).pipe(
|
||||
Layer.provide([
|
||||
httpApiAuthLayer,
|
||||
v2HttpApiAuthLayer,
|
||||
workspaceRoutingLayer.pipe(Layer.provide(Socket.layerWebSocketConstructorGlobal)),
|
||||
instanceContextLayer,
|
||||
schemaErrorLayer,
|
||||
]),
|
||||
const instanceRoutes = instanceApiRoutes.pipe(
|
||||
Layer.provide([httpApiAuthLayer, v2HttpApiAuthLayer, workspaceRoutingLive, instanceContextLayer, schemaErrorLayer]),
|
||||
)
|
||||
|
||||
// `OpenApi.fromApi` is non-trivial; defer until /doc is actually hit so
|
||||
@@ -184,7 +186,7 @@ type RouteRequirements =
|
||||
export function createRoutes(
|
||||
corsOptions?: CorsOptions,
|
||||
): Layer.Layer<never, EffectConfig.ConfigError, RouteRequirements> {
|
||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
|
||||
return Layer.mergeAll(rootApiRoutes, eventApiRoutes, ptyConnectApiRoutes, instanceRoutes, docRoute, uiRoute).pipe(
|
||||
Layer.provide([
|
||||
errorLayer,
|
||||
compressionLayer,
|
||||
|
||||
Reference in New Issue
Block a user