1/**
2 * `@alchemy.run/frontend-frameworks/solidstart/aws` — the AWS Lambda deploy
3 * target for the SolidStart integration.
4 *
5 * SolidStart's server half is nitro (`@solidjs/vite-plugin-nitro-2`), so its
6 * AWS story is nitro's `aws-lambda` preset: `.output/server` is a
7 * self-contained Node ESM deployment unit whose `index.mjs` exports a Lambda
8 * `handler`, and `.output/public` holds the client assets (prerendered pages
9 * included) for the CDN. What this target owns:
10 *
11 * - **`nitroPreset`** — `"aws-lambda"`, enforced as the last word on the
12 * nitro config the integration hands to the appended nitro plugin.
13 * - **`configureNitro`** — enables response streaming by default
14 * (`awsLambda.streaming: true`): the emitted handler wraps
15 * `awslambda.streamifyResponse` and expects a Lambda Function URL with
16 * `invokeMode: RESPONSE_STREAM`. Set `streaming: false` on the target
17 * config for the buffered APIGW-style handler.
18 * - **`bundle`** — Node resolve conditions and the `@aws-sdk/` externals
19 * (the Lambda Node.js runtime ships SDK v3) for callers that post-process
20 * server code.
21 *
22 * No `finish`: nitro's aws-lambda output already ships its own
23 * `package.json` (`type: "module"`) and copied externals, so the server
24 * directory deploys as-is.
25 *
26 * No `devPlatform` seam: `dev` runs SolidStart's own Vite dev server, which
27 * is already the AWS Lambda programming model (plain Node).
28 */
29import * as Effect from "effect/Effect";
30import { runBuildChild } from "../core/BuildChild.ts";
31import { DeployTargetError, makeDeployTarget } from "../core/index.ts";
32import {
33 make,
34 type SolidStartTarget,
35 type SolidStartTargetConfig,
36} from "./SolidStart.ts";
37
38/** The nitro deployment preset this target builds with. */
39export const NITRO_PRESET = "aws-lambda";
40
41/**
42 * The importable specifier of nitro's aws-lambda streaming runtime handler —
43 * the module a custom entry would re-export to wrap the framework's handler.
44 */
45export const NITRO_HANDLER_SPECIFIER =
46 "nitropack/presets/aws-lambda/runtime/aws-lambda-streaming";
47
48/** AWS-specific knobs carried on the shared {@link SolidStartTargetConfig}. */
49export interface SolidStartAwsTargetConfig extends SolidStartTargetConfig {
50 /**
51 * Whether the emitted Lambda handler streams its response
52 * (`awslambda.streamifyResponse`, requires a Function URL with
53 * `invokeMode: RESPONSE_STREAM`). The buffered handler answers
54 * APIGW/Function-URL events with a complete response object.
55 * @default true
56 */
57 readonly streaming?: boolean | undefined;
58}
59
60const fail = (message: string, cause?: unknown) =>
61 new DeployTargetError({ platform: "aws", message, cause });
62
63/**
64 * The adapter-driven target — the shape the framework's regular vite/nitro
65 * build pipeline consumes. Used directly in the build child (where
66 * `cwd === root` holds); {@link makeAwsTarget} wraps it with the wholesale
67 * `build` hook that spawns the child.
68 */
69const makeAwsAdapterTarget = (
70 config: SolidStartAwsTargetConfig = {},
71): SolidStartTarget =>
72 makeDeployTarget({
73 platform: "aws",
74 config,
75 bundle: {
76 conditions: ["node", "import", "module"],
77 external: ["@aws-sdk/"],
78 },
79 nitroPreset: NITRO_PRESET,
80 configureNitro: (nitroConfig) => {
81 const awsLambda =
82 nitroConfig.awsLambda !== null &&
83 typeof nitroConfig.awsLambda === "object"
84 ? (nitroConfig.awsLambda as Record<string, unknown>)
85 : {};
86 nitroConfig.awsLambda = {
87 ...awsLambda,
88 streaming: config.streaming ?? true,
89 };
90 },
91 });
92
93/**
94 * A SolidStart build loads and executes the project's `vite.config.*` (its
95 * `solidStart()` plugin scans the route directory from `process.cwd()`, and
96 * user plugins may mutate `process.env` or `process.chdir`) — none of which
97 * may touch the engine process, where many deploys share one event loop.
98 * This target's wholesale `build` therefore runs the framework in a
99 * disposable child process whose working directory IS the project root (see
100 * `core/BuildChild.ts`). The shared `core/BuildChildRunner` entry imports
101 * this module in the child and calls the exported {@link buildInChild}.
102 */
103export interface SolidStartAwsBuildChildConfig {
104 readonly rootDir: string;
105 /** The (JSON-serializable) target config the parent was created with. */
106 readonly config: SolidStartAwsTargetConfig;
107}
108
109export const buildInChild = (config: SolidStartAwsBuildChildConfig) =>
110 Effect.gen(function* () {
111 const framework = yield* make({
112 root: config.rootDir,
113 // The adapter-only target: no wholesale `build` hook, so the child
114 // runs the regular vite/nitro build pipeline (no recursion).
115 target: makeAwsAdapterTarget(config.config),
116 nitro: config.config.nitro,
117 });
118 return yield* framework.build({ root: config.rootDir });
119 });
120
121/**
122 * Create the AWS Lambda {@link SolidStartTarget}. See the module doc for the
123 * seams.
124 */
125export const makeAwsTarget = (
126 config: SolidStartAwsTargetConfig = {},
127): SolidStartTarget => ({
128 ...makeAwsAdapterTarget(config),
129 build: (context) =>
130 runBuildChild({
131 module: import.meta.url,
132 rootDir: context.root,
133 framework: "solidstart",
134 config: {
135 rootDir: context.root,
136 config,
137 } satisfies SolidStartAwsBuildChildConfig,
138 }).pipe(Effect.mapError((error) => fail(error.message, error.cause))),
139});
140
141/**
142 * The deploy-target module contract (`resolveDeployTarget` accepts the
143 * default export — or the named `target` export — as a value or factory).
144 */
145export const target = makeAwsTarget;
146
147export default makeAwsTarget;