@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/dist/index.d.ts CHANGED
@@ -26,11 +26,31 @@ interface IdempotencyReplayContext {
26
26
  readonly url?: string;
27
27
  readonly method?: string;
28
28
  }
29
+ interface LocaleMismatchContext {
30
+ /** Locale from `getLocale` before default omission. */
31
+ readonly requested?: string;
32
+ /** `Content-Language` from the response. */
33
+ readonly resolved: string;
34
+ readonly url?: string;
35
+ readonly method?: string;
36
+ }
37
+ interface LocaleClientOptions {
38
+ readonly getLocale?: () => string | null | undefined | Promise<string | null | undefined>;
39
+ /**
40
+ * When the resolved locale matches this value (primary subtag), `Accept-Language` is omitted.
41
+ */
42
+ readonly defaultLocale?: string;
43
+ readonly onLocaleMismatch?: 'warn' | ((ctx: Readonly<LocaleMismatchContext>) => void);
44
+ }
29
45
  interface ApiClientConfig {
30
46
  readonly baseURL: string;
31
47
  /** Default Mode B — see `BaseUrlMode`. */
32
48
  readonly baseUrlMode?: BaseUrlMode;
33
49
  readonly auth?: AuthConfig;
50
+ readonly locale?: LocaleClientOptions;
51
+ /**
52
+ * @deprecated Use `locale.getLocale` instead.
53
+ */
34
54
  readonly getAcceptLanguage?: () => string | null | undefined | Promise<string | null | undefined>;
35
55
  readonly defaultHeaders?: Readonly<Record<string, string>>;
36
56
  readonly timeout?: number;
@@ -124,6 +144,9 @@ interface MultiStatusBody {
124
144
  readonly headers: NormalizedResponseHeaders;
125
145
  }
126
146
  type ClientSuccess = JsonApiSuccessBody | NoContentBody | AcceptedBody | MultiStatusBody;
147
+ type ClientSuccessWithDocument<T extends JsonApiDocument = JsonApiDocument> = (JsonApiSuccessBody & {
148
+ readonly document: T;
149
+ }) | NoContentBody | AcceptedBody | MultiStatusBody;
127
150
  interface OkResult<T extends ClientSuccess> {
128
151
  readonly ok: true;
129
152
  readonly value: T;
@@ -154,30 +177,37 @@ interface RequestCallOptions {
154
177
  readonly signal?: AbortSignal;
155
178
  }
156
179
  interface ApiClient {
157
- readonly get: (path: string, opts?: RequestCallOptions) => Promise<ClientSuccess>;
158
- readonly head: (path: string, opts?: RequestCallOptions) => Promise<ClientSuccess>;
159
- readonly post: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccess>;
160
- readonly patch: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccess>;
161
- readonly put: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccess>;
162
- readonly delete: (path: string, opts?: RequestCallOptions) => Promise<ClientSuccess>;
163
- readonly request: (ax: AxiosRequestConfig, opts?: RequestCallOptions) => Promise<ClientSuccess>;
164
- readonly getByUrl: (fullUrl: string, opts?: RequestCallOptions) => Promise<ClientSuccess>;
165
- readonly patchWithVersion: (path: string, data: unknown, version: number, opts?: Omit<RequestCallOptions, 'ifMatchVersion'>) => Promise<ClientSuccess>;
166
- readonly safeGet: (path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
167
- readonly safePost: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
168
- readonly safePatch: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
169
- readonly safePut: (path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
170
- readonly safeDelete: (path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
171
- readonly safeHead: (path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
172
- readonly safeRequest: (ax: AxiosRequestConfig, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
173
- readonly safeGetByUrl: (fullUrl: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccess, ApiClientError>>;
174
- readonly safePatchWithVersion: (path: string, data: unknown, version: number, opts?: Omit<RequestCallOptions, 'ifMatchVersion'>) => Promise<Result<ClientSuccess, ApiClientError>>;
180
+ readonly get: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
181
+ readonly head: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
182
+ readonly post: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
183
+ readonly patch: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
184
+ readonly put: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
185
+ readonly delete: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
186
+ readonly postFormData: <T extends JsonApiDocument = JsonApiDocument>(path: string, data: FormData, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
187
+ readonly request: <T extends JsonApiDocument = JsonApiDocument>(ax: AxiosRequestConfig, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
188
+ readonly getByUrl: <T extends JsonApiDocument = JsonApiDocument>(fullUrl: string, opts?: RequestCallOptions) => Promise<ClientSuccessWithDocument<T>>;
189
+ readonly patchWithVersion: <T extends JsonApiDocument = JsonApiDocument>(path: string, data: unknown, version: number, opts?: Omit<RequestCallOptions, 'ifMatchVersion'>) => Promise<ClientSuccessWithDocument<T>>;
190
+ readonly safeGet: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
191
+ readonly safePost: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
192
+ readonly safePatch: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
193
+ readonly safePut: <T extends JsonApiDocument = JsonApiDocument>(path: string, data?: unknown, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
194
+ readonly safeDelete: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
195
+ readonly safeHead: <T extends JsonApiDocument = JsonApiDocument>(path: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
196
+ readonly safeRequest: <T extends JsonApiDocument = JsonApiDocument>(ax: AxiosRequestConfig, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
197
+ readonly safeGetByUrl: <T extends JsonApiDocument = JsonApiDocument>(fullUrl: string, opts?: RequestCallOptions) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
198
+ readonly safePatchWithVersion: <T extends JsonApiDocument = JsonApiDocument>(path: string, data: unknown, version: number, opts?: Omit<RequestCallOptions, 'ifMatchVersion'>) => Promise<Result<ClientSuccessWithDocument<T>, ApiClientError>>;
175
199
  }
176
200
  declare function createApiClient(config: ApiClientConfig): ApiClient;
177
201
 
202
+ declare function hasErrorCode(error: unknown, code: string): boolean;
203
+ declare function isApiClientErrorWithCode(error: unknown, code: string): error is ApiClientError;
178
204
  declare function isAuthenticationError(e: unknown): boolean;
179
205
  declare function isForbiddenError(e: unknown): boolean;
206
+ /** True for any HTTP 428. Prefer code-specific helpers when branching on `errors[].code`. */
180
207
  declare function isPreconditionRequiredError(e: unknown): boolean;
208
+ declare function isIdempotencyKeyRequiredError(e: unknown): boolean;
209
+ declare function isIfMatchRequiredError(e: unknown): boolean;
210
+ declare function isMfaVerificationRequiredError(e: unknown): boolean;
181
211
  declare function isPreconditionFailedError(e: unknown): boolean;
182
212
  declare function isValidationError(e: unknown): boolean;
183
213
  declare function isConflictError(e: unknown): boolean;
@@ -204,7 +234,23 @@ declare function isMutationMethod(method: string): boolean;
204
234
 
205
235
  declare function formatIfMatch(version: number): string;
206
236
 
207
- declare function resolveAcceptLanguage(provider?: () => string | null | undefined | Promise<string | null | undefined>): Promise<string | undefined>;
237
+ type LocaleProvider = () => string | null | undefined | Promise<string | null | undefined>;
238
+ /** Primary language subtag only: `fr-FR` → `fr`. */
239
+ declare function normalizeLocaleCode(tag: string | undefined): string | undefined;
240
+ /** First language tag from a `Content-Language` (or similar) header value. */
241
+ declare function parseContentLanguage(header: string | undefined): string | undefined;
242
+ /** Compare primary subtags (e.g. `fr-FR` and `fr` match). */
243
+ declare function localesMatch(a: string | undefined, b: string | undefined): boolean;
244
+ declare function resolveLocaleProvider(getAcceptLanguage?: LocaleProvider, locale?: LocaleClientOptions): LocaleProvider | undefined;
245
+ declare function resolveRequestLocale(getAcceptLanguage?: LocaleProvider, locale?: LocaleClientOptions): Promise<string | undefined>;
246
+ /**
247
+ * Value to send as `Accept-Language`, or `undefined` to omit the header
248
+ * when the resolved locale matches `defaultLocale` (normalized).
249
+ */
250
+ declare function acceptLanguageForRequest(resolved: string | undefined, defaultLocale?: string): string | undefined;
251
+ declare function resolveAcceptLanguage(provider?: LocaleProvider): Promise<string | undefined>;
252
+ declare function readResponseContentLanguage(flatHeaders: Readonly<Record<string, string>>): string | undefined;
253
+ declare function notifyLocaleMismatch(locale: LocaleClientOptions | undefined, ctx: Readonly<LocaleMismatchContext>): void;
208
254
 
209
255
  declare function resolveAuthorizationHeader(auth: AuthConfig | undefined): Promise<string | undefined>;
210
256
 
@@ -326,4 +372,4 @@ declare function pollAsyncResult(client: ApiClient, initial: Extract<ClientSucce
326
372
 
327
373
  declare const PACKAGE_VERSION: string;
328
374
 
329
- export { type AcceptedBody, type ApiClient, type ApiClientConfig, ApiClientError, type AuthConfig, type BaseUrlMode, type ClientSuccess, type CursorPagination, DEFAULT_PAGE_SIZE_CAP, DEFAULT_TIMEOUT_MS, type DeprecationInfo, type ErrResult, IDEMPOTENCY_MAX_LENGTH, type IdempotencyReplayContext, type IncludedIndex, type JsonApiDocument, type JsonApiErrorDocument, type JsonApiErrorObject, type JsonApiPrimaryData, type JsonApiQueryInput, type JsonApiResourceLinkage, type JsonApiResourceObject, type JsonApiSuccessBody, type MultiStatusBody, type MultiStatusItem, type NoContentBody, type NormalizedResponseHeaders, type OffsetPagination, type OkResult, PACKAGE_VERSION, type PollOptions, type RequestCallOptions, type Result, type RetryOptions, type TokenProvider, type TransformResponseKeysMode, type UnknownPagination, type ValidationGroups, applyJsonApiHeaders, applyTransformKeys, assertValidIdempotencyKey, buildCursorPageParams, buildJsonApiQuery, buildOffsetPageParams, createApiClient, createHealthCheck, defaultIdempotencyKey, dispatchWithRetry, etagFromResponseHeaders, flattenAxiosHeaders, formatIfMatch, getHeader, getNextPageUrl, groupValidationErrorsByPointer, indexIncluded, isApiClientError, isAuthenticationError, isConflictError, isForbiddenError, isMutationMethod, isPayloadTooLargeError, isPreconditionFailedError, isPreconditionRequiredError, isRetryablePerPolicy, isValidationError, normalizeAxiosResponse, normalizeHttpUrl, parseDeprecationHeaders, parseJsonApiDocument, parseJsonApiErrorBody, parseMultiStatusBody, parsePaginationKind, parseRetryAfterSeconds, pollAsyncResult, readResourceVersion, redactHeaderRecord, resolveAcceptLanguage, resolveAcceptedLocation, resolveAuthorizationHeader, resolveIncluded, resolveResourcePath, retryAllowed, truncateForLog };
375
+ export { type AcceptedBody, type ApiClient, type ApiClientConfig, ApiClientError, type AuthConfig, type BaseUrlMode, type ClientSuccess, type ClientSuccessWithDocument, type CursorPagination, DEFAULT_PAGE_SIZE_CAP, DEFAULT_TIMEOUT_MS, type DeprecationInfo, type ErrResult, IDEMPOTENCY_MAX_LENGTH, type IdempotencyReplayContext, type IncludedIndex, type JsonApiDocument, type JsonApiErrorDocument, type JsonApiErrorObject, type JsonApiPrimaryData, type JsonApiQueryInput, type JsonApiResourceLinkage, type JsonApiResourceObject, type JsonApiSuccessBody, type LocaleClientOptions, type LocaleMismatchContext, type LocaleProvider, type MultiStatusBody, type MultiStatusItem, type NoContentBody, type NormalizedResponseHeaders, type OffsetPagination, type OkResult, PACKAGE_VERSION, type PollOptions, type RequestCallOptions, type Result, type RetryOptions, type TokenProvider, type TransformResponseKeysMode, type UnknownPagination, type ValidationGroups, acceptLanguageForRequest, applyJsonApiHeaders, applyTransformKeys, assertValidIdempotencyKey, buildCursorPageParams, buildJsonApiQuery, buildOffsetPageParams, createApiClient, createHealthCheck, defaultIdempotencyKey, dispatchWithRetry, etagFromResponseHeaders, flattenAxiosHeaders, formatIfMatch, getHeader, getNextPageUrl, groupValidationErrorsByPointer, hasErrorCode, indexIncluded, isApiClientError, isApiClientErrorWithCode, isAuthenticationError, isConflictError, isForbiddenError, isIdempotencyKeyRequiredError, isIfMatchRequiredError, isMfaVerificationRequiredError, isMutationMethod, isPayloadTooLargeError, isPreconditionFailedError, isPreconditionRequiredError, isRetryablePerPolicy, isValidationError, localesMatch, normalizeAxiosResponse, normalizeHttpUrl, normalizeLocaleCode, notifyLocaleMismatch, parseContentLanguage, parseDeprecationHeaders, parseJsonApiDocument, parseJsonApiErrorBody, parseMultiStatusBody, parsePaginationKind, parseRetryAfterSeconds, pollAsyncResult, readResourceVersion, readResponseContentLanguage, redactHeaderRecord, resolveAcceptLanguage, resolveAcceptedLocation, resolveAuthorizationHeader, resolveIncluded, resolveLocaleProvider, resolveRequestLocale, resolveResourcePath, retryAllowed, truncateForLog };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // package.json
2
2
  var package_default = {
3
3
  name: "@vahidkaargar/customized-api-client",
4
- version: "0.2.4",
4
+ version: "0.4.0",
5
5
  description: "TypeScript Axios client for JSON:API v1.1 with idempotency, retries, and normalized results",
6
6
  type: "module",
7
7
  engines: {
@@ -87,14 +87,29 @@ function applyJsonApiHeaders(config, method) {
87
87
  const m = method.toUpperCase();
88
88
  const headers = { ...config.headers };
89
89
  headers.Accept = headers.Accept ?? JSON_API;
90
- if (hasJsonBody(m, config)) {
90
+ if (shouldSetJsonApiContentType(m, config)) {
91
91
  headers["Content-Type"] = headers["Content-Type"] ?? JSON_API;
92
92
  }
93
93
  return { ...config, headers };
94
94
  }
95
- function hasJsonBody(method, config) {
95
+ function shouldSetJsonApiContentType(method, config) {
96
96
  if (!["POST", "PATCH", "PUT"].includes(method)) return false;
97
- return config.data !== void 0 && config.data !== null;
97
+ const data = config.data;
98
+ if (data === void 0 || data === null) return false;
99
+ return isJsonApiSerializableBody(data);
100
+ }
101
+ function isJsonApiSerializableBody(data) {
102
+ if (typeof data !== "object") return false;
103
+ if (Array.isArray(data)) return true;
104
+ if (typeof FormData !== "undefined" && data instanceof FormData) return false;
105
+ if (typeof Blob !== "undefined" && data instanceof Blob) return false;
106
+ if (data instanceof ArrayBuffer) return false;
107
+ if (ArrayBuffer.isView(data)) return false;
108
+ if (typeof URLSearchParams !== "undefined" && data instanceof URLSearchParams) return false;
109
+ if (data instanceof Date) return false;
110
+ if (typeof ReadableStream !== "undefined" && data instanceof ReadableStream) return false;
111
+ const proto = Object.getPrototypeOf(data);
112
+ return proto === Object.prototype || proto === null;
98
113
  }
99
114
 
100
115
  // src/headers/auth.ts
@@ -110,11 +125,85 @@ async function resolveAuthorizationHeader(auth) {
110
125
  return `Bearer ${s}`;
111
126
  }
112
127
 
128
+ // src/http/header-utils.ts
129
+ function flattenAxiosHeaders(headers) {
130
+ if (!headers) return {};
131
+ if (typeof headers.forEach === "function") {
132
+ const out2 = {};
133
+ headers.forEach((value, key) => {
134
+ out2[key.toLowerCase()] = value;
135
+ });
136
+ return out2;
137
+ }
138
+ const o = headers;
139
+ const out = {};
140
+ for (const [k, v] of Object.entries(o)) {
141
+ if (typeof v === "string") out[k.toLowerCase()] = v;
142
+ else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
143
+ }
144
+ return out;
145
+ }
146
+ function getHeader(headers, name) {
147
+ return headers[name.toLowerCase()];
148
+ }
149
+
113
150
  // src/headers/locale.ts
151
+ function normalizeLocaleCode(tag) {
152
+ if (tag === void 0) return void 0;
153
+ const trimmed = tag.trim();
154
+ if (!trimmed) return void 0;
155
+ const base = trimmed.split(/[-_]/)[0]?.trim();
156
+ return base ? base.toLowerCase() : void 0;
157
+ }
158
+ function parseContentLanguage(header) {
159
+ if (header === void 0) return void 0;
160
+ const first = header.split(",")[0]?.trim();
161
+ if (!first) return void 0;
162
+ const tag = first.split(";")[0]?.trim();
163
+ return tag && tag.length > 0 ? tag : void 0;
164
+ }
165
+ function localesMatch(a, b) {
166
+ const na = normalizeLocaleCode(a);
167
+ const nb = normalizeLocaleCode(b);
168
+ if (na === void 0 || nb === void 0) return false;
169
+ return na === nb;
170
+ }
171
+ function resolveLocaleProvider(getAcceptLanguage, locale) {
172
+ return locale?.getLocale ?? getAcceptLanguage;
173
+ }
174
+ async function resolveRequestLocale(getAcceptLanguage, locale) {
175
+ return resolveAcceptLanguage(resolveLocaleProvider(getAcceptLanguage, locale));
176
+ }
177
+ function acceptLanguageForRequest(resolved, defaultLocale) {
178
+ if (resolved === void 0) return void 0;
179
+ if (defaultLocale !== void 0 && localesMatch(resolved, defaultLocale)) {
180
+ return void 0;
181
+ }
182
+ return resolved;
183
+ }
114
184
  async function resolveAcceptLanguage(provider) {
115
185
  if (!provider) return void 0;
116
186
  const v = await provider();
117
- return v ?? void 0;
187
+ if (v === null || v === void 0) return void 0;
188
+ const trimmed = v.trim();
189
+ return trimmed.length > 0 ? trimmed : void 0;
190
+ }
191
+ function readResponseContentLanguage(flatHeaders) {
192
+ return parseContentLanguage(getHeader(flatHeaders, "content-language"));
193
+ }
194
+ function notifyLocaleMismatch(locale, ctx) {
195
+ const handler = locale?.onLocaleMismatch;
196
+ if (!handler) return;
197
+ if (ctx.requested === void 0) return;
198
+ if (localesMatch(ctx.requested, ctx.resolved)) return;
199
+ if (handler === "warn") {
200
+ console.warn(
201
+ "[@vahidkaargar/customized-api-client] Content-Language mismatch",
202
+ ctx
203
+ );
204
+ return;
205
+ }
206
+ handler(ctx);
118
207
  }
119
208
 
120
209
  // src/headers/idempotency.ts
@@ -202,28 +291,6 @@ function parseRetryAfterSeconds(value) {
202
291
  return void 0;
203
292
  }
204
293
 
205
- // src/http/header-utils.ts
206
- function flattenAxiosHeaders(headers) {
207
- if (!headers) return {};
208
- if (typeof headers.forEach === "function") {
209
- const out2 = {};
210
- headers.forEach((value, key) => {
211
- out2[key.toLowerCase()] = value;
212
- });
213
- return out2;
214
- }
215
- const o = headers;
216
- const out = {};
217
- for (const [k, v] of Object.entries(o)) {
218
- if (typeof v === "string") out[k.toLowerCase()] = v;
219
- else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
220
- }
221
- return out;
222
- }
223
- function getHeader(headers, name) {
224
- return headers[name.toLowerCase()];
225
- }
226
-
227
294
  // src/retry/execute-with-retry.ts
228
295
  var DEFAULT_MAX_ATTEMPTS = 4;
229
296
  var DEFAULT_BASE_MS = 200;
@@ -647,6 +714,7 @@ function createApiClient(config) {
647
714
  const mode = config.baseUrlMode ?? "modeB";
648
715
  const genKey = config.generateIdempotencyKey ?? defaultIdempotencyKey;
649
716
  warnInsecureBaseUrl(config.baseURL);
717
+ const localeByRequest = /* @__PURE__ */ new WeakMap();
650
718
  const instance = axios.create({
651
719
  baseURL: config.baseURL,
652
720
  timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
@@ -663,9 +731,15 @@ function createApiClient(config) {
663
731
  if (authHeader) {
664
732
  next.headers.Authorization = authHeader;
665
733
  }
666
- const lang = await resolveAcceptLanguage(config.getAcceptLanguage);
667
- if (lang) {
668
- next.headers["Accept-Language"] = lang;
734
+ const resolved = await resolveRequestLocale(
735
+ // eslint-disable-next-line @typescript-eslint/no-deprecated -- legacy `getAcceptLanguage` fallback
736
+ config.getAcceptLanguage,
737
+ config.locale
738
+ );
739
+ localeByRequest.set(next, resolved);
740
+ const toSend = acceptLanguageForRequest(resolved, config.locale?.defaultLocale);
741
+ if (toSend) {
742
+ next.headers["Accept-Language"] = toSend;
669
743
  }
670
744
  if (isMutationMethod(method)) {
671
745
  const h = next.headers;
@@ -691,6 +765,17 @@ function createApiClient(config) {
691
765
  if (dep && config.onDeprecated) {
692
766
  config.onDeprecated(dep);
693
767
  }
768
+ const contentLang = readResponseContentLanguage(flat);
769
+ if (contentLang) {
770
+ notifyLocaleMismatch(config.locale, {
771
+ requested: localeByRequest.get(res.config),
772
+ resolved: contentLang,
773
+ /* v8 ignore start -- @preserve axios config url/method are strings */
774
+ url: typeof res.config.url === "string" ? res.config.url : void 0,
775
+ method: typeof res.config.method === "string" ? res.config.method : void 0
776
+ /* v8 ignore stop -- @preserve */
777
+ });
778
+ }
694
779
  return res;
695
780
  },
696
781
  (err) => Promise.reject(
@@ -762,6 +847,9 @@ function createApiClient(config) {
762
847
  async post(path, data, opts) {
763
848
  return perform("POST", resolvePath(path), { ...opts, data });
764
849
  },
850
+ async postFormData(path, data, opts) {
851
+ return perform("POST", resolvePath(path), { ...opts, data });
852
+ },
765
853
  async patch(path, data, opts) {
766
854
  return perform("PATCH", resolvePath(path), { ...opts, data });
767
855
  },
@@ -794,20 +882,42 @@ function createApiClient(config) {
794
882
  ifMatchVersion: version
795
883
  });
796
884
  },
797
- safeGet: (path, opts) => safe(() => client.get(path, opts)),
798
- safePost: (path, data, opts) => safe(() => client.post(path, data, opts)),
799
- safePatch: (path, data, opts) => safe(() => client.patch(path, data, opts)),
800
- safePut: (path, data, opts) => safe(() => client.put(path, data, opts)),
801
- safeDelete: (path, opts) => safe(() => client.delete(path, opts)),
802
- safeHead: (path, opts) => safe(() => client.head(path, opts)),
803
- safeRequest: (ax, opts) => safe(() => client.request(ax, opts)),
804
- safeGetByUrl: (url, opts) => safe(() => client.getByUrl(url, opts)),
805
- safePatchWithVersion: (path, data, version, opts) => safe(() => client.patchWithVersion(path, data, version, opts))
885
+ safeGet: (path, opts) => safe(() => perform("GET", resolvePath(path), opts ?? {})),
886
+ safePost: (path, data, opts) => safe(() => perform("POST", resolvePath(path), { ...opts, data })),
887
+ safePatch: (path, data, opts) => safe(() => perform("PATCH", resolvePath(path), { ...opts, data })),
888
+ safePut: (path, data, opts) => safe(() => perform("PUT", resolvePath(path), { ...opts, data })),
889
+ safeDelete: (path, opts) => safe(() => perform("DELETE", resolvePath(path), opts ?? {})),
890
+ safeHead: (path, opts) => safe(() => perform("HEAD", resolvePath(path), opts ?? {})),
891
+ safeRequest: (ax, opts) => safe(() => {
892
+ const method = (ax.method ?? "GET").toUpperCase();
893
+ const rawUrl = ax.url ?? "/";
894
+ const u = typeof rawUrl === "string" && /^https?:\/\//i.test(rawUrl) ? rawUrl : resolvePath(rawUrl);
895
+ return perform(method, u, {
896
+ ...opts,
897
+ data: ax.data,
898
+ idempotencyKey: opts?.idempotencyKey ?? readHeader(ax, "Idempotency-Key")
899
+ });
900
+ }),
901
+ safeGetByUrl: (fullUrl, opts) => safe(() => perform("GET", fullUrl, opts ?? {})),
902
+ safePatchWithVersion: (path, data, version, opts) => safe(
903
+ () => perform("PATCH", resolvePath(path), {
904
+ ...opts,
905
+ data,
906
+ ifMatchVersion: version
907
+ })
908
+ )
806
909
  };
807
910
  return client;
808
911
  }
809
912
 
810
913
  // src/guards.ts
914
+ function hasErrorCode(error, code) {
915
+ if (!(error instanceof ApiClientError)) return false;
916
+ return error.errors.some((e) => e.code === code);
917
+ }
918
+ function isApiClientErrorWithCode(error, code) {
919
+ return hasErrorCode(error, code);
920
+ }
811
921
  function isAuthenticationError(e) {
812
922
  return isApiErr(e, 401);
813
923
  }
@@ -817,6 +927,15 @@ function isForbiddenError(e) {
817
927
  function isPreconditionRequiredError(e) {
818
928
  return isApiErr(e, 428);
819
929
  }
930
+ function isIdempotencyKeyRequiredError(e) {
931
+ return isApiErrWithCode(e, 428, "IDEMPOTENCY_KEY_REQUIRED");
932
+ }
933
+ function isIfMatchRequiredError(e) {
934
+ return isApiErrWithCode(e, 428, "IF_MATCH_REQUIRED");
935
+ }
936
+ function isMfaVerificationRequiredError(e) {
937
+ return isApiErrWithCode(e, 428, "MFA_VERIFICATION_REQUIRED");
938
+ }
820
939
  function isPreconditionFailedError(e) {
821
940
  return isApiErr(e, 412);
822
941
  }
@@ -842,6 +961,9 @@ function isRetryablePerPolicy(e, policy) {
842
961
  function isApiErr(e, status) {
843
962
  return e instanceof ApiClientError && e.status === status;
844
963
  }
964
+ function isApiErrWithCode(e, status, code) {
965
+ return isApiErr(e, status) && hasErrorCode(e, code);
966
+ }
845
967
 
846
968
  // src/parse/pagination.ts
847
969
  function parsePaginationKind(meta, links) {
@@ -1037,6 +1159,7 @@ export {
1037
1159
  DEFAULT_TIMEOUT_MS,
1038
1160
  IDEMPOTENCY_MAX_LENGTH,
1039
1161
  PACKAGE_VERSION,
1162
+ acceptLanguageForRequest,
1040
1163
  applyJsonApiHeaders,
1041
1164
  applyTransformKeys,
1042
1165
  assertValidIdempotencyKey,
@@ -1053,19 +1176,28 @@ export {
1053
1176
  getHeader,
1054
1177
  getNextPageUrl,
1055
1178
  groupValidationErrorsByPointer,
1179
+ hasErrorCode,
1056
1180
  indexIncluded,
1057
1181
  isApiClientError,
1182
+ isApiClientErrorWithCode,
1058
1183
  isAuthenticationError,
1059
1184
  isConflictError,
1060
1185
  isForbiddenError,
1186
+ isIdempotencyKeyRequiredError,
1187
+ isIfMatchRequiredError,
1188
+ isMfaVerificationRequiredError,
1061
1189
  isMutationMethod,
1062
1190
  isPayloadTooLargeError,
1063
1191
  isPreconditionFailedError,
1064
1192
  isPreconditionRequiredError,
1065
1193
  isRetryablePerPolicy,
1066
1194
  isValidationError,
1195
+ localesMatch,
1067
1196
  normalizeAxiosResponse,
1068
1197
  normalizeHttpUrl,
1198
+ normalizeLocaleCode,
1199
+ notifyLocaleMismatch,
1200
+ parseContentLanguage,
1069
1201
  parseDeprecationHeaders,
1070
1202
  parseJsonApiDocument,
1071
1203
  parseJsonApiErrorBody,
@@ -1074,11 +1206,14 @@ export {
1074
1206
  parseRetryAfterSeconds,
1075
1207
  pollAsyncResult,
1076
1208
  readResourceVersion,
1209
+ readResponseContentLanguage,
1077
1210
  redactHeaderRecord,
1078
1211
  resolveAcceptLanguage,
1079
1212
  resolveAcceptedLocation,
1080
1213
  resolveAuthorizationHeader,
1081
1214
  resolveIncluded,
1215
+ resolveLocaleProvider,
1216
+ resolveRequestLocale,
1082
1217
  resolveResourcePath,
1083
1218
  retryAllowed,
1084
1219
  truncateForLog