@gusnips/server 0.1.1 → 0.2.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
@@ -42,6 +42,18 @@ paginated(rows, { total: 128, limit: 20, offset: 100 });
42
42
  `hasMore` is computed from the rows you actually returned, not from `limit`, so a page cut short
43
43
  by a filter still answers honestly.
44
44
 
45
+ **What comes back is an answer, not a body.** A framework wants the `body`:
46
+
47
+ ```ts
48
+ return c.json(ok(data), 200); // WRONG: {"status":200,"body":{"data":…}}
49
+ return c.json(ok(data).body, 200); // the envelope
50
+ ```
51
+
52
+ Nothing catches the first line. `c.json` takes any JSON value, so the types hold; the status is
53
+ still 200, so a health probe and a deploy gate both pass; and a test that calls `ok` never sees
54
+ the body its caller sends. That shipped, and a client found it fifty minutes later. On Hono,
55
+ import the four adapters from `@gusnips/server/hono` and the question does not arise.
56
+
45
57
  ## Refusing a request
46
58
 
47
59
  Declare your own codes and what status each one answers with. The `satisfies` is the line that
@@ -256,6 +268,19 @@ app.onError(errorHandler({ errorResponse, logger }));
256
268
  app.notFound(notFoundHandler(errorResponse(errors.notFound("Route"))));
257
269
  ```
258
270
 
271
+ ```ts
272
+ import { created, noContent, ok, paginated } from "@gusnips/server/hono";
273
+
274
+ app.get("/users/:id", (c) => ok(c, user)); // { data: user }, 200
275
+ app.post("/users", (c) => created(c, user)); // 201
276
+ app.get("/users", (c) => paginated(c, rows, { total, limit, offset }));
277
+ app.delete("/users/:id", (c) => noContent(c)); // 204, no body, no content-type
278
+ ```
279
+
280
+ Three adopters wrote those four functions by hand before they were here, and one of the three
281
+ got the unwrap wrong in production. `ok` takes an explicit status for the cases that are not
282
+ 200 — `ok(c, job, 202)` where the route accepted rather than answered.
283
+
259
284
  **`errorBoundary` is not optional.** Hono hands `onError` only what is `instanceof Error`.
260
285
  Anything else is rethrown past every layer and escapes as an unhandled rejection: no answer, a
261
286
  dropped connection, and a browser that reports it as a CORS failure — which sends whoever reads
@@ -6,6 +6,11 @@
6
6
  * app.onError(errorHandler({ errorResponse, logger }));
7
7
  * app.notFound(notFoundHandler(errorResponse(errors.notFound("Route"))));
8
8
  *
9
+ * `ok`, `created`, `paginated` and `noContent` are the success half: they take the `Context` and
10
+ * put the envelope on the wire, so no route has to unwrap the `{ status, body }` answer that the
11
+ * framework-free builders return. Three adopters wrote them by hand and one got that unwrap wrong
12
+ * in production — see `responses.ts` beside this file.
13
+ *
9
14
  * and, in a test of the real app, `assertEveryRouteGuarded(app, { isPublic })`, with the rule the
10
15
  * app itself uses for what anyone may call.
11
16
  */
@@ -13,6 +18,7 @@ export { errorBoundary, errorHandler, notFoundHandler } from "./errors.ts";
13
18
  export type { ErrorHandlerOptions } from "./errors.ts";
14
19
  export { assertEveryRouteGuarded, guard, underAny } from "./guards.ts";
15
20
  export type { GuardCheckOptions } from "./guards.ts";
21
+ export { created, noContent, ok, paginated } from "./responses.ts";
16
22
  export { requestLogger } from "./request-logger.ts";
17
23
  export type { RequestLoggerOptions, RequestVariables } from "./request-logger.ts";
18
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hono/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hono/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC3E,YAAY,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -6,10 +6,16 @@
6
6
  * app.onError(errorHandler({ errorResponse, logger }));
7
7
  * app.notFound(notFoundHandler(errorResponse(errors.notFound("Route"))));
8
8
  *
9
+ * `ok`, `created`, `paginated` and `noContent` are the success half: they take the `Context` and
10
+ * put the envelope on the wire, so no route has to unwrap the `{ status, body }` answer that the
11
+ * framework-free builders return. Three adopters wrote them by hand and one got that unwrap wrong
12
+ * in production — see `responses.ts` beside this file.
13
+ *
9
14
  * and, in a test of the real app, `assertEveryRouteGuarded(app, { isPublic })`, with the rule the
10
15
  * app itself uses for what anyone may call.
11
16
  */
12
17
  export { errorBoundary, errorHandler, notFoundHandler } from "./errors.js";
13
18
  export { assertEveryRouteGuarded, guard, underAny } from "./guards.js";
19
+ export { created, noContent, ok, paginated } from "./responses.js";
14
20
  export { requestLogger } from "./request-logger.js";
15
21
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hono/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/hono/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The success half of the Hono edge: four adapters that put the envelope on the wire.
3
+ *
4
+ * They exist because the builders in `../responses.ts` return an ANSWER — `{ status, body }` —
5
+ * and every Hono adopter therefore has to unwrap one before `c.json` sees it. Three of three
6
+ * wrote these same four functions by hand, and one of the three wrote `c.json(ok(data))` instead
7
+ * of `c.json(ok(data).body)`: every 200 from a live API answered
8
+ * `{"status":200,"body":{"data":…}}` for fifty minutes. Nothing caught it. `c.json` takes any
9
+ * JSON value, so the types were satisfied; the status was still 200, so every probe and every
10
+ * deploy gate was satisfied; and a test that calls the module never sees the body its caller
11
+ * sends. A client found it, because a client is the only reader that parses the envelope.
12
+ *
13
+ * A doc comment would have been read by whoever was already careful. These make the wrong line
14
+ * unreachable, which is the only fix available to a package that owns both sides of the seam.
15
+ */
16
+ import type { PaginationMeta } from "@gusnips/http";
17
+ import type { Context } from "hono";
18
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
19
+ /** `return ok(c, user)` — or `ok(c, job, 202)` where the route accepted rather than answered. */
20
+ export declare function ok<T>(c: Context, data: T, status?: ContentfulStatusCode): Response & import("hono").TypedResponse<PaginationMeta | T | undefined extends bigint | readonly bigint[] ? never : { [K in keyof {
21
+ data: T;
22
+ meta?: PaginationMeta | undefined;
23
+ } as (import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] extends infer T_1 ? T_1 extends import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] ? T_1 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) extends true ? never : K]: boolean extends (import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] extends infer T_2 ? T_2 extends import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] ? T_2 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) ? import("hono/utils/types").JSONParsed<import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K], bigint | readonly bigint[]> | undefined : import("hono/utils/types").JSONParsed<import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K], bigint | readonly bigint[]>; }, 429 | 500 | 200 | 201 | 400 | 401 | 100 | 102 | 103 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">;
24
+ export declare function created<T>(c: Context, data: T): Response & import("hono").TypedResponse<PaginationMeta | T | undefined extends bigint | readonly bigint[] ? never : { [K in keyof {
25
+ data: T;
26
+ meta?: PaginationMeta | undefined;
27
+ } as (import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] extends infer T_1 ? T_1 extends import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] ? T_1 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) extends true ? never : K]: boolean extends (import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] extends infer T_2 ? T_2 extends import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K] ? T_2 extends import("hono/utils/types").InvalidJSONValue ? true : false : never : never) ? import("hono/utils/types").JSONParsed<import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K], bigint | readonly bigint[]> | undefined : import("hono/utils/types").JSONParsed<import("@gusnips/http").ApiSuccess<T, PaginationMeta>[K], bigint | readonly bigint[]>; }, 429 | 500 | 200 | 201 | 400 | 401 | 100 | 102 | 103 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">;
28
+ /** `hasMore` is computed from the rows actually returned — see `paginated` in `../responses.ts`. */
29
+ export declare function paginated<T>(c: Context, rows: T[], meta: Omit<PaginationMeta, "hasMore">): Response & import("hono").TypedResponse<{
30
+ data: import("hono/utils/types").JSONParsed<T extends import("hono/utils/types").InvalidJSONValue ? null : T, bigint | readonly bigint[]>[];
31
+ meta?: {
32
+ total: number;
33
+ limit: number;
34
+ offset: number;
35
+ hasMore: boolean;
36
+ } | undefined;
37
+ }, 200, "json">;
38
+ /**
39
+ * `c.body(null, 204)`, not `c.json`: a 204 carries no body, and `c.json(null, 204)` writes the
40
+ * four bytes `null` and a `content-type` header under a status that promises neither.
41
+ */
42
+ export declare function noContent(c: Context): Response & import("hono").TypedResponse<null, 204, "body">;
43
+ //# sourceMappingURL=responses.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../../src/hono/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGnE,iGAAiG;AACjG,wBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,GAAE,oBAA0B;;;ooCAE5E;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;;;ooCAE7C;AAED,oGAAoG;AACpG,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC;;;;;;;;gBAExF;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,8DAEnC"}
@@ -0,0 +1,20 @@
1
+ import { ok as okBody, paginated as paginatedBody } from "../responses.js";
2
+ /** `return ok(c, user)` — or `ok(c, job, 202)` where the route accepted rather than answered. */
3
+ export function ok(c, data, status = 200) {
4
+ return c.json(okBody(data).body, status);
5
+ }
6
+ export function created(c, data) {
7
+ return ok(c, data, 201);
8
+ }
9
+ /** `hasMore` is computed from the rows actually returned — see `paginated` in `../responses.ts`. */
10
+ export function paginated(c, rows, meta) {
11
+ return c.json(paginatedBody(rows, meta).body, 200);
12
+ }
13
+ /**
14
+ * `c.body(null, 204)`, not `c.json`: a 204 carries no body, and `c.json(null, 204)` writes the
15
+ * four bytes `null` and a `content-type` header under a status that promises neither.
16
+ */
17
+ export function noContent(c) {
18
+ return c.body(null, 204);
19
+ }
20
+ //# sourceMappingURL=responses.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responses.js","sourceRoot":"","sources":["../../src/hono/responses.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,EAAE,IAAI,MAAM,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3E,iGAAiG;AACjG,MAAM,UAAU,EAAE,CAAI,CAAU,EAAE,IAAO,EAAE,SAA+B,GAAG;IAC3E,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,OAAO,CAAI,CAAU,EAAE,IAAO;IAC5C,OAAO,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,SAAS,CAAI,CAAU,EAAE,IAAS,EAAE,IAAqC;IACvF,OAAO,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,CAAU;IAClC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAC3B,CAAC"}
@@ -20,8 +20,15 @@
20
20
  * A plain object passed directly as `meta.error` is the other door. Hono wraps it, but a worker,
21
21
  * a fire-and-forget catch or a database client outside Hono does not. `error` is the raw-error slot
22
22
  * the logger documents, so it gets the same treatment as `cause`; an ordinary metadata object under
23
- * any other key stays untouched. `name` and `message` come along because a rejection object usually
24
- * carries them and a line with neither says nothing at all.
23
+ * any other key stays untouched. `name`, `message` and `stack` come along because a rejection
24
+ * object usually carries them and a line with none of them says nothing at all.
25
+ *
26
+ * `stack` is here because an adopter's queue found it missing. A job that dies is stored by its
27
+ * queue through a serializer, so the error reaching the dead-letter handler is a plain object with
28
+ * its stack in a string — and that stack is the whole of the "why" in a line whose job is to say
29
+ * which job died and why. The Error branch below has always written `stack` unfiltered; leaving it
30
+ * out here was an asymmetry, not a decision. It is a conventional field name, not one an SDK hangs
31
+ * its own inputs off, which is what the allow-list exists to stop.
25
32
  *
26
33
  * Deliberate state it does NOT keep: context an app attaches on purpose. That belongs in the
27
34
  * logger's `meta`, which is untouched — `cause` is not the place for it, and one incident of a
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/logger/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2EH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtE;AA+BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,aAAa,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAiCvF;AAED,qGAAqG;AACrG,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAAqB,CAAC"}
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/logger/serialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2EH;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEtE;AAiCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,aAAa,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAiCvF;AAED,qGAAqG;AACrG,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAAqB,CAAC"}
@@ -90,8 +90,15 @@ function keptValue(key, value) {
90
90
  * A plain object passed directly as `meta.error` is the other door. Hono wraps it, but a worker,
91
91
  * a fire-and-forget catch or a database client outside Hono does not. `error` is the raw-error slot
92
92
  * the logger documents, so it gets the same treatment as `cause`; an ordinary metadata object under
93
- * any other key stays untouched. `name` and `message` come along because a rejection object usually
94
- * carries them and a line with neither says nothing at all.
93
+ * any other key stays untouched. `name`, `message` and `stack` come along because a rejection
94
+ * object usually carries them and a line with none of them says nothing at all.
95
+ *
96
+ * `stack` is here because an adopter's queue found it missing. A job that dies is stored by its
97
+ * queue through a serializer, so the error reaching the dead-letter handler is a plain object with
98
+ * its stack in a string — and that stack is the whole of the "why" in a line whose job is to say
99
+ * which job died and why. The Error branch below has always written `stack` unfiltered; leaving it
100
+ * out here was an asymmetry, not a decision. It is a conventional field name, not one an SDK hangs
101
+ * its own inputs off, which is what the allow-list exists to stop.
95
102
  *
96
103
  * Deliberate state it does NOT keep: context an app attaches on purpose. That belongs in the
97
104
  * logger's `meta`, which is untouched — `cause` is not the place for it, and one incident of a
@@ -103,11 +110,13 @@ export function narrowErrorLike(value) {
103
110
  function narrow(value, seen) {
104
111
  seen.add(value);
105
112
  const out = {};
106
- const { name, message, cause } = value;
113
+ const { name, message, stack, cause } = value;
107
114
  if (typeof name === "string")
108
115
  out.name = name;
109
116
  if (typeof message === "string")
110
117
  out.message = message;
118
+ if (typeof stack === "string")
119
+ out.stack = stack;
111
120
  for (const [k, v] of Object.entries(value)) {
112
121
  if (KEPT_ERROR_FIELDS.has(k))
113
122
  out[k] = keptValue(k, v);
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/logger/serialize.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"serialize.js","sourceRoot":"","sources":["../../src/logger/serialize.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,20 @@
11
11
  * callers. The framework adapter is eight lines and lives in `/hono`.
12
12
  */
13
13
  import type { ApiError, ApiSuccess, PaginationMeta } from "@gusnips/http";
14
- /** Every 2xx body is `{ data }`, or `{ data, meta }` where a route has counts to report. */
14
+ /**
15
+ * The `body` is `{ data }`, or `{ data, meta }` where a route has counts to report.
16
+ *
17
+ * What comes back is an ANSWER — `{ status, body }` — not a body, because this layer is
18
+ * framework-free and has to hand its caller a status too. An adapter takes `.body`:
19
+ *
20
+ * return c.json(ok(data), status); // WRONG: {"status":200,"body":{"data":…}}
21
+ * return c.json(ok(data).body, status); // the envelope
22
+ *
23
+ * Nothing catches the first line — `c.json` takes any JSON value, the status is still whatever
24
+ * you passed, and a test that calls this module never sees the body its caller sends. It shipped,
25
+ * and a client found it. On Hono, import `ok` from `@gusnips/server/hono` instead: the four
26
+ * adapters there take the `Context`, and the question does not arise.
27
+ */
15
28
  export declare function ok<T, M = PaginationMeta>(data: T, meta?: M): {
16
29
  status: 200;
17
30
  body: ApiSuccess<T, M>;
@@ -1 +1 @@
1
- {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../src/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG1E,4FAA4F;AAC5F,wBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;;;EAG1D;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;;;EAGjC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC;;;EAM5E;AAED,wBAAgB,SAAS;;;EAExB;AAED,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrB,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;CAC1C;AAED,0FAA0F;AAC1F,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM;IAClE,IAAI,EAAE,IAAI,CAAC;IACX,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM;IAC3E,sDAAsD;IACtD,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACjC,oCAAoC;IACpC,UAAU,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnC;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAC9B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuDD;;;;GAIG;AACH,UAAU,QAAQ;IAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA6BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;CACtC,GAAG,eAAe,EAAE,CAUpB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3F,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,IAIP,KAAK,OAAO,KAAG,WAAW,CAAC,IAAI,CAAC,CA8C/D"}
1
+ {"version":3,"file":"responses.d.ts","sourceRoot":"","sources":["../src/responses.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG1E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,CAAC;;;EAG1D;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;;;EAGjC;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC;;;EAM5E;AAED,wBAAgB,SAAS;;;EAExB;AAED,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACrB,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC;;;;;OAKG;IACH,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,YAAY,CAAC;CAC1C;AAED,0FAA0F;AAC1F,MAAM,WAAW,WAAW,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM;IAClE,IAAI,EAAE,IAAI,CAAC;IACX,kFAAkF;IAClF,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB,CAAC,IAAI,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM;IAC3E,sDAAsD;IACtD,QAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACjC,oCAAoC;IACpC,UAAU,EAAE,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACnC;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,SAAS,IAAI,EAAE,CAAC;IAC9B,6FAA6F;IAC7F,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAuDD;;;;GAIG;AACH,UAAU,QAAQ;IAChB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA6BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;CACtC,GAAG,eAAe,EAAE,CAUpB;AAaD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,MAAM,GAAG,MAAM,EAC3F,IAAI,EAAE,oBAAoB,CAAC,IAAI,EAAE,GAAG,CAAC,IAIP,KAAK,OAAO,KAAG,WAAW,CAAC,IAAI,CAAC,CA8C/D"}
package/dist/responses.js CHANGED
@@ -1,5 +1,18 @@
1
1
  import { AppError } from "./errors.js";
2
- /** Every 2xx body is `{ data }`, or `{ data, meta }` where a route has counts to report. */
2
+ /**
3
+ * The `body` is `{ data }`, or `{ data, meta }` where a route has counts to report.
4
+ *
5
+ * What comes back is an ANSWER — `{ status, body }` — not a body, because this layer is
6
+ * framework-free and has to hand its caller a status too. An adapter takes `.body`:
7
+ *
8
+ * return c.json(ok(data), status); // WRONG: {"status":200,"body":{"data":…}}
9
+ * return c.json(ok(data).body, status); // the envelope
10
+ *
11
+ * Nothing catches the first line — `c.json` takes any JSON value, the status is still whatever
12
+ * you passed, and a test that calls this module never sees the body its caller sends. It shipped,
13
+ * and a client found it. On Hono, import `ok` from `@gusnips/server/hono` instead: the four
14
+ * adapters there take the `Context`, and the question does not arise.
15
+ */
3
16
  export function ok(data, meta) {
4
17
  const body = meta === undefined ? { data } : { data, meta };
5
18
  return { status: 200, body };
@@ -1 +1 @@
1
- {"version":3,"file":"responses.js","sourceRoot":"","sources":["../src/responses.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"responses.js","sourceRoot":"","sources":["../src/responses.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gusnips/server",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "The layer under a TypeScript backend: one error shape, one response envelope, one retry rule, one logger.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/hono/index.ts CHANGED
@@ -6,6 +6,11 @@
6
6
  * app.onError(errorHandler({ errorResponse, logger }));
7
7
  * app.notFound(notFoundHandler(errorResponse(errors.notFound("Route"))));
8
8
  *
9
+ * `ok`, `created`, `paginated` and `noContent` are the success half: they take the `Context` and
10
+ * put the envelope on the wire, so no route has to unwrap the `{ status, body }` answer that the
11
+ * framework-free builders return. Three adopters wrote them by hand and one got that unwrap wrong
12
+ * in production — see `responses.ts` beside this file.
13
+ *
9
14
  * and, in a test of the real app, `assertEveryRouteGuarded(app, { isPublic })`, with the rule the
10
15
  * app itself uses for what anyone may call.
11
16
  */
@@ -13,5 +18,6 @@ export { errorBoundary, errorHandler, notFoundHandler } from "./errors.ts";
13
18
  export type { ErrorHandlerOptions } from "./errors.ts";
14
19
  export { assertEveryRouteGuarded, guard, underAny } from "./guards.ts";
15
20
  export type { GuardCheckOptions } from "./guards.ts";
21
+ export { created, noContent, ok, paginated } from "./responses.ts";
16
22
  export { requestLogger } from "./request-logger.ts";
17
23
  export type { RequestLoggerOptions, RequestVariables } from "./request-logger.ts";
@@ -0,0 +1,50 @@
1
+ import { Hono } from "hono";
2
+ import { describe, expect, it } from "vitest";
3
+ import { created, noContent, ok, paginated } from "./responses.ts";
4
+
5
+ /**
6
+ * Every assertion here reads the BYTES off a real request, never the return value of a builder.
7
+ * That is the point: the outage this file exists to prevent was invisible to any test that calls
8
+ * the module, because the wrong value was still a perfectly good JSON value.
9
+ */
10
+ const app = new Hono()
11
+ .get("/ok", (c) => ok(c, { id: "a" }))
12
+ .get("/accepted", (c) => ok(c, { id: "a" }, 202))
13
+ .post("/created", (c) => created(c, { id: "a" }))
14
+ .get("/page", (c) => paginated(c, [1, 2], { total: 9, limit: 2, offset: 0 }))
15
+ .get("/last-page", (c) => paginated(c, [9], { total: 9, limit: 2, offset: 8 }))
16
+ .delete("/gone", (c) => noContent(c));
17
+
18
+ describe("the Hono success adapters", () => {
19
+ it("answers the envelope itself, never the { status, body } wrapper around it", async () => {
20
+ const res = await app.request("/ok");
21
+ expect(res.status).toBe(200);
22
+ expect(await res.json()).toEqual({ data: { id: "a" } });
23
+ });
24
+
25
+ it("keeps the status the route asked for", async () => {
26
+ expect((await app.request("/accepted")).status).toBe(202);
27
+ expect((await app.request("/created", { method: "POST" })).status).toBe(201);
28
+ expect(await (await app.request("/created", { method: "POST" })).json()).toEqual({
29
+ data: { id: "a" },
30
+ });
31
+ });
32
+
33
+ it("computes hasMore from the rows returned, not from the limit", async () => {
34
+ expect(await (await app.request("/page")).json()).toEqual({
35
+ data: [1, 2],
36
+ meta: { total: 9, limit: 2, offset: 0, hasMore: true },
37
+ });
38
+ expect(await (await app.request("/last-page")).json()).toEqual({
39
+ data: [9],
40
+ meta: { total: 9, limit: 2, offset: 8, hasMore: false },
41
+ });
42
+ });
43
+
44
+ it("sends a 204 with no body and no content-type", async () => {
45
+ const res = await app.request("/gone", { method: "DELETE" });
46
+ expect(res.status).toBe(204);
47
+ expect(res.headers.get("content-type")).toBeNull();
48
+ expect(await res.text()).toBe("");
49
+ });
50
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The success half of the Hono edge: four adapters that put the envelope on the wire.
3
+ *
4
+ * They exist because the builders in `../responses.ts` return an ANSWER — `{ status, body }` —
5
+ * and every Hono adopter therefore has to unwrap one before `c.json` sees it. Three of three
6
+ * wrote these same four functions by hand, and one of the three wrote `c.json(ok(data))` instead
7
+ * of `c.json(ok(data).body)`: every 200 from a live API answered
8
+ * `{"status":200,"body":{"data":…}}` for fifty minutes. Nothing caught it. `c.json` takes any
9
+ * JSON value, so the types were satisfied; the status was still 200, so every probe and every
10
+ * deploy gate was satisfied; and a test that calls the module never sees the body its caller
11
+ * sends. A client found it, because a client is the only reader that parses the envelope.
12
+ *
13
+ * A doc comment would have been read by whoever was already careful. These make the wrong line
14
+ * unreachable, which is the only fix available to a package that owns both sides of the seam.
15
+ */
16
+ import type { PaginationMeta } from "@gusnips/http";
17
+ import type { Context } from "hono";
18
+ import type { ContentfulStatusCode } from "hono/utils/http-status";
19
+ import { ok as okBody, paginated as paginatedBody } from "../responses.ts";
20
+
21
+ /** `return ok(c, user)` — or `ok(c, job, 202)` where the route accepted rather than answered. */
22
+ export function ok<T>(c: Context, data: T, status: ContentfulStatusCode = 200) {
23
+ return c.json(okBody(data).body, status);
24
+ }
25
+
26
+ export function created<T>(c: Context, data: T) {
27
+ return ok(c, data, 201);
28
+ }
29
+
30
+ /** `hasMore` is computed from the rows actually returned — see `paginated` in `../responses.ts`. */
31
+ export function paginated<T>(c: Context, rows: T[], meta: Omit<PaginationMeta, "hasMore">) {
32
+ return c.json(paginatedBody(rows, meta).body, 200);
33
+ }
34
+
35
+ /**
36
+ * `c.body(null, 204)`, not `c.json`: a 204 carries no body, and `c.json(null, 204)` writes the
37
+ * four bytes `null` and a `content-type` header under a status that promises neither.
38
+ */
39
+ export function noContent(c: Context) {
40
+ return c.body(null, 204);
41
+ }
@@ -221,6 +221,31 @@ describe("what an Error contributes to a log line", () => {
221
221
  expect(JSON.stringify(line)).not.toContain("4242");
222
222
  });
223
223
 
224
+ it("keeps the stack of an error that crossed a queue and arrived as a plain object", () => {
225
+ // A job queue stores a failed job's error through a serializer, so the dead-letter handler is
226
+ // handed a plain object — and the stack is the whole of the "why" in a line whose job is to
227
+ // say which job died and why. Everything the SDK hung beside it still goes.
228
+ const stored = {
229
+ name: "HttpError",
230
+ message: "Not Found",
231
+ stack: "HttpError: Not Found\n at workers.ts:1:1",
232
+ status: 404,
233
+ request: { method: "GET", body: '{"query":"private"}' },
234
+ };
235
+
236
+ const line = JSON.parse(JSON.stringify({ error: stored }, errorReplacer())) as Record<
237
+ string,
238
+ Record<string, unknown>
239
+ >;
240
+
241
+ expect(line.error).toEqual({
242
+ name: "HttpError",
243
+ message: "Not Found",
244
+ stack: "HttpError: Not Found\n at workers.ts:1:1",
245
+ status: 404,
246
+ });
247
+ });
248
+
224
249
  it("allow-lists a thrown plain object passed directly as the error", () => {
225
250
  // Hono wraps a non-Error throw as a cause, but workers, fire-and-forget catches and a database
226
251
  // client's `{ code, message, details }` rejection reach the logger directly. `error` is the
@@ -94,8 +94,15 @@ function keptValue(key: string, value: unknown): unknown {
94
94
  * A plain object passed directly as `meta.error` is the other door. Hono wraps it, but a worker,
95
95
  * a fire-and-forget catch or a database client outside Hono does not. `error` is the raw-error slot
96
96
  * the logger documents, so it gets the same treatment as `cause`; an ordinary metadata object under
97
- * any other key stays untouched. `name` and `message` come along because a rejection object usually
98
- * carries them and a line with neither says nothing at all.
97
+ * any other key stays untouched. `name`, `message` and `stack` come along because a rejection
98
+ * object usually carries them and a line with none of them says nothing at all.
99
+ *
100
+ * `stack` is here because an adopter's queue found it missing. A job that dies is stored by its
101
+ * queue through a serializer, so the error reaching the dead-letter handler is a plain object with
102
+ * its stack in a string — and that stack is the whole of the "why" in a line whose job is to say
103
+ * which job died and why. The Error branch below has always written `stack` unfiltered; leaving it
104
+ * out here was an asymmetry, not a decision. It is a conventional field name, not one an SDK hangs
105
+ * its own inputs off, which is what the allow-list exists to stop.
99
106
  *
100
107
  * Deliberate state it does NOT keep: context an app attaches on purpose. That belongs in the
101
108
  * logger's `meta`, which is untouched — `cause` is not the place for it, and one incident of a
@@ -108,13 +115,15 @@ export function narrowErrorLike(value: object): Record<string, unknown> {
108
115
  function narrow(value: object, seen: WeakSet<object>): Record<string, unknown> {
109
116
  seen.add(value);
110
117
  const out: Record<string, unknown> = {};
111
- const { name, message, cause } = value as {
118
+ const { name, message, stack, cause } = value as {
112
119
  name?: unknown;
113
120
  message?: unknown;
121
+ stack?: unknown;
114
122
  cause?: unknown;
115
123
  };
116
124
  if (typeof name === "string") out.name = name;
117
125
  if (typeof message === "string") out.message = message;
126
+ if (typeof stack === "string") out.stack = stack;
118
127
  for (const [k, v] of Object.entries(value)) {
119
128
  if (KEPT_ERROR_FIELDS.has(k)) out[k] = keptValue(k, v);
120
129
  }
package/src/responses.ts CHANGED
@@ -13,7 +13,20 @@
13
13
  import type { ApiError, ApiSuccess, PaginationMeta } from "@gusnips/http";
14
14
  import { AppError } from "./errors.ts";
15
15
 
16
- /** Every 2xx body is `{ data }`, or `{ data, meta }` where a route has counts to report. */
16
+ /**
17
+ * The `body` is `{ data }`, or `{ data, meta }` where a route has counts to report.
18
+ *
19
+ * What comes back is an ANSWER — `{ status, body }` — not a body, because this layer is
20
+ * framework-free and has to hand its caller a status too. An adapter takes `.body`:
21
+ *
22
+ * return c.json(ok(data), status); // WRONG: {"status":200,"body":{"data":…}}
23
+ * return c.json(ok(data).body, status); // the envelope
24
+ *
25
+ * Nothing catches the first line — `c.json` takes any JSON value, the status is still whatever
26
+ * you passed, and a test that calls this module never sees the body its caller sends. It shipped,
27
+ * and a client found it. On Hono, import `ok` from `@gusnips/server/hono` instead: the four
28
+ * adapters there take the `Context`, and the question does not arise.
29
+ */
17
30
  export function ok<T, M = PaginationMeta>(data: T, meta?: M) {
18
31
  const body: ApiSuccess<T, M> = meta === undefined ? { data } : { data, meta };
19
32
  return { status: 200 as const, body };