@zeroxsolutions/jsonapi 0.13.7 → 0.14.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 +33 -0
- package/README.md +46 -2
- 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/dist/client/response-error.d.ts +39 -0
- package/dist/client/response-error.d.ts.map +1 -0
- package/dist/client/response-error.js +41 -0
- package/dist/client/retry-after.d.ts +7 -0
- package/dist/client/retry-after.d.ts.map +1 -0
- package/dist/client/retry-after.js +69 -0
- package/package.json +1 -1
- package/dist/client/api-error.d.ts +0 -14
- package/dist/client/api-error.d.ts.map +0 -1
- package/dist/client/api-error.js +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
## 0.14.0 (2026-10-23)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- ⚠️ **jsonapi:** carry a refusal as ResponseError with its Retry-After ([b176c1f](https://github.com/zeroxsolutions/zeroxsolutions/commit/b176c1f))
|
|
6
|
+
|
|
7
|
+
### ⚠️ Breaking Changes
|
|
8
|
+
|
|
9
|
+
- **jsonapi:** carry a refusal as ResponseError with its Retry-After ([b176c1f](https://github.com/zeroxsolutions/zeroxsolutions/commit/b176c1f))
|
|
10
|
+
ApiError is renamed ResponseError, with no alias left
|
|
11
|
+
behind; its constructor takes retryAfterSeconds as a third argument;
|
|
12
|
+
errorOf takes a clock ({ now(): Date }) as its second argument, and the
|
|
13
|
+
response it reads must carry headers.
|
|
14
|
+
Refs: #76
|
|
15
|
+
|
|
16
|
+
### ❤️ Thank You
|
|
17
|
+
|
|
18
|
+
- Lương Văn Tú
|
|
19
|
+
|
|
20
|
+
## 0.13.8 (2026-10-22)
|
|
21
|
+
|
|
22
|
+
### 🚀 Features
|
|
23
|
+
|
|
24
|
+
- **jsonapi:** read a refused response into an api error ([723fa9a](https://github.com/zeroxsolutions/zeroxsolutions/commit/723fa9a))
|
|
25
|
+
|
|
26
|
+
### 💅 Refactors
|
|
27
|
+
|
|
28
|
+
- **jsonapi:** name the refused-response read errorOf ([7bd0c02](https://github.com/zeroxsolutions/zeroxsolutions/commit/7bd0c02))
|
|
29
|
+
|
|
30
|
+
### ❤️ Thank You
|
|
31
|
+
|
|
32
|
+
- Lương Văn Tú
|
|
33
|
+
|
|
1
34
|
## 0.13.7 (2026-10-21)
|
|
2
35
|
|
|
3
36
|
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; `ResponseError`, 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,54 @@ 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, clock); // clock: { now(): Date }, what a Retry-After date counts from
|
|
51
51
|
const { data, meta } = readCollection(await res.json());
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
A refused response is read into a `ResponseError` 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
|
+
It reads `Retry-After` too, on every status, into `retryAfterSeconds`: delay-seconds as sent, an HTTP-date as the whole
|
|
60
|
+
seconds from the clock's `now()` until it, rounded up and never below 0, and `null` where the header is missing or in
|
|
61
|
+
neither form. The clock is the caller's, so a spec hands it a fixed one:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { ResponseError, errorOf } from '@zeroxsolutions/jsonapi/client';
|
|
65
|
+
|
|
66
|
+
const clock = { now: (): Date => new Date() };
|
|
67
|
+
|
|
68
|
+
async function read(res: Response): Promise<unknown> {
|
|
69
|
+
if (!res.ok) throw await errorOf(res, clock);
|
|
70
|
+
return res.json();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const refusal = {
|
|
74
|
+
errors: [{ code: 'api.bad_request', source: { pointer: '/data/attributes/name' } }, { code: 'api.conflict' }],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await read(new Response(JSON.stringify(refusal), { status: 422 }));
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof ResponseError) {
|
|
81
|
+
error.status; // 422
|
|
82
|
+
error.body?.errors.map((object) => object.code); // ['api.bad_request', 'api.conflict']
|
|
83
|
+
error.retryAfterSeconds; // null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
(await errorOf(new Response('<html>502 Bad Gateway</html>', { status: 502 }), clock)).body; // null
|
|
88
|
+
(await errorOf(new Response(null, { status: 429, headers: { 'Retry-After': '10' } }), clock)).retryAfterSeconds; // 10
|
|
89
|
+
|
|
90
|
+
const fixed = { now: (): Date => new Date('2026-10-21T07:28:00Z') };
|
|
91
|
+
const unavailable = new Response(null, { status: 503, headers: { 'Retry-After': 'Wed, 21 Oct 2026 07:30:00 GMT' } });
|
|
92
|
+
(await errorOf(unavailable, fixed)).retryAfterSeconds; // 120
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The body is read once, so a caller that reads it too hands `errorOf` a `clone()`. What a screen draws for each
|
|
96
|
+
code, and which field a pointer names, is the screen's, so neither is here.
|
|
97
|
+
|
|
54
98
|
Only `data` is flattened. `links` and `meta` keep the names the standard gives them, and `meta` stays
|
|
55
99
|
free-form - the standard puts no member in it, so a row count there is one surface's own convention.
|
|
56
100
|
|
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 {
|
|
5
|
+
export { ResponseError, errorOf } from './response-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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC7D,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 {
|
|
5
|
+
export { ResponseError, errorOf } from './response-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';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ErrorDocument } from '@zeroxsolutions/response';
|
|
2
|
+
/**
|
|
3
|
+
* A non-2xx response, as the client that made the request holds it.
|
|
4
|
+
*
|
|
5
|
+
* The counterpart of `JsonApiError`, which a SERVER raises for its `onError` to render: this one
|
|
6
|
+
* is what a caller throws once a response has already come back. `body` is null wherever the failing
|
|
7
|
+
* tier emitted no document - a proxy, a gateway timeout, or an auth server with a shape of its own.
|
|
8
|
+
* `retryAfterSeconds` is how long the response's `Retry-After` asked the caller to wait before asking
|
|
9
|
+
* again, and null where it named no wait or one in neither of the header's forms.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ResponseError extends Error {
|
|
12
|
+
readonly status: number;
|
|
13
|
+
readonly body: ErrorDocument | null;
|
|
14
|
+
readonly retryAfterSeconds: number | null;
|
|
15
|
+
constructor(status: number, body: ErrorDocument | null, retryAfterSeconds: number | null);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The {@link ResponseError} a refused response yields: its status, the error document its body holds, every error
|
|
19
|
+
* object kept in the order the server sent them, and the wait its `Retry-After` names.
|
|
20
|
+
*
|
|
21
|
+
* Reads the body once, so the response must not have been read before; a caller that needs the body for anything
|
|
22
|
+
* 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,
|
|
23
|
+
* and where it is JSON but no error document. It never rejects.
|
|
24
|
+
*
|
|
25
|
+
* @param clock - what a `Retry-After` HTTP-date is counted from; `retryAfterSeconds` is the whole seconds from
|
|
26
|
+
* `clock.now()` until that date, rounded up and never below 0. Delay-seconds are taken as sent.
|
|
27
|
+
* @example
|
|
28
|
+
* if (!response.ok) throw await errorOf(response, { now: () => new Date() });
|
|
29
|
+
*/
|
|
30
|
+
export declare function errorOf(response: {
|
|
31
|
+
readonly status: number;
|
|
32
|
+
readonly headers: {
|
|
33
|
+
get(name: string): string | null;
|
|
34
|
+
};
|
|
35
|
+
json(): Promise<unknown>;
|
|
36
|
+
}, clock: {
|
|
37
|
+
now(): Date;
|
|
38
|
+
}): Promise<ResponseError>;
|
|
39
|
+
//# sourceMappingURL=response-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-error.d.ts","sourceRoot":"","sources":["../../src/client/response-error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAGnF;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,KAAK;IAEpC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IACnC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;gBAFhC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,aAAa,GAAG,IAAI,EAC1B,iBAAiB,EAAE,MAAM,GAAG,IAAI;CAK5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE;IACR,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IACvD,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B,EACD,KAAK,EAAE;IAAE,GAAG,IAAI,IAAI,CAAA;CAAE,GACrB,OAAO,CAAC,aAAa,CAAC,CAKxB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { errorDocumentSchema } from '@zeroxsolutions/response';
|
|
2
|
+
import { retryAfterSecondsOf } from './retry-after.js';
|
|
3
|
+
/**
|
|
4
|
+
* A non-2xx response, as the client that made the request holds it.
|
|
5
|
+
*
|
|
6
|
+
* The counterpart of `JsonApiError`, which a SERVER raises for its `onError` to render: this one
|
|
7
|
+
* is what a caller throws once a response has already come back. `body` is null wherever the failing
|
|
8
|
+
* tier emitted no document - a proxy, a gateway timeout, or an auth server with a shape of its own.
|
|
9
|
+
* `retryAfterSeconds` is how long the response's `Retry-After` asked the caller to wait before asking
|
|
10
|
+
* again, and null where it named no wait or one in neither of the header's forms.
|
|
11
|
+
*/
|
|
12
|
+
export class ResponseError extends Error {
|
|
13
|
+
status;
|
|
14
|
+
body;
|
|
15
|
+
retryAfterSeconds;
|
|
16
|
+
constructor(status, body, retryAfterSeconds) {
|
|
17
|
+
super(`Request failed with status ${status}`);
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.body = body;
|
|
20
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
21
|
+
this.name = 'ResponseError';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The {@link ResponseError} a refused response yields: its status, the error document its body holds, every error
|
|
26
|
+
* object kept in the order the server sent them, and the wait its `Retry-After` names.
|
|
27
|
+
*
|
|
28
|
+
* Reads the body once, so the response must not have been read before; a caller that needs the body for anything
|
|
29
|
+
* 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,
|
|
30
|
+
* and where it is JSON but no error document. It never rejects.
|
|
31
|
+
*
|
|
32
|
+
* @param clock - what a `Retry-After` HTTP-date is counted from; `retryAfterSeconds` is the whole seconds from
|
|
33
|
+
* `clock.now()` until that date, rounded up and never below 0. Delay-seconds are taken as sent.
|
|
34
|
+
* @example
|
|
35
|
+
* if (!response.ok) throw await errorOf(response, { now: () => new Date() });
|
|
36
|
+
*/
|
|
37
|
+
export async function errorOf(response, clock) {
|
|
38
|
+
const document = errorDocumentSchema.safeParse(await response.json().catch(() => null));
|
|
39
|
+
const retryAfterSeconds = retryAfterSecondsOf(response.headers.get('Retry-After'), clock.now());
|
|
40
|
+
return new ResponseError(response.status, document.success ? document.data : null, retryAfterSeconds);
|
|
41
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seconds a `Retry-After` value (RFC 9110 section 10.2.3) asks the caller to wait from `now`: delay-seconds as
|
|
3
|
+
* they are, an HTTP-date as the whole seconds until it, rounded up and never below 0. Null where `value` is null or
|
|
4
|
+
* is neither form.
|
|
5
|
+
*/
|
|
6
|
+
export declare function retryAfterSecondsOf(value: string | null, now: Date): number | null;
|
|
7
|
+
//# sourceMappingURL=retry-after.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-after.d.ts","sourceRoot":"","sources":["../../src/client/retry-after.ts"],"names":[],"mappings":"AAmFA;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAOlF"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const DELAY_SECONDS = /^\d+$/;
|
|
2
|
+
const DAY_NAMES = '(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)';
|
|
3
|
+
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
4
|
+
const MONTH = `(${MONTHS.join('|')})`;
|
|
5
|
+
const TIME_OF_DAY = '(\\d{2}):(\\d{2}):(\\d{2})';
|
|
6
|
+
/** `Sun, 06 Nov 1994 08:49:37 GMT`: day, month, year, hour, minute, second. */
|
|
7
|
+
const IMF_FIXDATE = new RegExp(`^${DAY_NAMES}, (\\d{2}) ${MONTH} (\\d{4}) ${TIME_OF_DAY} GMT$`);
|
|
8
|
+
/** `Sunday, 06-Nov-94 08:49:37 GMT`: day, month, two-digit year, hour, minute, second. */
|
|
9
|
+
const RFC850_DATE = new RegExp(`^(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\\d{2})-${MONTH}-(\\d{2}) ${TIME_OF_DAY} GMT$`);
|
|
10
|
+
/** `Sun Nov 6 08:49:37 1994`: month, day padded with a space, hour, minute, second, year. */
|
|
11
|
+
const ASCTIME_DATE = new RegExp(`^${DAY_NAMES} ${MONTH} ([ \\d]\\d) ${TIME_OF_DAY} (\\d{4})$`);
|
|
12
|
+
/**
|
|
13
|
+
* The instant an HTTP-date names, in any of the three forms RFC 9110 section 5.6.7 has a recipient accept, or null
|
|
14
|
+
* where `value` is none of them or names no such day or time; a second of 60 is the leap second the RFC allows.
|
|
15
|
+
* `now` places an RFC 850 date's two-digit year.
|
|
16
|
+
*/
|
|
17
|
+
function instantOf(value, now) {
|
|
18
|
+
const parts = partsOf(value, now);
|
|
19
|
+
if (parts === null)
|
|
20
|
+
return null;
|
|
21
|
+
const [day, hour, minute, second] = [parts.day, parts.hour, parts.minute, parts.second].map(Number);
|
|
22
|
+
const month = MONTHS.indexOf(parts.month);
|
|
23
|
+
if (hour > 23 || minute > 59 || second > 60)
|
|
24
|
+
return null;
|
|
25
|
+
const minuteStart = new Date(Date.UTC(parts.year, month, day, hour, minute));
|
|
26
|
+
if (minuteStart.getUTCDate() !== day || minuteStart.getUTCMonth() !== month)
|
|
27
|
+
return null;
|
|
28
|
+
return minuteStart.getTime() + second * 1000;
|
|
29
|
+
}
|
|
30
|
+
/** The fields of whichever of the three forms `value` is, or null where it is none of them. */
|
|
31
|
+
function partsOf(value, now) {
|
|
32
|
+
const fixdate = IMF_FIXDATE.exec(value);
|
|
33
|
+
if (fixdate) {
|
|
34
|
+
const [, day, month, year, hour, minute, second] = fixdate;
|
|
35
|
+
return { year: Number(year), month, day, hour, minute, second };
|
|
36
|
+
}
|
|
37
|
+
const rfc850 = RFC850_DATE.exec(value);
|
|
38
|
+
if (rfc850) {
|
|
39
|
+
const [, day, month, year, hour, minute, second] = rfc850;
|
|
40
|
+
return { year: fullYearOf(Number(year), now), month, day, hour, minute, second };
|
|
41
|
+
}
|
|
42
|
+
const asctime = ASCTIME_DATE.exec(value);
|
|
43
|
+
if (asctime) {
|
|
44
|
+
const [, month, day, hour, minute, second, year] = asctime;
|
|
45
|
+
return { year: Number(year), month, day: day.trim(), hour, minute, second };
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The year a two-digit year names: the one ending in those digits that is no more than fifty years after `now`,
|
|
51
|
+
* which is how RFC 9110 section 5.6.7 has a recipient read an RFC 850 date.
|
|
52
|
+
*/
|
|
53
|
+
function fullYearOf(twoDigits, now) {
|
|
54
|
+
const year = now.getUTCFullYear() - (now.getUTCFullYear() % 100) + twoDigits;
|
|
55
|
+
return year > now.getUTCFullYear() + 50 ? year - 100 : year;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The seconds a `Retry-After` value (RFC 9110 section 10.2.3) asks the caller to wait from `now`: delay-seconds as
|
|
59
|
+
* they are, an HTTP-date as the whole seconds until it, rounded up and never below 0. Null where `value` is null or
|
|
60
|
+
* is neither form.
|
|
61
|
+
*/
|
|
62
|
+
export function retryAfterSecondsOf(value, now) {
|
|
63
|
+
if (value === null)
|
|
64
|
+
return null;
|
|
65
|
+
if (DELAY_SECONDS.test(value))
|
|
66
|
+
return Number(value);
|
|
67
|
+
const instant = instantOf(value, now);
|
|
68
|
+
return instant === null ? null : Math.max(0, Math.ceil((instant - now.getTime()) / 1000));
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { ErrorDocument } from '@zeroxsolutions/response';
|
|
2
|
-
/**
|
|
3
|
-
* A non-2xx response, as the client that made the request holds it.
|
|
4
|
-
*
|
|
5
|
-
* The counterpart of `JsonApiError`, which a SERVER raises for its `onError` to render: this one
|
|
6
|
-
* is what a caller throws once a response has already come back. `body` is null wherever the failing
|
|
7
|
-
* tier emitted no document - a proxy, a gateway timeout, or an auth server with a shape of its own.
|
|
8
|
-
*/
|
|
9
|
-
export declare class ApiError extends Error {
|
|
10
|
-
readonly status: number;
|
|
11
|
-
readonly body: ErrorDocument | null;
|
|
12
|
-
constructor(status: number, body: ErrorDocument | null);
|
|
13
|
-
}
|
|
14
|
-
//# sourceMappingURL=api-error.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../src/client/api-error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;;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"}
|
package/dist/client/api-error.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A non-2xx response, as the client that made the request holds it.
|
|
3
|
-
*
|
|
4
|
-
* The counterpart of `JsonApiError`, which a SERVER raises for its `onError` to render: this one
|
|
5
|
-
* is what a caller throws once a response has already come back. `body` is null wherever the failing
|
|
6
|
-
* tier emitted no document - a proxy, a gateway timeout, or an auth server with a shape of its own.
|
|
7
|
-
*/
|
|
8
|
-
export class ApiError extends Error {
|
|
9
|
-
status;
|
|
10
|
-
body;
|
|
11
|
-
constructor(status, body) {
|
|
12
|
-
super(`Request failed with status ${status}`);
|
|
13
|
-
this.status = status;
|
|
14
|
-
this.body = body;
|
|
15
|
-
this.name = 'ApiError';
|
|
16
|
-
}
|
|
17
|
-
}
|