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