| 1 | import * as Context from "effect/Context"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Layer from "effect/Layer"; |
| 4 | import * as HttpServerResponse from "effect/http/HttpServerResponse"; |
| 5 | import type { HttpEffect } from "../Http.ts"; |
| 6 | import * as Output from "../Output.ts"; |
| 7 | import { |
| 8 | packEnvValueKeepRedacted, |
| 9 | unpackEnvValue, |
| 10 | type BaseRuntimeContext, |
| 11 | } from "../RuntimeContext.ts"; |
| 12 | import { |
| 13 | FunctionEnvironment, |
| 14 | type FunctionRequest, |
| 15 | } from "./FunctionEnvironment.ts"; |
| 16 | |
| 17 | export interface FunctionRuntimeContext extends BaseRuntimeContext { |
| 18 | /** Register a request route during init. More specific routes precede the default handler. */ |
| 19 | route( |
| 20 | path: string, |
| 21 | handler: HttpEffect<FunctionRequest>, |
| 22 | ): Effect.Effect<void>; |
| 23 | /** Request dispatcher and the services captured at init. */ |
| 24 | handler: Effect.Effect<{ |
| 25 | dispatch: (path: string) => HttpEffect<FunctionRequest>; |
| 26 | context: Context.Context<never>; |
| 27 | }>; |
| 28 | } |
| 29 | |
| 30 | export const makeFunctionRuntimeContext = ( |
| 31 | id: string, |
| 32 | ): FunctionRuntimeContext => { |
| 33 | const env: Record<string, Output.Output> = {}; |
| 34 | const routes = new Map<string, HttpEffect<FunctionRequest>>(); |
| 35 | let handler: HttpEffect = Effect.succeed( |
| 36 | HttpServerResponse.empty({ status: 404 }), |
| 37 | ); |
| 38 | let context = Context.empty(); |
| 39 | return { |
| 40 | Type: "Neon.Function", |
| 41 | id, |
| 42 | env, |
| 43 | get: <T>(key: string) => |
| 44 | Effect.sync(() => unpackEnvValue<T>(process.env[key])), |
| 45 | set: (key, value) => |
| 46 | Effect.sync(() => { |
| 47 | env[key] = value.pipe(Output.map(packEnvValueKeepRedacted)); |
| 48 | return key; |
| 49 | }), |
| 50 | serve: (next) => |
| 51 | Effect.gen(function* () { |
| 52 | handler = next as HttpEffect; |
| 53 | context = Context.omit(Layer.CurrentMemoMap)( |
| 54 | yield* Effect.context<never>(), |
| 55 | ); |
| 56 | }), |
| 57 | route: (path, next) => |
| 58 | Effect.gen(function* () { |
| 59 | if (routes.has(path)) |
| 60 | return yield* Effect.die( |
| 61 | new Error(`Duplicate Neon Function route: ${path}`), |
| 62 | ); |
| 63 | routes.set(path, next); |
| 64 | context = Context.omit(Layer.CurrentMemoMap)( |
| 65 | yield* Effect.context<never>(), |
| 66 | ); |
| 67 | }), |
| 68 | handler: Effect.sync(() => ({ |
| 69 | dispatch: (path) => routes.get(path) ?? handler, |
| 70 | context, |
| 71 | })), |
| 72 | planServices: Layer.succeed(FunctionEnvironment, {}), |
| 73 | }; |
| 74 | }; |