npm package: @glorhythm/request
Description: Glorhythm library for request
Author: glorhythm
Latest version: 1.0.0-beta

# @glorhythm/request

A small, typed HTTP request wrapper for Node.js `IncomingMessage`, built for the Glorhythm stack.

It provides:

- A simple `Request` class that wraps Node's `IncomingMessage`
- Typed access to common HTTP headers
- Helpers for method, URL, query params, body, and IP detection
- JSON body parsing for non-GET/HEAD style methods

---

## Installation

```bash
npm install @glorhythm/request
```

> Note: This package is designed to be used inside the broader Glorhythm ecosystem where these packages are typically present.

---

## Basic Usage

```ts
import http from "http";
import Request from "@glorhythm/request";

const server = http.createServer(async (rawReq, res) => {
  const req = new Request(rawReq);

  // Parse JSON body if applicable
  await req.parseBody();

  console.log("Method:", req.method());
  console.log("URL:", req.url());
  console.log("IP:", req.ip());

  console.log("User-Agent:", req.header("user-agent"));
  console.log("Query params:", Object.fromEntries(req.query));
  console.log("Body params:", Object.fromEntries(req.body));

  res.statusCode = 200;
  res.end("OK");
});

server.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});
```

---

## API

### `type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"`

A narrow union of supported HTTP methods.

---

### `class Request`

#### Constructor

```ts
new Request(raw: IncomingMessage);
```

- **`raw`** – Node.js `IncomingMessage` instance from `http.createServer`, `https.createServer`, or compatible frameworks.

The constructor:

- Stores the raw request
- Immediately parses the query string from the URL into `req.query`

---

### Properties

#### `query: Map<string, any>`

A `Map` of query-string parameters parsed from the URL.

Example:

```ts
// /users?page=2&role=admin
req.query.get("page"); // "2"
req.query.get("role"); // "admin"
```

#### `body: Map<string, any>`

A `Map` of parsed body parameters. Filled only after calling `await req.parseBody()`.

Example (JSON):

```ts
// Body: { "email": "test@example.com", "password": "123456" }
await req.parseBody();
req.body.get("email"); // "test@example.com"
req.body.get("password"); // "123456"
```

---

### Methods

#### `method(): HttpMethod`

Returns the HTTP method, defaulting to `"GET"` if undefined:

```ts
const method = req.method(); // "GET", "POST", "PUT", "PATCH", or "DELETE"
```

#### `url(): string`

Returns the raw URL string from the incoming request, defaulting to `"/"` if missing:

```ts
const url = req.url(); // e.g. "/users?page=2"
```

#### `header<K extends StrictHeaderKey>(key: K): StrictHeaderValue<K>`

Typed header getter.

- **`StrictHeaderKey`** – A union of known/whitelisted HTTP header names (all lowercase).
- **`StrictHeaderValue<K>`** – The corresponding type for that header (usually `string` or `string[]`).

Example:

```ts
const userAgent = req.header("user-agent"); // string | undefined
const contentType = req.header("content-type"); // string | undefined
const accept = req.header("accept"); // string | undefined
```

Since the keys are constrained, TypeScript will catch typos in header names.

#### `ip(): string`

Returns the best guess IPv4 address for the client:

1. Checks `x-forwarded-for` header, taking the first entry.
2. Falls back to `raw.socket.remoteAddress`.
3. Strips the IPv6-mapped IPv4 prefix (`::ffff:`).
4. Caches the result for subsequent calls.

Usage:

```ts
const ip = req.ip(); // e.g. "203.0.113.42"
```

> Note: For correct IP detection behind proxies/load balancers, ensure `x-forwarded-for` is properly set by your infrastructure.

#### `parseBody(): Promise<void>`

Reads and parses the request body **if the method is body-capable**.

##### Method Filter

`parseBody` will **skip** parsing for:

- `GET`
- `HEAD`
- `OPTIONS`
- `TRACE`
- `CONNECT`

Only methods like `POST`, `PUT`, `PATCH`, and `DELETE` are processed.

##### Parsing Rules

- Collects all incoming data chunks into a `Buffer`.
- If there is no content, it returns early.
- Inspects the `content-type` header:
  - If empty or includes `"application/json"`, attempts to parse the body as JSON.
  - On successful JSON parse, flattens top-level keys into `req.body` `Map`.

Example:

```ts
await req.parseBody();

if (req.method() === "POST") {
  const payload = Object.fromEntries(req.body);
  console.log("Received JSON:", payload);
}
```

> **Important:** If the body is not valid JSON for a JSON request, `JSON.parse` will throw. Handle this in your higher-level request handling by wrapping `parseBody` in `try/catch`, or adjust the implementation to catch and handle the error.

---

## Header Typings

The library defines a `StrictIncomingHttpHeaders` interface and the utility types:

```ts
type StrictHeaderKey = keyof StrictIncomingHttpHeaders;
type StrictHeaderValue<K extends StrictHeaderKey> =
  StrictIncomingHttpHeaders[K];
```

This gives you strict, auto-completed header names instead of arbitrary strings.

---

## Example: Minimal Router

```ts
import http from "http";
import Request from "@glorhythm/request";

const server = http.createServer(async (rawReq, res) => {
  const req = new Request(rawReq);

  if (req.method() === "GET" && req.url().startsWith("/health")) {
    res.statusCode = 200;
    res.setHeader("content-type", "application/json");
    return res.end(JSON.stringify({ status: "ok" }));
  }

  if (req.method() === "POST" && req.url() === "/echo") {
    await req.parseBody();
    const body = Object.fromEntries(req.body);
    res.statusCode = 200;
    res.setHeader("content-type", "application/json");
    return res.end(JSON.stringify({ body, ip: req.ip() }));
  }

  res.statusCode = 404;
  res.end("Not Found");
});

server.listen(3000, () => {
  console.log("Listening on http://localhost:3000");
});
```

---

## TypeScript

The package ships with type definitions:

- `main`: `dist/index.js`
- `types`: `dist/index.d.ts`
- `exports` configured for ESM/CJS.

Typical TS usage:

```ts
import Request from "@glorhythm/request";
import { IncomingMessage } from "http";

function wrap(raw: IncomingMessage) {
  const req = new Request(raw);
  return req;
}
```

---

## License

MIT © Glorhythm