@quilla-be-kit/http 0.5.0 → 0.7.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.
Files changed (63) hide show
  1. package/README.md +130 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/adapter/hono/hono-request.adapter.d.ts +6 -1
  4. package/dist/adapter/hono/hono-request.adapter.d.ts.map +1 -1
  5. package/dist/adapter/hono/hono-request.adapter.js +23 -22
  6. package/dist/adapter/hono/hono-request.adapter.js.map +1 -1
  7. package/dist/adapter/hono/hono.server.d.ts +3 -0
  8. package/dist/adapter/hono/hono.server.d.ts.map +1 -1
  9. package/dist/adapter/hono/hono.server.js +9 -3
  10. package/dist/adapter/hono/hono.server.js.map +1 -1
  11. package/dist/decorator/controller.decorator.d.ts.map +1 -1
  12. package/dist/decorator/controller.decorator.js +1 -3
  13. package/dist/decorator/controller.decorator.js.map +1 -1
  14. package/dist/decorator/route.metadata.d.ts +1 -1
  15. package/dist/decorator/route.metadata.d.ts.map +1 -1
  16. package/dist/decorator/route.metadata.js +2 -0
  17. package/dist/decorator/route.metadata.js.map +1 -1
  18. package/dist/error/default.resolver.d.ts +6 -0
  19. package/dist/error/default.resolver.d.ts.map +1 -0
  20. package/dist/error/default.resolver.js +45 -0
  21. package/dist/error/default.resolver.js.map +1 -0
  22. package/dist/error/{resolve-http-error.d.ts → error-resolver.interface.d.ts} +4 -2
  23. package/dist/error/error-resolver.interface.d.ts.map +1 -0
  24. package/dist/error/error-resolver.interface.js +2 -0
  25. package/dist/error/error-resolver.interface.js.map +1 -0
  26. package/dist/error/index.d.ts +2 -1
  27. package/dist/error/index.d.ts.map +1 -1
  28. package/dist/error/index.js +1 -1
  29. package/dist/error/index.js.map +1 -1
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +1 -0
  33. package/dist/index.js.map +1 -1
  34. package/dist/request/default.deserializer.d.ts +20 -0
  35. package/dist/request/default.deserializer.d.ts.map +1 -0
  36. package/dist/request/default.deserializer.js +22 -0
  37. package/dist/request/default.deserializer.js.map +1 -0
  38. package/dist/request/default.serializer.d.ts +6 -0
  39. package/dist/request/default.serializer.d.ts.map +1 -0
  40. package/dist/request/default.serializer.js +7 -0
  41. package/dist/request/default.serializer.js.map +1 -0
  42. package/dist/request/index.d.ts +4 -0
  43. package/dist/request/index.d.ts.map +1 -1
  44. package/dist/request/index.js +2 -0
  45. package/dist/request/index.js.map +1 -1
  46. package/dist/request/request-deserializer.interface.d.ts +4 -0
  47. package/dist/request/request-deserializer.interface.d.ts.map +1 -0
  48. package/dist/request/request-deserializer.interface.js +2 -0
  49. package/dist/request/request-deserializer.interface.js.map +1 -0
  50. package/dist/request/response-serializer.interface.d.ts +5 -0
  51. package/dist/request/response-serializer.interface.d.ts.map +1 -0
  52. package/dist/request/response-serializer.interface.js +2 -0
  53. package/dist/request/response-serializer.interface.js.map +1 -0
  54. package/dist/server/http-conventions.type.d.ts +9 -0
  55. package/dist/server/http-conventions.type.d.ts.map +1 -0
  56. package/dist/server/http-conventions.type.js +2 -0
  57. package/dist/server/http-conventions.type.js.map +1 -0
  58. package/dist/server/index.d.ts +1 -0
  59. package/dist/server/index.d.ts.map +1 -1
  60. package/package.json +1 -1
  61. package/dist/error/resolve-http-error.d.ts.map +0 -1
  62. package/dist/error/resolve-http-error.js +0 -43
  63. package/dist/error/resolve-http-error.js.map +0 -1
package/README.md CHANGED
@@ -248,7 +248,7 @@ async create(req: HttpRequest): Promise<HttpResponse> {
248
248
  }
249
249
  ```
250
250
 
251
- On validation failure, throws `ValidationError` with `context.issues` containing the validator's raw error array (e.g. Zod issues, Joi details). `resolveHttpError` surfaces this as a 400 response with `body.error.details.issues`.
251
+ On validation failure, throws `ValidationError` with `context.issues` containing the validator's raw error array (e.g. Zod issues, Joi details). The default error resolver (`DefaultErrorResolver`) surfaces this as a 400 response with `body.error.details.issues`. See [Response and error conventions](#response-and-error-conventions) to override the wire shape.
252
252
 
253
253
  ## Multipart / form-data
254
254
 
@@ -320,7 +320,7 @@ async export(req: HttpRequest): Promise<HttpStreamResponse> {
320
320
 
321
321
  **The real tradeoff is that binary responses lose the envelope convention — no `payload` / `metadata` wrapper around the bytes, and middleware can't introspect stream contents post-hoc.** That's intrinsic to streaming, not a flaw: logging, response shaping, and validators that read response bodies all become no-ops on the binary path. You're opting out of the standard JSON shape so the framework can hand bytes directly to the socket.
322
322
 
323
- Error handling caveat: a handler that throws **before** producing the response still goes through `resolveHttpError` and emits a normal JSON error envelope. A handler that throws **mid-stream** — after the response status is already committed — aborts the connection; the client sees a truncated body, not a JSON error.
323
+ Error handling caveat: a handler that throws **before** producing the response still goes through the configured error resolver (`DefaultErrorResolver` by default) and emits a normal JSON error envelope. A handler that throws **mid-stream** — after the response status is already committed — aborts the connection; the client sees a truncated body, not a JSON error.
324
324
 
325
325
  ## `RequestValidator` adapter
326
326
 
@@ -500,6 +500,7 @@ const server = new HonoServer({
500
500
  router, // HonoServer reads the execution-context provider from Router
501
501
  requestValidator, // optional — required only if any route uses @ValidateRequest
502
502
  logger, // optional — used for startup/shutdown/error logs
503
+ conventions, // optional — override the error / success wire shapes (see below)
503
504
  serve: honoServe,
504
505
  });
505
506
  ```
@@ -536,6 +537,133 @@ Defaults applied when `cors` is set:
536
537
 
537
538
  If you need non-default values, omit `cors` and wire `hono/cors` yourself inside the `serve` callback, or raise an issue.
538
539
 
540
+ ### Response and error conventions
541
+
542
+ The outbound success/envelope shape, the error shape, and the inbound query keys are all
543
+ consumer-overridable through the optional `conventions` facade on `HonoServer`. It groups three
544
+ strategies, each defaulting to a class that reproduces the built-in behavior byte-for-byte — omit
545
+ `conventions` and nothing changes.
546
+
547
+ ```ts
548
+ type HttpConventions = {
549
+ readonly errorResolver?: ErrorResolver; // controls the error status + body
550
+ readonly responseSerializer?: ResponseSerializer; // controls the JSON success/envelope body
551
+ readonly requestDeserializer?: RequestDeserializer; // controls the inbound query keys
552
+ };
553
+
554
+ interface ErrorResolver {
555
+ resolve(err: unknown): ResolvedHttpError; // { httpCode, body }
556
+ }
557
+ interface ResponseSerializer {
558
+ serialize(response: HttpJsonResponse): unknown; // return the wire body, or undefined for no body
559
+ }
560
+ interface RequestDeserializer {
561
+ deserializeQuery(query: Record<string, string | readonly string[]>): Record<string, string | readonly string[]>;
562
+ }
563
+ ```
564
+
565
+ Defaults are exported so a custom strategy can delegate to them: `DefaultErrorResolver` (the
566
+ status mapping — `ValidationError` → 400, `NotFoundError` → 404, …), `DefaultResponseSerializer`
567
+ (strips `httpCode`/`headers`, keeps `payload` / `error` / `metadata`, and returns `undefined`
568
+ for an empty body so it becomes a bodyless response), and `DefaultRequestDeserializer` (an
569
+ identity pass unless configured with `paginationKeys`).
570
+
571
+ **Custom error format** — e.g. RFC 7807 Problem Details, reusing the default status mapping:
572
+
573
+ ```ts
574
+ import {
575
+ DefaultErrorResolver,
576
+ type ErrorResolver,
577
+ type ResolvedHttpError,
578
+ } from '@quilla-be-kit/http';
579
+
580
+ class ProblemDetailsResolver implements ErrorResolver {
581
+ private readonly base = new DefaultErrorResolver();
582
+
583
+ resolve(err: unknown): ResolvedHttpError {
584
+ const { httpCode, body } = this.base.resolve(err); // reuse status mapping
585
+ return {
586
+ httpCode,
587
+ body: {
588
+ error: {
589
+ name: 'about:blank',
590
+ message: body.error?.message ?? 'Error',
591
+ details: { status: httpCode, ...(body.error?.details ?? {}) },
592
+ },
593
+ },
594
+ };
595
+ }
596
+ }
597
+
598
+ const server = new HonoServer({
599
+ port: 3000,
600
+ router,
601
+ serve: honoServe,
602
+ conventions: { errorResolver: new ProblemDetailsResolver() },
603
+ });
604
+ ```
605
+
606
+ **Custom success envelope** — e.g. rename `payload` → `data` and lift pagination to the top level:
607
+
608
+ ```ts
609
+ import {
610
+ type HttpJsonResponse,
611
+ type ResponseSerializer,
612
+ } from '@quilla-be-kit/http';
613
+
614
+ class DataEnvelopeSerializer implements ResponseSerializer {
615
+ serialize(r: HttpJsonResponse): unknown {
616
+ if (r.error) return { error: r.error };
617
+ if (r.payload === undefined) return undefined; // preserve the bodyless-response branch
618
+ const p = r.metadata?.pagination;
619
+ return {
620
+ data: r.payload,
621
+ ...(p ? { pagination: { page: p.page, perPage: p.limit, total: p.total } } : {}),
622
+ };
623
+ }
624
+ }
625
+
626
+ const server = new HonoServer({
627
+ port: 3000,
628
+ router,
629
+ serve: honoServe,
630
+ conventions: { responseSerializer: new DataEnvelopeSerializer() },
631
+ });
632
+ ```
633
+
634
+ The binary/stream response paths never touch the serializer — they still write bytes directly.
635
+ On the frontend, `@quilla-fe-kit/api-client-react-query` reconciles a custom envelope with a
636
+ `queryTransformer`, and `@quilla-fe-kit/api-client` a custom error shape with an `errorParser`.
637
+
638
+ **Custom request query dialect** — the request-side mirror of `responseSerializer`. A pagination
639
+ dialect is API-wide, so rather than repeat it at every list schema, rename the query keys once at
640
+ the boundary. `DefaultRequestDeserializer` rewrites a consumer's keys onto the canonical `page` /
641
+ `pageSize` every handler (and `@ValidateRequest`) already reads, so no DTO changes:
642
+
643
+ ```ts
644
+ import { DefaultRequestDeserializer } from '@quilla-be-kit/http';
645
+
646
+ const server = new HonoServer({
647
+ port: 3000,
648
+ router,
649
+ serve: honoServe,
650
+ conventions: {
651
+ requestDeserializer: new DefaultRequestDeserializer({
652
+ paginationKeys: { page: 'p', pageSize: 'per_page' },
653
+ }),
654
+ },
655
+ });
656
+ ```
657
+
658
+ Now `GET /roles?p=2&per_page=50` reaches handlers as `page` / `pageSize`. This lines up with the
659
+ frontend: `@quilla-fe-kit`'s `RepeatParamsSerializer` renames the same slots on the emitting end
660
+ (also configured once, in its constructor), so both ends speak one dialect for full round-trip
661
+ symmetry. `sort` is intentionally not remappable — the `sort` key already agrees across ends.
662
+
663
+ Only pagination keys are renamed; filter keys and all other query params pass through untouched.
664
+ For a bespoke rule, implement `RequestDeserializer` directly. Non-query sources (params, body)
665
+ are never touched.
666
+
539
667
  ## Other frameworks
540
668
 
541
669
  If you need Express or Fastify: open an issue. Adapter sub-paths ship as library additions when they exist, not as consumer extension points.