@vahidkaargar/customized-api-client 0.2.4 → 0.4.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/README.md +53 -5
- package/dist/index.cjs +187 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -20
- package/dist/index.d.ts +66 -20
- package/dist/index.js +174 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,6 +187,19 @@ setTimeout(() => controller.abort(), 5_000);
|
|
|
187
187
|
await promise; // throws when aborted
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
+
### Non-JSON:API bodies (multipart)
|
|
191
|
+
|
|
192
|
+
For file uploads, send `FormData` — the client keeps `Accept: application/vnd.api+json`, omits JSON:API `Content-Type` (Axios sets the multipart boundary), and still sends `Idempotency-Key` on POST.
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
const fd = new FormData();
|
|
196
|
+
fd.append('file', file, file.name);
|
|
197
|
+
|
|
198
|
+
await client.request({ method: 'POST', url: '/media', data: fd });
|
|
199
|
+
// or:
|
|
200
|
+
await client.postFormData('/media', fd);
|
|
201
|
+
```
|
|
202
|
+
|
|
190
203
|
### Poll an async job (202)
|
|
191
204
|
|
|
192
205
|
```typescript
|
|
@@ -216,7 +229,8 @@ Client-level options for `createApiClient({ … })`:
|
|
|
216
229
|
| `defaultHeaders` | — | Merged into every request |
|
|
217
230
|
| `retry` | see [Retries](#retries) | `maxAttempts`, backoff, jitter |
|
|
218
231
|
| `generateIdempotencyKey` | ULID | Factory for mutation keys |
|
|
219
|
-
| `
|
|
232
|
+
| `locale` | — | `getLocale`, `defaultLocale`, `onLocaleMismatch` — see [Locale](#locale-accept-language--content-language) |
|
|
233
|
+
| `getAcceptLanguage` | — | **Deprecated** — use `locale.getLocale`; sets `Accept-Language` when non-empty |
|
|
220
234
|
| `onIdempotencyReplay` | — | Fired when `Idempotent-Replayed: true` |
|
|
221
235
|
| `onUnauthorized` | — | Fired on normalized **401** |
|
|
222
236
|
| `onDeprecated` | — | Deprecation / sunset headers |
|
|
@@ -234,6 +248,30 @@ auth: { type: 'partner-bearer', getSecret: () => process.env.PARTNER_SECRET }
|
|
|
234
248
|
|
|
235
249
|
If `getToken` / `getSecret` returns `null` or `undefined`, no `Authorization` header is sent.
|
|
236
250
|
|
|
251
|
+
### Locale (Accept-Language / Content-Language)
|
|
252
|
+
|
|
253
|
+
Configure locale once on the client (e.g. for backends with `SetLocaleMiddleware`). This package only sets HTTP headers and optional mismatch reporting — not vue-i18n, `GET /locales`, or `GET /translations`.
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
const client = createApiClient({
|
|
257
|
+
baseURL: 'https://api.example.com/api/v1',
|
|
258
|
+
locale: {
|
|
259
|
+
getLocale: () => getStoredLocale(), // 'en' | 'fr' | 'fa'
|
|
260
|
+
defaultLocale: 'en', // omit Accept-Language when UI locale is English (server default)
|
|
261
|
+
onLocaleMismatch: import.meta.env.DEV ? 'warn' : undefined,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
| Behavior | Detail |
|
|
267
|
+
|----------|--------|
|
|
268
|
+
| **Accept-Language** | From `locale.getLocale()` on every request via this client |
|
|
269
|
+
| **Omit for default** | When resolved locale matches `defaultLocale` (primary subtag), header is not sent |
|
|
270
|
+
| **Content-Language** | Exposed on success as `res.headers.contentLanguage` |
|
|
271
|
+
| **Mismatch** | If response `Content-Language` differs from requested locale (base tag: `fr` vs `fr-FR` match), `'warn'` or your callback runs — UI locale is never changed |
|
|
272
|
+
|
|
273
|
+
Legacy `getAcceptLanguage` still works; `locale.getLocale` takes precedence when both are set.
|
|
274
|
+
|
|
237
275
|
---
|
|
238
276
|
|
|
239
277
|
## Making requests
|
|
@@ -302,7 +340,11 @@ Synthetic codes when the body is missing or invalid: `EMPTY_ERROR_BODY`, `INVALI
|
|
|
302
340
|
| `isAuthenticationError` | 401 |
|
|
303
341
|
| `isForbiddenError` | 403 |
|
|
304
342
|
| `isValidationError` | 422 |
|
|
305
|
-
| `isPreconditionRequiredError` | 428 |
|
|
343
|
+
| `isPreconditionRequiredError` | any **428** |
|
|
344
|
+
| `isIdempotencyKeyRequiredError` | **428** + `IDEMPOTENCY_KEY_REQUIRED` |
|
|
345
|
+
| `isIfMatchRequiredError` | **428** + `IF_MATCH_REQUIRED` |
|
|
346
|
+
| `isMfaVerificationRequiredError` | **428** + `MFA_VERIFICATION_REQUIRED` |
|
|
347
|
+
| `hasErrorCode` / `isApiClientErrorWithCode` | matching `errors[].code` |
|
|
306
348
|
| `isPreconditionFailedError` | 412 |
|
|
307
349
|
| `isConflictError` | 409 |
|
|
308
350
|
| `isPayloadTooLargeError` | 413 |
|
|
@@ -439,13 +481,19 @@ This package ships **generic JSON:API types**, not endpoint-specific OpenAPI typ
|
|
|
439
481
|
3. Use generics at call sites:
|
|
440
482
|
|
|
441
483
|
```typescript
|
|
484
|
+
import type { JsonApiDocument, JsonApiResourceObject } from '@vahidkaargar/customized-api-client';
|
|
442
485
|
import type { operations } from '@myorg/api-types';
|
|
443
486
|
|
|
444
|
-
type
|
|
445
|
-
|
|
487
|
+
type MeDoc = JsonApiDocument<JsonApiResourceObject>;
|
|
488
|
+
// Or from OpenAPI: operations['getMe']['responses'][200]['content']['application/vnd.api+json']
|
|
489
|
+
|
|
490
|
+
const res = await client.get<MeDoc>('/me');
|
|
491
|
+
if (res.kind === 'jsonapi-success') {
|
|
492
|
+
const me = res.document.data; // document typed as MeDoc
|
|
493
|
+
}
|
|
446
494
|
```
|
|
447
495
|
|
|
448
|
-
String paths and manual types work without OpenAPI.
|
|
496
|
+
The generic narrows `document` on **`jsonapi-success`**; `accepted`, `no-content`, and `multi-status` shapes are unchanged. String paths and manual types work without OpenAPI.
|
|
449
497
|
|
|
450
498
|
---
|
|
451
499
|
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ __export(index_exports, {
|
|
|
35
35
|
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
36
36
|
IDEMPOTENCY_MAX_LENGTH: () => IDEMPOTENCY_MAX_LENGTH,
|
|
37
37
|
PACKAGE_VERSION: () => PACKAGE_VERSION,
|
|
38
|
+
acceptLanguageForRequest: () => acceptLanguageForRequest,
|
|
38
39
|
applyJsonApiHeaders: () => applyJsonApiHeaders,
|
|
39
40
|
applyTransformKeys: () => applyTransformKeys,
|
|
40
41
|
assertValidIdempotencyKey: () => assertValidIdempotencyKey,
|
|
@@ -51,19 +52,28 @@ __export(index_exports, {
|
|
|
51
52
|
getHeader: () => getHeader,
|
|
52
53
|
getNextPageUrl: () => getNextPageUrl,
|
|
53
54
|
groupValidationErrorsByPointer: () => groupValidationErrorsByPointer,
|
|
55
|
+
hasErrorCode: () => hasErrorCode,
|
|
54
56
|
indexIncluded: () => indexIncluded,
|
|
55
57
|
isApiClientError: () => isApiClientError,
|
|
58
|
+
isApiClientErrorWithCode: () => isApiClientErrorWithCode,
|
|
56
59
|
isAuthenticationError: () => isAuthenticationError,
|
|
57
60
|
isConflictError: () => isConflictError,
|
|
58
61
|
isForbiddenError: () => isForbiddenError,
|
|
62
|
+
isIdempotencyKeyRequiredError: () => isIdempotencyKeyRequiredError,
|
|
63
|
+
isIfMatchRequiredError: () => isIfMatchRequiredError,
|
|
64
|
+
isMfaVerificationRequiredError: () => isMfaVerificationRequiredError,
|
|
59
65
|
isMutationMethod: () => isMutationMethod,
|
|
60
66
|
isPayloadTooLargeError: () => isPayloadTooLargeError,
|
|
61
67
|
isPreconditionFailedError: () => isPreconditionFailedError,
|
|
62
68
|
isPreconditionRequiredError: () => isPreconditionRequiredError,
|
|
63
69
|
isRetryablePerPolicy: () => isRetryablePerPolicy,
|
|
64
70
|
isValidationError: () => isValidationError,
|
|
71
|
+
localesMatch: () => localesMatch,
|
|
65
72
|
normalizeAxiosResponse: () => normalizeAxiosResponse,
|
|
66
73
|
normalizeHttpUrl: () => normalizeHttpUrl,
|
|
74
|
+
normalizeLocaleCode: () => normalizeLocaleCode,
|
|
75
|
+
notifyLocaleMismatch: () => notifyLocaleMismatch,
|
|
76
|
+
parseContentLanguage: () => parseContentLanguage,
|
|
67
77
|
parseDeprecationHeaders: () => parseDeprecationHeaders,
|
|
68
78
|
parseJsonApiDocument: () => parseJsonApiDocument,
|
|
69
79
|
parseJsonApiErrorBody: () => parseJsonApiErrorBody,
|
|
@@ -72,11 +82,14 @@ __export(index_exports, {
|
|
|
72
82
|
parseRetryAfterSeconds: () => parseRetryAfterSeconds,
|
|
73
83
|
pollAsyncResult: () => pollAsyncResult,
|
|
74
84
|
readResourceVersion: () => readResourceVersion,
|
|
85
|
+
readResponseContentLanguage: () => readResponseContentLanguage,
|
|
75
86
|
redactHeaderRecord: () => redactHeaderRecord,
|
|
76
87
|
resolveAcceptLanguage: () => resolveAcceptLanguage,
|
|
77
88
|
resolveAcceptedLocation: () => resolveAcceptedLocation,
|
|
78
89
|
resolveAuthorizationHeader: () => resolveAuthorizationHeader,
|
|
79
90
|
resolveIncluded: () => resolveIncluded,
|
|
91
|
+
resolveLocaleProvider: () => resolveLocaleProvider,
|
|
92
|
+
resolveRequestLocale: () => resolveRequestLocale,
|
|
80
93
|
resolveResourcePath: () => resolveResourcePath,
|
|
81
94
|
retryAllowed: () => retryAllowed,
|
|
82
95
|
truncateForLog: () => truncateForLog
|
|
@@ -86,7 +99,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
86
99
|
// package.json
|
|
87
100
|
var package_default = {
|
|
88
101
|
name: "@vahidkaargar/customized-api-client",
|
|
89
|
-
version: "0.
|
|
102
|
+
version: "0.4.0",
|
|
90
103
|
description: "TypeScript Axios client for JSON:API v1.1 with idempotency, retries, and normalized results",
|
|
91
104
|
type: "module",
|
|
92
105
|
engines: {
|
|
@@ -170,14 +183,29 @@ function applyJsonApiHeaders(config, method) {
|
|
|
170
183
|
const m = method.toUpperCase();
|
|
171
184
|
const headers = { ...config.headers };
|
|
172
185
|
headers.Accept = headers.Accept ?? JSON_API;
|
|
173
|
-
if (
|
|
186
|
+
if (shouldSetJsonApiContentType(m, config)) {
|
|
174
187
|
headers["Content-Type"] = headers["Content-Type"] ?? JSON_API;
|
|
175
188
|
}
|
|
176
189
|
return { ...config, headers };
|
|
177
190
|
}
|
|
178
|
-
function
|
|
191
|
+
function shouldSetJsonApiContentType(method, config) {
|
|
179
192
|
if (!["POST", "PATCH", "PUT"].includes(method)) return false;
|
|
180
|
-
|
|
193
|
+
const data = config.data;
|
|
194
|
+
if (data === void 0 || data === null) return false;
|
|
195
|
+
return isJsonApiSerializableBody(data);
|
|
196
|
+
}
|
|
197
|
+
function isJsonApiSerializableBody(data) {
|
|
198
|
+
if (typeof data !== "object") return false;
|
|
199
|
+
if (Array.isArray(data)) return true;
|
|
200
|
+
if (typeof FormData !== "undefined" && data instanceof FormData) return false;
|
|
201
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) return false;
|
|
202
|
+
if (data instanceof ArrayBuffer) return false;
|
|
203
|
+
if (ArrayBuffer.isView(data)) return false;
|
|
204
|
+
if (typeof URLSearchParams !== "undefined" && data instanceof URLSearchParams) return false;
|
|
205
|
+
if (data instanceof Date) return false;
|
|
206
|
+
if (typeof ReadableStream !== "undefined" && data instanceof ReadableStream) return false;
|
|
207
|
+
const proto = Object.getPrototypeOf(data);
|
|
208
|
+
return proto === Object.prototype || proto === null;
|
|
181
209
|
}
|
|
182
210
|
|
|
183
211
|
// src/headers/auth.ts
|
|
@@ -193,11 +221,85 @@ async function resolveAuthorizationHeader(auth) {
|
|
|
193
221
|
return `Bearer ${s}`;
|
|
194
222
|
}
|
|
195
223
|
|
|
224
|
+
// src/http/header-utils.ts
|
|
225
|
+
function flattenAxiosHeaders(headers) {
|
|
226
|
+
if (!headers) return {};
|
|
227
|
+
if (typeof headers.forEach === "function") {
|
|
228
|
+
const out2 = {};
|
|
229
|
+
headers.forEach((value, key) => {
|
|
230
|
+
out2[key.toLowerCase()] = value;
|
|
231
|
+
});
|
|
232
|
+
return out2;
|
|
233
|
+
}
|
|
234
|
+
const o = headers;
|
|
235
|
+
const out = {};
|
|
236
|
+
for (const [k, v] of Object.entries(o)) {
|
|
237
|
+
if (typeof v === "string") out[k.toLowerCase()] = v;
|
|
238
|
+
else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function getHeader(headers, name) {
|
|
243
|
+
return headers[name.toLowerCase()];
|
|
244
|
+
}
|
|
245
|
+
|
|
196
246
|
// src/headers/locale.ts
|
|
247
|
+
function normalizeLocaleCode(tag) {
|
|
248
|
+
if (tag === void 0) return void 0;
|
|
249
|
+
const trimmed = tag.trim();
|
|
250
|
+
if (!trimmed) return void 0;
|
|
251
|
+
const base = trimmed.split(/[-_]/)[0]?.trim();
|
|
252
|
+
return base ? base.toLowerCase() : void 0;
|
|
253
|
+
}
|
|
254
|
+
function parseContentLanguage(header) {
|
|
255
|
+
if (header === void 0) return void 0;
|
|
256
|
+
const first = header.split(",")[0]?.trim();
|
|
257
|
+
if (!first) return void 0;
|
|
258
|
+
const tag = first.split(";")[0]?.trim();
|
|
259
|
+
return tag && tag.length > 0 ? tag : void 0;
|
|
260
|
+
}
|
|
261
|
+
function localesMatch(a, b) {
|
|
262
|
+
const na = normalizeLocaleCode(a);
|
|
263
|
+
const nb = normalizeLocaleCode(b);
|
|
264
|
+
if (na === void 0 || nb === void 0) return false;
|
|
265
|
+
return na === nb;
|
|
266
|
+
}
|
|
267
|
+
function resolveLocaleProvider(getAcceptLanguage, locale) {
|
|
268
|
+
return locale?.getLocale ?? getAcceptLanguage;
|
|
269
|
+
}
|
|
270
|
+
async function resolveRequestLocale(getAcceptLanguage, locale) {
|
|
271
|
+
return resolveAcceptLanguage(resolveLocaleProvider(getAcceptLanguage, locale));
|
|
272
|
+
}
|
|
273
|
+
function acceptLanguageForRequest(resolved, defaultLocale) {
|
|
274
|
+
if (resolved === void 0) return void 0;
|
|
275
|
+
if (defaultLocale !== void 0 && localesMatch(resolved, defaultLocale)) {
|
|
276
|
+
return void 0;
|
|
277
|
+
}
|
|
278
|
+
return resolved;
|
|
279
|
+
}
|
|
197
280
|
async function resolveAcceptLanguage(provider) {
|
|
198
281
|
if (!provider) return void 0;
|
|
199
282
|
const v = await provider();
|
|
200
|
-
|
|
283
|
+
if (v === null || v === void 0) return void 0;
|
|
284
|
+
const trimmed = v.trim();
|
|
285
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
286
|
+
}
|
|
287
|
+
function readResponseContentLanguage(flatHeaders) {
|
|
288
|
+
return parseContentLanguage(getHeader(flatHeaders, "content-language"));
|
|
289
|
+
}
|
|
290
|
+
function notifyLocaleMismatch(locale, ctx) {
|
|
291
|
+
const handler = locale?.onLocaleMismatch;
|
|
292
|
+
if (!handler) return;
|
|
293
|
+
if (ctx.requested === void 0) return;
|
|
294
|
+
if (localesMatch(ctx.requested, ctx.resolved)) return;
|
|
295
|
+
if (handler === "warn") {
|
|
296
|
+
console.warn(
|
|
297
|
+
"[@vahidkaargar/customized-api-client] Content-Language mismatch",
|
|
298
|
+
ctx
|
|
299
|
+
);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
handler(ctx);
|
|
201
303
|
}
|
|
202
304
|
|
|
203
305
|
// src/headers/idempotency.ts
|
|
@@ -285,28 +387,6 @@ function parseRetryAfterSeconds(value) {
|
|
|
285
387
|
return void 0;
|
|
286
388
|
}
|
|
287
389
|
|
|
288
|
-
// src/http/header-utils.ts
|
|
289
|
-
function flattenAxiosHeaders(headers) {
|
|
290
|
-
if (!headers) return {};
|
|
291
|
-
if (typeof headers.forEach === "function") {
|
|
292
|
-
const out2 = {};
|
|
293
|
-
headers.forEach((value, key) => {
|
|
294
|
-
out2[key.toLowerCase()] = value;
|
|
295
|
-
});
|
|
296
|
-
return out2;
|
|
297
|
-
}
|
|
298
|
-
const o = headers;
|
|
299
|
-
const out = {};
|
|
300
|
-
for (const [k, v] of Object.entries(o)) {
|
|
301
|
-
if (typeof v === "string") out[k.toLowerCase()] = v;
|
|
302
|
-
else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
|
|
303
|
-
}
|
|
304
|
-
return out;
|
|
305
|
-
}
|
|
306
|
-
function getHeader(headers, name) {
|
|
307
|
-
return headers[name.toLowerCase()];
|
|
308
|
-
}
|
|
309
|
-
|
|
310
390
|
// src/retry/execute-with-retry.ts
|
|
311
391
|
var DEFAULT_MAX_ATTEMPTS = 4;
|
|
312
392
|
var DEFAULT_BASE_MS = 200;
|
|
@@ -730,6 +810,7 @@ function createApiClient(config) {
|
|
|
730
810
|
const mode = config.baseUrlMode ?? "modeB";
|
|
731
811
|
const genKey = config.generateIdempotencyKey ?? defaultIdempotencyKey;
|
|
732
812
|
warnInsecureBaseUrl(config.baseURL);
|
|
813
|
+
const localeByRequest = /* @__PURE__ */ new WeakMap();
|
|
733
814
|
const instance = import_axios.default.create({
|
|
734
815
|
baseURL: config.baseURL,
|
|
735
816
|
timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
|
|
@@ -746,9 +827,15 @@ function createApiClient(config) {
|
|
|
746
827
|
if (authHeader) {
|
|
747
828
|
next.headers.Authorization = authHeader;
|
|
748
829
|
}
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
830
|
+
const resolved = await resolveRequestLocale(
|
|
831
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated -- legacy `getAcceptLanguage` fallback
|
|
832
|
+
config.getAcceptLanguage,
|
|
833
|
+
config.locale
|
|
834
|
+
);
|
|
835
|
+
localeByRequest.set(next, resolved);
|
|
836
|
+
const toSend = acceptLanguageForRequest(resolved, config.locale?.defaultLocale);
|
|
837
|
+
if (toSend) {
|
|
838
|
+
next.headers["Accept-Language"] = toSend;
|
|
752
839
|
}
|
|
753
840
|
if (isMutationMethod(method)) {
|
|
754
841
|
const h = next.headers;
|
|
@@ -774,6 +861,17 @@ function createApiClient(config) {
|
|
|
774
861
|
if (dep && config.onDeprecated) {
|
|
775
862
|
config.onDeprecated(dep);
|
|
776
863
|
}
|
|
864
|
+
const contentLang = readResponseContentLanguage(flat);
|
|
865
|
+
if (contentLang) {
|
|
866
|
+
notifyLocaleMismatch(config.locale, {
|
|
867
|
+
requested: localeByRequest.get(res.config),
|
|
868
|
+
resolved: contentLang,
|
|
869
|
+
/* v8 ignore start -- @preserve axios config url/method are strings */
|
|
870
|
+
url: typeof res.config.url === "string" ? res.config.url : void 0,
|
|
871
|
+
method: typeof res.config.method === "string" ? res.config.method : void 0
|
|
872
|
+
/* v8 ignore stop -- @preserve */
|
|
873
|
+
});
|
|
874
|
+
}
|
|
777
875
|
return res;
|
|
778
876
|
},
|
|
779
877
|
(err) => Promise.reject(
|
|
@@ -845,6 +943,9 @@ function createApiClient(config) {
|
|
|
845
943
|
async post(path, data, opts) {
|
|
846
944
|
return perform("POST", resolvePath(path), { ...opts, data });
|
|
847
945
|
},
|
|
946
|
+
async postFormData(path, data, opts) {
|
|
947
|
+
return perform("POST", resolvePath(path), { ...opts, data });
|
|
948
|
+
},
|
|
848
949
|
async patch(path, data, opts) {
|
|
849
950
|
return perform("PATCH", resolvePath(path), { ...opts, data });
|
|
850
951
|
},
|
|
@@ -877,20 +978,42 @@ function createApiClient(config) {
|
|
|
877
978
|
ifMatchVersion: version
|
|
878
979
|
});
|
|
879
980
|
},
|
|
880
|
-
safeGet: (path, opts) => safe(() =>
|
|
881
|
-
safePost: (path, data, opts) => safe(() =>
|
|
882
|
-
safePatch: (path, data, opts) => safe(() =>
|
|
883
|
-
safePut: (path, data, opts) => safe(() =>
|
|
884
|
-
safeDelete: (path, opts) => safe(() =>
|
|
885
|
-
safeHead: (path, opts) => safe(() =>
|
|
886
|
-
safeRequest: (ax, opts) => safe(() =>
|
|
887
|
-
|
|
888
|
-
|
|
981
|
+
safeGet: (path, opts) => safe(() => perform("GET", resolvePath(path), opts ?? {})),
|
|
982
|
+
safePost: (path, data, opts) => safe(() => perform("POST", resolvePath(path), { ...opts, data })),
|
|
983
|
+
safePatch: (path, data, opts) => safe(() => perform("PATCH", resolvePath(path), { ...opts, data })),
|
|
984
|
+
safePut: (path, data, opts) => safe(() => perform("PUT", resolvePath(path), { ...opts, data })),
|
|
985
|
+
safeDelete: (path, opts) => safe(() => perform("DELETE", resolvePath(path), opts ?? {})),
|
|
986
|
+
safeHead: (path, opts) => safe(() => perform("HEAD", resolvePath(path), opts ?? {})),
|
|
987
|
+
safeRequest: (ax, opts) => safe(() => {
|
|
988
|
+
const method = (ax.method ?? "GET").toUpperCase();
|
|
989
|
+
const rawUrl = ax.url ?? "/";
|
|
990
|
+
const u = typeof rawUrl === "string" && /^https?:\/\//i.test(rawUrl) ? rawUrl : resolvePath(rawUrl);
|
|
991
|
+
return perform(method, u, {
|
|
992
|
+
...opts,
|
|
993
|
+
data: ax.data,
|
|
994
|
+
idempotencyKey: opts?.idempotencyKey ?? readHeader(ax, "Idempotency-Key")
|
|
995
|
+
});
|
|
996
|
+
}),
|
|
997
|
+
safeGetByUrl: (fullUrl, opts) => safe(() => perform("GET", fullUrl, opts ?? {})),
|
|
998
|
+
safePatchWithVersion: (path, data, version, opts) => safe(
|
|
999
|
+
() => perform("PATCH", resolvePath(path), {
|
|
1000
|
+
...opts,
|
|
1001
|
+
data,
|
|
1002
|
+
ifMatchVersion: version
|
|
1003
|
+
})
|
|
1004
|
+
)
|
|
889
1005
|
};
|
|
890
1006
|
return client;
|
|
891
1007
|
}
|
|
892
1008
|
|
|
893
1009
|
// src/guards.ts
|
|
1010
|
+
function hasErrorCode(error, code) {
|
|
1011
|
+
if (!(error instanceof ApiClientError)) return false;
|
|
1012
|
+
return error.errors.some((e) => e.code === code);
|
|
1013
|
+
}
|
|
1014
|
+
function isApiClientErrorWithCode(error, code) {
|
|
1015
|
+
return hasErrorCode(error, code);
|
|
1016
|
+
}
|
|
894
1017
|
function isAuthenticationError(e) {
|
|
895
1018
|
return isApiErr(e, 401);
|
|
896
1019
|
}
|
|
@@ -900,6 +1023,15 @@ function isForbiddenError(e) {
|
|
|
900
1023
|
function isPreconditionRequiredError(e) {
|
|
901
1024
|
return isApiErr(e, 428);
|
|
902
1025
|
}
|
|
1026
|
+
function isIdempotencyKeyRequiredError(e) {
|
|
1027
|
+
return isApiErrWithCode(e, 428, "IDEMPOTENCY_KEY_REQUIRED");
|
|
1028
|
+
}
|
|
1029
|
+
function isIfMatchRequiredError(e) {
|
|
1030
|
+
return isApiErrWithCode(e, 428, "IF_MATCH_REQUIRED");
|
|
1031
|
+
}
|
|
1032
|
+
function isMfaVerificationRequiredError(e) {
|
|
1033
|
+
return isApiErrWithCode(e, 428, "MFA_VERIFICATION_REQUIRED");
|
|
1034
|
+
}
|
|
903
1035
|
function isPreconditionFailedError(e) {
|
|
904
1036
|
return isApiErr(e, 412);
|
|
905
1037
|
}
|
|
@@ -925,6 +1057,9 @@ function isRetryablePerPolicy(e, policy) {
|
|
|
925
1057
|
function isApiErr(e, status) {
|
|
926
1058
|
return e instanceof ApiClientError && e.status === status;
|
|
927
1059
|
}
|
|
1060
|
+
function isApiErrWithCode(e, status, code) {
|
|
1061
|
+
return isApiErr(e, status) && hasErrorCode(e, code);
|
|
1062
|
+
}
|
|
928
1063
|
|
|
929
1064
|
// src/parse/pagination.ts
|
|
930
1065
|
function parsePaginationKind(meta, links) {
|
|
@@ -1121,6 +1256,7 @@ var PACKAGE_VERSION = package_default.version;
|
|
|
1121
1256
|
DEFAULT_TIMEOUT_MS,
|
|
1122
1257
|
IDEMPOTENCY_MAX_LENGTH,
|
|
1123
1258
|
PACKAGE_VERSION,
|
|
1259
|
+
acceptLanguageForRequest,
|
|
1124
1260
|
applyJsonApiHeaders,
|
|
1125
1261
|
applyTransformKeys,
|
|
1126
1262
|
assertValidIdempotencyKey,
|
|
@@ -1137,19 +1273,28 @@ var PACKAGE_VERSION = package_default.version;
|
|
|
1137
1273
|
getHeader,
|
|
1138
1274
|
getNextPageUrl,
|
|
1139
1275
|
groupValidationErrorsByPointer,
|
|
1276
|
+
hasErrorCode,
|
|
1140
1277
|
indexIncluded,
|
|
1141
1278
|
isApiClientError,
|
|
1279
|
+
isApiClientErrorWithCode,
|
|
1142
1280
|
isAuthenticationError,
|
|
1143
1281
|
isConflictError,
|
|
1144
1282
|
isForbiddenError,
|
|
1283
|
+
isIdempotencyKeyRequiredError,
|
|
1284
|
+
isIfMatchRequiredError,
|
|
1285
|
+
isMfaVerificationRequiredError,
|
|
1145
1286
|
isMutationMethod,
|
|
1146
1287
|
isPayloadTooLargeError,
|
|
1147
1288
|
isPreconditionFailedError,
|
|
1148
1289
|
isPreconditionRequiredError,
|
|
1149
1290
|
isRetryablePerPolicy,
|
|
1150
1291
|
isValidationError,
|
|
1292
|
+
localesMatch,
|
|
1151
1293
|
normalizeAxiosResponse,
|
|
1152
1294
|
normalizeHttpUrl,
|
|
1295
|
+
normalizeLocaleCode,
|
|
1296
|
+
notifyLocaleMismatch,
|
|
1297
|
+
parseContentLanguage,
|
|
1153
1298
|
parseDeprecationHeaders,
|
|
1154
1299
|
parseJsonApiDocument,
|
|
1155
1300
|
parseJsonApiErrorBody,
|
|
@@ -1158,11 +1303,14 @@ var PACKAGE_VERSION = package_default.version;
|
|
|
1158
1303
|
parseRetryAfterSeconds,
|
|
1159
1304
|
pollAsyncResult,
|
|
1160
1305
|
readResourceVersion,
|
|
1306
|
+
readResponseContentLanguage,
|
|
1161
1307
|
redactHeaderRecord,
|
|
1162
1308
|
resolveAcceptLanguage,
|
|
1163
1309
|
resolveAcceptedLocation,
|
|
1164
1310
|
resolveAuthorizationHeader,
|
|
1165
1311
|
resolveIncluded,
|
|
1312
|
+
resolveLocaleProvider,
|
|
1313
|
+
resolveRequestLocale,
|
|
1166
1314
|
resolveResourcePath,
|
|
1167
1315
|
retryAllowed,
|
|
1168
1316
|
truncateForLog
|