1import * as Context from "effect/Context";
2import * as Effect from "effect/Effect";
3import * as Layer from "effect/Layer";
4import * as HttpServerResponse from "effect/http/HttpServerResponse";
5import type { HttpEffect } from "../Http.ts";
6import * as Output from "../Output.ts";
7import {
8 packEnvValueKeepRedacted,
9 unpackEnvValue,
10 type BaseRuntimeContext,
11} from "../RuntimeContext.ts";
12import {
13 FunctionEnvironment,
14 type FunctionRequest,
15} from "./FunctionEnvironment.ts";
16
17export 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
30export 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};