Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: integrate router with app and app.fetch #822

Merged
merged 11 commits into from
Jul 11, 2024
Merged
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
156 changes: 95 additions & 61 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,42 +12,64 @@ h3 v2 includes some behavior and API changes that you need to consider applying
> [!NOTE]
> This is an undergoing migration guide and is not finished yet.

## Fully decoupled from Node.js
## Web adapter

We started migrating h3 towards Web standards since [v1.8](https://unjs.io/blog/2023-08-15-h3-towards-the-edge-of-the-web). h3 apps are now fully decoupled from Node.js using an adapter-based abstraction layer to support Web and Node.js runtime features and performances natively.
H3 v2 is now web native and you can directly use `app.fetch(request, init?, { context })`.

This migration significantly reduces your bundle sizes and overhead in Web-native runtimes such as [Bun](https://bun.sh/), [Deno](https://deno.com) and [Cloudflare Workers](https://workers.cloudflare.com/).
Old utils for plain handler and web handler are removed to embrace web standards.

Since v2, Event properties `event.node.{req,res}` and `event.web` is not available anymore, instead, you can use `getNodeContext(event)` and `getWebContext(event)` to access raw objects for each runtime.
## Event interface

## Response handling
Event properties `event.node.{req,res}` and `event.web` are not available anymore, instead, you can use `getNodeContext(event)` and `getWebContext(event)` utils to access raw objects for each runtime.

`event.handler` property is removed since h3 relies on explicit responses.

You should always explicitly `return` or `throw` responses and errors from event handlers.
## Response handling

Previously h3 had `send*` utils that could interop the response handling lifecycle **anywhere** in any utility or middleware causing unpredictable application state control. To mitigate edge cases of this, previously h3 added `event.handler` property which is now gone!
You should always explicitly use `return` for response and `throw` for errors from event handlers.

If you were previously using these methods, you can replace them with `return` statements returning a text, JSON value, stream, or web `Response` (h3 smartly detects and handles them):
If you were previously using these methods, you can replace them with `return` statements returning a text, JSON, stream, or web `Response` (h3 smartly detects and handles them):

- `send(event, value)`: Use `return <value>`
- `sendNoContent(event)`: Use `return null`
- `sendError(event, error)`: Use `throw createError()`
- `sendStream(event, stream)`: Use `return stream`
- `sendWebResponse(event, response)`: Use `return response`
- `send(event, value)`: Migrate to `return <value>`.
- `sendError(event, <error>)`: Migrate to `throw createError(<error>)`.
- `sendStream(event, <stream>)`: Migrate to `return <stream>`.
- `sendWebResponse(event, <response>)`: Migrate to `return <response>`.

Other send utils that are renamed and need explicit `return`:

- `sendIterable(event, value)`: Use `return iterable()`
- `sendRedirect(event, location, code)`: Use `return redirect(event, location, code)`
- `sendProxy(event, target)`: Use `return proxy(event, target)`
- `sendNoContent(event)` / `return null`: Migrate to `return noContent(event)`.
- `sendIterable(event, <value>)`: Migrate to `return iterable(<value>)`.
- `sendRedirect(event, location, code)`: Migrate to `return redirect(event, location, code)`.
- `sendProxy(event, target)`: Migrate to `return proxy(event, target)`.
- `handleCors(event)`: Check return value (boolean) and early `return` if handled.
- `serveStatic(event, content)`: Make sure to add `return` before.

## App interface and router

- `app.use(() => handler, { lazy: true })` is no supported anymore. Instead you can use `app.use(defineLazyHabndler(() => handler), { lazy: true })`
Router functionality is now integrated in h3 app core. Instead of `createApp()` and `createRouter()` you can use `const app = createH3()`.

Methods:

- `app.use(handler)`: Add a global middleware.
- `app.use(route, handler)`: Add a routed middleware.
- `app.on(method, handler)` / `app.all(handler)` / `app.[METHOD](handler)`: Add a route handler.

Running order:

- All global middleware by the same order they added
- All routed middleware by from least specific to most specific paths (auto sorted)
- Matched route handler

Any of middleware or route handler, can return a response.

Other changes from v1:

- Handlers registered with `app.use("/path", handler)` only match `/path` (not `/path/foo/bar`). For matching all subpaths like before, it should be updated to `app.use("/path/**", handler)`.
- The `event.path` received in each handler will have a full path without omitting the prefixes. use `withBase(base, handler)` utility to make prefixed app. (example: `withBase("/api", app.handler)`).
- `app.use(() => handler, { lazy: true })` is no supported anymore. Instead you can use `app.use(defineLazyEventHandler(() => handler), { lazy: true })`
- `app.use(["/path1", "/path2"], ...)` and `app.use("/path", [handler1, handler2])` are not supported anymore. Instead use multiple `app.use()` calls.
- `app.use({ route, handler })` should be updated to `app.use({ prefix, handler })` (route property is used for router patterns)
- `app.resolve(path) => { route, handler }` changed to `app.resolve(method, path) => { prefix?, route?, handler }` (`prefix` is the registred prefix and `route` is the route pattern).
- Custom `match` function for `app.use` is not supported anymore (middleware can skip themselves).
- `app.resolve(path) => { route, handler }` changed to `app.resolve(method, path) => { method route, handler }`

### Router

Expand All @@ -73,7 +95,7 @@ The legacy `readBody` and `readRawBody` utils are replaced with a new set of bod
- Body utils won't throw an error if the incoming request has no body (or is a `GET` method for example) but instead, returns `undefined`
- `readJSONBody` does not use [unjs/destr](https://destr.unjs.io) anymore. You should always filter and sanitize data coming from user to avoid [prototype-poisoning](https://medium.com/intrinsic-blog/javascript-prototype-poisoning-vulnerabilities-in-the-wild-7bc15347c96)

## Cookie and Headers
## Cookie and headers

h3 migrated to leverage standard web [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers) for all utils.

Expand All @@ -85,65 +107,77 @@ For the [`Set-Cookie`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers

h3 v2 deprecated some legacy and aliased utilities.

**App and router:**

- `createRouter`: Migrate to `createH3`
- `createApp`: Migrate to `createH3`

**Handler:**

- `eventHandler`: Use `defineEventHandler`
- `toEventHandler`: (it is not required anymore)
- `lazyEventHandler`: Use `defineLazyEventHandler`
- `eventHandler`: Migrate to `defineEventHandler`
- `lazyEventHandler`: Migrate to `defineLazyEventHandler`
- `useBase`: Migrate to `withbase`
- `toEventHandler` / `isEventHandler`: (removed) Any function can be an event handler.

**Request:**

- `getHeader`: Use `getRequestHeader`
- `getHeaders`: Use `getRequestHeaders`
- `getRequestPath`: Use `event.path`
- `getHeader`: Migrate to `getRequestHeader`.
- `getHeaders`: Migrate to `getRequestHeaders`.
- `getRequestPath`: Migrate to `event.path`.

**Response:**

- `appendHeader`: Use `appendResponseHeader`
- `appendHeaders`: Use `appendResponseHeaders`
- `setHeader`: Use `setResponseHeader`
- `setHeaders` => Use `setResponseHeaders`
- `appendHeader`: Migrate to `appendResponseHeader`.
- `appendHeaders`: Migrate to `appendResponseHeaders`.
- `setHeader`: Migrate to `setResponseHeader`.
- `setHeaders`: Migrate to `setResponseHeaders`.

**Node.js:**

- `defineNodeListener`: Use `defineNodeHandler`
- `fromNodeMiddleware`: Use `fromNodeHandler`
- `createEvent`: Use `fromNodeRequest`
- `toNodeListener`: Use `toNodeHandler`
- `callNodeListener`: Use `callNodeHandler`
- `promisifyNodeListener` (removed)
- `callNodeHandler`: (internal)
- `defineNodeListener`: Migrate to `defineNodeHandler`.
- `fromNodeMiddleware`: Migrate to `fromNodeHandler`.
- `toNodeListener`: Migrate to `toNodeHandler`.
- `createEvent`: (removed): Use Node.js adapter (`toNodeHandler(app)`).
- `fromNodeRequest`: (removed): Use Node.js adapter (`toNodeHandler(app)`).
- `promisifyNodeListener` (removed).
- `callNodeListener`: (removed).

**Web:**

- `callWithWebRequest`: (removed)
- `fromPlainHandler`: (removed) Migrate to Web API.
- `toPlainHandler`: (removed) Migrate to Web API.
- `fromPlainRequest` (removed) Migrate to Web API or use `mockEvent` util for testing.
- `callWithPlainRequest` (removed) Migrate to Web API.
- `fromWebRequest`: (removed) Migrate to Web API.
- `callWithWebRequest`: (removed).

**Body:**

- `readBody`: Use `readJSONBody`
- `readFormData`: Use `readFormDataBody`
- `readValidatedBody`: Use `readValidatedJSONBody`
- `getRequestWebStream`: Use `getBodyStream`
- `readMultipartFormData`: Migrate to `readFormDataBody`
- `readBody`: Migrate to `readJSONBody`.
- `readFormData`: Migrate to `readFormDataBody`.
- `readValidatedBody`: Migrate to `readValidatedJSONBody`.
- `getRequestWebStream`: Migrate to `getBodyStream`.
- `readMultipartFormData`: Migrate to `readFormDataBody`.

**Types:**
- **Utils:**

- `_RequestMiddleware`: Use `RequestMiddleware`
- `_ResponseMiddleware`: Use `ResponseMiddleware`
- `NodeListener`: Use `NodeHandler`
- `TypedHeaders`: Use `RequestHeaders` and `ResponseHeaders`
- `HTTPHeaderName`: Use `RequestHeaderName` and `ResponseHeaderName`
- `H3Headers`: Use native `Headers`
- `H3Response`: Use native `Response`
- `WebEventContext`
- `NodeEventContext`
- `NodePromisifiedHandler`
- `MultiPartData`: Use `FormData`
- `RouteNode`: Use `RouterEntry`
`CreateRouterOptions`: use `RouterOptions`
- `isStream`: Migrate to `instanceof ReadableStream` and `.pipe` properties for detecting Node.js `ReadableStream`.
- `isWebResponse`: Migrate to `use instanceof Response`.
- `MIMES`: (removed).

- **Utils:**
**Types:**

- `isStream`: Use `instanceof ReadableStream` and `.pipe` properties for detecting Node.js `ReadableStream`
- `isWebResponse`: Use `use instanceof Response`
- `MIMES`: Removed internal map.
- `App`: Migrate to `H3`.
- `AppOptions`: Migrate to `H3Config`.
- `_RequestMiddleware`: Migrate to `RequestMiddleware`.
- `_ResponseMiddleware`: Migrate to `ResponseMiddleware`.
- `NodeListener`: Migrate to `NodeHandler`.
- `TypedHeaders`: Migrate to `RequestHeaders` and `ResponseHeaders`.
- `HTTPHeaderName`: Migrate to `RequestHeaderName` and `ResponseHeaderName`.
- `H3Headers`: Migrate to native `Headers`.
- `H3Response`: Migrate to native `Response`.
- `MultiPartData`: Migrate to native `FormData`.
- `RouteNode`: Migrate to `RouterEntry`.
`CreateRouterOptions`: Migrate to `RouterOptions`.

Removed type exports: `WebEventContext`, `NodeEventContext`, `NodePromisifiedHandler`, `AppUse`, `Stack`, `InputLayer`, `InputStack`, `Layer`, `Matcher`, `PlainHandler`, `PlainRequest`, `PlainReponse`, `WebHandler`
11 changes: 5 additions & 6 deletions docs/2.utils/98.advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,17 @@ Allowed characters: horizontal tabs, spaces or visible ascii characters: https:/

<!-- automd:jsdocs src="../../src/utils/base.ts" -->

### `useBase(base, handler)`
### `withBase(base, input)`

Prefixes and executes a handler with a base path.
Returns a new event handler that removes the base url of the event before calling the original handler.

**Example:**

```ts
const api = createApp()
.get("/", () => "Hello API!");
const app = createApp();
const router = createRouter();
const apiRouter = createRouter().get("/hello", () => "Hello API!");
router.use("/api/**", useBase("/api", apiRouter.handler));
app.use(router.handler);
.use("/api/**", withBase("/api", api.handler));
```

<!-- /automd -->
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"build": "unbuild",
"dev": "vitest",
"lint": "eslint --cache . && prettier -c src test examples docs",
"lint:fix": "eslint --cache . --fix && prettier -c src test examples docs -w",
"lint:fix": "automd && eslint --cache . --fix && prettier -c src test examples docs -w",
"play:bun": "bun ./test/fixture/bun.ts",
"play:node": "node --import jiti/register test/fixture/node.ts",
"play:plain": "node --import jiti/register test/fixture/plain.ts",
Expand All @@ -42,7 +42,7 @@
"cookie-es": "^1.1.0",
"iron-webcrypto": "^1.2.1",
"ohash": "^1.1.3",
"rou3": "^0.2.0",
"rou3": "^0.4.0",
"ufo": "^1.5.3",
"uncrypto": "^0.1.3"
},
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

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

15 changes: 11 additions & 4 deletions src/deprecated.ts → src/_deprecated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import {
import {
defineNodeHandler,
fromNodeHandler,
fromNodeRequest,
toNodeHandler,
} from "./adapters/node";
import {
Expand All @@ -35,6 +34,8 @@ import {
} from "./utils/body";
import { defineEventHandler, defineLazyEventHandler } from "./handler";
import { proxy } from "./utils/proxy";
import { createH3 } from "./h3";
import { withBase } from "./utils/base";

/** @deprecated Please use `getRequestHeader` */
export const getHeader = getRequestHeader;
Expand Down Expand Up @@ -74,9 +75,6 @@ export const defineNodeListener = defineNodeHandler;
/** @deprecated Please use `defineNodeHandler` */
export const fromNodeMiddleware = fromNodeHandler;

/** @deprecated Please use `fromNodeRequest` */
export const createEvent = fromNodeRequest;

/** @deprecated Please use `toNodeHandler` */
export const toNodeListener = toNodeHandler;

Expand Down Expand Up @@ -122,6 +120,15 @@ export function toEventHandler(
return input;
}

/** @deprecated Use `createH3()` */
export const createApp = createH3;

/** @deprecated Use `createH3()` */
export const createRouter = createH3;

/** @deprecated Use `withBase()` */
export const useBase = withBase;

// --- Types ---

/** @deprecated Please use `RequestMiddleware` */
Expand Down
11 changes: 3 additions & 8 deletions src/adapters/node/event.ts → src/adapters/node/_event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import type { HTTPMethod } from "../../types";
import type { RawEvent } from "../../types/event";
import { splitCookiesString } from "../../utils/cookie";
import { NodeHeadersProxy } from "./_headers";
import {
_normalizeHeaders,
_readBody,
_getBodyStream,
_sendResponse,
} from "./_internal";
import { readNodeReqBody, getBodyStream } from "./_utils";

import type { NodeIncomingMessage, NodeServerResponse } from "../../types/node";

Expand Down Expand Up @@ -85,7 +80,7 @@ export class NodeEvent implements RawEvent {

readRawBody() {
if (!this._rawBody) {
this._rawBody = _readBody(this._req);
this._rawBody = readNodeReqBody(this._req);
}
return this._rawBody;
}
Expand Down Expand Up @@ -115,7 +110,7 @@ export class NodeEvent implements RawEvent {

getBodyStream() {
if (!this._bodyStream) {
this._bodyStream = _getBodyStream(this._req);
this._bodyStream = getBodyStream(this._req);
}
return this._bodyStream;
}
Expand Down
Loading
Loading