@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 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
- | `getAcceptLanguage` | — | Sets `Accept-Language` when non-empty |
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 MeResponse = operations['getMe']['responses'][200]['content']['application/vnd.api+json'];
445
- const me = await client.get<MeResponse>('/me');
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.2.4",
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 (hasJsonBody(m, config)) {
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 hasJsonBody(method, config) {
191
+ function shouldSetJsonApiContentType(method, config) {
179
192
  if (!["POST", "PATCH", "PUT"].includes(method)) return false;
180
- return config.data !== void 0 && config.data !== null;
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
- return v ?? void 0;
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 lang = await resolveAcceptLanguage(config.getAcceptLanguage);
750
- if (lang) {
751
- next.headers["Accept-Language"] = lang;
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(() => client.get(path, opts)),
881
- safePost: (path, data, opts) => safe(() => client.post(path, data, opts)),
882
- safePatch: (path, data, opts) => safe(() => client.patch(path, data, opts)),
883
- safePut: (path, data, opts) => safe(() => client.put(path, data, opts)),
884
- safeDelete: (path, opts) => safe(() => client.delete(path, opts)),
885
- safeHead: (path, opts) => safe(() => client.head(path, opts)),
886
- safeRequest: (ax, opts) => safe(() => client.request(ax, opts)),
887
- safeGetByUrl: (url, opts) => safe(() => client.getByUrl(url, opts)),
888
- safePatchWithVersion: (path, data, version, opts) => safe(() => client.patchWithVersion(path, data, version, opts))
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