Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .worklogs/peterp/2025-11-11-vercel-fluid-transition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Vercel Fluid request/response migration

## Attempt 1: Lambda signature adapter

- Wrapped lambda handlers with `webRequestToLambdaEvent` and `lambdaResponseToWebResponse` bridge.
- Hooked the wrapper into the esbuild pipeline when `deploy.target` was `vercel`.
- Confirmed approach still relied on translating back to AWS Lambda semantics, which blocks access to Fluid-specific features like streaming responses.

## Decision point

- Fluid compute requires user handlers to expose the Request/Response surface natively, per Vercel documentation ([Fluid Compute docs](https://vercel.com/docs/fluid-compute)).
- Builder-level translation preserves legacy API but prevents adoption of streaming APIs defined in the [Build Output API primitives for Node.js runtimes](https://vercel.com/docs/build-output-api/primitives#node.js-config).

## Current direction

- Ship an opt-in package variant that expects handlers to export web-standard `Request`/`Response` entrypoints (e.g. `export const GET`).
- Deprecate the AWS Lambda signature for projects that install the Fluid package.
- Update generators, type definitions, and docs to steer users toward the new interface.

## Implementation (Completed)

### Configuration

- Added TypeScript types for Vercel Fluid configuration in `packages/project-config/src/config.ts`
- Added `DeployConfig` and `VercelDeployConfig` interfaces
- `isVercelFluidDeploy()` function gates behavior based on `deploy.vercel.fluid` flag in `redwood.toml`

### @redwoodjs/api/fluid Package

Created new entry point at `packages/api/src/fluid.ts`:

- `FluidHandler` type: `(request: Request, context?: FluidContext) => Response | Promise<Response>`
- `FluidContext` interface with `waitUntil` and `requestId` properties
- Helper utilities: `json()`, `parseBody()`, `getQueryParams()`, `createFluidResponse()`
- Support for named HTTP method exports: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`
- Added export to `package.json`

### Build Pipeline

Modified `packages/internal/src/build/vercel.ts`:

- Added `validateFluidHandler()` function that detects legacy Lambda handlers
- Fails build with clear error message pointing to migration guide
- Detects HTTP method exports (`GET`, `POST`, etc.) and default exports

Updated `packages/internal/src/build/api.ts`:

- Calls `validateFluidHandler()` when Fluid mode is enabled
- Uses `transformAsync` directly to handle pre-validated code

### Dev Server

Created `packages/api-server/src/requestHandlers/fluidFastify.ts`:

- Converts Fastify requests to web-standard `Request` objects
- Invokes Fluid handlers with `Request`/`Response` signature
- Provides mock `FluidContext` for local development

Modified `packages/api-server/src/plugins/lambdaLoader.ts`:

- Added `FLUID_FUNCTIONS` registry for Fluid handlers
- Added `isFluidMode()` detection function
- Modified `setLambdaFunctions()` to load HTTP method exports when in Fluid mode
- Updated `lambdaRequestHandler()` to route to appropriate handler based on mode
- Supports method-specific routing with 405 responses for unsupported methods

### Documentation

Created `docs/docs/vercel-fluid-migration.md`:

- Configuration instructions
- Before/after code examples
- HTTP method handler patterns
- Helper utility usage
- Streaming response examples
- Background task examples with `waitUntil`
- Migration checklist
- Common patterns and limitations
41 changes: 41 additions & 0 deletions docs/architecture/2025-11-11-vercel-fluid-request-response.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Vercel Fluid Request/Response Handlers

## Context

- Vercel Fluid compute requires web-standard `Request`/`Response` handlers to unlock streaming, `waitUntil`, and shared concurrency features ([Fluid Compute docs](https://vercel.com/docs/fluid-compute)).
- The Build Output API for Node.js documents the framework-level entrypoints (`export const GET`, `POST`, etc.) that Vercel expects for routing ([Build Output API primitives](https://vercel.com/docs/build-output-api/primitives#node.js-config)).
- Redwood API functions currently expose the AWS Lambda `(event, context)` signature, which is incompatible with Fluid-only capabilities.

## Goals

- Provide an opt-in package variant that requires Redwood API functions to export web-standard handlers and drops the Lambda adapter.
- Ensure generated scaffold code, TypeScript types, and CLI validation enforce `Request`/`Response` usage when the Fluid package is installed.
- Maintain backwards compatibility for existing deployments that stay on the Lambda-compatible package.

## Non-goals

- Do not migrate non-Vercel providers to the new surface in this iteration.
- Do not remove legacy Lambda support from the default package.

## High-level design

1. Publish a new entry point (e.g. `@redwoodjs/api/fluid`) that exports a base handler signature `(request: Request, context?: FluidContext) => Response | Promise<Response>` and helper utilities.
2. Update the API build pipeline to fail fast if a function exports `handler` with Lambda parameters while Fluid mode is enabled; require named exports like `GET`, `POST`, or a default `handle` compatible with Vercel's routing.
3. Adjust CLI generators (`yarn rw g function`) to emit Request/Response templates when the Fluid package is detected.
4. Extend dev server and test harness to call functions using the new signature so local development matches production.
5. Provide codemods or lint rules to help migrate existing functions to the new API when opting in.

## Open questions

- How should multi-method functions (`GET`, `POST`, etc.) map onto Redwood's router and auth hooks?
- What fallback context (if any) needs to be supplied to mimic `event.requestContext` data previously available in Lambda?
- Do background tasks like `waitUntil` require additional runtime plumbing inside the dev server?

## Tasks

1. Detect Fluid mode via package flag or `redwood.toml` config and gate behavior across CLI, builder, and dev server.
2. Implement runtime adapters in `@redwoodjs/api/fluid` that expose helper APIs (auth decoding, logger, db access) over `Request`/`Response`.
3. Update API function generator templates and associated tests to emit new signatures.
4. Enforce signature validation in the build step with clear diagnostics when legacy `handler(event, context)` exports are found.
5. Modify dev server (`@redwoodjs/api-server`) to invoke functions through the web-standard signature.
6. Refresh documentation and migration guides covering Fluid opt-in, generator changes, and manual migration steps.
209 changes: 209 additions & 0 deletions docs/docs/vercel-fluid-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Migrating to Vercel Fluid Request/Response Handlers

## Overview

Vercel Fluid compute requires API functions to use web-standard `Request`/`Response` handlers instead of AWS Lambda `(event, context)` signatures. This unlocks streaming responses, `waitUntil`, and shared concurrency features.

## Configuration

Enable Fluid mode in `redwood.toml`:

```toml
[deploy]
target = "vercel"

[deploy.vercel]
fluid = true
```

## Handler Migration

### Before (Lambda signature)

```typescript
import type { APIGatewayEvent, Context } from 'aws-lambda'

export const handler = async (event: APIGatewayEvent, context: Context) => {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ data: 'example' }),
}
}
```

### After (Request/Response signature)

```typescript
import { json } from '@redwoodjs/api/fluid'

export const GET = async (request: Request) => {
return json({ data: 'example' })
}
```

## HTTP Method Handlers

Fluid functions support named HTTP method exports:

```typescript
import { json, parseBody } from '@redwoodjs/api/fluid'

export const GET = async (request: Request) => {
return json({ message: 'GET request' })
}

export const POST = async (request: Request) => {
const body = await parseBody(request)
return json({ received: body })
}

export const PUT = async (request: Request) => {
return json({ message: 'PUT request' })
}
```

Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `HEAD`

## Default Handler

Functions can export a default handler that handles all methods:

```typescript
export default async (request: Request) => {
return new Response(JSON.stringify({ method: request.method }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
})
}
```

## Helper Utilities

### JSON Response

```typescript
import { json } from '@redwoodjs/api/fluid'

export const GET = async (request: Request) => {
return json({ data: 'value' }, { status: 200 })
}
```

### Parse Request Body

```typescript
import { parseBody } from '@redwoodjs/api/fluid'

export const POST = async (request: Request) => {
const body = await parseBody(request)
return json({ received: body })
}
```

### Query Parameters

```typescript
import { getQueryParams, json } from '@redwoodjs/api/fluid'

export const GET = async (request: Request) => {
const params = getQueryParams(request)
return json({ params })
}
```

## Streaming Responses

```typescript
export const GET = async (request: Request) => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue('chunk 1\n')
controller.enqueue('chunk 2\n')
controller.close()
},
})

return new Response(stream, {
headers: { 'Content-Type': 'text/plain' },
})
}
```

## Background Tasks with waitUntil

```typescript
import type { FluidContext } from '@redwoodjs/api/fluid'
import { json } from '@redwoodjs/api/fluid'

export const POST = async (request: Request, context?: FluidContext) => {
context?.waitUntil?.(
fetch('https://api.example.com/log', {
method: 'POST',
body: JSON.stringify({ event: 'processed' }),
})
)

return json({ status: 'accepted' })
}
```

## Migration Checklist

- [ ] Enable Fluid mode in `redwood.toml`
- [ ] Replace Lambda `handler` exports with HTTP method exports (`GET`, `POST`, etc.)
- [ ] Convert `APIGatewayEvent` parameter access to `Request` API
- [ ] Convert `APIGatewayProxyResult` returns to `Response` objects
- [ ] Replace `event.queryStringParameters` with `getQueryParams(request)`
- [ ] Replace `JSON.parse(event.body)` with `parseBody(request)`
- [ ] Test functions locally with `yarn rw dev`
- [ ] Deploy to Vercel

## Common Patterns

### Accessing Headers

```typescript
export const GET = async (request: Request) => {
const authHeader = request.headers.get('authorization')
return json({ authenticated: !!authHeader })
}
```

### Setting Cookies

```typescript
export const POST = async (request: Request) => {
return new Response(JSON.stringify({ success: true }), {
headers: {
'Content-Type': 'application/json',
'Set-Cookie': 'session=abc123; Path=/; HttpOnly',
},
})
}
```

### Error Handling

```typescript
export const GET = async (request: Request) => {
try {
const data = await fetchData()
return json(data)
} catch (error) {
return json({ error: error.message }, { status: 500 })
}
}
```

## Limitations

- Fluid mode only applies when deploying to Vercel
- Legacy Lambda signature is not supported in Fluid mode
- Build fails with validation error if `handler` export is detected

## Support

For questions or issues, see the [Vercel Fluid documentation](https://vercel.com/docs/fluid-compute).
Loading
Loading