# @miqro/core

promise based http router with TypeScript inference.

## Router

```ts
import { Router } from "@miqro/core";

const router = new Router();

router.use(handler);                          // all methods, all paths
router.use(handler, "/path");                 // all methods, /path
router.use(handler, "/path", "GET");          // GET /path
router.get("/path", handler);                 // GET /path
router.catch(errorHandler);                   // error handler

await router.run(req, res);                   // returns true if no response sent
```

handler return values:

```ts
return false;         // stop chain, no response sent
return true;          // continue chain
return undefined;     // continue chain
return { ... };       // pushed to req.results, continue chain
```

## App

`http.createServer` with router built in.

```ts
import { App } from "@miqro/core";

const app = new App({
  loggerFactory: (uuid, req) => logger,
  onUpgrade: (req, socket, head) => { }   // WebSocket upgrade
});

app.use(router);
await app.listen("3000");
await app.close();
```

## APIRoute

complete endpoint declaration.

```ts
import { APIRoute, JSONParser } from "@miqro/core";

export default {
  name: "create post",
  description: "creates a post",
  path: "/posts",
  method: "POST",
  policy: {
    groups: ["editor"],
    groupPolicy: "at_least_one"
  },
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string",
      tags: "string[]?"
    },
    query: {
      draft: "boolean?"
    }
  },
  response: {
    status: [200, 400],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    //req.body.title // is coerced to string
    return res.json({ id: 1 });
  }
} as APIRoute;
```

## APIRoute with TypeScript inference (experimental)

complete endpoint declaration.

```ts
import { defineRoute, JSONParser } from "@miqro/core";

export default defineRoute({
  name: "create post",
  description: "creates a post",
  path: "/posts",
  method: "POST",
  policy: {
    groups: ["editor"],
    groupPolicy: "at_least_one"
  },
  middleware: [JSONParser()],
  request: {
    body: {
      title: "string",
      content: "string",
      tags: "string[]?"
    },
    query: {
      draft: "boolean?"
    }
  },
  response: {
    status: [200, 400],
    body: {
      id: "number"
    }
  },
  handler: async (req, res) => {
    //req.body.title // is coerced and typed string
    return res.json({ id: 1 });
  }
});
```

`method: "use"` matches all methods — use for middleware:

```ts
export default {
  path: "/admin",
  method: "use",
  handler: adminRouter
} as APIRoute;
```

## middleware

### JSONParser

parses `application/json` body into `req.body`.

```ts
import { JSONParser } from "@miqro/core";
router.use(JSONParser());
```

### URLEncodedParser

parses `application/x-www-form-urlencoded` into `req.body`.

```ts
import { URLEncodedParser } from "@miqro/core";
router.use(URLEncodedParser());
```

### ReadBuffer

reads raw body into `req.buffer`.

```ts
import { ReadBuffer } from "@miqro/core";
router.use(ReadBuffer());
```

### TextParser

reads body as string into `req.body`.

```ts
import { TextParser } from "@miqro/core";
router.use(TextParser());
```

### CORS

```ts
import { CORS } from "@miqro/core";

router.use(CORS({
  origins: ["https://myapp.com"],
  methods: "GET,POST,PUT,DELETE"
}));
```

without options reads `CORS_ORIGINS`, `CORS_METHODS` env vars. default: all origins.

invalid origins throw `BadRequestError`.

### SessionHandler

verifies token and sets `req.session`.

```ts
import { SessionHandler } from "@miqro/core";

router.use(SessionHandler({
  verify: async ({ token }) => {
    // return session or null
  }
}));
```

token location via env vars: `TOKEN_VERIFY_LOCATION` (`header`|`query`|`cookie`), `TOKEN_HEADER`, `TOKEN_QUERY`, `TOKEN_COOKIE`.

or via remote endpoint: `TOKEN_VERIFY_ENDPOINT`.

### GroupPolicyHandler

restricts access by `req.session.groups`.

```ts
import { GroupPolicyHandler } from "@miqro/core";

router.use(GroupPolicyHandler({
  groups: ["admin", "editor"],
  groupPolicy: "at_least_one"   // or "all"
}));
```

throws `ForbiddenError` if policy not met.

### ParseRequest

validates `req.body`, `req.query`, `req.params`, `req.headers` against schema.

```ts
import { ParseRequest } from "@miqro/core";

router.use(ParseRequest({
  body: { title: "string", count: "number?" },
  query: { page: "number?" },
  params: { id: "integer" }
}));
```

throws `BadRequestError` if validation fails.

## errors

```ts
import { 
  BadRequestError,      // 400
  UnAuthorizedError,    // 401
  ForbiddenError,       // 403
  NotFoundError,        // 404
  MethodNotImplementedError  // 501
} from "@miqro/core";

// throw from any handler
throw new BadRequestError("invalid input");
throw new UnAuthorizedError();
throw new ForbiddenError();
```

caught by `router.catch()` handlers or the default error handler.

## Logger

```ts
import { Logger, ConsoleTransport, FileTransport } from "@miqro/core";

const logger = new Logger("MY_SERVICE", "info", {
  transports: [ConsoleTransport(), FileTransport("server.log")]
});

logger.error("message %s", value);
logger.warn("...");
logger.info("...");
logger.debug("...");
logger.trace("...");
```

level override via env: `LOG_LEVEL=debug`, `LOG_LEVEL_MY_SERVICE=trace`.

## Request

```ts
import { Request } from "@miqro/core";

// all properties set before handlers run
req.path          // normalized pathname
req.query         // { [key]: string | string[] } — Object.create(null)
req.params        // { [key]: string } — Object.create(null)
req.cookies       // { [name]: string } — Object.create(null)
req.uuid          // randomUUID()
req.startMS       // Date.now() at request start
req.logger        // per-request logger
req.results       // []
req.body          // set by body parser middleware
req.buffer        // set by ReadBuffer
req.session       // set by SessionHandler
```

## Static

serve static files.

```ts
import { Static } from "@miqro/core";

router.use(new Static({
  directory: "./public",
  autoIndex: true
}));
```

## Proxy

proxy requests to another server.

```ts
import { Proxy } from "@miqro/core";

router.use(new Proxy({
  url: "http://backend:3001"
}));
```