-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(node-utils): introduces @edge-runtime/node-utils to run edge com…
…pliant code in Node.js environment (#219) chore(node-utils): introduces edge-related utilities chore(primitives): improves `fetch` type chore: only considers src/ folder when running coverage with jest chore(ponyfill): ignores types for fetch parameters doc(node-utils): adds README and documentation page Co-authored-by: Javi Velasco <javier.velasco86@gmail.com> Co-authored-by: Kiko Beats <josefrancisco.verdu@gmail.com>
- Loading branch information
1 parent
f80789b
commit eef6b34
Showing
29 changed files
with
1,535 additions
and
203 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@edge-runtime/node-utils': patch | ||
'@edge-runtime/primitives': patch | ||
--- | ||
|
||
Introducing @edge-runtime/node-utils to run edge-compliant code in Node.js environment |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
import { Callout } from 'nextra-theme-docs' | ||
import { Tabs, Tab } from '../../components/tabs' | ||
|
||
# Edge Runtime Node Utils | ||
|
||
The **@edge-runtime/node-utils** package contains utilities to run web compliant code into a Node.js environment. | ||
|
||
<Callout type='warning' emoji='⚠️'> | ||
Please note this is an alpha version. | ||
</Callout> | ||
|
||
## Installation | ||
|
||
<Tabs items={['npm', 'yarn', 'pnpm']} storageKey="selected-pkg-manager"> | ||
<Tab> | ||
```sh | ||
npm install @edge-runtime/node-utils --save | ||
``` | ||
</Tab> | ||
<Tab> | ||
```sh | ||
yarn add @edge-runtime/node-utils | ||
``` | ||
</Tab> | ||
<Tab> | ||
```sh | ||
pnpm install @edge-runtime/node-utils --save | ||
``` | ||
</Tab> | ||
</Tabs> | ||
|
||
This package includes built-in TypeScript support. | ||
|
||
## Usage | ||
|
||
```ts | ||
import { once } = from 'node:events' | ||
import { createServer } from 'node:http' | ||
import { buildToNodeHandler } from '@edge-runtime/node-utils' | ||
|
||
// 1. builds a transformer, using Node.js@18 globals, and a base url for URL constructor. | ||
const transformToNode = buildToNodeHandler(global, { | ||
origin: 'http://example.com', | ||
}) | ||
|
||
const server = await createServer( | ||
// 2. takes an web compliant request handler, that uses Web globals like Request and Response, | ||
// and turn it into a Node.js compliant request handler. | ||
transformToNode(async (req: Request) => new Response(req.body)) | ||
) | ||
|
||
// 3. start the node.js server | ||
server.listen() | ||
await once(server, 'listening') | ||
|
||
// 4. invoke the request handler | ||
const response = await fetch( | ||
`http://localhost:${(server.address() as AddressInfo).port}`, | ||
{ method: 'POST', body: 'hello world' } | ||
) | ||
|
||
console.log(await response.text()) // is 'hello world' | ||
await server.close() | ||
``` | ||
|
||
## API | ||
|
||
### buildToNodeHandler(dependencies, options): toNodeHandler(handler: WebHandler): NodeHandler | ||
|
||
Builds a transformer function to turn an web compliant request handler (`(req: Request) => Promise<Response> | Response | null | undefined`) into | ||
a Node.js compliant [request handler](https://nodejs.org/api/http.html#httpcreateserveroptions-requestlistener) (`(req: IncomingMessage, res: ServerResponse) => Promise<void> | void`). | ||
|
||
**Limitation:** it does support the web handler second parameter, so `waitUntil` is not implemented yet. | ||
|
||
#### dependencies: object | ||
|
||
List of Web globals used by the transformer function: | ||
|
||
- [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) | ||
- [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) | ||
- [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) | ||
- [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | ||
|
||
When running in Node.js 18+ (or Node.js 16 with [--experimental-fetch](https://nodejs.org/docs/latest-v16.x/api/cli.html#node_optionsoptions)), you may pass the ones from `global` scope. | ||
|
||
```js | ||
import { buildToNodeHandler } from '@edge-runtime/node-utils' | ||
|
||
buildToNodeHandler(globals, { | ||
/* ... options */ | ||
}) | ||
``` | ||
|
||
Otherwise, you can reuse primitives from [@edge-runtime/primitives](/packages/primitives) | ||
|
||
```js | ||
import { buildToNodeHandler } from '@edge-runtime/node-utils' | ||
import * as primitives from '@edge-runtime/primitives' | ||
|
||
buildToNodeHandler(primitives, { | ||
/* ... options */ | ||
}) | ||
``` | ||
|
||
#### options: object | ||
|
||
Options used to build the transformer function, including: | ||
|
||
##### origin: string | ||
|
||
Origin used to turn the incoming request relative url into a full [Request.url](https://developer.mozilla.org/en-US/docs/Web/API/Request/url) string. | ||
|
||
### buildToRequest(dependencies, options): toRequest(request: IncomingMessage, options: object): Request | ||
|
||
Builds a transformer function to turn a Node.js [IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage) into a Web [Request]. | ||
It needs globals Web contstructor a [dependencies](#dependencies-object), as well as [options](#options-object) to handle incoming url. | ||
|
||
### toOutgoingHeaders(headers: Headers): OutgoingHttpHeaders | ||
|
||
Turns Web [Request.headers](https://developer.mozilla.org/en-US/docs/Web/API/Request/headers) into | ||
Node.js `ServerResponse` [OutgoingHttpHeaders](https://nodejs.org/api/http.html#responsegetheaders). | ||
|
||
Includes `set-cookie` special handling, spliting multiple values when relevant. | ||
|
||
### buildToHeaders(dependencies): toHeaders(nodeHeaders: IncomingHttpHeaders): Headers | ||
|
||
Builds a transformer to turn Node.js [IncomingHttpHeaders](https://nodejs.org/api/http.html#messageheaders) into | ||
Web [Request.headers](https://developer.mozilla.org/en-US/docs/Web/API/Request/headers). | ||
|
||
### toToReadable(webStream: ReadableStream, options: object): Readable | ||
|
||
Turns Web [ReadableStream](https://nodejs.org/api/stream.html#readable-streams) | ||
(typically, the [Response.body](https://developer.mozilla.org/en-US/docs/Web/API/Response/body)) into | ||
Node.js [Readable](https://nodejs.org/api/stream.html#readable-streams) stream. | ||
|
||
### buildToReadableStream(dependencies): toReadableStream(stream: Readable): ReadableStream | ||
|
||
Builds a transformer to turn Node.js [Readable](https://nodejs.org/api/stream.html#readable-streams) | ||
(typically, the [IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)'s payload) into | ||
Web [ReadableStream](https://nodejs.org/api/stream.html#readable-streams). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<div align="center"> | ||
<br> | ||
<img src="https://edge-runtime.vercel.app/og-image.png" alt="edge-runtime logo"> | ||
<br> | ||
<br> | ||
<p align="center"><strong>@edge-runtime/node-utils</strong>: Utilities to run web compliant code into a Node.js environment.</p> | ||
<p align="center">See <a href="https://edge-runtime.vercel.app/packages/node-utils" target='_blank' rel='noopener noreferrer'>@edge-runtime/node-utils</a> section in our <a href="https://edge-runtime.vercel.app/" target='_blank' rel='noopener noreferrer'>website</a> for more information.</p> | ||
<br> | ||
</div> | ||
|
||
## Install | ||
|
||
**Note: this is an alpha version.** | ||
|
||
Using npm: | ||
|
||
```sh | ||
npm install @edge-runtime/node-utils --save | ||
``` | ||
|
||
or using yarn: | ||
|
||
```sh | ||
yarn add @edge-runtime/node-utils --dev | ||
``` | ||
|
||
or using pnpm: | ||
|
||
```sh | ||
pnpm install @edge-runtime/node-utils --save | ||
``` | ||
|
||
## License | ||
|
||
**@edge-runtime/node-utils** © [Vercel](https://vercel.com), released under the [MPLv2](https://github.com/vercel/edge-runtime/blob/main/LICENSE.md) License.<br> | ||
Authored and maintained by [Vercel](https://vercel.com) with help from [contributors](https://github.com/vercel/edge-runtime/contributors). | ||
|
||
> [vercel.com](https://vercel.com) · GitHub [Vercel](https://github.com/vercel) · Twitter [@vercel](https://twitter.com/vercel) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import buildConfig from '../../jest.config' | ||
import type { Config } from '@jest/types' | ||
|
||
const config: Config.InitialOptions = buildConfig(__dirname) | ||
export default config |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "@edge-runtime/node-utils", | ||
"description": "Helpers for running edge-compliant code in Node.js environment", | ||
"homepage": "https://edge-runtime.vercel.app/packages/node-utils", | ||
"version": "1.0.0-alpha.1", | ||
"main": "dist/index.js", | ||
"module": "dist/index.mjs", | ||
"repository": { | ||
"directory": "packages/node-utils", | ||
"type": "git", | ||
"url": "git+https://github.com/vercel/edge-runtime.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/vercel/edge-runtime/issues" | ||
}, | ||
"keywords": [], | ||
"devDependencies": { | ||
"@edge-runtime/primitives": "workspace:2.0.2", | ||
"@types/test-listen": "1.1.0", | ||
"test-listen": "1.1.0", | ||
"tsup": "6" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "tsup", | ||
"clean:build": "rm -rf dist", | ||
"clean:node": "rm -rf node_modules", | ||
"prebuild": "pnpm run clean:build", | ||
"test": "jest" | ||
}, | ||
"license": "MPLv2", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"types": "dist/index.d.ts" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import type { IncomingMessage, ServerResponse } from 'node:http' | ||
import type { | ||
WebHandler, | ||
NodeHandler, | ||
BuildDependencies, | ||
RequestOptions, | ||
} from '../types' | ||
import { buildToRequest } from '../node-to-edge/request' | ||
import { mergeIntoServerResponse, toOutgoingHeaders } from './headers' | ||
import { toToReadable } from './stream' | ||
|
||
export function buildToNodeHandler( | ||
dependencies: BuildDependencies, | ||
options: RequestOptions | ||
) { | ||
const toRequest = buildToRequest(dependencies) | ||
return function toNodeHandler(webHandler: WebHandler): NodeHandler { | ||
return (request: IncomingMessage, response: ServerResponse) => { | ||
const maybePromise = webHandler(toRequest(request, options)) | ||
if (maybePromise instanceof Promise) { | ||
maybePromise.then((webResponse) => | ||
toServerResponse(webResponse, response) | ||
) | ||
} else { | ||
toServerResponse(maybePromise, response) | ||
} | ||
} | ||
} | ||
} | ||
|
||
function toServerResponse( | ||
webResponse: Response | null | undefined, | ||
serverResponse: ServerResponse | ||
) { | ||
if (!webResponse) { | ||
serverResponse.end() | ||
return | ||
} | ||
mergeIntoServerResponse( | ||
// @ts-ignore getAll() is not standard https://fetch.spec.whatwg.org/#headers-class | ||
toOutgoingHeaders(webResponse.headers), | ||
serverResponse | ||
) | ||
|
||
serverResponse.statusCode = webResponse.status | ||
serverResponse.statusMessage = webResponse.statusText | ||
if (!webResponse.body) { | ||
serverResponse.end() | ||
return | ||
} | ||
toToReadable(webResponse.body).pipe(serverResponse) | ||
} |
Oops, something went wrong.
eef6b34
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
edge-runtime – ./
edge-runtime.vercel.app
edge-runtime.vercel.sh
edge-runtime-git-main.vercel.sh