@sia-ui/api 0.3.2 → 0.5.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 +51 -0
- package/README.md +81 -12
- package/dist/index.cjs +49 -8
- package/dist/index.d.cts +77 -36
- package/dist/index.d.ts +77 -36
- package/dist/index.js +49 -9
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
# @sia-ui/api
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Erreurs serveur dans les formulaires, client HTTP plus sûr, registre vérifié.
|
|
8
|
+
|
|
9
|
+
## Défauts
|
|
10
|
+
- **Registre `drawer`** — `context.ts` et la dépendance `icons` manquaient :
|
|
11
|
+
`sia-ui add app-shell` produisait un projet qui ne compilait pas. Un test
|
|
12
|
+
vérifie désormais que chaque import relatif de chaque entrée est apporté par
|
|
13
|
+
son installation.
|
|
14
|
+
- **`sia-ui.css`** pose `box-sizing: border-box` (spécificité nulle). Sans
|
|
15
|
+
reset côté projet, `AppShell` débordait de 32 px.
|
|
16
|
+
- **`@sia-ui/api`** ne rejoue plus d'office que `GET` et `HEAD`
|
|
17
|
+
(`retryMethods`). Une écriture n'est rejouée que si l'appel pose `retry`.
|
|
18
|
+
- Plus aucun avertissement `react-hooks/exhaustive-deps` dans les composants.
|
|
19
|
+
|
|
20
|
+
## Formulaires et erreurs serveur
|
|
21
|
+
- `Form` intercepte un envoi rejeté : erreurs de champ sous les champs, le
|
|
22
|
+
reste dans une alerte. `onSubmit` peut aussi rendre `{ champ: message }`
|
|
23
|
+
(`FormSubmitResult`), dans `useLocalForm`, `Form`, `CrudPage` et
|
|
24
|
+
l'adaptateur react-hook-form. `CrudPage` garde alors sa boîte ouverte.
|
|
25
|
+
- Les erreurs posées par `setErrors` survivent à la validation locale jusqu'à
|
|
26
|
+
la modification de leur champ.
|
|
27
|
+
- `HttpError.toFormErrors()`, option `fieldErrors` du client et préréglage
|
|
28
|
+
`nestFieldErrors` (class-validator). `readSubmitError` et `hasFormErrors`
|
|
29
|
+
dans `@sia-ui/headless`.
|
|
30
|
+
- La pagination à plat `{ data, total, page, limit }` est reconnue, et `get`
|
|
31
|
+
ne déballe plus `data` quand l'objet porte une pagination.
|
|
32
|
+
|
|
33
|
+
## Tableaux
|
|
34
|
+
- `searchDelay` sur `DataTable` et `CrudPage`.
|
|
35
|
+
- `CrudPage` place les `extraRowActions` avant « Supprimer ».
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies
|
|
40
|
+
- @sia-ui/utils@0.5.0
|
|
41
|
+
|
|
42
|
+
## 0.4.0
|
|
43
|
+
|
|
44
|
+
### Minor Changes
|
|
45
|
+
|
|
46
|
+
- Aligne tous les packages publics SIA UI sur la version 0.4.0 afin de fournir
|
|
47
|
+
un ensemble cohérent à installer, documenter et maintenir.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies
|
|
52
|
+
- @sia-ui/utils@0.4.0
|
|
53
|
+
|
|
3
54
|
## 0.3.2
|
|
4
55
|
|
|
5
56
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<p align="center"><img src="https://beatjo.github.io/sia-ui-site/brand/sia-ui-logo.png" alt="SIA UI" width="320"></p>
|
|
2
|
-
|
|
1
|
+
<p align="center"><img src="https://beatjo.github.io/sia-ui-site/brand/sia-ui-logo.png" alt="SIA UI" width="320"></p>
|
|
2
|
+
|
|
3
3
|
# @sia-ui/api
|
|
4
4
|
|
|
5
5
|
Un client HTTP typé, ses services CRUD et ses greffons. Aucun rapport avec
|
|
@@ -40,22 +40,90 @@ le projet utilise déjà axios. Le paquet ne dépend d'aucun des deux.
|
|
|
40
40
|
| Filtres | `$and` / `$or` portés par un en-tête plutôt que par l'URL |
|
|
41
41
|
|
|
42
42
|
Voir [docs/fondations/module-api.md](https://beatjo.github.io/sia-ui-site/fondations/module-api.html).
|
|
43
|
-
|
|
44
|
-
## Contribuer
|
|
45
|
-
|
|
46
|
-
Le code source principal de SIA UI est privé pour le moment. Les retours publics passent par le dépôt `BeatJo/sia-ui-site` : bugs, demandes de composants, corrections de documentation et questions d'usage.
|
|
47
|
-
|
|
48
|
-
- Signaler un bug : https://github.com/BeatJo/sia-ui-site/issues
|
|
49
|
-
- Lire la documentation : https://beatjo.github.io/sia-ui-site/
|
|
50
|
-
- Voir la démonstration : https://beatjo.github.io/sia-ui-site/preview/
|
|
51
|
-
|
|
52
|
-
Les contributions directes au code des packages se font sur invitation dans le dépôt source privé.
|
|
43
|
+
|
|
44
|
+
## Contribuer
|
|
45
|
+
|
|
46
|
+
Le code source principal de SIA UI est privé pour le moment. Les retours publics passent par le dépôt `BeatJo/sia-ui-site` : bugs, demandes de composants, corrections de documentation et questions d'usage.
|
|
47
|
+
|
|
48
|
+
- Signaler un bug : https://github.com/BeatJo/sia-ui-site/issues
|
|
49
|
+
- Lire la documentation : https://beatjo.github.io/sia-ui-site/
|
|
50
|
+
- Voir la démonstration : https://beatjo.github.io/sia-ui-site/preview/
|
|
51
|
+
|
|
52
|
+
Les contributions directes au code des packages se font sur invitation dans le dépôt source privé.
|
|
53
53
|
## Journal des changements
|
|
54
54
|
|
|
55
55
|
Le contenu ci-dessous reprend intégralement `CHANGELOG.md` pour rester visible sur npm.
|
|
56
56
|
|
|
57
|
+
<!-- sia:changelog:start -->
|
|
57
58
|
# @sia-ui/api
|
|
58
59
|
|
|
60
|
+
## 0.5.0
|
|
61
|
+
|
|
62
|
+
### Minor Changes
|
|
63
|
+
|
|
64
|
+
- Erreurs serveur dans les formulaires, client HTTP plus sûr, registre vérifié.
|
|
65
|
+
|
|
66
|
+
## Défauts
|
|
67
|
+
- **Registre `drawer`** — `context.ts` et la dépendance `icons` manquaient :
|
|
68
|
+
`sia-ui add app-shell` produisait un projet qui ne compilait pas. Un test
|
|
69
|
+
vérifie désormais que chaque import relatif de chaque entrée est apporté par
|
|
70
|
+
son installation.
|
|
71
|
+
- **`sia-ui.css`** pose `box-sizing: border-box` (spécificité nulle). Sans
|
|
72
|
+
reset côté projet, `AppShell` débordait de 32 px.
|
|
73
|
+
- **`@sia-ui/api`** ne rejoue plus d'office que `GET` et `HEAD`
|
|
74
|
+
(`retryMethods`). Une écriture n'est rejouée que si l'appel pose `retry`.
|
|
75
|
+
- Plus aucun avertissement `react-hooks/exhaustive-deps` dans les composants.
|
|
76
|
+
|
|
77
|
+
## Formulaires et erreurs serveur
|
|
78
|
+
- `Form` intercepte un envoi rejeté : erreurs de champ sous les champs, le
|
|
79
|
+
reste dans une alerte. `onSubmit` peut aussi rendre `{ champ: message }`
|
|
80
|
+
(`FormSubmitResult`), dans `useLocalForm`, `Form`, `CrudPage` et
|
|
81
|
+
l'adaptateur react-hook-form. `CrudPage` garde alors sa boîte ouverte.
|
|
82
|
+
- Les erreurs posées par `setErrors` survivent à la validation locale jusqu'à
|
|
83
|
+
la modification de leur champ.
|
|
84
|
+
- `HttpError.toFormErrors()`, option `fieldErrors` du client et préréglage
|
|
85
|
+
`nestFieldErrors` (class-validator). `readSubmitError` et `hasFormErrors`
|
|
86
|
+
dans `@sia-ui/headless`.
|
|
87
|
+
- La pagination à plat `{ data, total, page, limit }` est reconnue, et `get`
|
|
88
|
+
ne déballe plus `data` quand l'objet porte une pagination.
|
|
89
|
+
|
|
90
|
+
## Tableaux
|
|
91
|
+
- `searchDelay` sur `DataTable` et `CrudPage`.
|
|
92
|
+
- `CrudPage` place les `extraRowActions` avant « Supprimer ».
|
|
93
|
+
|
|
94
|
+
### Patch Changes
|
|
95
|
+
|
|
96
|
+
- Updated dependencies
|
|
97
|
+
- @sia-ui/utils@0.5.0
|
|
98
|
+
|
|
99
|
+
## 0.4.0
|
|
100
|
+
|
|
101
|
+
### Minor Changes
|
|
102
|
+
|
|
103
|
+
- Aligne tous les packages publics SIA UI sur la version 0.4.0 afin de fournir
|
|
104
|
+
un ensemble cohérent à installer, documenter et maintenir.
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- Updated dependencies
|
|
109
|
+
- @sia-ui/utils@0.4.0
|
|
110
|
+
|
|
111
|
+
## 0.3.2
|
|
112
|
+
|
|
113
|
+
### Patch Changes
|
|
114
|
+
|
|
115
|
+
- Ajoute l’identité visuelle officielle de SIA UI et améliore la documentation publique, les changelogs et les ressources de contribution.
|
|
116
|
+
- Updated dependencies
|
|
117
|
+
- @sia-ui/utils@0.3.2
|
|
118
|
+
|
|
119
|
+
## 0.3.1
|
|
120
|
+
|
|
121
|
+
### Patch Changes
|
|
122
|
+
|
|
123
|
+
- Expose public contribution flow on npm
|
|
124
|
+
- Updated dependencies
|
|
125
|
+
- @sia-ui/utils@0.3.1
|
|
126
|
+
|
|
59
127
|
## 0.3.0
|
|
60
128
|
|
|
61
129
|
### Minor Changes
|
|
@@ -210,6 +278,7 @@ Le contenu ci-dessous reprend intégralement `CHANGELOG.md` pour rester visible
|
|
|
210
278
|
|
|
211
279
|
- Updated dependencies [e4e4a72]
|
|
212
280
|
- @sia-ui/utils@0.2.0
|
|
281
|
+
<!-- sia:changelog:end -->
|
|
213
282
|
|
|
214
283
|
## Licence
|
|
215
284
|
|
package/dist/index.cjs
CHANGED
|
@@ -4,21 +4,46 @@ var async = require('@sia-ui/utils/async');
|
|
|
4
4
|
var query = require('@sia-ui/utils/query');
|
|
5
5
|
|
|
6
6
|
// src/http-error.ts
|
|
7
|
+
var NEST_FIELD = /^([A-Za-z_$][\w$]*(?:\.[\w$]+)*)\s/;
|
|
8
|
+
var nestFieldErrors = (body) => {
|
|
9
|
+
const standard = HttpError.extractFields(body);
|
|
10
|
+
if (standard.length > 0 || !body || typeof body !== "object") return standard;
|
|
11
|
+
const messages = body.message;
|
|
12
|
+
if (!Array.isArray(messages)) return [];
|
|
13
|
+
return messages.flatMap((message) => {
|
|
14
|
+
if (typeof message !== "string") return [];
|
|
15
|
+
const field = NEST_FIELD.exec(message)?.[1];
|
|
16
|
+
return field ? [{ field, message }] : [];
|
|
17
|
+
});
|
|
18
|
+
};
|
|
7
19
|
var HttpError = class _HttpError extends Error {
|
|
8
20
|
status;
|
|
9
21
|
body;
|
|
10
22
|
headers;
|
|
11
23
|
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
12
24
|
fields;
|
|
13
|
-
constructor(status, body = null, message, headers = {}) {
|
|
25
|
+
constructor(status, body = null, message, headers = {}, extractFields = _HttpError.extractFields) {
|
|
14
26
|
super(message ?? _HttpError.extractMessage(body) ?? `HTTP ${status}`);
|
|
15
27
|
this.name = "HttpError";
|
|
16
28
|
this.status = status;
|
|
17
29
|
this.body = body;
|
|
18
30
|
this.headers = headers;
|
|
19
|
-
this.fields =
|
|
31
|
+
this.fields = extractFields(body);
|
|
20
32
|
Object.setPrototypeOf(this, _HttpError.prototype);
|
|
21
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Les erreurs par champ, sous la forme qu'attend un formulaire.
|
|
36
|
+
*
|
|
37
|
+
* `FormAdapter.setErrors` prend `{ champ: message }` : c'est cette carte.
|
|
38
|
+
* Le premier message d'un champ l'emporte — un champ n'en affiche qu'un.
|
|
39
|
+
*/
|
|
40
|
+
toFormErrors() {
|
|
41
|
+
const errors = {};
|
|
42
|
+
for (const { field, message } of this.fields) {
|
|
43
|
+
if (!(field in errors)) errors[field] = message;
|
|
44
|
+
}
|
|
45
|
+
return errors;
|
|
46
|
+
}
|
|
22
47
|
static async fromResponse(response) {
|
|
23
48
|
const headers = _HttpError.normalizeHeaders(response.headers);
|
|
24
49
|
const contentType = headers["content-type"] ?? "";
|
|
@@ -121,6 +146,10 @@ function buildQueryString(params) {
|
|
|
121
146
|
function asRecord(value) {
|
|
122
147
|
return value && typeof value === "object" ? value : null;
|
|
123
148
|
}
|
|
149
|
+
var PAGE_KEYS = ["total", "page", "limit", "totalPages"];
|
|
150
|
+
function hasPageKeys(record) {
|
|
151
|
+
return PAGE_KEYS.some((key) => record[key] !== void 0);
|
|
152
|
+
}
|
|
124
153
|
var ResponseHandler = class {
|
|
125
154
|
config;
|
|
126
155
|
constructor(config = {}) {
|
|
@@ -129,7 +158,9 @@ var ResponseHandler = class {
|
|
|
129
158
|
extractData(raw) {
|
|
130
159
|
if (this.config.extractData) return this.config.extractData(raw);
|
|
131
160
|
const record = asRecord(raw);
|
|
132
|
-
if (record && record.data !== void 0
|
|
161
|
+
if (record && record.data !== void 0 && !hasPageKeys(record)) {
|
|
162
|
+
return record.data;
|
|
163
|
+
}
|
|
133
164
|
return raw;
|
|
134
165
|
}
|
|
135
166
|
extractPaginated(raw) {
|
|
@@ -155,11 +186,12 @@ var ResponseHandler = class {
|
|
|
155
186
|
if (Array.isArray(data) && meta) {
|
|
156
187
|
return { items: data, meta };
|
|
157
188
|
}
|
|
158
|
-
|
|
159
|
-
|
|
189
|
+
const items = Array.isArray(record.items) ? record.items : Array.isArray(data) && hasPageKeys(record) ? data : null;
|
|
190
|
+
if (items) {
|
|
191
|
+
const limit = Number(record.limit ?? items.length);
|
|
160
192
|
const total = record.total === void 0 ? void 0 : Number(record.total);
|
|
161
193
|
return {
|
|
162
|
-
items
|
|
194
|
+
items,
|
|
163
195
|
meta: {
|
|
164
196
|
page: Number(record.page ?? 1),
|
|
165
197
|
limit,
|
|
@@ -305,6 +337,7 @@ function createAxiosTransport(axios) {
|
|
|
305
337
|
|
|
306
338
|
// src/client.ts
|
|
307
339
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
340
|
+
var DEFAULT_RETRY_METHODS = ["GET", "HEAD"];
|
|
308
341
|
function isAbsoluteUrl(path) {
|
|
309
342
|
return /^https?:\/\//i.test(path);
|
|
310
343
|
}
|
|
@@ -321,6 +354,8 @@ var ApiClient = class {
|
|
|
321
354
|
getLanguage;
|
|
322
355
|
defaultHeaders;
|
|
323
356
|
defaultRetry;
|
|
357
|
+
retryMethods;
|
|
358
|
+
fieldErrors;
|
|
324
359
|
defaultTimeoutMs;
|
|
325
360
|
plugins;
|
|
326
361
|
responseHandler;
|
|
@@ -333,6 +368,10 @@ var ApiClient = class {
|
|
|
333
368
|
"Content-Type": "application/json"
|
|
334
369
|
};
|
|
335
370
|
this.defaultRetry = options.defaultRetry ?? 2;
|
|
371
|
+
this.retryMethods = new Set(
|
|
372
|
+
(options.retryMethods ?? DEFAULT_RETRY_METHODS).map(normalizeMethod)
|
|
373
|
+
);
|
|
374
|
+
this.fieldErrors = options.fieldErrors;
|
|
336
375
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 3e4;
|
|
337
376
|
this.plugins = options.plugins ?? [];
|
|
338
377
|
this.responseHandler = new ResponseHandler(options.responseHandler);
|
|
@@ -397,7 +436,7 @@ var ApiClient = class {
|
|
|
397
436
|
};
|
|
398
437
|
const onRequestResult = await this.runPlugins("onRequest", ctx);
|
|
399
438
|
if (onRequestResult !== void 0) return onRequestResult;
|
|
400
|
-
const maxRetries = config.retry ?? this.defaultRetry;
|
|
439
|
+
const maxRetries = config.retry ?? (this.retryMethods.has(ctx.method) ? this.defaultRetry : 0);
|
|
401
440
|
let attempt = 0;
|
|
402
441
|
while (true) {
|
|
403
442
|
try {
|
|
@@ -421,7 +460,8 @@ var ApiClient = class {
|
|
|
421
460
|
response.status,
|
|
422
461
|
response.data,
|
|
423
462
|
void 0,
|
|
424
|
-
response.headers
|
|
463
|
+
response.headers,
|
|
464
|
+
this.fieldErrors
|
|
425
465
|
);
|
|
426
466
|
}
|
|
427
467
|
if (response.status === 204) return null;
|
|
@@ -925,4 +965,5 @@ exports.createRefreshTokenPlugin = createRefreshTokenPlugin;
|
|
|
925
965
|
exports.createResourceService = createResourceService;
|
|
926
966
|
exports.createUnauthorizedPlugin = createUnauthorizedPlugin;
|
|
927
967
|
exports.filtersHeader = filtersHeader;
|
|
968
|
+
exports.nestFieldErrors = nestFieldErrors;
|
|
928
969
|
exports.normalizeFilters = normalizeFilters;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Une erreur de validation rattachée à un champ.
|
|
3
|
+
*
|
|
4
|
+
* C'est ce qui permet à un formulaire d'afficher « adresse invalide » sous
|
|
5
|
+
* l'adresse plutôt qu'un bandeau générique en haut de page.
|
|
6
|
+
*/
|
|
7
|
+
interface FieldError {
|
|
8
|
+
field: string;
|
|
9
|
+
message: string;
|
|
10
|
+
code?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Lit les erreurs par champ dans un corps d'erreur.
|
|
14
|
+
*
|
|
15
|
+
* Chaque serveur a sa forme ; celle-ci se règle une fois sur le client
|
|
16
|
+
* (`fieldErrors` de `createApiClient`) plutôt qu'à chaque formulaire.
|
|
17
|
+
*/
|
|
18
|
+
type FieldErrorExtractor = (body: unknown) => FieldError[];
|
|
19
|
+
/**
|
|
20
|
+
* Le préréglage NestJS.
|
|
21
|
+
*
|
|
22
|
+
* `ValidationPipe` renvoie `{ message: string[] }`, chaque phrase commençant
|
|
23
|
+
* par le chemin du champ : « email must be an email », « address.city should
|
|
24
|
+
* not be empty ». Le champ est ce premier mot ; le message reste la phrase
|
|
25
|
+
* entière, que le serveur a écrite pour être lue. Les formes génériques
|
|
26
|
+
* (`errors`, `fieldErrors`, `violations`) passent d'abord : un
|
|
27
|
+
* `exceptionFactory` personnalisé les produit souvent.
|
|
28
|
+
*/
|
|
29
|
+
declare const nestFieldErrors: FieldErrorExtractor;
|
|
30
|
+
declare class HttpError extends Error {
|
|
31
|
+
status: number;
|
|
32
|
+
body: unknown;
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
35
|
+
fields: FieldError[];
|
|
36
|
+
constructor(status: number, body?: unknown, message?: string, headers?: Record<string, string>, extractFields?: FieldErrorExtractor);
|
|
37
|
+
/**
|
|
38
|
+
* Les erreurs par champ, sous la forme qu'attend un formulaire.
|
|
39
|
+
*
|
|
40
|
+
* `FormAdapter.setErrors` prend `{ champ: message }` : c'est cette carte.
|
|
41
|
+
* Le premier message d'un champ l'emporte — un champ n'en affiche qu'un.
|
|
42
|
+
*/
|
|
43
|
+
toFormErrors(): Record<string, string>;
|
|
44
|
+
static fromResponse(response: Response): Promise<HttpError>;
|
|
45
|
+
/**
|
|
46
|
+
* Retrouve les erreurs par champ dans un corps d'erreur.
|
|
47
|
+
*
|
|
48
|
+
* Trois formes couvertes, parce que trois serveurs sur quatre en utilisent
|
|
49
|
+
* une : la liste (`errors: [{ field, message }]`), la carte
|
|
50
|
+
* (`errors: { email: "…" }`) et la carte de listes, que produit Laravel.
|
|
51
|
+
*/
|
|
52
|
+
static extractFields(body: unknown): FieldError[];
|
|
53
|
+
/** Le message rattaché à ce champ, s'il y en a un. */
|
|
54
|
+
fieldError(field: string): string | undefined;
|
|
55
|
+
static normalizeHeaders(headers: unknown): Record<string, string>;
|
|
56
|
+
static extractMessage(body: unknown): string | null;
|
|
57
|
+
isClientError(): boolean;
|
|
58
|
+
isServerError(): boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
1
61
|
type ApiMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
2
62
|
type QueryParamObject = Record<string, unknown>;
|
|
3
63
|
type QueryParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | QueryParamObject | QueryParamObject[] | null | undefined>;
|
|
@@ -79,10 +139,24 @@ interface ApiClientOptions {
|
|
|
79
139
|
getToken?: (() => string | null | Promise<string | null>) | undefined;
|
|
80
140
|
getLanguage?: (() => string | null | Promise<string | null>) | undefined;
|
|
81
141
|
defaultHeaders?: Record<string, string> | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Nouvelles tentatives sur erreur réseau ou statut transitoire (408, 429,
|
|
144
|
+
* 502, 503, 504), pour les seules `retryMethods`. `retry` sur un appel
|
|
145
|
+
* passe devant.
|
|
146
|
+
*/
|
|
82
147
|
defaultRetry?: number | undefined;
|
|
148
|
+
/** Les méthodes rejouées d'office. Par défaut `GET` et `HEAD`. */
|
|
149
|
+
retryMethods?: ApiMethod[] | undefined;
|
|
83
150
|
defaultTimeoutMs?: number | undefined;
|
|
84
151
|
plugins?: ApiPlugin[] | undefined;
|
|
85
152
|
responseHandler?: ResponseHandlerConfig | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Où lire les erreurs par champ d'une réponse en échec.
|
|
155
|
+
*
|
|
156
|
+
* Par défaut `errors`, `fieldErrors` ou `violations`. `nestFieldErrors`
|
|
157
|
+
* lit en plus le `message: string[]` de class-validator.
|
|
158
|
+
*/
|
|
159
|
+
fieldErrors?: ((body: unknown) => FieldError[]) | undefined;
|
|
86
160
|
}
|
|
87
161
|
type ApiRequestInput = string | {
|
|
88
162
|
path: string;
|
|
@@ -104,6 +178,8 @@ declare class ApiClient {
|
|
|
104
178
|
private getLanguage;
|
|
105
179
|
private defaultHeaders;
|
|
106
180
|
private defaultRetry;
|
|
181
|
+
private retryMethods;
|
|
182
|
+
private fieldErrors;
|
|
107
183
|
private defaultTimeoutMs;
|
|
108
184
|
private plugins;
|
|
109
185
|
private responseHandler;
|
|
@@ -158,41 +234,6 @@ declare class ApiClient {
|
|
|
158
234
|
}
|
|
159
235
|
declare function createApiClient(options: ApiClientOptions): ApiClient;
|
|
160
236
|
|
|
161
|
-
/**
|
|
162
|
-
* Une erreur de validation rattachée à un champ.
|
|
163
|
-
*
|
|
164
|
-
* C'est ce qui permet à un formulaire d'afficher « adresse invalide » sous
|
|
165
|
-
* l'adresse plutôt qu'un bandeau générique en haut de page.
|
|
166
|
-
*/
|
|
167
|
-
interface FieldError {
|
|
168
|
-
field: string;
|
|
169
|
-
message: string;
|
|
170
|
-
code?: string | undefined;
|
|
171
|
-
}
|
|
172
|
-
declare class HttpError extends Error {
|
|
173
|
-
status: number;
|
|
174
|
-
body: unknown;
|
|
175
|
-
headers: Record<string, string>;
|
|
176
|
-
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
177
|
-
fields: FieldError[];
|
|
178
|
-
constructor(status: number, body?: unknown, message?: string, headers?: Record<string, string>);
|
|
179
|
-
static fromResponse(response: Response): Promise<HttpError>;
|
|
180
|
-
/**
|
|
181
|
-
* Retrouve les erreurs par champ dans un corps d'erreur.
|
|
182
|
-
*
|
|
183
|
-
* Trois formes couvertes, parce que trois serveurs sur quatre en utilisent
|
|
184
|
-
* une : la liste (`errors: [{ field, message }]`), la carte
|
|
185
|
-
* (`errors: { email: "…" }`) et la carte de listes, que produit Laravel.
|
|
186
|
-
*/
|
|
187
|
-
static extractFields(body: unknown): FieldError[];
|
|
188
|
-
/** Le message rattaché à ce champ, s'il y en a un. */
|
|
189
|
-
fieldError(field: string): string | undefined;
|
|
190
|
-
static normalizeHeaders(headers: unknown): Record<string, string>;
|
|
191
|
-
static extractMessage(body: unknown): string | null;
|
|
192
|
-
isClientError(): boolean;
|
|
193
|
-
isServerError(): boolean;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
237
|
interface AuthPluginOptions {
|
|
197
238
|
getToken: () => Promise<string | null> | string | null;
|
|
198
239
|
scheme?: string;
|
|
@@ -410,4 +451,4 @@ interface AxiosLikeInstance {
|
|
|
410
451
|
declare function createFetchTransport(fetcher?: typeof fetch): ApiTransport;
|
|
411
452
|
declare function createAxiosTransport(axios: AxiosLikeInstance): ApiTransport;
|
|
412
453
|
|
|
413
|
-
export { ACCESS_TOKEN_KEY, ApiClient, type ApiClientOptions, type ApiFilters, type ApiMethod, type ApiPlugin, type ApiPluginHook, type ApiRequestConfig, type ApiRequestContext, type ApiRequestInput, type ApiTransport, type ApiTransportRequest, type ApiTransportResponse, type AuthPluginOptions, BaseService, type BrowserTokenStorageOptions, type CursorMeta, FILTERS_HEADER, type FieldError, type FilterCondition, type FilterGroup, type FilterOperator, type FilterValue, type HeaderSignalPluginOptions, HttpError, type IdempotencyPluginOptions, type LoggerPluginOptions, type PageMeta, type PaginatedResponse, type QueryParamObject, type QueryParams, REFRESH_TOKEN_KEY, type RefreshTokenConfig, ResponseHandler, type ResponseHandlerConfig, type RtkBaseQueryResult, type TokenStorage, type UnauthorizedPluginOptions, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, normalizeFilters };
|
|
454
|
+
export { ACCESS_TOKEN_KEY, ApiClient, type ApiClientOptions, type ApiFilters, type ApiMethod, type ApiPlugin, type ApiPluginHook, type ApiRequestConfig, type ApiRequestContext, type ApiRequestInput, type ApiTransport, type ApiTransportRequest, type ApiTransportResponse, type AuthPluginOptions, BaseService, type BrowserTokenStorageOptions, type CursorMeta, FILTERS_HEADER, type FieldError, type FieldErrorExtractor, type FilterCondition, type FilterGroup, type FilterOperator, type FilterValue, type HeaderSignalPluginOptions, HttpError, type IdempotencyPluginOptions, type LoggerPluginOptions, type PageMeta, type PaginatedResponse, type QueryParamObject, type QueryParams, REFRESH_TOKEN_KEY, type RefreshTokenConfig, ResponseHandler, type ResponseHandlerConfig, type RtkBaseQueryResult, type TokenStorage, type UnauthorizedPluginOptions, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, nestFieldErrors, normalizeFilters };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Une erreur de validation rattachée à un champ.
|
|
3
|
+
*
|
|
4
|
+
* C'est ce qui permet à un formulaire d'afficher « adresse invalide » sous
|
|
5
|
+
* l'adresse plutôt qu'un bandeau générique en haut de page.
|
|
6
|
+
*/
|
|
7
|
+
interface FieldError {
|
|
8
|
+
field: string;
|
|
9
|
+
message: string;
|
|
10
|
+
code?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Lit les erreurs par champ dans un corps d'erreur.
|
|
14
|
+
*
|
|
15
|
+
* Chaque serveur a sa forme ; celle-ci se règle une fois sur le client
|
|
16
|
+
* (`fieldErrors` de `createApiClient`) plutôt qu'à chaque formulaire.
|
|
17
|
+
*/
|
|
18
|
+
type FieldErrorExtractor = (body: unknown) => FieldError[];
|
|
19
|
+
/**
|
|
20
|
+
* Le préréglage NestJS.
|
|
21
|
+
*
|
|
22
|
+
* `ValidationPipe` renvoie `{ message: string[] }`, chaque phrase commençant
|
|
23
|
+
* par le chemin du champ : « email must be an email », « address.city should
|
|
24
|
+
* not be empty ». Le champ est ce premier mot ; le message reste la phrase
|
|
25
|
+
* entière, que le serveur a écrite pour être lue. Les formes génériques
|
|
26
|
+
* (`errors`, `fieldErrors`, `violations`) passent d'abord : un
|
|
27
|
+
* `exceptionFactory` personnalisé les produit souvent.
|
|
28
|
+
*/
|
|
29
|
+
declare const nestFieldErrors: FieldErrorExtractor;
|
|
30
|
+
declare class HttpError extends Error {
|
|
31
|
+
status: number;
|
|
32
|
+
body: unknown;
|
|
33
|
+
headers: Record<string, string>;
|
|
34
|
+
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
35
|
+
fields: FieldError[];
|
|
36
|
+
constructor(status: number, body?: unknown, message?: string, headers?: Record<string, string>, extractFields?: FieldErrorExtractor);
|
|
37
|
+
/**
|
|
38
|
+
* Les erreurs par champ, sous la forme qu'attend un formulaire.
|
|
39
|
+
*
|
|
40
|
+
* `FormAdapter.setErrors` prend `{ champ: message }` : c'est cette carte.
|
|
41
|
+
* Le premier message d'un champ l'emporte — un champ n'en affiche qu'un.
|
|
42
|
+
*/
|
|
43
|
+
toFormErrors(): Record<string, string>;
|
|
44
|
+
static fromResponse(response: Response): Promise<HttpError>;
|
|
45
|
+
/**
|
|
46
|
+
* Retrouve les erreurs par champ dans un corps d'erreur.
|
|
47
|
+
*
|
|
48
|
+
* Trois formes couvertes, parce que trois serveurs sur quatre en utilisent
|
|
49
|
+
* une : la liste (`errors: [{ field, message }]`), la carte
|
|
50
|
+
* (`errors: { email: "…" }`) et la carte de listes, que produit Laravel.
|
|
51
|
+
*/
|
|
52
|
+
static extractFields(body: unknown): FieldError[];
|
|
53
|
+
/** Le message rattaché à ce champ, s'il y en a un. */
|
|
54
|
+
fieldError(field: string): string | undefined;
|
|
55
|
+
static normalizeHeaders(headers: unknown): Record<string, string>;
|
|
56
|
+
static extractMessage(body: unknown): string | null;
|
|
57
|
+
isClientError(): boolean;
|
|
58
|
+
isServerError(): boolean;
|
|
59
|
+
}
|
|
60
|
+
|
|
1
61
|
type ApiMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD";
|
|
2
62
|
type QueryParamObject = Record<string, unknown>;
|
|
3
63
|
type QueryParams = Record<string, string | string[] | number | number[] | boolean | boolean[] | QueryParamObject | QueryParamObject[] | null | undefined>;
|
|
@@ -79,10 +139,24 @@ interface ApiClientOptions {
|
|
|
79
139
|
getToken?: (() => string | null | Promise<string | null>) | undefined;
|
|
80
140
|
getLanguage?: (() => string | null | Promise<string | null>) | undefined;
|
|
81
141
|
defaultHeaders?: Record<string, string> | undefined;
|
|
142
|
+
/**
|
|
143
|
+
* Nouvelles tentatives sur erreur réseau ou statut transitoire (408, 429,
|
|
144
|
+
* 502, 503, 504), pour les seules `retryMethods`. `retry` sur un appel
|
|
145
|
+
* passe devant.
|
|
146
|
+
*/
|
|
82
147
|
defaultRetry?: number | undefined;
|
|
148
|
+
/** Les méthodes rejouées d'office. Par défaut `GET` et `HEAD`. */
|
|
149
|
+
retryMethods?: ApiMethod[] | undefined;
|
|
83
150
|
defaultTimeoutMs?: number | undefined;
|
|
84
151
|
plugins?: ApiPlugin[] | undefined;
|
|
85
152
|
responseHandler?: ResponseHandlerConfig | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* Où lire les erreurs par champ d'une réponse en échec.
|
|
155
|
+
*
|
|
156
|
+
* Par défaut `errors`, `fieldErrors` ou `violations`. `nestFieldErrors`
|
|
157
|
+
* lit en plus le `message: string[]` de class-validator.
|
|
158
|
+
*/
|
|
159
|
+
fieldErrors?: ((body: unknown) => FieldError[]) | undefined;
|
|
86
160
|
}
|
|
87
161
|
type ApiRequestInput = string | {
|
|
88
162
|
path: string;
|
|
@@ -104,6 +178,8 @@ declare class ApiClient {
|
|
|
104
178
|
private getLanguage;
|
|
105
179
|
private defaultHeaders;
|
|
106
180
|
private defaultRetry;
|
|
181
|
+
private retryMethods;
|
|
182
|
+
private fieldErrors;
|
|
107
183
|
private defaultTimeoutMs;
|
|
108
184
|
private plugins;
|
|
109
185
|
private responseHandler;
|
|
@@ -158,41 +234,6 @@ declare class ApiClient {
|
|
|
158
234
|
}
|
|
159
235
|
declare function createApiClient(options: ApiClientOptions): ApiClient;
|
|
160
236
|
|
|
161
|
-
/**
|
|
162
|
-
* Une erreur de validation rattachée à un champ.
|
|
163
|
-
*
|
|
164
|
-
* C'est ce qui permet à un formulaire d'afficher « adresse invalide » sous
|
|
165
|
-
* l'adresse plutôt qu'un bandeau générique en haut de page.
|
|
166
|
-
*/
|
|
167
|
-
interface FieldError {
|
|
168
|
-
field: string;
|
|
169
|
-
message: string;
|
|
170
|
-
code?: string | undefined;
|
|
171
|
-
}
|
|
172
|
-
declare class HttpError extends Error {
|
|
173
|
-
status: number;
|
|
174
|
-
body: unknown;
|
|
175
|
-
headers: Record<string, string>;
|
|
176
|
-
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
177
|
-
fields: FieldError[];
|
|
178
|
-
constructor(status: number, body?: unknown, message?: string, headers?: Record<string, string>);
|
|
179
|
-
static fromResponse(response: Response): Promise<HttpError>;
|
|
180
|
-
/**
|
|
181
|
-
* Retrouve les erreurs par champ dans un corps d'erreur.
|
|
182
|
-
*
|
|
183
|
-
* Trois formes couvertes, parce que trois serveurs sur quatre en utilisent
|
|
184
|
-
* une : la liste (`errors: [{ field, message }]`), la carte
|
|
185
|
-
* (`errors: { email: "…" }`) et la carte de listes, que produit Laravel.
|
|
186
|
-
*/
|
|
187
|
-
static extractFields(body: unknown): FieldError[];
|
|
188
|
-
/** Le message rattaché à ce champ, s'il y en a un. */
|
|
189
|
-
fieldError(field: string): string | undefined;
|
|
190
|
-
static normalizeHeaders(headers: unknown): Record<string, string>;
|
|
191
|
-
static extractMessage(body: unknown): string | null;
|
|
192
|
-
isClientError(): boolean;
|
|
193
|
-
isServerError(): boolean;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
237
|
interface AuthPluginOptions {
|
|
197
238
|
getToken: () => Promise<string | null> | string | null;
|
|
198
239
|
scheme?: string;
|
|
@@ -410,4 +451,4 @@ interface AxiosLikeInstance {
|
|
|
410
451
|
declare function createFetchTransport(fetcher?: typeof fetch): ApiTransport;
|
|
411
452
|
declare function createAxiosTransport(axios: AxiosLikeInstance): ApiTransport;
|
|
412
453
|
|
|
413
|
-
export { ACCESS_TOKEN_KEY, ApiClient, type ApiClientOptions, type ApiFilters, type ApiMethod, type ApiPlugin, type ApiPluginHook, type ApiRequestConfig, type ApiRequestContext, type ApiRequestInput, type ApiTransport, type ApiTransportRequest, type ApiTransportResponse, type AuthPluginOptions, BaseService, type BrowserTokenStorageOptions, type CursorMeta, FILTERS_HEADER, type FieldError, type FilterCondition, type FilterGroup, type FilterOperator, type FilterValue, type HeaderSignalPluginOptions, HttpError, type IdempotencyPluginOptions, type LoggerPluginOptions, type PageMeta, type PaginatedResponse, type QueryParamObject, type QueryParams, REFRESH_TOKEN_KEY, type RefreshTokenConfig, ResponseHandler, type ResponseHandlerConfig, type RtkBaseQueryResult, type TokenStorage, type UnauthorizedPluginOptions, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, normalizeFilters };
|
|
454
|
+
export { ACCESS_TOKEN_KEY, ApiClient, type ApiClientOptions, type ApiFilters, type ApiMethod, type ApiPlugin, type ApiPluginHook, type ApiRequestConfig, type ApiRequestContext, type ApiRequestInput, type ApiTransport, type ApiTransportRequest, type ApiTransportResponse, type AuthPluginOptions, BaseService, type BrowserTokenStorageOptions, type CursorMeta, FILTERS_HEADER, type FieldError, type FieldErrorExtractor, type FilterCondition, type FilterGroup, type FilterOperator, type FilterValue, type HeaderSignalPluginOptions, HttpError, type IdempotencyPluginOptions, type LoggerPluginOptions, type PageMeta, type PaginatedResponse, type QueryParamObject, type QueryParams, REFRESH_TOKEN_KEY, type RefreshTokenConfig, ResponseHandler, type ResponseHandlerConfig, type RtkBaseQueryResult, type TokenStorage, type UnauthorizedPluginOptions, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, nestFieldErrors, normalizeFilters };
|
package/dist/index.js
CHANGED
|
@@ -2,21 +2,46 @@ import { sleep } from '@sia-ui/utils/async';
|
|
|
2
2
|
import { toQueryString } from '@sia-ui/utils/query';
|
|
3
3
|
|
|
4
4
|
// src/http-error.ts
|
|
5
|
+
var NEST_FIELD = /^([A-Za-z_$][\w$]*(?:\.[\w$]+)*)\s/;
|
|
6
|
+
var nestFieldErrors = (body) => {
|
|
7
|
+
const standard = HttpError.extractFields(body);
|
|
8
|
+
if (standard.length > 0 || !body || typeof body !== "object") return standard;
|
|
9
|
+
const messages = body.message;
|
|
10
|
+
if (!Array.isArray(messages)) return [];
|
|
11
|
+
return messages.flatMap((message) => {
|
|
12
|
+
if (typeof message !== "string") return [];
|
|
13
|
+
const field = NEST_FIELD.exec(message)?.[1];
|
|
14
|
+
return field ? [{ field, message }] : [];
|
|
15
|
+
});
|
|
16
|
+
};
|
|
5
17
|
var HttpError = class _HttpError extends Error {
|
|
6
18
|
status;
|
|
7
19
|
body;
|
|
8
20
|
headers;
|
|
9
21
|
/** Les erreurs par champ, quand le serveur en renvoie. */
|
|
10
22
|
fields;
|
|
11
|
-
constructor(status, body = null, message, headers = {}) {
|
|
23
|
+
constructor(status, body = null, message, headers = {}, extractFields = _HttpError.extractFields) {
|
|
12
24
|
super(message ?? _HttpError.extractMessage(body) ?? `HTTP ${status}`);
|
|
13
25
|
this.name = "HttpError";
|
|
14
26
|
this.status = status;
|
|
15
27
|
this.body = body;
|
|
16
28
|
this.headers = headers;
|
|
17
|
-
this.fields =
|
|
29
|
+
this.fields = extractFields(body);
|
|
18
30
|
Object.setPrototypeOf(this, _HttpError.prototype);
|
|
19
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Les erreurs par champ, sous la forme qu'attend un formulaire.
|
|
34
|
+
*
|
|
35
|
+
* `FormAdapter.setErrors` prend `{ champ: message }` : c'est cette carte.
|
|
36
|
+
* Le premier message d'un champ l'emporte — un champ n'en affiche qu'un.
|
|
37
|
+
*/
|
|
38
|
+
toFormErrors() {
|
|
39
|
+
const errors = {};
|
|
40
|
+
for (const { field, message } of this.fields) {
|
|
41
|
+
if (!(field in errors)) errors[field] = message;
|
|
42
|
+
}
|
|
43
|
+
return errors;
|
|
44
|
+
}
|
|
20
45
|
static async fromResponse(response) {
|
|
21
46
|
const headers = _HttpError.normalizeHeaders(response.headers);
|
|
22
47
|
const contentType = headers["content-type"] ?? "";
|
|
@@ -119,6 +144,10 @@ function buildQueryString(params) {
|
|
|
119
144
|
function asRecord(value) {
|
|
120
145
|
return value && typeof value === "object" ? value : null;
|
|
121
146
|
}
|
|
147
|
+
var PAGE_KEYS = ["total", "page", "limit", "totalPages"];
|
|
148
|
+
function hasPageKeys(record) {
|
|
149
|
+
return PAGE_KEYS.some((key) => record[key] !== void 0);
|
|
150
|
+
}
|
|
122
151
|
var ResponseHandler = class {
|
|
123
152
|
config;
|
|
124
153
|
constructor(config = {}) {
|
|
@@ -127,7 +156,9 @@ var ResponseHandler = class {
|
|
|
127
156
|
extractData(raw) {
|
|
128
157
|
if (this.config.extractData) return this.config.extractData(raw);
|
|
129
158
|
const record = asRecord(raw);
|
|
130
|
-
if (record && record.data !== void 0
|
|
159
|
+
if (record && record.data !== void 0 && !hasPageKeys(record)) {
|
|
160
|
+
return record.data;
|
|
161
|
+
}
|
|
131
162
|
return raw;
|
|
132
163
|
}
|
|
133
164
|
extractPaginated(raw) {
|
|
@@ -153,11 +184,12 @@ var ResponseHandler = class {
|
|
|
153
184
|
if (Array.isArray(data) && meta) {
|
|
154
185
|
return { items: data, meta };
|
|
155
186
|
}
|
|
156
|
-
|
|
157
|
-
|
|
187
|
+
const items = Array.isArray(record.items) ? record.items : Array.isArray(data) && hasPageKeys(record) ? data : null;
|
|
188
|
+
if (items) {
|
|
189
|
+
const limit = Number(record.limit ?? items.length);
|
|
158
190
|
const total = record.total === void 0 ? void 0 : Number(record.total);
|
|
159
191
|
return {
|
|
160
|
-
items
|
|
192
|
+
items,
|
|
161
193
|
meta: {
|
|
162
194
|
page: Number(record.page ?? 1),
|
|
163
195
|
limit,
|
|
@@ -303,6 +335,7 @@ function createAxiosTransport(axios) {
|
|
|
303
335
|
|
|
304
336
|
// src/client.ts
|
|
305
337
|
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([408, 429, 502, 503, 504]);
|
|
338
|
+
var DEFAULT_RETRY_METHODS = ["GET", "HEAD"];
|
|
306
339
|
function isAbsoluteUrl(path) {
|
|
307
340
|
return /^https?:\/\//i.test(path);
|
|
308
341
|
}
|
|
@@ -319,6 +352,8 @@ var ApiClient = class {
|
|
|
319
352
|
getLanguage;
|
|
320
353
|
defaultHeaders;
|
|
321
354
|
defaultRetry;
|
|
355
|
+
retryMethods;
|
|
356
|
+
fieldErrors;
|
|
322
357
|
defaultTimeoutMs;
|
|
323
358
|
plugins;
|
|
324
359
|
responseHandler;
|
|
@@ -331,6 +366,10 @@ var ApiClient = class {
|
|
|
331
366
|
"Content-Type": "application/json"
|
|
332
367
|
};
|
|
333
368
|
this.defaultRetry = options.defaultRetry ?? 2;
|
|
369
|
+
this.retryMethods = new Set(
|
|
370
|
+
(options.retryMethods ?? DEFAULT_RETRY_METHODS).map(normalizeMethod)
|
|
371
|
+
);
|
|
372
|
+
this.fieldErrors = options.fieldErrors;
|
|
334
373
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 3e4;
|
|
335
374
|
this.plugins = options.plugins ?? [];
|
|
336
375
|
this.responseHandler = new ResponseHandler(options.responseHandler);
|
|
@@ -395,7 +434,7 @@ var ApiClient = class {
|
|
|
395
434
|
};
|
|
396
435
|
const onRequestResult = await this.runPlugins("onRequest", ctx);
|
|
397
436
|
if (onRequestResult !== void 0) return onRequestResult;
|
|
398
|
-
const maxRetries = config.retry ?? this.defaultRetry;
|
|
437
|
+
const maxRetries = config.retry ?? (this.retryMethods.has(ctx.method) ? this.defaultRetry : 0);
|
|
399
438
|
let attempt = 0;
|
|
400
439
|
while (true) {
|
|
401
440
|
try {
|
|
@@ -419,7 +458,8 @@ var ApiClient = class {
|
|
|
419
458
|
response.status,
|
|
420
459
|
response.data,
|
|
421
460
|
void 0,
|
|
422
|
-
response.headers
|
|
461
|
+
response.headers,
|
|
462
|
+
this.fieldErrors
|
|
423
463
|
);
|
|
424
464
|
}
|
|
425
465
|
if (response.status === 204) return null;
|
|
@@ -901,4 +941,4 @@ function createResourceService(client, resourcePath) {
|
|
|
901
941
|
return new BaseService(client, resourcePath);
|
|
902
942
|
}
|
|
903
943
|
|
|
904
|
-
export { ACCESS_TOKEN_KEY, ApiClient, BaseService, FILTERS_HEADER, HttpError, REFRESH_TOKEN_KEY, ResponseHandler, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, normalizeFilters };
|
|
944
|
+
export { ACCESS_TOKEN_KEY, ApiClient, BaseService, FILTERS_HEADER, HttpError, REFRESH_TOKEN_KEY, ResponseHandler, buildQueryString, createApiClient, createAuthPlugin, createAxiosTransport, createBrowserTokenStorage, createFetchTransport, createHeaderSignalPlugin, createIdempotencyPlugin, createLoggerPlugin, createMemoryTokenStorage, createReadOnlyPlugin, createRefreshTokenPlugin, createResourceService, createUnauthorizedPlugin, filtersHeader, nestFieldErrors, normalizeFilters };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sia-ui/api",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Typed HTTP client with CRUD services, plugins and injectable transports.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sia-ui",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@sia-ui/utils": "0.
|
|
45
|
+
"@sia-ui/utils": "0.5.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"tsup": "^8.5.1",
|