@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.
- package/README.md +130 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapter/hono/hono-request.adapter.d.ts +6 -1
- package/dist/adapter/hono/hono-request.adapter.d.ts.map +1 -1
- package/dist/adapter/hono/hono-request.adapter.js +23 -22
- package/dist/adapter/hono/hono-request.adapter.js.map +1 -1
- package/dist/adapter/hono/hono.server.d.ts +3 -0
- package/dist/adapter/hono/hono.server.d.ts.map +1 -1
- package/dist/adapter/hono/hono.server.js +9 -3
- package/dist/adapter/hono/hono.server.js.map +1 -1
- package/dist/decorator/controller.decorator.d.ts.map +1 -1
- package/dist/decorator/controller.decorator.js +1 -3
- package/dist/decorator/controller.decorator.js.map +1 -1
- package/dist/decorator/route.metadata.d.ts +1 -1
- package/dist/decorator/route.metadata.d.ts.map +1 -1
- package/dist/decorator/route.metadata.js +2 -0
- package/dist/decorator/route.metadata.js.map +1 -1
- package/dist/error/default.resolver.d.ts +6 -0
- package/dist/error/default.resolver.d.ts.map +1 -0
- package/dist/error/default.resolver.js +45 -0
- package/dist/error/default.resolver.js.map +1 -0
- package/dist/error/{resolve-http-error.d.ts → error-resolver.interface.d.ts} +4 -2
- package/dist/error/error-resolver.interface.d.ts.map +1 -0
- package/dist/error/error-resolver.interface.js +2 -0
- package/dist/error/error-resolver.interface.js.map +1 -0
- package/dist/error/index.d.ts +2 -1
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +1 -1
- package/dist/error/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/request/default.deserializer.d.ts +20 -0
- package/dist/request/default.deserializer.d.ts.map +1 -0
- package/dist/request/default.deserializer.js +22 -0
- package/dist/request/default.deserializer.js.map +1 -0
- package/dist/request/default.serializer.d.ts +6 -0
- package/dist/request/default.serializer.d.ts.map +1 -0
- package/dist/request/default.serializer.js +7 -0
- package/dist/request/default.serializer.js.map +1 -0
- package/dist/request/index.d.ts +4 -0
- package/dist/request/index.d.ts.map +1 -1
- package/dist/request/index.js +2 -0
- package/dist/request/index.js.map +1 -1
- package/dist/request/request-deserializer.interface.d.ts +4 -0
- package/dist/request/request-deserializer.interface.d.ts.map +1 -0
- package/dist/request/request-deserializer.interface.js +2 -0
- package/dist/request/request-deserializer.interface.js.map +1 -0
- package/dist/request/response-serializer.interface.d.ts +5 -0
- package/dist/request/response-serializer.interface.d.ts.map +1 -0
- package/dist/request/response-serializer.interface.js +2 -0
- package/dist/request/response-serializer.interface.js.map +1 -0
- package/dist/server/http-conventions.type.d.ts +9 -0
- package/dist/server/http-conventions.type.d.ts.map +1 -0
- package/dist/server/http-conventions.type.js +2 -0
- package/dist/server/http-conventions.type.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/error/resolve-http-error.d.ts.map +0 -1
- package/dist/error/resolve-http-error.js +0 -43
- 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). `
|
|
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 `
|
|
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.
|