diff --git a/.changeset/cyan-numbers-change.md b/.changeset/cyan-numbers-change.md new file mode 100644 index 000000000000..ba9d38fdde0e --- /dev/null +++ b/.changeset/cyan-numbers-change.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/adapter-cloudflare': patch +--- + +Pass `env` object to SvelteKit via `platform` diff --git a/.changeset/many-mayflies-clap.md b/.changeset/many-mayflies-clap.md new file mode 100644 index 000000000000..82532212ad34 --- /dev/null +++ b/.changeset/many-mayflies-clap.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +Allow adapters to pass in `platform` object diff --git a/documentation/docs/01-routing.md b/documentation/docs/01-routing.md index d4c64ef237e9..344b71c2e323 100644 --- a/documentation/docs/01-routing.md +++ b/documentation/docs/01-routing.md @@ -51,11 +51,12 @@ Endpoints are modules written in `.js` (or `.ts`) files that export functions co // Declaration types for Endpoints // * declarations that are not exported are for internal use -export interface RequestEvent> { +export interface RequestEvent, Platform = Record> { request: Request; url: URL; params: Record; locals: Locals; + platform: Platform; } type Body = JSONString | Uint8Array | ReadableStream | stream.Readable; @@ -69,8 +70,13 @@ type MaybePromise = T | Promise; interface Fallthrough { fallthrough: true; } -export interface RequestHandler, Output extends Body = Body> { - (event: RequestEvent): MaybePromise< + +export interface RequestHandler< + Locals = Record, + Platform = Record, + Output extends Body = Body +> { + (event: RequestEvent): MaybePromise< Either, Fallthrough> >; } diff --git a/documentation/docs/04-hooks.md b/documentation/docs/04-hooks.md index e48b45be91f1..f3378de7d5fc 100644 --- a/documentation/docs/04-hooks.md +++ b/documentation/docs/04-hooks.md @@ -22,21 +22,22 @@ If unimplemented, defaults to `({ event, resolve }) => resolve(event)`. // everything else must be a type of string type ResponseHeaders = Record; -export interface RequestEvent> { +export interface RequestEvent, Platform = Record> { request: Request; url: URL; params: Record; locals: Locals; + platform: Platform; } export interface ResolveOpts { ssr?: boolean; } -export interface Handle> { +export interface Handle, Platform = Record> { (input: { - event: RequestEvent; - resolve(event: RequestEvent, opts?: ResolveOpts): MaybePromise; + event: RequestEvent; + resolve(event: RequestEvent, opts?: ResolveOpts): MaybePromise; }): MaybePromise; } ``` @@ -84,8 +85,8 @@ If unimplemented, SvelteKit will log the error with default formatting. ```ts // Declaration types for handleError hook -export interface HandleError> { - (input: { error: Error & { frame?: string }; event: RequestEvent }): void; +export interface HandleError, Platform = Record> { + (input: { error: Error & { frame?: string }; event: RequestEvent }): void; } ``` @@ -107,8 +108,12 @@ If unimplemented, session is `{}`. ```ts // Declaration types for getSession hook -export interface GetSession, Session = any> { - (event: RequestEvent): Session | Promise; +export interface GetSession< + Locals = Record, + Platform = Record, + Session = any +> { + (event: RequestEvent): MaybePromise; } ``` diff --git a/documentation/docs/10-adapters.md b/documentation/docs/10-adapters.md index ce2194e50222..b54832970d92 100644 --- a/documentation/docs/10-adapters.md +++ b/documentation/docs/10-adapters.md @@ -54,6 +54,10 @@ Most adapters will generate static HTML for any [prerenderable](#page-options-pr You can also use `adapter-static` to generate single-page apps (SPAs) by specifying a [fallback page](https://github.com/sveltejs/kit/tree/master/packages/adapter-static#spa-mode). +#### Platform-specific context + +Some adapters may have access to additional information about the request. For example, Cloudflare Workers can access an `env` object containing KV namespaces etc. This can be passed to the `RequestEvent` used in [hooks](#hooks) and [endpoints](#routing-endpoints) as the `platform` property — consult each adapter's documentation to learn more. + ### Community adapters Additional [community-provided adapters](https://sveltesociety.dev/components#adapters) exist for other platforms. After installing the relevant adapter with your package manager, update your `svelte.config.js`: @@ -93,6 +97,7 @@ Within the `adapt` method, there are a number of things that an adapter should d - Imports `App` from `${builder.getServerDirectory()}/app.js` - Instantiates the app with a manifest generated with `builder.generateManifest({ relativePath })` - Listens for requests from the platform, converts them to a standard [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) if necessary, calls the `render` function to generate a [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) and responds with it + - expose any platform-specific information to SvelteKit via the `platform` option passed to `app.render` - Globally shims `fetch` to work on the target platform, if necessary. SvelteKit provides a `@sveltejs/kit/install-fetch` helper for platforms that can use `node-fetch` - Bundle the output to avoid needing to install dependencies on the target platform, if necessary - Put the user's static files and the generated JS/CSS in the correct location for the target platform diff --git a/packages/adapter-cloudflare/README.md b/packages/adapter-cloudflare/README.md index 90cb9f6ab073..1a73dbf3ea47 100644 --- a/packages/adapter-cloudflare/README.md +++ b/packages/adapter-cloudflare/README.md @@ -50,6 +50,26 @@ When configuring your project settings, you must use the following settings: > **Important:** You need to add a `NODE_VERSION` environment variable to both the "production" and "preview" environments. You can add this during project setup or later in the Pages project settings. SvelteKit requires Node `14.13` or later, so you should use `14` or `16` as the `NODE_VERSION` value. +## Environment variables + +The [`env`](https://developers.cloudflare.com/workers/runtime-apis/fetch-event#parameters) object, containing KV namespaces etc, is passed to SvelteKit via the `platform` property, meaning you can access it in hooks and endpoints: + +```ts +interface Locals {} + +interface Platform { + env: { + COUNTER: DurableObjectNamespace; + }; +} + +export async function post({ request, platform }) { + const counter = platform.env.COUNTER.idFromName('A'); +} +``` + +> `platform.env` is only available in the production build. Use [wrangler](https://developers.cloudflare.com/workers/cli-wrangler) to test it locally + ## Changelog [The Changelog for this package is available on GitHub](https://github.com/sveltejs/kit/blob/master/packages/adapter-cloudflare/CHANGELOG.md). diff --git a/packages/adapter-cloudflare/files/worker.js b/packages/adapter-cloudflare/files/worker.js index 9829bb501ffb..7a2b067a3d2e 100644 --- a/packages/adapter-cloudflare/files/worker.js +++ b/packages/adapter-cloudflare/files/worker.js @@ -45,7 +45,7 @@ export default { // dynamically-generated pages try { - return await app.render(req); + return await app.render(req, { platform: { env } }); } catch (e) { return new Response('Error rendering route: ' + (e.message || e.toString()), { status: 500 }); } diff --git a/packages/kit/src/core/build/build_server.js b/packages/kit/src/core/build/build_server.js index 5f0954e49115..be3423eb700a 100644 --- a/packages/kit/src/core/build/build_server.js +++ b/packages/kit/src/core/build/build_server.js @@ -96,14 +96,12 @@ export class App { }; } - render(request, { - prerender - } = {}) { + render(request, options = {}) { if (!(request instanceof Request)) { throw new Error('The first argument to app.render must be a Request object. See https://github.com/sveltejs/kit/pull/3384 for details'); } - return respond(request, this.options, { prerender }); + return respond(request, this.options, options); } } `; @@ -172,7 +170,6 @@ export async function build_server( return relative_file[0] === '.' ? relative_file : `./${relative_file}`; }; - // prettier-ignore fs.writeFileSync( input.app, app_template({ diff --git a/packages/kit/src/runtime/server/index.js b/packages/kit/src/runtime/server/index.js index e74dc732d8bf..3f1edbd16583 100644 --- a/packages/kit/src/runtime/server/index.js +++ b/packages/kit/src/runtime/server/index.js @@ -60,7 +60,8 @@ export async function respond(request, options, state = {}) { request, url, params: {}, - locals: {} + locals: {}, + platform: state.platform }; // TODO remove this for 1.0 diff --git a/packages/kit/types/app.d.ts b/packages/kit/types/app.d.ts index 818b89eee68d..ddfa1dd94c1b 100644 --- a/packages/kit/types/app.d.ts +++ b/packages/kit/types/app.d.ts @@ -1,15 +1,19 @@ import { PrerenderOptions, SSRNodeLoader, SSRRoute } from './internal'; +export interface RequestOptions> { + platform?: Platform; +} + export class App { constructor(manifest: SSRManifest); - render(request: Request): Promise; + render(request: Request, options?: RequestOptions): Promise; } export class InternalApp extends App { render( request: Request, - options?: { - prerender: PrerenderOptions; + options?: RequestOptions & { + prerender?: PrerenderOptions; } ): Promise; } diff --git a/packages/kit/types/endpoint.d.ts b/packages/kit/types/endpoint.d.ts index b1cc06516ec5..d48099433d00 100644 --- a/packages/kit/types/endpoint.d.ts +++ b/packages/kit/types/endpoint.d.ts @@ -13,8 +13,12 @@ export interface Fallthrough { fallthrough: true; } -export interface RequestHandler, Output extends Body = Body> { - (event: RequestEvent): MaybePromise< +export interface RequestHandler< + Locals = Record, + Platform = Record, + Output extends Body = Body +> { + (event: RequestEvent): MaybePromise< Either, Fallthrough> >; } diff --git a/packages/kit/types/hooks.d.ts b/packages/kit/types/hooks.d.ts index 0fe4373e561a..afaca61969c3 100644 --- a/packages/kit/types/hooks.d.ts +++ b/packages/kit/types/hooks.d.ts @@ -2,25 +2,30 @@ import { MaybePromise } from './helper'; export type StrictBody = string | Uint8Array; -export interface RequestEvent> { +export interface RequestEvent, Platform = Record> { request: Request; url: URL; params: Record; locals: Locals; + platform: Readonly; } -export interface GetSession, Session = any> { - (event: RequestEvent): MaybePromise; +export interface GetSession< + Locals = Record, + Platform = Record, + Session = any +> { + (event: RequestEvent): MaybePromise; } export interface ResolveOpts { ssr?: boolean; } -export interface Handle> { +export interface Handle, Platform = Record> { (input: { - event: RequestEvent; - resolve(event: RequestEvent, opts?: ResolveOpts): MaybePromise; + event: RequestEvent; + resolve(event: RequestEvent, opts?: ResolveOpts): MaybePromise; }): MaybePromise; } diff --git a/packages/kit/types/internal.d.ts b/packages/kit/types/internal.d.ts index 4d5a248a8596..8fa01b89a964 100644 --- a/packages/kit/types/internal.d.ts +++ b/packages/kit/types/internal.d.ts @@ -159,6 +159,7 @@ export interface SSRRenderOptions { export interface SSRRenderState { fetched?: string; initiator?: SSRPage | null; + platform?: any; prerender?: PrerenderOptions; fallback?: string; }