@zeroxsolutions/jsonapi 0.13.6 → 0.13.8
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 +18 -0
- package/README.md +34 -2
- package/dist/client/api-error.d.ts +16 -1
- package/dist/client/api-error.d.ts.map +1 -1
- package/dist/client/api-error.js +16 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## 0.13.8 (2026-10-22)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- **jsonapi:** read a refused response into an api error ([723fa9a](https://github.com/zeroxsolutions/zeroxsolutions/commit/723fa9a))
|
|
6
|
+
|
|
7
|
+
### 💅 Refactors
|
|
8
|
+
|
|
9
|
+
- **jsonapi:** name the refused-response read errorOf ([7bd0c02](https://github.com/zeroxsolutions/zeroxsolutions/commit/7bd0c02))
|
|
10
|
+
|
|
11
|
+
### ❤️ Thank You
|
|
12
|
+
|
|
13
|
+
- Lương Văn Tú
|
|
14
|
+
|
|
15
|
+
## 0.13.7 (2026-10-21)
|
|
16
|
+
|
|
17
|
+
This was a version bump only for @zeroxsolutions/jsonapi to align it with other projects, there were no code changes.
|
|
18
|
+
|
|
1
19
|
## 0.13.6 (2026-10-21)
|
|
2
20
|
|
|
3
21
|
This was a version bump only for @zeroxsolutions/jsonapi to align it with other projects, there were no code changes.
|
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@ what keeps `hono` out of the package an SPA also loads.
|
|
|
27
27
|
| Import | Use in | What it holds |
|
|
28
28
|
| -------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
29
|
| `@zeroxsolutions/jsonapi` | services, gateway | `negotiateJsonApi` - the whole decision, from `Content-Type`, `Accept` and the `ext` URIs this surface supports (`supportedExtensions`), to the `JsonApiError` to reject with or `null`: 415 where `Content-Type` is the JSON:API media type carrying a parameter beyond `ext`/`profile` or an `ext` outside `supportedExtensions`; 406 where `Accept` holds JSON:API instances and every one carries such a parameter or `ext`; nothing else. `findOptionsOf` - a guarded query to the options `@zeroxsolutions/db`'s finders take |
|
|
30
|
-
| `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; `toRequestDocument`, the JSON:API request document a write sends, each relationship wrapped in its relationship object;
|
|
30
|
+
| `@zeroxsolutions/jsonapi/client` | SPA, external consumers | `deserializeDocument` (jsona), `readResource` / `readCollection` over it - the same flattening, typed off the route's own document - `readRelated` for what an `include` resolved, which no document type can name; `toRequestDocument`, the JSON:API request document a write sends, each relationship wrapped in its relationship object; `ApiError`, the non-2xx a caller throws once a response has come back, and `errorOf`, which reads one off that response |
|
|
31
31
|
|
|
32
32
|
A transport mounts the decision rather than calling it: `jsonApiContentNegotiation` is the Hono adapter
|
|
33
33
|
over it, and it lives with the other framework wiring. `negotiateJsonApi` itself takes the surface's
|
|
@@ -47,10 +47,42 @@ response type, which is what keeps the model off a hand-written interface:
|
|
|
47
47
|
```ts
|
|
48
48
|
// the query layer - the only place the document is spelled
|
|
49
49
|
type SubjectList = CollectionOf<InferResponseType<(typeof api.v1.orgs)[':orgId']['subjects']['$get'], 200>>;
|
|
50
|
-
if (!res.ok) throw
|
|
50
|
+
if (!res.ok) throw await errorOf(res);
|
|
51
51
|
const { data, meta } = readCollection(await res.json());
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
A refused response is read into an `ApiError` by `errorOf`, never by reading the body by hand. It reads the body
|
|
55
|
+
once and parses it as an error document, so `body` holds every error object the server sent, in its order, and is
|
|
56
|
+
`null` where the failing tier sent no error document: a proxy's HTML page, an empty body, or JSON of another shape.
|
|
57
|
+
Parsed rather than trusted, a body with no `errors` cannot make the throw itself throw:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { ApiError, errorOf } from '@zeroxsolutions/jsonapi/client';
|
|
61
|
+
|
|
62
|
+
async function read(res: Response): Promise<unknown> {
|
|
63
|
+
if (!res.ok) throw await errorOf(res);
|
|
64
|
+
return res.json();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const refusal = {
|
|
68
|
+
errors: [{ code: 'api.bad_request', source: { pointer: '/data/attributes/name' } }, { code: 'api.conflict' }],
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await read(new Response(JSON.stringify(refusal), { status: 422 }));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
if (error instanceof ApiError) {
|
|
75
|
+
error.status; // 422
|
|
76
|
+
error.body?.errors.map((object) => object.code); // ['api.bad_request', 'api.conflict']
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
(await errorOf(new Response('<html>502 Bad Gateway</html>', { status: 502 }))).body; // null
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The body is read once, so a caller that reads it too hands `errorOf` a `clone()`. What a screen draws for each
|
|
84
|
+
code, and which field a pointer names, is the screen's, so neither is here.
|
|
85
|
+
|
|
54
86
|
Only `data` is flattened. `links` and `meta` keep the names the standard gives them, and `meta` stays
|
|
55
87
|
free-form - the standard puts no member in it, so a row count there is one surface's own convention.
|
|
56
88
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type ErrorDocument } from '@zeroxsolutions/response';
|
|
2
2
|
/**
|
|
3
3
|
* A non-2xx response, as the client that made the request holds it.
|
|
4
4
|
*
|
|
@@ -11,4 +11,19 @@ export declare class ApiError extends Error {
|
|
|
11
11
|
readonly body: ErrorDocument | null;
|
|
12
12
|
constructor(status: number, body: ErrorDocument | null);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The {@link ApiError} a refused response yields: its status, and the error document its body holds, every error
|
|
16
|
+
* object kept in the order the server sent them.
|
|
17
|
+
*
|
|
18
|
+
* Reads the body once, so the response must not have been read before; a caller that needs the body for anything
|
|
19
|
+
* else hands this a clone. `body` is null where the body is no JSON, such as a proxy's HTML page or an empty body,
|
|
20
|
+
* and where it is JSON but no error document. It never rejects.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* if (!response.ok) throw await errorOf(response);
|
|
24
|
+
*/
|
|
25
|
+
export declare function errorOf(response: {
|
|
26
|
+
readonly status: number;
|
|
27
|
+
json(): Promise<unknown>;
|
|
28
|
+
}): Promise<ApiError>;
|
|
14
29
|
//# sourceMappingURL=api-error.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/client/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/client/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAEnF;;;;;;GAMG;AACH,qBAAa,QAAS,SAAQ,KAAK;IAE/B,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;gBAD1B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAAG,IAAI;CAKtC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,QAAQ,CAAC,CAIhH"}
|
package/dist/client/api-error.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { errorDocumentSchema } from '@zeroxsolutions/response';
|
|
1
2
|
/**
|
|
2
3
|
* A non-2xx response, as the client that made the request holds it.
|
|
3
4
|
*
|
|
@@ -15,3 +16,18 @@ export class ApiError extends Error {
|
|
|
15
16
|
this.name = 'ApiError';
|
|
16
17
|
}
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The {@link ApiError} a refused response yields: its status, and the error document its body holds, every error
|
|
21
|
+
* object kept in the order the server sent them.
|
|
22
|
+
*
|
|
23
|
+
* Reads the body once, so the response must not have been read before; a caller that needs the body for anything
|
|
24
|
+
* else hands this a clone. `body` is null where the body is no JSON, such as a proxy's HTML page or an empty body,
|
|
25
|
+
* and where it is JSON but no error document. It never rejects.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* if (!response.ok) throw await errorOf(response);
|
|
29
|
+
*/
|
|
30
|
+
export async function errorOf(response) {
|
|
31
|
+
const document = errorDocumentSchema.safeParse(await response.json().catch(() => null));
|
|
32
|
+
return new ApiError(response.status, document.success ? document.data : null);
|
|
33
|
+
}
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The client half of the standard: the document a response carries, flattened once, the document a write sends, and
|
|
3
|
-
* the failure a caller throws.
|
|
3
|
+
* the failure a caller throws, read off the response that refused it.
|
|
4
4
|
*/
|
|
5
|
-
export { ApiError } from './api-error.js';
|
|
5
|
+
export { ApiError, errorOf } from './api-error.js';
|
|
6
6
|
export { deserializeDocument } from './deserialize.js';
|
|
7
7
|
export { readCollection, readRelated, readResource, type Collection, type CollectionOf, type Resource, type ResourceOf, } from './read-document.js';
|
|
8
8
|
export { toRequestDocument, type RelationshipInput, type ToRequestDocument } from './request-document.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EACL,cAAc,EACd,WAAW,EACX,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,KAAK,UAAU,GAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/client/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The client half of the standard: the document a response carries, flattened once, the document a write sends, and
|
|
3
|
-
* the failure a caller throws.
|
|
3
|
+
* the failure a caller throws, read off the response that refused it.
|
|
4
4
|
*/
|
|
5
|
-
export { ApiError } from './api-error.js';
|
|
5
|
+
export { ApiError, errorOf } from './api-error.js';
|
|
6
6
|
export { deserializeDocument } from './deserialize.js';
|
|
7
7
|
export { readCollection, readRelated, readResource, } from './read-document.js';
|
|
8
8
|
export { toRequestDocument } from './request-document.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroxsolutions/jsonapi",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.8",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"jsona": "^1.12.0",
|
|
32
32
|
"tslib": "^2.3.0",
|
|
33
33
|
"@zeroxsolutions/query": "0.7.5",
|
|
34
|
-
"@zeroxsolutions/response": "0.14.
|
|
34
|
+
"@zeroxsolutions/response": "0.14.7"
|
|
35
35
|
},
|
|
36
36
|
"nx": {
|
|
37
37
|
"targets": {
|