@zeroxsolutions/server 0.5.1 → 0.5.3
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/CHANGELOG.md +11 -0
- package/README.md +13 -6
- package/dist/error-handler.d.ts +4 -3
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +20 -6
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
## 0.5.2 (2026-09-30)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **server:** answer a refused request document 409 or 403 ([3792166](https://github.com/zeroxsolutions/zeroxsolutions/commit/3792166))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- Claude Opus 5.5 (1M context)
|
|
10
|
+
- Lương Văn Tú
|
|
11
|
+
|
|
1
12
|
## 0.5.1 (2026-09-29)
|
|
2
13
|
|
|
3
14
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -80,17 +80,24 @@ why `Content-Type` is read on a write only, and why an empty `Accept` is refused
|
|
|
80
80
|
|
|
81
81
|
1. a query the permit-list refused (`QueryNotPermitted`), answered `400` as
|
|
82
82
|
`<namespace>.query_not_permitted`, naming the parameter in `source.parameter`, before `resolve` sees it;
|
|
83
|
-
2.
|
|
83
|
+
2. a request document the route does not accept (`RequestConflict` / `RequestForbidden` from
|
|
84
|
+
`@zeroxsolutions/response`, which its `requestDocumentSchema` and `readRequestDocument` throw),
|
|
85
|
+
answered `409` as `<namespace>.request_conflict` or `403` as `<namespace>.request_forbidden`, naming the
|
|
86
|
+
member in `source.pointer`, before `resolve` sees it;
|
|
87
|
+
3. `resolve(err, c)` - where a surface's domain error map answers, through
|
|
84
88
|
`@zeroxsolutions/response`'s `createDomainErrorResolver`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
5.
|
|
89
|
+
4. a request a guard refused - `Unauthenticated`, answered `401` as `<namespace>.unauthenticated`, or
|
|
90
|
+
`PermissionRefused`, answered `403` as `<namespace>.forbidden`;
|
|
91
|
+
5. a request the validation hook rejected, one error object per issue;
|
|
92
|
+
6. a `JsonApiError`, rendered as it was thrown;
|
|
93
|
+
7. otherwise the status alone - a hono `HTTPException` at its own, anything else as a 500.
|
|
88
94
|
|
|
89
95
|
Whichever step answers, the handler writes one `http.error.answered` line under `category` before the
|
|
90
96
|
document goes out, at the level the answered status class decides: a 5xx at error, anything else at
|
|
91
97
|
warn. The line carries the request id, the method, the path, the answered status, the step that claimed
|
|
92
|
-
the failure (`resolve`, `validation`, `jsonapi`, `status`) and the error's type
|
|
93
|
-
a mapped 500 or a guard's 403 reaches the client with nothing in the log for an
|
|
98
|
+
the failure (`query`, `request`, `resolve`, `auth`, `validation`, `jsonapi`, `status`) and the error's type
|
|
99
|
+
and message. Without it a mapped 500 or a guard's 403 reaches the client with nothing in the log for an
|
|
100
|
+
operator to join it to.
|
|
94
101
|
|
|
95
102
|
The `requestId` option's value becomes the `id` of each error object that carries none, so a client
|
|
96
103
|
quotes the id the request's log lines carry.
|
package/dist/error-handler.d.ts
CHANGED
|
@@ -22,9 +22,10 @@ export interface ErrorHandlerOptions extends SurfaceVocabulary {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* The one central `onError` a transport installs, rendering every failure as one `application/vnd.api+json`
|
|
25
|
-
* `errors[]` document. It asks, in order: a query the permit-list refused, then
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* `errors[]` document. It asks, in order: a query the permit-list refused, then a request document the route does not
|
|
26
|
+
* accept (`RequestConflict` as 409, `RequestForbidden` as 403, each naming its member in `source.pointer`), then
|
|
27
|
+
* `resolve`, then a guard's own refusal, then a request the validation hook rejected, then a {@link JsonApiError} as
|
|
28
|
+
* thrown, then the HTTP status alone.
|
|
28
29
|
*
|
|
29
30
|
* Every answer is written to the log before it goes out, at the level its status class decides: a 5xx at error,
|
|
30
31
|
* because this side went wrong, anything else at warn, because the request was refused. Without the line, a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIlD,OAAO,
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../src/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIlD,OAAO,EAQL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AA4BjE,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAS9F;AAED,iGAAiG;AACjG,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D;;;;OAIG;IACH,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,uGAAuG;IACvG,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,KAAK,aAAa,GAAG,SAAS,CAAC;IAChE,0GAA0G;IAC1G,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI,CAAC;IACtD;;;OAGG;IACH,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAsED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAkC7E"}
|
package/dist/error-handler.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getLogger } from '@logtape/logtape';
|
|
2
2
|
import { HTTPException } from 'hono/http-exception';
|
|
3
3
|
import { QueryNotPermitted } from '@zeroxsolutions/query';
|
|
4
|
-
import { BASE_ERROR_CODES, JsonApiError, JSON_API_MEDIA_TYPE, toErrorDocument, toErrorObject, } from '@zeroxsolutions/response';
|
|
4
|
+
import { BASE_ERROR_CODES, JsonApiError, JSON_API_MEDIA_TYPE, RequestConflict, RequestForbidden, toErrorDocument, toErrorObject, } from '@zeroxsolutions/response';
|
|
5
5
|
import { PermissionRefused } from './permission-refused.js';
|
|
6
6
|
import { RequestInvalid } from './request-invalid.js';
|
|
7
7
|
import { Unauthenticated } from './unauthenticated.js';
|
|
@@ -25,8 +25,9 @@ export function errorObjectByStatus(err, status, namespace) {
|
|
|
25
25
|
...(detail && detail !== meta.title ? { detail } : {}),
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
// Asks each step of the dispatch in turn: a query the permit-list refused,
|
|
29
|
-
// the validation hook rejected, a `JsonApiError` as thrown, the HTTP
|
|
28
|
+
// Asks each step of the dispatch in turn: a query the permit-list refused, a request document the route does not
|
|
29
|
+
// accept, `resolve`, a guard's refusal, a request the validation hook rejected, a `JsonApiError` as thrown, the HTTP
|
|
30
|
+
// status alone.
|
|
30
31
|
function claimError(err, c, options) {
|
|
31
32
|
if (err instanceof QueryNotPermitted) {
|
|
32
33
|
const error = {
|
|
@@ -38,6 +39,18 @@ function claimError(err, c, options) {
|
|
|
38
39
|
};
|
|
39
40
|
return { claimedBy: 'query', resolved: { status: 400, errors: [error] } };
|
|
40
41
|
}
|
|
42
|
+
if (err instanceof RequestConflict || err instanceof RequestForbidden) {
|
|
43
|
+
const conflict = err instanceof RequestConflict;
|
|
44
|
+
const { status, title } = conflict ? BASE_ERROR_CODES.CONFLICT : BASE_ERROR_CODES.FORBIDDEN;
|
|
45
|
+
const error = {
|
|
46
|
+
status: String(status),
|
|
47
|
+
code: `${options.namespace}.${conflict ? 'request_conflict' : 'request_forbidden'}`,
|
|
48
|
+
title,
|
|
49
|
+
detail: err.message,
|
|
50
|
+
source: { pointer: err.pointer },
|
|
51
|
+
};
|
|
52
|
+
return { claimedBy: 'request', resolved: { status, errors: [error] } };
|
|
53
|
+
}
|
|
41
54
|
const resolved = options.resolve?.(err, c);
|
|
42
55
|
if (resolved) {
|
|
43
56
|
return { claimedBy: 'resolve', resolved };
|
|
@@ -81,9 +94,10 @@ function withChallenge(claimed, authenticationChallenge) {
|
|
|
81
94
|
}
|
|
82
95
|
/**
|
|
83
96
|
* The one central `onError` a transport installs, rendering every failure as one `application/vnd.api+json`
|
|
84
|
-
* `errors[]` document. It asks, in order: a query the permit-list refused, then
|
|
85
|
-
*
|
|
86
|
-
*
|
|
97
|
+
* `errors[]` document. It asks, in order: a query the permit-list refused, then a request document the route does not
|
|
98
|
+
* accept (`RequestConflict` as 409, `RequestForbidden` as 403, each naming its member in `source.pointer`), then
|
|
99
|
+
* `resolve`, then a guard's own refusal, then a request the validation hook rejected, then a {@link JsonApiError} as
|
|
100
|
+
* thrown, then the HTTP status alone.
|
|
87
101
|
*
|
|
88
102
|
* Every answer is written to the log before it goes out, at the level its status class decides: a 5xx at error,
|
|
89
103
|
* because this side went wrong, anything else at warn, because the request was refused. Without the line, a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxsolutions/server",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -21,9 +21,9 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"tslib": "^2.3.0",
|
|
24
|
-
"@zeroxsolutions/
|
|
25
|
-
"@zeroxsolutions/
|
|
26
|
-
"@zeroxsolutions/response": "0.14.
|
|
24
|
+
"@zeroxsolutions/jsonapi": "0.13.1",
|
|
25
|
+
"@zeroxsolutions/query": "0.7.2",
|
|
26
|
+
"@zeroxsolutions/response": "0.14.3"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@hono/zod-openapi": "^1.0.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"@hono/zod-openapi": "^1.4.0",
|
|
36
36
|
"@logtape/logtape": "^2.3.2",
|
|
37
37
|
"hono": "^4.12.27",
|
|
38
|
-
"zod": "^4.
|
|
38
|
+
"zod": "^4.3.6"
|
|
39
39
|
},
|
|
40
40
|
"nx": {
|
|
41
41
|
"name": "@zeroxsolutions/server"
|