@vahidkaargar/customized-api-client 0.3.0 → 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.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.3.0",
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: {
@@ -125,11 +125,85 @@ async function resolveAuthorizationHeader(auth) {
125
125
  return `Bearer ${s}`;
126
126
  }
127
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
+
128
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
+ }
129
184
  async function resolveAcceptLanguage(provider) {
130
185
  if (!provider) return void 0;
131
186
  const v = await provider();
132
- 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);
133
207
  }
134
208
 
135
209
  // src/headers/idempotency.ts
@@ -217,28 +291,6 @@ function parseRetryAfterSeconds(value) {
217
291
  return void 0;
218
292
  }
219
293
 
220
- // src/http/header-utils.ts
221
- function flattenAxiosHeaders(headers) {
222
- if (!headers) return {};
223
- if (typeof headers.forEach === "function") {
224
- const out2 = {};
225
- headers.forEach((value, key) => {
226
- out2[key.toLowerCase()] = value;
227
- });
228
- return out2;
229
- }
230
- const o = headers;
231
- const out = {};
232
- for (const [k, v] of Object.entries(o)) {
233
- if (typeof v === "string") out[k.toLowerCase()] = v;
234
- else if (Array.isArray(v) && v[0]) out[k.toLowerCase()] = v[0];
235
- }
236
- return out;
237
- }
238
- function getHeader(headers, name) {
239
- return headers[name.toLowerCase()];
240
- }
241
-
242
294
  // src/retry/execute-with-retry.ts
243
295
  var DEFAULT_MAX_ATTEMPTS = 4;
244
296
  var DEFAULT_BASE_MS = 200;
@@ -662,6 +714,7 @@ function createApiClient(config) {
662
714
  const mode = config.baseUrlMode ?? "modeB";
663
715
  const genKey = config.generateIdempotencyKey ?? defaultIdempotencyKey;
664
716
  warnInsecureBaseUrl(config.baseURL);
717
+ const localeByRequest = /* @__PURE__ */ new WeakMap();
665
718
  const instance = axios.create({
666
719
  baseURL: config.baseURL,
667
720
  timeout: config.timeout ?? DEFAULT_TIMEOUT_MS,
@@ -678,9 +731,15 @@ function createApiClient(config) {
678
731
  if (authHeader) {
679
732
  next.headers.Authorization = authHeader;
680
733
  }
681
- const lang = await resolveAcceptLanguage(config.getAcceptLanguage);
682
- if (lang) {
683
- 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;
684
743
  }
685
744
  if (isMutationMethod(method)) {
686
745
  const h = next.headers;
@@ -706,6 +765,17 @@ function createApiClient(config) {
706
765
  if (dep && config.onDeprecated) {
707
766
  config.onDeprecated(dep);
708
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
+ }
709
779
  return res;
710
780
  },
711
781
  (err) => Promise.reject(
@@ -1089,6 +1159,7 @@ export {
1089
1159
  DEFAULT_TIMEOUT_MS,
1090
1160
  IDEMPOTENCY_MAX_LENGTH,
1091
1161
  PACKAGE_VERSION,
1162
+ acceptLanguageForRequest,
1092
1163
  applyJsonApiHeaders,
1093
1164
  applyTransformKeys,
1094
1165
  assertValidIdempotencyKey,
@@ -1121,8 +1192,12 @@ export {
1121
1192
  isPreconditionRequiredError,
1122
1193
  isRetryablePerPolicy,
1123
1194
  isValidationError,
1195
+ localesMatch,
1124
1196
  normalizeAxiosResponse,
1125
1197
  normalizeHttpUrl,
1198
+ normalizeLocaleCode,
1199
+ notifyLocaleMismatch,
1200
+ parseContentLanguage,
1126
1201
  parseDeprecationHeaders,
1127
1202
  parseJsonApiDocument,
1128
1203
  parseJsonApiErrorBody,
@@ -1131,11 +1206,14 @@ export {
1131
1206
  parseRetryAfterSeconds,
1132
1207
  pollAsyncResult,
1133
1208
  readResourceVersion,
1209
+ readResponseContentLanguage,
1134
1210
  redactHeaderRecord,
1135
1211
  resolveAcceptLanguage,
1136
1212
  resolveAcceptedLocation,
1137
1213
  resolveAuthorizationHeader,
1138
1214
  resolveIncluded,
1215
+ resolveLocaleProvider,
1216
+ resolveRequestLocale,
1139
1217
  resolveResourcePath,
1140
1218
  retryAllowed,
1141
1219
  truncateForLog