@fougere/http 0.2.0-alpha.2 → 0.4.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,4 +11,4 @@ pnpm add @fougere/http
11
11
 
12
12
  Part of [Fougere](https://github.com/chok/fougere) — one schema, a gradient from
13
13
  monolith to distributed, the same user code.
14
- Reference documentation: [the site](https://chok.github.io/fougere/) (en/fr).
14
+ Reference documentation: [the site](https://fougere.dev/) (en/fr).
@@ -1 +1 @@
1
- {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAsB,KAAK,UAAU,EAA4F,MAAM,aAAa,CAAC;AAE5J,UAAU,WAAW;IACnB,GAAG,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AAYD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAkB1D;AA6CD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,CAsDhE"}
1
+ {"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAsB,KAAK,UAAU,EAA4F,MAAM,aAAa,CAAC;AAE5J,UAAU,WAAW;IACnB,GAAG,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AAYD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAqB1D;AAoDD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,WAAW,GAAG,UAAU,CAsDhE"}
package/dist/express.js CHANGED
@@ -58,7 +58,10 @@ export function readExpressBody(req) {
58
58
  req.__fougereBody = (async () => {
59
59
  if (verb === 'GET' || verb === 'HEAD')
60
60
  return {};
61
- if (req.body !== undefined && req.body !== null)
61
+ // `null` is parsed JSON, not evidence that no parser ran. Falling through here
62
+ // turned an explicit domain value into `{}` (or tried to drain an already consumed
63
+ // stream), while every other door kept it.
64
+ if (req.body !== undefined)
62
65
  return req.body;
63
66
  const contentType = String(req.headers?.['content-type'] ?? '');
64
67
  if (!contentType.toLowerCase().includes('json'))
@@ -76,22 +79,29 @@ export function readExpressBody(req) {
76
79
  return req.__fougereBody;
77
80
  }
78
81
  function buildContext(req) {
79
- const host = req.headers?.host ?? 'localhost';
80
- const protocol = req.protocol ?? 'http';
81
- const url = `${protocol}://${host}${req.originalUrl ?? req.url}`;
82
82
  // Kept as a plain string: a real request may be HEAD or OPTIONS, which the
83
83
  // interface's `HttpMethod` does not name. Casting here would have made the
84
84
  // "does this verb carry a body" question unaskable — it is asked below.
85
85
  const verb = req.method.toUpperCase();
86
- const headers = new Headers(Object.entries(req.headers ?? {})
87
- .filter((entry) => typeof entry[1] === 'string'));
86
+ let request;
88
87
  // One reader for both consumers — see `readExpressBody`.
89
88
  const body = () => readExpressBody(req);
90
89
  return {
91
90
  // The Request carries no body: Express may have consumed the stream already,
92
91
  // and a Request built around a drained one lies. `body()` is the honest reader,
93
92
  // and it is what every consumer in this repo calls.
94
- request: new Request(url, { method: verb, headers }),
93
+ get request() {
94
+ if (!request) {
95
+ const host = req.headers?.host ?? 'localhost';
96
+ const url = `${req.protocol ?? 'http'}://${host}${req.originalUrl ?? req.url}`;
97
+ request = new Request(url, {
98
+ method: verb,
99
+ headers: new Headers(Object.entries(req.headers ?? {})
100
+ .filter((entry) => typeof entry[1] === 'string')),
101
+ });
102
+ }
103
+ return request;
104
+ },
95
105
  method: verb,
96
106
  path: req.path ?? (req.originalUrl ?? req.url ?? '').split('?')[0],
97
107
  params: req.params ?? {},
@@ -1 +1 @@
1
- {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"express.js","sourceRoot":"","sources":["../src/express.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAmE,MAAM,aAAa,CAAC;AAE/G,UAAU,WAAW;IACnB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AA4CD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CA+CnE"}
1
+ {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,UAAU,EAAmE,MAAM,aAAa,CAAC;AAE/G,UAAU,WAAW;IACnB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AAoDD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,UAAU,CA+CnE"}
package/dist/fastify.js CHANGED
@@ -6,22 +6,30 @@ const METHOD_MAP = {
6
6
  DELETE: 'delete',
7
7
  };
8
8
  function buildContext(req) {
9
- const url = `${req.protocol ?? 'http'}://${req.hostname ?? 'localhost'}${req.url}`;
10
- const headers = new Headers(req.headers);
11
9
  const method = req.method.toUpperCase();
12
- const hasBody = method !== 'GET' && method !== 'HEAD';
13
- const request = new Request(url, {
14
- method,
15
- headers,
16
- ...(hasBody && req.body ? { body: JSON.stringify(req.body) } : {}),
17
- });
10
+ let request;
18
11
  return {
19
- request,
12
+ // Deferred, and it re-serializes a body fastify already parsed — which is why
13
+ // paying for it on every call was worth removing rather than optimising.
14
+ get request() {
15
+ if (!request) {
16
+ const url = `${req.protocol ?? 'http'}://${req.hostname ?? 'localhost'}${req.url}`;
17
+ const hasBody = method !== 'GET' && method !== 'HEAD';
18
+ request = new Request(url, {
19
+ method,
20
+ headers: new Headers(req.headers),
21
+ ...(hasBody && req.body !== undefined ? { body: JSON.stringify(req.body) } : {}),
22
+ });
23
+ }
24
+ return request;
25
+ },
20
26
  method: method,
21
27
  path: req.url,
22
28
  params: req.params ?? {},
23
29
  query: req.query ?? {},
24
- body: async () => req.body ?? {},
30
+ // Absence alone gets the empty protocol object. `null` is valid parsed JSON and must
31
+ // reach the core unchanged when the contract admits it.
32
+ body: async () => req.body === undefined ? {} : req.body,
25
33
  state: {},
26
34
  };
27
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAsB,KAAK,UAAU,EAA4F,MAAM,aAAa,CAAC;AAE5J,UAAU,QAAQ;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjD,GAAG,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AAyDD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAgD1D"}
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB,UAAU,QAAQ;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjD,GAAG,CAAC,GAAG,QAAQ,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACnC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5C,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC7C,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC/C;AAyDD;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,GAAG,UAAU,CAiD1D"}
package/dist/hono.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Hono adapter — bridges a Hono app to the HttpRouter interface.
3
3
  */
4
- import { MalformedJsonError } from './router.js';
4
+ import { MalformedJsonError, PASSTHROUGH, } from './router.js';
5
5
  const METHOD_MAP = {
6
6
  GET: 'get',
7
7
  POST: 'post',
@@ -79,11 +79,12 @@ export function createHonoRouter(app) {
79
79
  const ctx = buildContext(c);
80
80
  const result = await mw(ctx, async () => {
81
81
  await next();
82
- // After next(), Hono has already set the response return a passthrough
83
- return { status: c.res.status, data: null };
82
+ // After next(), Hono has already set the response. The sentinel says so, and it
83
+ // is a symbol on purpose — see PASSTHROUGH.
84
+ return { status: c.res.status, data: PASSTHROUGH };
84
85
  });
85
- // If the middleware returned a custom response (short-circuit), send it
86
- if (result.data !== null) {
86
+ // Anything else is the middleware's own response, empty body included.
87
+ if (result.data !== PASSTHROUGH) {
87
88
  return sendResponse(c, result);
88
89
  }
89
90
  }
package/dist/hono.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type { HttpMethod, RequestContext, ResponseResult, Handler, Next, Middleware, HttpRouter, } from './router.js';
2
- export { MalformedJsonError } from './router.js';
2
+ export { MalformedJsonError, PASSTHROUGH } from './router.js';
3
3
  export { createHonoRouter } from './hono.js';
4
4
  export { createFastifyRouter } from './fastify.js';
5
5
  export { createExpressRouter, readExpressBody } from './express.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,OAAO,EACP,IAAI,EACJ,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,OAAO,EACP,IAAI,EACJ,UAAU,EACV,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { MalformedJsonError } from './router.js';
1
+ export { MalformedJsonError, PASSTHROUGH } from './router.js';
2
2
  export { createHonoRouter } from './hono.js';
3
3
  export { createFastifyRouter } from './fastify.js';
4
4
  export { createExpressRouter, readExpressBody } from './express.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
package/dist/router.d.ts CHANGED
@@ -6,8 +6,19 @@
6
6
  */
7
7
  export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
8
8
  export interface RequestContext {
9
- /** The original Web Standard Request — source of truth. */
10
- request: Request;
9
+ /**
10
+ * The request as a Web Standard `Request`.
11
+ *
12
+ * Built on FIRST ACCESS. Every adapter used to build one eagerly, and no consumer
13
+ * in this repo reads it — measured, zero call sites outside the adapters and their
14
+ * tests, while the construction cost a fifth of the port's throughput on the engines
15
+ * that have no `Request` of their own (express, fastify). Hono hands over the one it
16
+ * already has, so nothing is deferred there.
17
+ *
18
+ * The fields beside it — `method`, `path`, `params`, `query`, `body` — are what the
19
+ * projections read, and they are not derived from this one.
20
+ */
21
+ readonly request: Request;
11
22
  method: HttpMethod;
12
23
  path: string;
13
24
  params: Record<string, string>;
@@ -29,6 +40,21 @@ export declare class MalformedJsonError extends Error {
29
40
  }
30
41
  export type Handler = (ctx: RequestContext) => Promise<ResponseResult>;
31
42
  export type Next = () => Promise<ResponseResult>;
43
+ /**
44
+ * What `next()` answers when the host framework — not us — owns the chain.
45
+ *
46
+ * Express and Fastify build their own middleware chain here, so a middleware's return IS
47
+ * the response and there is nothing to distinguish. Hono owns its chain, so its adapter has
48
+ * to tell "the middleware answered" from "the middleware delegated", and it used
49
+ * `data === null` for the second. `null` is a legal body — `{ status: 403, data: null }` is
50
+ * the ordinary spelling of a deny — so a middleware that REFUSED with no body was read as
51
+ * having delegated: it was silently bypassed on Hono and honoured on Fastify. One contract,
52
+ * two verdicts, on a value inside the value space.
53
+ *
54
+ * A symbol cannot be produced by accident, and no middleware author ever writes it: only an
55
+ * adapter that does not own its chain returns it from the `next` it hands out.
56
+ */
57
+ export declare const PASSTHROUGH: unique symbol;
32
58
  export type Middleware = (ctx: RequestContext, next: Next) => Promise<ResponseResult>;
33
59
  export interface HttpRouter {
34
60
  /** Register a global middleware. */
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,4FAA4F;IAC5F,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,4EAA4E;AAC5E,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,CAAC,EAAE,YAAY,EAGjC;CACF;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AACvE,MAAM,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;AACjD,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEtF,MAAM,WAAW,UAAU;IACzB,oCAAoC;IACpC,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,qDAAqD;IACrD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAChD,gCAAgC;IAChC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9D"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,cAAc;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7B,4EAA4E;IAC5E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAC5C,4FAA4F;IAC5F,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,4EAA4E;AAC5E,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAO,CAAC,EAAE,YAAY,EAGjC;CACF;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AACvE,MAAM,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,CAAC;AACjD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW,EAAE,OAAO,MAA2C,CAAC;AAE7E,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAEtF,MAAM,WAAW,UAAU;IACzB,oCAAoC;IACpC,GAAG,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAClC,qDAAqD;IACrD,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAChD,gCAAgC;IAChC,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9D"}
package/dist/router.js CHANGED
@@ -11,4 +11,19 @@ export class MalformedJsonError extends Error {
11
11
  this.name = 'MalformedJsonError';
12
12
  }
13
13
  }
14
+ /**
15
+ * What `next()` answers when the host framework — not us — owns the chain.
16
+ *
17
+ * Express and Fastify build their own middleware chain here, so a middleware's return IS
18
+ * the response and there is nothing to distinguish. Hono owns its chain, so its adapter has
19
+ * to tell "the middleware answered" from "the middleware delegated", and it used
20
+ * `data === null` for the second. `null` is a legal body — `{ status: 403, data: null }` is
21
+ * the ordinary spelling of a deny — so a middleware that REFUSED with no body was read as
22
+ * having delegated: it was silently bypassed on Hono and honoured on Fastify. One contract,
23
+ * two verdicts, on a value inside the value space.
24
+ *
25
+ * A symbol cannot be produced by accident, and no middleware author ever writes it: only an
26
+ * adapter that does not own its chain returns it from the `next` it hands out.
27
+ */
28
+ export const PASSTHROUGH = Symbol('fougere.http.passthrough');
14
29
  //# sourceMappingURL=router.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH,4EAA4E;AAC5E,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAsB;QAChC,KAAK,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF"}
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmCH,4EAA4E;AAC5E,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,OAAsB;QAChC,KAAK,CAAC,qBAAqB,EAAE,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAID;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,WAAW,GAAkB,MAAM,CAAC,0BAA0B,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/http",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.4.0-alpha.0",
4
4
  "description": "HTTP router and middleware.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -25,7 +25,8 @@
25
25
  }
26
26
  },
27
27
  "files": [
28
- "dist"
28
+ "dist",
29
+ "src"
29
30
  ],
30
31
  "devDependencies": {
31
32
  "@types/node": "^22.19.15",
package/src/express.ts ADDED
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Express adapter — bridges an Express app to the HttpRouter interface.
3
+ *
4
+ * The one that is not Web-standard, and that is the whole substance of this file.
5
+ * Hono hands over `c.req.raw`, Fastify parses the body for us; Express hands a
6
+ * Node `IncomingMessage` and parses nothing unless the app happens to have mounted
7
+ * `express.json()`. So the conversion lives here — read the stream when nobody
8
+ * else did, and build the `Request` the interface promises.
9
+ *
10
+ * Working with or without `express.json()` is deliberate: an adapter that only
11
+ * works when the host app is configured a particular way is a footgun, and this
12
+ * one is meant to be dropped into an app that already exists.
13
+ */
14
+ import { MalformedJsonError, type HttpRouter, type HttpMethod, type RequestContext, type ResponseResult, type Middleware, type Handler } from './router.js';
15
+
16
+ interface ExpressLike {
17
+ use(...handlers: Function[]): void;
18
+ get(path: string, handler: Function): void;
19
+ post(path: string, handler: Function): void;
20
+ put(path: string, handler: Function): void;
21
+ patch(path: string, handler: Function): void;
22
+ delete(path: string, handler: Function): void;
23
+ }
24
+
25
+ const METHOD_MAP: Record<HttpMethod, 'get' | 'post' | 'put' | 'patch' | 'delete'> = {
26
+ GET: 'get',
27
+ POST: 'post',
28
+ PUT: 'put',
29
+ PATCH: 'patch',
30
+ DELETE: 'delete',
31
+ };
32
+
33
+ const MAX_BODY_BYTES = 1024 * 1024;
34
+
35
+ /** Drain the Node stream. Only reached when no body parser ran before us. */
36
+ export function readRawBody(req: any): Promise<string> {
37
+ return new Promise((resolve, reject) => {
38
+ const chunks: Buffer[] = [];
39
+ let size = 0;
40
+ let exceeded = false;
41
+ req.on('data', (chunk: Buffer) => {
42
+ if (exceeded) return;
43
+ size += chunk.length;
44
+ if (size > MAX_BODY_BYTES) {
45
+ exceeded = true;
46
+ reject(Object.assign(new Error('Payload too large'), { statusCode: 413 }));
47
+ return;
48
+ }
49
+ chunks.push(Buffer.from(chunk));
50
+ });
51
+ req.on('end', () => { if (!exceeded) resolve(Buffer.concat(chunks).toString('utf8')); });
52
+ req.on('error', reject);
53
+ });
54
+ }
55
+
56
+ /**
57
+ * The JSON body of an Express request, whoever parsed it.
58
+ *
59
+ * Exported because two consumers need the same Express fact: this adapter, and the
60
+ * middlewares in `@fougere/app/express`. `express.json()` may or may not have run —
61
+ * an adapter that only works when the host app is configured a particular way is a
62
+ * footgun — so this trusts `req.body` when it is there and drains the stream when it
63
+ * is not. Parsed once per request: a drained stream answers empty the second time.
64
+ */
65
+ export function readExpressBody(req: any): Promise<unknown> {
66
+ if (req.__fougereBody) return req.__fougereBody;
67
+
68
+ const verb = String(req.method ?? 'GET').toUpperCase();
69
+ req.__fougereBody = (async () => {
70
+ if (verb === 'GET' || verb === 'HEAD') return {};
71
+ // `null` is parsed JSON, not evidence that no parser ran. Falling through here
72
+ // turned an explicit domain value into `{}` (or tried to drain an already consumed
73
+ // stream), while every other door kept it.
74
+ if (req.body !== undefined) return req.body;
75
+ const contentType = String(req.headers?.['content-type'] ?? '');
76
+ if (!contentType.toLowerCase().includes('json')) return {};
77
+ const raw = await readRawBody(req);
78
+ if (!raw) return {};
79
+ try {
80
+ return JSON.parse(raw);
81
+ } catch (cause) {
82
+ throw new MalformedJsonError({ cause });
83
+ }
84
+ })();
85
+ return req.__fougereBody;
86
+ }
87
+
88
+ function buildContext(req: any): RequestContext {
89
+ // Kept as a plain string: a real request may be HEAD or OPTIONS, which the
90
+ // interface's `HttpMethod` does not name. Casting here would have made the
91
+ // "does this verb carry a body" question unaskable — it is asked below.
92
+ const verb: string = req.method.toUpperCase();
93
+ let request: Request | undefined;
94
+
95
+ // One reader for both consumers — see `readExpressBody`.
96
+ const body = () => readExpressBody(req);
97
+
98
+ return {
99
+ // The Request carries no body: Express may have consumed the stream already,
100
+ // and a Request built around a drained one lies. `body()` is the honest reader,
101
+ // and it is what every consumer in this repo calls.
102
+ get request(): Request {
103
+ if (!request) {
104
+ const host = req.headers?.host ?? 'localhost';
105
+ const url = `${req.protocol ?? 'http'}://${host}${req.originalUrl ?? req.url}`;
106
+ request = new Request(url, {
107
+ method: verb,
108
+ headers: new Headers(
109
+ Object.entries(req.headers ?? {})
110
+ .filter((entry): entry is [string, string] => typeof entry[1] === 'string'),
111
+ ),
112
+ });
113
+ }
114
+ return request;
115
+ },
116
+ method: verb as HttpMethod,
117
+ path: req.path ?? (req.originalUrl ?? req.url ?? '').split('?')[0],
118
+ params: req.params ?? {},
119
+ query: Object.fromEntries(
120
+ Object.entries(req.query ?? {}).map(([key, value]) => [key, String(Array.isArray(value) ? value[0] : value)]),
121
+ ),
122
+ body,
123
+ state: {},
124
+ };
125
+ }
126
+
127
+ function sendResponse(res: any, result: ResponseResult): void {
128
+ if (result.headers) {
129
+ for (const [key, value] of Object.entries(result.headers)) res.set(key, value);
130
+ }
131
+ res.status(result.status);
132
+ // `raw` means "do not JSON-serialize" — `send` on a string writes it as-is, and
133
+ // the content-type rides in `headers`, stated by the producer rather than guessed.
134
+ if (result.raw) res.send(result.data);
135
+ else res.json(result.data);
136
+ }
137
+
138
+ /**
139
+ * Create an HttpRouter backed by an Express app.
140
+ *
141
+ * ```ts
142
+ * import express from 'express'
143
+ * import { createExpressRouter } from '@fougere/http'
144
+ *
145
+ * const app = express()
146
+ * const router = createExpressRouter(app)
147
+ * ```
148
+ */
149
+ export function createExpressRouter(app: ExpressLike): HttpRouter {
150
+ const globalMiddlewares: Middleware[] = [];
151
+ const scopedMiddlewares: { path: string; mw: Middleware }[] = [];
152
+
153
+ // Run matching middlewares as an onion chain around the handler
154
+ function runMiddlewares(ctx: RequestContext, handler: Handler): Promise<ResponseResult> {
155
+ const matching = [
156
+ ...globalMiddlewares,
157
+ ...scopedMiddlewares.filter((s) => ctx.path.startsWith(s.path)).map((s) => s.mw),
158
+ ];
159
+
160
+ let index = 0;
161
+ const next = (): Promise<ResponseResult> => {
162
+ if (index < matching.length) return matching[index++](ctx, next);
163
+ return handler(ctx);
164
+ };
165
+ return next();
166
+ }
167
+
168
+ return {
169
+ use(...args: [Middleware] | [string, Middleware]): void {
170
+ const [pathOrMw, maybeMw] = args;
171
+ if (typeof pathOrMw === 'string') {
172
+ // Strip a trailing /* — prefix matching here is `startsWith`, as in the
173
+ // Fastify adapter, and Express 5 spells its own splat differently anyway.
174
+ scopedMiddlewares.push({ path: pathOrMw.replace(/\/\*$/, ''), mw: maybeMw as Middleware });
175
+ } else {
176
+ globalMiddlewares.push(pathOrMw as Middleware);
177
+ }
178
+ },
179
+
180
+ on(method: HttpMethod, path: string, handler: Handler): void {
181
+ app[METHOD_MAP[method]](path, async (req: any, res: any, next: Function) => {
182
+ try {
183
+ sendResponse(res, await runMiddlewares(buildContext(req), handler));
184
+ } catch (err) {
185
+ // This adapter is the one that parses JSON, so it is the one that answers
186
+ // for it — same 400 the Hono adapter gives, which is the other self-parsing
187
+ // one. Fastify's own parser answers before we ever see the request.
188
+ if (err instanceof MalformedJsonError) {
189
+ sendResponse(res, { status: 400, data: { code: 'BAD_REQUEST', message: 'Malformed JSON body' } });
190
+ return;
191
+ }
192
+ if ((err as { statusCode?: number })?.statusCode === 413) {
193
+ sendResponse(res, { status: 413, data: { code: 'PAYLOAD_TOO_LARGE', message: 'Payload too large' } });
194
+ return;
195
+ }
196
+ // Anything else goes to Express's error pipeline rather than crashing the
197
+ // process — an app that already has an error handler keeps using it.
198
+ next(err);
199
+ }
200
+ });
201
+ },
202
+ };
203
+ }
package/src/fastify.ts ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Fastify adapter — bridges a Fastify server to the HttpRouter interface.
3
+ */
4
+ import type { HttpRouter, HttpMethod, RequestContext, ResponseResult, Middleware, Handler } from './router.js';
5
+
6
+ interface FastifyLike {
7
+ addHook(hook: string, handler: Function): void;
8
+ get(path: string, handler: Function): void;
9
+ post(path: string, handler: Function): void;
10
+ put(path: string, handler: Function): void;
11
+ patch(path: string, handler: Function): void;
12
+ delete(path: string, handler: Function): void;
13
+ }
14
+
15
+ const METHOD_MAP: Record<HttpMethod, 'get' | 'post' | 'put' | 'patch' | 'delete'> = {
16
+ GET: 'get',
17
+ POST: 'post',
18
+ PUT: 'put',
19
+ PATCH: 'patch',
20
+ DELETE: 'delete',
21
+ };
22
+
23
+ function buildContext(req: any): RequestContext {
24
+ const method = req.method.toUpperCase();
25
+ let request: Request | undefined;
26
+
27
+ return {
28
+ // Deferred, and it re-serializes a body fastify already parsed — which is why
29
+ // paying for it on every call was worth removing rather than optimising.
30
+ get request(): Request {
31
+ if (!request) {
32
+ const url = `${req.protocol ?? 'http'}://${req.hostname ?? 'localhost'}${req.url}`;
33
+ const hasBody = method !== 'GET' && method !== 'HEAD';
34
+ request = new Request(url, {
35
+ method,
36
+ headers: new Headers(req.headers as Record<string, string>),
37
+ ...(hasBody && req.body !== undefined ? { body: JSON.stringify(req.body) } : {}),
38
+ });
39
+ }
40
+ return request;
41
+ },
42
+ method: method as HttpMethod,
43
+ path: req.url,
44
+ params: req.params ?? {},
45
+ query: req.query ?? {},
46
+ // Absence alone gets the empty protocol object. `null` is valid parsed JSON and must
47
+ // reach the core unchanged when the contract admits it.
48
+ body: async () => req.body === undefined ? {} : req.body,
49
+ state: {},
50
+ };
51
+ }
52
+
53
+ function sendResponse(reply: any, result: ResponseResult): void {
54
+ if (result.headers) {
55
+ for (const [k, v] of Object.entries(result.headers)) {
56
+ // Fastify handles arrays natively (multiple headers)
57
+ reply.header(k, v);
58
+ }
59
+ }
60
+ // `raw` needs no branch here: fastify serializes a string as-is and an object as JSON,
61
+ // and the content-type rides in `headers` — the producer states it, we do not guess.
62
+ reply.status(result.status).send(result.data);
63
+ }
64
+
65
+ /**
66
+ * Create an HttpRouter backed by a Fastify server.
67
+ *
68
+ * ```ts
69
+ * import Fastify from 'fastify'
70
+ * import { createFastifyRouter } from '@fougere/http'
71
+ *
72
+ * const fastify = Fastify()
73
+ * const router = createFastifyRouter(fastify)
74
+ * ```
75
+ */
76
+ export function createFastifyRouter(server: FastifyLike): HttpRouter {
77
+ const globalMiddlewares: Middleware[] = [];
78
+ const scopedMiddlewares: { path: string; mw: Middleware }[] = [];
79
+
80
+ // Run matching middlewares as an onion chain around the handler
81
+ function runMiddlewares(
82
+ ctx: RequestContext,
83
+ handler: Handler,
84
+ ): Promise<ResponseResult> {
85
+ const matching = [
86
+ ...globalMiddlewares,
87
+ ...scopedMiddlewares
88
+ .filter((s) => ctx.path.startsWith(s.path))
89
+ .map((s) => s.mw),
90
+ ];
91
+
92
+ let index = 0;
93
+ const next = (): Promise<ResponseResult> => {
94
+ if (index < matching.length) {
95
+ return matching[index++](ctx, next);
96
+ }
97
+ return handler(ctx);
98
+ };
99
+ return next();
100
+ }
101
+
102
+ return {
103
+ use(...args: [Middleware] | [string, Middleware]): void {
104
+ const [pathOrMw, maybeMw] = args;
105
+ if (typeof pathOrMw === 'string') {
106
+ // Strip trailing /* — Fastify adapter uses startsWith for prefix matching
107
+ const normalizedPath = pathOrMw.replace(/\/\*$/, '');
108
+ scopedMiddlewares.push({ path: normalizedPath, mw: maybeMw as Middleware });
109
+ } else {
110
+ globalMiddlewares.push(pathOrMw as Middleware);
111
+ }
112
+ },
113
+
114
+ on(method: HttpMethod, path: string, handler: Handler): void {
115
+ const m = METHOD_MAP[method];
116
+ server[m](path, async (req: any, reply: any) => {
117
+ const ctx = buildContext(req);
118
+ const result = await runMiddlewares(ctx, handler);
119
+ sendResponse(reply, result);
120
+ });
121
+ },
122
+ };
123
+ }
package/src/hono.ts ADDED
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Hono adapter — bridges a Hono app to the HttpRouter interface.
3
+ */
4
+ import {
5
+ MalformedJsonError, PASSTHROUGH,
6
+ type HttpRouter, type HttpMethod, type RequestContext, type ResponseResult,
7
+ type Middleware, type Handler,
8
+ } from './router.js';
9
+
10
+ interface HonoLike {
11
+ use(path: string, ...handlers: Function[]): void;
12
+ use(...handlers: Function[]): void;
13
+ get(path: string, handler: Function): void;
14
+ post(path: string, handler: Function): void;
15
+ put(path: string, handler: Function): void;
16
+ patch(path: string, handler: Function): void;
17
+ delete(path: string, handler: Function): void;
18
+ }
19
+
20
+ const METHOD_MAP: Record<HttpMethod, 'get' | 'post' | 'put' | 'patch' | 'delete'> = {
21
+ GET: 'get',
22
+ POST: 'post',
23
+ PUT: 'put',
24
+ PATCH: 'patch',
25
+ DELETE: 'delete',
26
+ };
27
+
28
+ function buildContext(c: any): RequestContext {
29
+ // Reuse state across middleware/handler calls within the same request
30
+ const state = c.get('_fougereState') ?? {};
31
+ c.set('_fougereState', state);
32
+ return {
33
+ request: c.req.raw,
34
+ method: c.req.method.toUpperCase() as HttpMethod,
35
+ path: c.req.path,
36
+ params: c.req.param() ?? {},
37
+ query: c.req.query() ?? {},
38
+ body: async () => {
39
+ const method = c.req.method.toUpperCase();
40
+ const contentType = c.req.header?.('content-type') ?? c.req.raw?.headers?.get?.('content-type') ?? '';
41
+ if (method === 'GET' || method === 'HEAD' || !contentType.toLowerCase().includes('json')) return {};
42
+ try {
43
+ return await c.req.json();
44
+ } catch (cause) {
45
+ throw new MalformedJsonError({ cause });
46
+ }
47
+ },
48
+ state,
49
+ };
50
+ }
51
+
52
+ function sendResponse(c: any, result: ResponseResult): Response {
53
+ if (result.headers) {
54
+ for (const [k, v] of Object.entries(result.headers)) {
55
+ if (Array.isArray(v)) {
56
+ for (const item of v) c.header(k, item, { append: true });
57
+ } else {
58
+ c.header(k, v);
59
+ }
60
+ }
61
+ }
62
+ if (result.data === undefined || result.data === null) {
63
+ return c.body(null, result.status);
64
+ }
65
+ if (result.raw) {
66
+ return c.body(result.data, result.status);
67
+ }
68
+ return c.json(result.data, result.status);
69
+ }
70
+
71
+ function malformedJsonResponse(c: any): Response {
72
+ return sendResponse(c, { status: 400, data: { code: 'BAD_REQUEST', message: 'Malformed JSON body' } });
73
+ }
74
+
75
+ /**
76
+ * Create an HttpRouter backed by a Hono app.
77
+ *
78
+ * ```ts
79
+ * import { Hono } from 'hono'
80
+ * import { createHonoRouter } from '@fougere/http'
81
+ *
82
+ * const hono = new Hono()
83
+ * const router = createHonoRouter(hono)
84
+ * ```
85
+ */
86
+ export function createHonoRouter(app: HonoLike): HttpRouter {
87
+ return {
88
+ use(...args: [Middleware] | [string, Middleware]): void {
89
+ const [pathOrMw, maybeMw] = args;
90
+ const path = typeof pathOrMw === 'string' ? pathOrMw : undefined;
91
+ const mw: Middleware = (typeof pathOrMw === 'function' ? pathOrMw : maybeMw) as Middleware;
92
+
93
+ const honoMiddleware = async (c: any, next: Function) => {
94
+ try {
95
+ const ctx = buildContext(c);
96
+ const result = await mw(ctx, async () => {
97
+ await next();
98
+ // After next(), Hono has already set the response. The sentinel says so, and it
99
+ // is a symbol on purpose — see PASSTHROUGH.
100
+ return { status: c.res.status, data: PASSTHROUGH };
101
+ });
102
+ // Anything else is the middleware's own response, empty body included.
103
+ if (result.data !== PASSTHROUGH) {
104
+ return sendResponse(c, result);
105
+ }
106
+ } catch (err) {
107
+ if (err instanceof MalformedJsonError) return malformedJsonResponse(c);
108
+ throw err;
109
+ }
110
+ };
111
+
112
+ if (path) {
113
+ // Hono requires /* suffix for prefix matching on middleware
114
+ const honoPath = path.endsWith('/*') ? path : `${path}/*`;
115
+ app.use(honoPath, honoMiddleware);
116
+ } else {
117
+ app.use(honoMiddleware);
118
+ }
119
+ },
120
+
121
+ on(method: HttpMethod, path: string, handler: Handler): void {
122
+ const m = METHOD_MAP[method];
123
+ app[m](path, async (c: any) => {
124
+ const ctx = buildContext(c);
125
+ try {
126
+ const result = await handler(ctx);
127
+ return sendResponse(c, result);
128
+ } catch (err) {
129
+ if (err instanceof MalformedJsonError) return malformedJsonResponse(c);
130
+ throw err;
131
+ }
132
+ });
133
+ },
134
+ };
135
+ }
package/src/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type {
2
+ HttpMethod,
3
+ RequestContext,
4
+ ResponseResult,
5
+ Handler,
6
+ Next,
7
+ Middleware,
8
+ HttpRouter,
9
+ } from './router.js';
10
+ export { MalformedJsonError, PASSTHROUGH } from './router.js';
11
+
12
+ export { createHonoRouter } from './hono.js';
13
+ export { createFastifyRouter } from './fastify.js';
14
+ export { createExpressRouter, readExpressBody } from './express.js';
15
+ export { httpLogger } from './logger.js';
package/src/logger.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * HTTP request logger middleware.
3
+ */
4
+ import type { Middleware } from './router.js';
5
+
6
+ interface LoggerLike {
7
+ info(msg: string, ...args: unknown[]): void;
8
+ warn(msg: string, ...args: unknown[]): void;
9
+ }
10
+
11
+ const STATUS_COLORS: Record<string, string> = {
12
+ '2': '\x1b[32m', // green
13
+ '3': '\x1b[36m', // cyan
14
+ '4': '\x1b[33m', // yellow
15
+ '5': '\x1b[31m', // red
16
+ };
17
+
18
+ /**
19
+ * HTTP request logger — logs method, path, status, and duration.
20
+ *
21
+ * ```ts
22
+ * import { httpLogger } from '@fougere/http'
23
+ *
24
+ * router.use(httpLogger(logger))
25
+ * ```
26
+ */
27
+ export function httpLogger(logger: LoggerLike): Middleware {
28
+ return async (ctx, next) => {
29
+ const start = performance.now();
30
+ const result = await next();
31
+ const ms = (performance.now() - start).toFixed(1);
32
+
33
+ const method = ctx.method.padEnd(7);
34
+ const status = result.status;
35
+ const log = status >= 400 ? 'warn' : 'info';
36
+
37
+ logger[log](`${method} ${ctx.path} ${status} ${ms}ms`);
38
+
39
+ return result;
40
+ };
41
+ }
package/src/router.ts ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * @fougere/http — framework-agnostic HTTP router interface.
3
+ *
4
+ * Adapters (Hono, Fastify, etc.) implement HttpRouter.
5
+ * Consumers (schema-rest, schema-graphql) program against it.
6
+ */
7
+
8
+ export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
9
+
10
+ export interface RequestContext {
11
+ /**
12
+ * The request as a Web Standard `Request`.
13
+ *
14
+ * Built on FIRST ACCESS. Every adapter used to build one eagerly, and no consumer
15
+ * in this repo reads it — measured, zero call sites outside the adapters and their
16
+ * tests, while the construction cost a fifth of the port's throughput on the engines
17
+ * that have no `Request` of their own (express, fastify). Hono hands over the one it
18
+ * already has, so nothing is deferred there.
19
+ *
20
+ * The fields beside it — `method`, `path`, `params`, `query`, `body` — are what the
21
+ * projections read, and they are not derived from this one.
22
+ */
23
+ readonly request: Request;
24
+ method: HttpMethod;
25
+ path: string;
26
+ params: Record<string, string>;
27
+ query: Record<string, string>;
28
+ body: () => Promise<unknown>;
29
+ /** Extensible bag — middlewares deposit data here (auth, session, etc.). */
30
+ state: Record<string, unknown>;
31
+ }
32
+
33
+ export interface ResponseResult {
34
+ status: number;
35
+ data: unknown;
36
+ headers?: Record<string, string | string[]>;
37
+ /** Send data as raw body (not JSON-serialized). Use for HTML, XML, CSV, plain text, etc. */
38
+ raw?: boolean;
39
+ }
40
+
41
+ /** Raised by an adapter when a request claims JSON but cannot be parsed. */
42
+ export class MalformedJsonError extends Error {
43
+ constructor(options?: ErrorOptions) {
44
+ super('Malformed JSON body', options);
45
+ this.name = 'MalformedJsonError';
46
+ }
47
+ }
48
+
49
+ export type Handler = (ctx: RequestContext) => Promise<ResponseResult>;
50
+ export type Next = () => Promise<ResponseResult>;
51
+ /**
52
+ * What `next()` answers when the host framework — not us — owns the chain.
53
+ *
54
+ * Express and Fastify build their own middleware chain here, so a middleware's return IS
55
+ * the response and there is nothing to distinguish. Hono owns its chain, so its adapter has
56
+ * to tell "the middleware answered" from "the middleware delegated", and it used
57
+ * `data === null` for the second. `null` is a legal body — `{ status: 403, data: null }` is
58
+ * the ordinary spelling of a deny — so a middleware that REFUSED with no body was read as
59
+ * having delegated: it was silently bypassed on Hono and honoured on Fastify. One contract,
60
+ * two verdicts, on a value inside the value space.
61
+ *
62
+ * A symbol cannot be produced by accident, and no middleware author ever writes it: only an
63
+ * adapter that does not own its chain returns it from the `next` it hands out.
64
+ */
65
+ export const PASSTHROUGH: unique symbol = Symbol('fougere.http.passthrough');
66
+
67
+ export type Middleware = (ctx: RequestContext, next: Next) => Promise<ResponseResult>;
68
+
69
+ export interface HttpRouter {
70
+ /** Register a global middleware. */
71
+ use(middleware: Middleware): void;
72
+ /** Register a middleware scoped to a path prefix. */
73
+ use(path: string, middleware: Middleware): void;
74
+ /** Register a route handler. */
75
+ on(method: HttpMethod, path: string, handler: Handler): void;
76
+ }