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
5 changes: 5 additions & 0 deletions .changeset/slimy-tips-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/adapter-vercel': minor
---

feat: expose `waitUntil` also for serverless runtime
6 changes: 6 additions & 0 deletions documentation/docs/25-build-and-deploy/90-adapter-vercel.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ Cookie-based skew protection comes with one caveat: if a user has multiple versi

## Notes

### Platform-specific context

The [`event.platform`](https://svelte.dev/docs/kit/adapters#Platform-specific-context) property is available in server load functions and endpoints and exposes the [`waitUntil`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package#waituntil) function.

Use the package [`@vercel/functions`](https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package) for more utilities.

### Vercel functions

If you have Vercel functions contained in the `api` directory at the project's root, any requests for `/api/*` will _not_ be handled by SvelteKit. You should implement these as [API routes](routing#server) in your SvelteKit app instead, unless you need to use a non-JavaScript language in which case you will need to ensure that you don't have any `/api/*` routes in your SvelteKit app.
Expand Down
45 changes: 43 additions & 2 deletions packages/adapter-vercel/ambient.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,53 @@
import { RequestContext } from './index.js';
import type { waitUntil } from '@vercel/functions';

declare global {
namespace App {
export interface Platform {
/**
* `context` is only available in Edge Functions
*
* @deprecated Vercel's context is deprecated. Use top-level properties instead.
*/
context?: RequestContext;
context?: {
/**
* `context` is only available in Edge Functions
*
* @deprecated Use `event.platform.waitUntil` instead.
*/
waitUntil: typeof waitUntil;
};

/**
* A method that can be used to keep the function running after a response has been sent.
*
* This is useful when you have an async task that you want to keep running even after the
* response has been sent and the request has ended.
*
* The maximum duration depends on your plan and settings (see [here](https://vercel.com/docs/functions/limitations)).
*
* See https://vercel.com/docs/functions/functions-api-reference/vercel-functions-package#waituntil.
*
* @example
*
* <caption>Perform a long-running task in the background without blocking the sending of the response</caption>
*
* ```ts
* // src/routes/+page.server.ts
*
* export const load = async (event) => {
* event.platform.waitUntil(longRunningTask())
*
* return {
* // ...some data
* }
* }
*
* async function longRunningTask() {
* // ...
* }
* ```
*/
waitUntil: typeof waitUntil;
}
}
}
9 changes: 6 additions & 3 deletions packages/adapter-vercel/files/edge.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Vercel Edge Runtime does not support node:process */
import { Server } from 'SERVER';
import { manifest } from 'MANIFEST';
import { waitUntil } from '@vercel/functions';

const server = new Server(manifest);

Expand Down Expand Up @@ -49,9 +50,8 @@ const initialized = server.init({

/**
* @param {Request} request
* @param {import('../index.js').RequestContext} context
*/
export default async (request, context) => {
export default async (request) => {
if (!origin) {
origin = new URL(request.url).origin;
await initialized;
Expand All @@ -62,7 +62,10 @@ export default async (request, context) => {
return /** @type {string} */ (request.headers.get('x-forwarded-for'));
},
platform: {
context
context: {
waitUntil
},
waitUntil
}
});
};
7 changes: 7 additions & 0 deletions packages/adapter-vercel/files/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createReadableStream } from '@sveltejs/kit/node';
import { Server } from 'SERVER';
import { manifest } from 'MANIFEST';
import process from 'node:process';
import { waitUntil } from '@vercel/functions';

installPolyfills();

Expand Down Expand Up @@ -39,6 +40,12 @@ export default {
return server.respond(request, {
getClientAddress() {
return /** @type {string} */ (request.headers.get('x-forwarded-for'));
},
platform: {
context: {
waitUntil
},
waitUntil
}
});
}
Expand Down
56 changes: 0 additions & 56 deletions packages/adapter-vercel/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,59 +103,3 @@ export type Config = (EdgeConfig | ServerlessConfig) & {
*/
images?: ImagesConfig;
};

// we copy the RequestContext interface from `@vercel/edge` because that package can't co-exist with `@types/node`.
// see https://github.com/sveltejs/kit/pull/9280#issuecomment-1452110035

/**
* An extension to the standard `Request` object that is passed to every Edge Function.
*
* @example
* ```ts
* import type { RequestContext } from '@vercel/edge';
*
* export default async function handler(request: Request, ctx: RequestContext): Promise<Response> {
* // ctx is the RequestContext
* }
* ```
*/
export interface RequestContext {
/**
* A method that can be used to keep the function running after a response has been sent.
* This is useful when you have an async task that you want to keep running even after the
* response has been sent and the request has ended.
*
* @example
*
* <caption>Sending an internal error to an error tracking service</caption>
*
* ```ts
* import type { RequestContext } from '@vercel/edge';
*
* export async function handleRequest(request: Request, ctx: RequestContext): Promise<Response> {
* try {
* return await myFunctionThatReturnsResponse();
* } catch (e) {
* ctx.waitUntil((async () => {
* // report this error to your error tracking service
* await fetch('https://my-error-tracking-service.com', {
* method: 'POST',
* body: JSON.stringify({
* stack: e.stack,
* message: e.message,
* name: e.name,
* url: request.url,
* }),
* });
* })());
* return new Response('Internal Server Error', { status: 500 });
* }
* }
* ```
*/
waitUntil(
/**
* A promise that will be kept alive until it resolves or rejects.
*/ promise: Promise<unknown>
): void;
}
1 change: 1 addition & 0 deletions packages/adapter-vercel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"test": "vitest run"
},
"dependencies": {
"@vercel/functions": "^3.1.3",
"@vercel/nft": "^0.30.0",
"esbuild": "^0.25.4"
},
Expand Down
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading