@zeroxsolutions/server 0.5.3 → 0.6.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/CHANGELOG.md +22 -0
- package/README.md +14 -7
- package/dist/error-handler.d.ts +1 -2
- package/dist/error-handler.d.ts.map +1 -1
- package/dist/error-handler.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/not-found.d.ts +18 -2
- package/dist/not-found.d.ts.map +1 -1
- package/dist/not-found.js +35 -6
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## 0.6.0 (2026-10-05)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- ⚠️ **server:** answer an update a resource does not support 403 ([7607c77](https://github.com/zeroxsolutions/zeroxsolutions/commit/7607c77))
|
|
6
|
+
|
|
7
|
+
### ⚠️ Breaking Changes
|
|
8
|
+
|
|
9
|
+
- **server:** answer an update a resource does not support 403 ([7607c77](https://github.com/zeroxsolutions/zeroxsolutions/commit/7607c77))
|
|
10
|
+
jsonApiNotFound throws instead of rendering, so a surface
|
|
11
|
+
installing it installs createErrorHandler as its onError.
|
|
12
|
+
Refs: zeroxsolutions/travel#283
|
|
13
|
+
|
|
14
|
+
### ❤️ Thank You
|
|
15
|
+
|
|
16
|
+
- Claude Opus 5.5 (1M context)
|
|
17
|
+
- Lương Văn Tú
|
|
18
|
+
|
|
19
|
+
## 0.5.3 (2026-10-03)
|
|
20
|
+
|
|
21
|
+
This was a version bump only for @zeroxsolutions/server to align it with other projects, there were no code changes.
|
|
22
|
+
|
|
1
23
|
## 0.5.2 (2026-09-30)
|
|
2
24
|
|
|
3
25
|
### 🚀 Features
|
package/README.md
CHANGED
|
@@ -116,19 +116,26 @@ app.onError(
|
|
|
116
116
|
category: LOG_CATEGORY,
|
|
117
117
|
resolve: createDomainErrorResolver(apiErrors),
|
|
118
118
|
requestId: (c) => c.var.requestId,
|
|
119
|
-
authenticationChallenge: '
|
|
119
|
+
authenticationChallenge: 'Session realm="api"',
|
|
120
120
|
}),
|
|
121
121
|
);
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
`jsonApiNotFound({ namespace })` is `app.notFound(...)` - the answer for a
|
|
125
|
-
|
|
126
|
-
`http.error.answered` line
|
|
127
|
-
|
|
124
|
+
`jsonApiNotFound({ namespace, routes })` is `app.notFound(...)` - the answer for a request no route
|
|
125
|
+
matched. It throws rather than rendering, so `createErrorHandler` answers it, writes its
|
|
126
|
+
`http.error.answered` line and stamps its request id; an app installing it installs
|
|
127
|
+
`createErrorHandler` too, or hono answers the throw with a text 500.
|
|
128
|
+
|
|
129
|
+
A PATCH to a path one of `routes` serves for another method throws `RequestForbidden` at `/data`,
|
|
130
|
+
answered `403` as `<namespace>.request_forbidden`, because JSON:API 1.1 answers an update a resource
|
|
131
|
+
does not support 403. Anything else, and every request when `routes` is left out, throws a 404
|
|
132
|
+
`HTTPException`, answered as `<namespace>.not_found`. `routes` is the app's own `app.routes`, read on
|
|
133
|
+
the first unmatched request; middleware (method `ALL`) serves no path, and a trailing slash the app
|
|
134
|
+
does not serve stays a 404.
|
|
128
135
|
|
|
129
136
|
```ts
|
|
130
|
-
// entrypoints/http/app.ts
|
|
131
|
-
app.notFound(jsonApiNotFound({ namespace: 'api' }));
|
|
137
|
+
// entrypoints/http/app.ts, after app.onError(createErrorHandler(...))
|
|
138
|
+
app.notFound(jsonApiNotFound({ namespace: 'api', routes: app.routes }));
|
|
132
139
|
```
|
|
133
140
|
|
|
134
141
|
## Why a binder rather than a ready middleware
|
package/dist/error-handler.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Context, ErrorHandler } from 'hono';
|
|
2
|
-
import { type
|
|
2
|
+
import { type ResolvedError } from '@zeroxsolutions/response';
|
|
3
3
|
import type { SurfaceVocabulary } from './surface-vocabulary.js';
|
|
4
|
-
export declare function errorObjectByStatus(err: Error, status: number, namespace: string): ErrorObject;
|
|
5
4
|
/** Tunes {@link createErrorHandler} for a specific transport (the edge gateway vs a service). */
|
|
6
5
|
export interface ErrorHandlerOptions extends SurfaceVocabulary {
|
|
7
6
|
/**
|
|
@@ -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,EASL,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAuCjE,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
|
@@ -15,7 +15,7 @@ for (const [name, meta] of Object.entries(BASE_ERROR_CODES)) {
|
|
|
15
15
|
// The message template both levels write, so one search finds every error this handler answered.
|
|
16
16
|
const ANSWERED_LINE = 'http.error.answered {method} {path} {status} {claimedBy}';
|
|
17
17
|
// Projects a failure hono raised - or one nothing claimed - onto a JSON:API error object by HTTP status.
|
|
18
|
-
|
|
18
|
+
function errorObjectByStatus(err, status, namespace) {
|
|
19
19
|
const meta = META_BY_STATUS.get(status) ?? { code: 'error', title: 'Error' };
|
|
20
20
|
const detail = err instanceof HTTPException ? err.message : undefined;
|
|
21
21
|
return {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { createEnvironment, type EnvironmentOptions } from './environment.js';
|
|
|
5
5
|
export type { SurfaceVocabulary } from './surface-vocabulary.js';
|
|
6
6
|
export { jsonApiContentNegotiation, NEGOTIATION_RESPONSES } from './content-negotiation.js';
|
|
7
7
|
export { createErrorHandler, type ErrorHandlerOptions } from './error-handler.js';
|
|
8
|
-
export { jsonApiNotFound } from './not-found.js';
|
|
8
|
+
export { jsonApiNotFound, type NotFoundOptions } from './not-found.js';
|
|
9
9
|
export { PermissionRefused } from './permission-refused.js';
|
|
10
10
|
export { Unauthenticated } from './unauthenticated.js';
|
|
11
11
|
export { createValidationHook } from './validation-hook.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAC9E,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAC5F,OAAO,EAAE,kBAAkB,EAAE,KAAK,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAClF,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/not-found.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { NotFoundHandler } from 'hono';
|
|
2
2
|
import type { SurfaceVocabulary } from './surface-vocabulary.js';
|
|
3
|
-
/**
|
|
4
|
-
export
|
|
3
|
+
/** What {@link jsonApiNotFound} is told about the surface it answers for. */
|
|
4
|
+
export interface NotFoundOptions extends SurfaceVocabulary {
|
|
5
|
+
/** The routes the app serves, as `app.routes` lists them; a PATCH to a path one serves for another method answers 403. */
|
|
6
|
+
readonly routes?: readonly {
|
|
7
|
+
readonly method: string;
|
|
8
|
+
readonly path: string;
|
|
9
|
+
}[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The `notFound` handler a JSON:API surface installs; it throws, so the surface's `onError` renders and logs the
|
|
13
|
+
* answer. A surface installing it installs `createErrorHandler` as its `onError`, or hono answers the throw
|
|
14
|
+
* with its own text 500.
|
|
15
|
+
*
|
|
16
|
+
* A PATCH to a path some route in {@link NotFoundOptions.routes} serves for another method throws
|
|
17
|
+
* `RequestForbidden` at `/data`, which JSON:API 1.1 answers 403; anything else throws a 404 `HTTPException`,
|
|
18
|
+
* answered `<namespace>.not_found`. Without `routes`, every request is the 404.
|
|
19
|
+
*/
|
|
20
|
+
export declare function jsonApiNotFound(options: NotFoundOptions): NotFoundHandler;
|
|
5
21
|
//# sourceMappingURL=not-found.d.ts.map
|
package/dist/not-found.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"not-found.d.ts","sourceRoot":"","sources":["../src/not-found.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"not-found.d.ts","sourceRoot":"","sources":["../src/not-found.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,MAAM,CAAC;AAI5C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,6EAA6E;AAC7E,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD,0HAA0H;IAC1H,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS;QAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjF;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,eAAe,CAczE"}
|
package/dist/not-found.js
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { RequestForbidden } from '@zeroxsolutions/response';
|
|
2
|
+
import { HTTPException } from 'hono/http-exception';
|
|
3
|
+
import { METHOD_NAME_ALL } from 'hono/router';
|
|
4
|
+
import { TrieRouter } from 'hono/router/trie-router';
|
|
5
|
+
/**
|
|
6
|
+
* The `notFound` handler a JSON:API surface installs; it throws, so the surface's `onError` renders and logs the
|
|
7
|
+
* answer. A surface installing it installs `createErrorHandler` as its `onError`, or hono answers the throw
|
|
8
|
+
* with its own text 500.
|
|
9
|
+
*
|
|
10
|
+
* A PATCH to a path some route in {@link NotFoundOptions.routes} serves for another method throws
|
|
11
|
+
* `RequestForbidden` at `/data`, which JSON:API 1.1 answers 403; anything else throws a 404 `HTTPException`,
|
|
12
|
+
* answered `<namespace>.not_found`. Without `routes`, every request is the 404.
|
|
13
|
+
*/
|
|
4
14
|
export function jsonApiNotFound(options) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
// Built on the first unmatched request, because `routes` is the app's own array and fills as routes mount.
|
|
16
|
+
// TrieRouter matches strictly, as the app's default router does; PatternRouter and LinearRouter also match
|
|
17
|
+
// `/things/1/` against `/things/:id`, which the app answers 404.
|
|
18
|
+
let served;
|
|
19
|
+
return (c) => {
|
|
20
|
+
if (c.req.method === 'PATCH' && options.routes !== undefined) {
|
|
21
|
+
served ??= routerServing(options.routes);
|
|
22
|
+
if (served.match(c.req.method, c.req.path)[0].length > 0) {
|
|
23
|
+
throw new RequestForbidden('/data', 'This resource does not support an update.');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new HTTPException(404);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Middleware mounts with method `ALL`, so leaving those out keeps a path only middleware covers a 404.
|
|
30
|
+
function routerServing(routes) {
|
|
31
|
+
const router = new TrieRouter();
|
|
32
|
+
for (const route of routes) {
|
|
33
|
+
if (route.method !== METHOD_NAME_ALL)
|
|
34
|
+
router.add(METHOD_NAME_ALL, route.path, true);
|
|
35
|
+
}
|
|
36
|
+
return router;
|
|
8
37
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxsolutions/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"tslib": "^2.3.0",
|
|
24
24
|
"@zeroxsolutions/jsonapi": "0.13.1",
|
|
25
|
-
"@zeroxsolutions/
|
|
26
|
-
"@zeroxsolutions/
|
|
25
|
+
"@zeroxsolutions/response": "0.14.3",
|
|
26
|
+
"@zeroxsolutions/query": "0.7.3"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
29
|
"@hono/zod-openapi": "^1.0.0",
|