@cedvict/http-guardian 0.0.1-next.5 → 0.0.1-next.7

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -1
  3. package/README.md +76 -0
  4. package/dist/aliases.d.ts +35 -0
  5. package/dist/aliases.d.ts.map +1 -0
  6. package/dist/auth/authConfig.d.ts +91 -0
  7. package/dist/auth/authConfig.d.ts.map +1 -0
  8. package/dist/cache/inFlight.d.ts +10 -0
  9. package/dist/cache/inFlight.d.ts.map +1 -0
  10. package/dist/cache/key.d.ts +10 -0
  11. package/dist/cache/key.d.ts.map +1 -0
  12. package/dist/cache/memoryCache.d.ts +16 -0
  13. package/dist/cache/memoryCache.d.ts.map +1 -0
  14. package/dist/cache/types.d.ts +13 -0
  15. package/dist/cache/types.d.ts.map +1 -0
  16. package/dist/client/errors.d.ts +29 -0
  17. package/dist/client/errors.d.ts.map +1 -0
  18. package/dist/client/httpClient.d.ts +13 -0
  19. package/dist/client/httpClient.d.ts.map +1 -0
  20. package/dist/client/types.d.ts +84 -0
  21. package/dist/client/types.d.ts.map +1 -0
  22. package/dist/guards/cookieSafeRefreshGuard.d.ts +40 -0
  23. package/dist/guards/cookieSafeRefreshGuard.d.ts.map +1 -0
  24. package/dist/guards/instrumentGuard.d.ts +12 -0
  25. package/dist/guards/instrumentGuard.d.ts.map +1 -0
  26. package/dist/guards/retryGuard.d.ts +12 -0
  27. package/dist/guards/retryGuard.d.ts.map +1 -0
  28. package/dist/guards/types.d.ts +3 -0
  29. package/dist/guards/types.d.ts.map +1 -0
  30. package/dist/guards/unauthorizedGuard.d.ts +19 -0
  31. package/dist/guards/unauthorizedGuard.d.ts.map +1 -0
  32. package/dist/index.cjs +1413 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.ts +22 -270
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +736 -40
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal/authGuard.d.ts +4 -0
  39. package/dist/internal/authGuard.d.ts.map +1 -0
  40. package/dist/internal/compose.d.ts +7 -0
  41. package/dist/internal/compose.d.ts.map +1 -0
  42. package/dist/internal/context.d.ts +31 -0
  43. package/dist/internal/context.d.ts.map +1 -0
  44. package/dist/internal/headersPolyfill.d.ts +25 -0
  45. package/dist/internal/headersPolyfill.d.ts.map +1 -0
  46. package/dist/internal/stableStringify.d.ts +2 -0
  47. package/dist/internal/stableStringify.d.ts.map +1 -0
  48. package/dist/internal/url.d.ts +3 -0
  49. package/dist/internal/url.d.ts.map +1 -0
  50. package/dist/notify/defaults.d.ts +9 -0
  51. package/dist/notify/defaults.d.ts.map +1 -0
  52. package/dist/notify/types.d.ts +33 -0
  53. package/dist/notify/types.d.ts.map +1 -0
  54. package/dist/parsing/presets.d.ts +15 -0
  55. package/dist/parsing/presets.d.ts.map +1 -0
  56. package/dist/parsing/schemaAdapter.d.ts +18 -0
  57. package/dist/parsing/schemaAdapter.d.ts.map +1 -0
  58. package/dist/parsing/shapeParser.d.ts +8 -0
  59. package/dist/parsing/shapeParser.d.ts.map +1 -0
  60. package/dist/plugins/types.d.ts +22 -0
  61. package/dist/plugins/types.d.ts.map +1 -0
  62. package/dist/presets/presetBuilder.d.ts +181 -0
  63. package/dist/presets/presetBuilder.d.ts.map +1 -0
  64. package/dist/publicTypes.d.ts +12 -0
  65. package/dist/publicTypes.d.ts.map +1 -0
  66. package/package.json +10 -5
package/dist/index.cjs ADDED
@@ -0,0 +1,1413 @@
1
+ 'use strict';
2
+
3
+ // src/internal/compose.ts
4
+ function composeGuards(guards, terminal) {
5
+ if (!guards.length) return terminal;
6
+ return (ctx) => dispatch(0, ctx);
7
+ function dispatch(i, c) {
8
+ const guard = guards[i];
9
+ if (!guard) return terminal(c);
10
+ return Promise.resolve(
11
+ guard(c, (nextCtx) => dispatch(i + 1, nextCtx ?? c))
12
+ );
13
+ }
14
+ }
15
+
16
+ // src/internal/url.ts
17
+ function joinUrl(baseUrl, path) {
18
+ if (!baseUrl) return path;
19
+ if (path.startsWith("http://") || path.startsWith("https://")) return path;
20
+ const b = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
21
+ const p = path.startsWith("/") ? path : `/${path}`;
22
+ return `${b}${p}`;
23
+ }
24
+ function withQuery(url, query) {
25
+ if (!query) return url;
26
+ const u = new URL(url);
27
+ for (const [k, v] of Object.entries(query)) {
28
+ if (v === void 0 || v === null) continue;
29
+ u.searchParams.set(k, String(v));
30
+ }
31
+ return u.toString();
32
+ }
33
+
34
+ // src/internal/authGuard.ts
35
+ function sanitizeHeaderValue(v) {
36
+ if (/[\r\n]/.test(v)) return null;
37
+ return v;
38
+ }
39
+ function base64Encode(input) {
40
+ if (typeof globalThis.btoa === "function") return globalThis.btoa(input);
41
+ const B = globalThis.Buffer;
42
+ if (typeof B?.from === "function") return B.from(input, "utf8").toString("base64");
43
+ throw new Error("No base64 encoder available in this runtime");
44
+ }
45
+ function isUnsafeMethod(m) {
46
+ const u = m.toUpperCase();
47
+ return u !== "GET" && u !== "HEAD" && u !== "OPTIONS";
48
+ }
49
+ function sameOrigin(url, baseOrigin) {
50
+ if (!baseOrigin) return true;
51
+ try {
52
+ return new URL(url).origin === baseOrigin;
53
+ } catch {
54
+ return true;
55
+ }
56
+ }
57
+ function isAllowedOrigin(url, baseOrigin, allowed) {
58
+ if (!allowed) return sameOrigin(url, baseOrigin);
59
+ if (typeof allowed === "function") return !!allowed(url);
60
+ try {
61
+ const origin = new URL(url).origin;
62
+ return allowed.includes(origin);
63
+ } catch {
64
+ return true;
65
+ }
66
+ }
67
+ function authGuard(auth) {
68
+ return async (ctx, next) => {
69
+ if (ctx.noAuth) return next(ctx);
70
+ if (auth.mode === "none") return next(ctx);
71
+ if (auth.mode === "bearer") {
72
+ const token = await auth.getToken();
73
+ if (token) {
74
+ if (!isAllowedOrigin(ctx.url, ctx.baseOrigin, auth.allowedOrigins)) {
75
+ return next(ctx);
76
+ }
77
+ const headerName = auth.headerName ?? "Authorization";
78
+ const prefix = auth.prefix ?? "Bearer";
79
+ const safe = sanitizeHeaderValue(`${prefix} ${token}`);
80
+ if (safe) ctx.headers.set(headerName, safe);
81
+ }
82
+ return next(ctx);
83
+ }
84
+ if (auth.mode === "basic") {
85
+ const cred = await auth.getCredentials();
86
+ if (cred) {
87
+ if (!isAllowedOrigin(ctx.url, ctx.baseOrigin, auth.allowedOrigins)) {
88
+ return next(ctx);
89
+ }
90
+ const headerName = auth.headerName ?? "Authorization";
91
+ const prefix = auth.prefix ?? "Basic";
92
+ const raw = typeof cred === "string" ? cred : `${cred.username}:${cred.password}`;
93
+ const safe = sanitizeHeaderValue(`${prefix} ${base64Encode(raw)}`);
94
+ if (safe) ctx.headers.set(headerName, safe);
95
+ }
96
+ return next(ctx);
97
+ }
98
+ if (auth.mode === "apiKey") {
99
+ const key = await auth.getKey();
100
+ if (key) {
101
+ if (!isAllowedOrigin(ctx.url, ctx.baseOrigin, auth.allowedOrigins)) {
102
+ return next(ctx);
103
+ }
104
+ const headerName = auth.headerName ?? "X-API-Key";
105
+ const prefix = auth.prefix ?? "";
106
+ const value = prefix ? `${prefix} ${key}` : key;
107
+ const safe = sanitizeHeaderValue(value);
108
+ if (safe) ctx.headers.set(headerName, safe);
109
+ }
110
+ return next(ctx);
111
+ }
112
+ ctx.credentials = auth.credentials ?? "include";
113
+ if (auth.csrf && isUnsafeMethod(ctx.method)) {
114
+ const csrf = await auth.csrf.getToken();
115
+ if (csrf) ctx.headers.set(auth.csrf.headerName, csrf);
116
+ }
117
+ return next(ctx);
118
+ };
119
+ }
120
+
121
+ // src/internal/headersPolyfill.ts
122
+ function norm(name) {
123
+ return String(name).toLowerCase();
124
+ }
125
+ var HeadersPolyfill = class {
126
+ map = /* @__PURE__ */ new Map();
127
+ constructor(init) {
128
+ if (!init) return;
129
+ if (typeof init.forEach === "function" && typeof init.get === "function") {
130
+ init.forEach((v, k) => this.set(k, v));
131
+ return;
132
+ }
133
+ if (Symbol.iterator in Object(init)) {
134
+ for (const [k, v] of init) this.append(k, v);
135
+ return;
136
+ }
137
+ for (const [k, v] of Object.entries(init)) {
138
+ this.set(k, String(v));
139
+ }
140
+ }
141
+ get(name) {
142
+ const hit = this.map.get(norm(name));
143
+ return hit ? hit.value : null;
144
+ }
145
+ has(name) {
146
+ return this.map.has(norm(name));
147
+ }
148
+ set(name, value) {
149
+ this.map.set(norm(name), { name, value: String(value) });
150
+ }
151
+ append(name, value) {
152
+ const key = norm(name);
153
+ const prev = this.map.get(key);
154
+ if (!prev) {
155
+ this.map.set(key, { name, value: String(value) });
156
+ return;
157
+ }
158
+ this.map.set(key, { name: prev.name, value: `${prev.value}, ${String(value)}` });
159
+ }
160
+ delete(name) {
161
+ this.map.delete(norm(name));
162
+ }
163
+ forEach(cb) {
164
+ for (const { name, value } of this.map.values()) cb(value, name);
165
+ }
166
+ entries() {
167
+ const it = this.map.values();
168
+ return {
169
+ [Symbol.iterator]() {
170
+ return this;
171
+ },
172
+ next() {
173
+ const n = it.next();
174
+ if (n.done) return { done: true, value: void 0 };
175
+ return { done: false, value: [n.value.name, n.value.value] };
176
+ }
177
+ };
178
+ }
179
+ /** Convert to HeadersInit that any fetch can consume. */
180
+ toObject() {
181
+ const out = {};
182
+ for (const { name, value } of this.map.values()) out[name] = value;
183
+ return out;
184
+ }
185
+ };
186
+ function createHeaders(init) {
187
+ if (typeof globalThis.Headers === "function") {
188
+ return new globalThis.Headers(init);
189
+ }
190
+ return new HeadersPolyfill(init);
191
+ }
192
+ function toFetchHeaders(headers) {
193
+ if (typeof globalThis.Headers === "function" && headers instanceof globalThis.Headers) {
194
+ return headers;
195
+ }
196
+ return headers.toObject();
197
+ }
198
+
199
+ // src/internal/stableStringify.ts
200
+ function stableStringify(value) {
201
+ return JSON.stringify(sortValue(value));
202
+ }
203
+ function sortValue(v) {
204
+ if (v === null || typeof v !== "object") return v;
205
+ if (Array.isArray(v)) return v.map(sortValue);
206
+ const proto = Object.getPrototypeOf(v);
207
+ if (proto !== Object.prototype && proto !== null) return v;
208
+ const out = {};
209
+ for (const key of Object.keys(v).sort()) out[key] = sortValue(v[key]);
210
+ return out;
211
+ }
212
+
213
+ // src/cache/key.ts
214
+ function makeCacheKey(parts) {
215
+ if (parts.keyOverride) return parts.keyOverride;
216
+ const headerObj = {};
217
+ if (parts.headers) {
218
+ const accept = parts.headers.get("accept");
219
+ const contentType = parts.headers.get("content-type");
220
+ if (accept) headerObj["accept"] = accept;
221
+ if (contentType) headerObj["content-type"] = contentType;
222
+ }
223
+ return stableStringify({
224
+ m: parts.method,
225
+ u: parts.url,
226
+ h: headerObj,
227
+ b: parts.body ?? null,
228
+ i: parts.idempotencyKey ?? null
229
+ });
230
+ }
231
+
232
+ // src/cache/inFlight.ts
233
+ var InFlight = class {
234
+ map = /* @__PURE__ */ new Map();
235
+ get(key) {
236
+ return this.map.get(key);
237
+ }
238
+ set(key, p) {
239
+ this.map.set(key, p);
240
+ p.finally(() => {
241
+ if (this.map.get(key) === p) this.map.delete(key);
242
+ }).catch(() => {
243
+ });
244
+ }
245
+ clear() {
246
+ this.map.clear();
247
+ }
248
+ };
249
+
250
+ // src/notify/defaults.ts
251
+ var consoleNotifier = {
252
+ notify: (e) => {
253
+ console[e.level === "error" ? "error" : "warn"](`[${e.type}] ${e.title}: ${e.message}`, e);
254
+ }
255
+ };
256
+ var alertNotifier = {
257
+ notify: (e) => {
258
+ if (typeof window !== "undefined" && typeof window.alert === "function") window.alert(`${e.title}
259
+
260
+ ${e.message}`);
261
+ }
262
+ };
263
+ function tailwindAlertRenderer(event) {
264
+ const base = "rounded-lg border p-4 shadow-sm";
265
+ const color = event.level === "warning" ? "border-amber-200 bg-amber-50 text-amber-900" : "border-red-200 bg-red-50 text-red-900";
266
+ const title = escapeHtml(event.title);
267
+ const msg = escapeHtml(event.message);
268
+ return `<div class="${base} ${color}"><div class="font-semibold">${title}</div><div class="mt-1 text-sm">${msg}</div></div>`;
269
+ }
270
+ function escapeHtml(s) {
271
+ return s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
272
+ }
273
+
274
+ // src/client/errors.ts
275
+ var NetworkError = class extends Error {
276
+ constructor(message, cause) {
277
+ super(message);
278
+ this.cause = cause;
279
+ }
280
+ name = "NetworkError";
281
+ };
282
+ var TimeoutError = class extends Error {
283
+ name = "TimeoutError";
284
+ constructor(message = "Request timed out") {
285
+ super(message);
286
+ }
287
+ };
288
+ var RedirectError = class extends Error {
289
+ constructor(message, fromUrl, toUrl) {
290
+ super(message);
291
+ this.fromUrl = fromUrl;
292
+ this.toUrl = toUrl;
293
+ }
294
+ name = "RedirectError";
295
+ };
296
+ var ParseError = class extends Error {
297
+ constructor(message, raw, cause) {
298
+ super(message);
299
+ this.raw = raw;
300
+ this.cause = cause;
301
+ }
302
+ name = "ParseError";
303
+ };
304
+ var ILLEGAL_INVOCATION_HINT = `Detected a browser fetch invocation error ("Illegal invocation"). This usually happens when passing a detached reference to window.fetch. Fix by configuring the client with fetch: (input, init) => window.fetch(input, init) or fetch: window.fetch.bind(window).`;
305
+
306
+ // src/client/httpClient.ts
307
+ function isIllegalInvocation(err) {
308
+ const msg = String(err?.message ?? err);
309
+ return /Illegal invocation/i.test(msg);
310
+ }
311
+ function normalizeFetch(f) {
312
+ const bound = f.bind ? f.bind(globalThis) : f;
313
+ return ((input, init) => bound(input, init));
314
+ }
315
+ function getDefaultFetch() {
316
+ if (typeof globalThis.fetch === "function") return globalThis.fetch;
317
+ throw new Error(
318
+ "No global fetch detected. Provide a fetch implementation via createHttpClient({ fetch }) or run in an environment that supports fetch (Node >= 18 / modern browsers)."
319
+ );
320
+ }
321
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
322
+ function createHttpClient(options) {
323
+ const draft = { ...options };
324
+ if (draft.plugins?.length) {
325
+ const seen = /* @__PURE__ */ new Set();
326
+ for (const p of draft.plugins) {
327
+ if (!p || !p.name || typeof p.apply !== "function") continue;
328
+ if (seen.has(p.name)) continue;
329
+ seen.add(p.name);
330
+ p.apply(draft);
331
+ }
332
+ }
333
+ const resolvedFetch = (() => {
334
+ if (draft.fetch) return normalizeFetch(draft.fetch);
335
+ let lastRef = null;
336
+ let lastBound = null;
337
+ return ((input, init) => {
338
+ const cur = getDefaultFetch();
339
+ if (cur !== lastRef || !lastBound) {
340
+ lastRef = cur;
341
+ lastBound = normalizeFetch(cur);
342
+ }
343
+ return lastBound(input, init);
344
+ });
345
+ })();
346
+ const cache = draft.cache;
347
+ const inFlight = new InFlight();
348
+ const defaultNotifier = draft.notifier ?? consoleNotifier;
349
+ const shouldNotify = draft.defaults?.shouldNotify ?? ((r) => !r.ok);
350
+ const baseGuards = [authGuard(draft.auth), ...draft.guards ?? []];
351
+ async function terminal(ctx) {
352
+ const controller = ctx.timeoutMs ? new AbortController() : void 0;
353
+ const timeout = ctx.timeoutMs ? setTimeout(() => controller?.abort(new TimeoutError()), ctx.timeoutMs) : void 0;
354
+ try {
355
+ const init = {
356
+ method: ctx.method,
357
+ headers: toFetchHeaders(ctx.headers),
358
+ ...ctx.body !== void 0 ? { body: ctx.body } : {},
359
+ ...ctx.signal ? { signal: controller ? anySignal([ctx.signal, controller.signal]) : ctx.signal } : controller ? { signal: controller.signal } : {},
360
+ ...ctx.credentials !== void 0 ? { credentials: ctx.credentials } : {},
361
+ ...ctx.redirect !== void 0 ? { redirect: ctx.redirect } : {}
362
+ };
363
+ try {
364
+ return await ctx._fetch(ctx.url, init);
365
+ } catch (err) {
366
+ if (isIllegalInvocation(err) && typeof globalThis.fetch === "function") {
367
+ return await globalThis.fetch(ctx.url, init);
368
+ }
369
+ throw err;
370
+ }
371
+ } finally {
372
+ if (timeout) clearTimeout(timeout);
373
+ }
374
+ }
375
+ const pipeline = composeGuards(baseGuards, terminal);
376
+ async function request(method, path, ro) {
377
+ const requestUrl = withQuery(joinUrl(draft.baseUrl, path), ro?.query);
378
+ const headers = createHeaders();
379
+ if (draft.defaults?.headers) for (const [k, v] of Object.entries(draft.defaults.headers)) headers.set(k, v);
380
+ if (ro?.headers) {
381
+ for (const [k, v] of Object.entries(ro.headers)) if (v !== void 0) headers.set(k, v);
382
+ }
383
+ let body;
384
+ let bodyForKey = void 0;
385
+ if (ro?.json !== void 0) {
386
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
387
+ if (!headers.has("accept")) headers.set("accept", "application/json");
388
+ body = JSON.stringify(ro.json);
389
+ bodyForKey = ro.json;
390
+ } else if (ro?.body !== void 0) {
391
+ body = ro.body;
392
+ bodyForKey = "[body]";
393
+ if (!headers.has("accept")) headers.set("accept", "application/json");
394
+ } else {
395
+ if (!headers.has("accept")) headers.set("accept", "application/json");
396
+ }
397
+ const timeoutMs = ro?.timeoutMs ?? draft.defaults?.timeoutMs;
398
+ const notifier = ro?.notifier ?? defaultNotifier;
399
+ const emit = (event) => {
400
+ const redact = notifier.redact;
401
+ const n = notifier.notify?.bind(notifier) ?? defaultNotifier.notify.bind(defaultNotifier);
402
+ return n(redact ? redact(event) : event);
403
+ };
404
+ const cacheOpts = ro?.cache;
405
+ const wantsCache = method === "GET" && !!cache && !!cacheOpts;
406
+ const dedupe = cacheOpts?.dedupe ?? true;
407
+ const canDedupeWrite = method !== "GET" && !!ro?.idempotencyKey;
408
+ const inflightKey = (wantsCache || canDedupeWrite) && dedupe ? makeCacheKey({
409
+ method,
410
+ url: requestUrl,
411
+ headers,
412
+ ...method === "GET" ? {} : { body: bodyForKey },
413
+ ...ro?.idempotencyKey !== void 0 ? { idempotencyKey: ro.idempotencyKey } : {},
414
+ ...cacheOpts?.key !== void 0 ? { keyOverride: cacheOpts.key } : {}
415
+ }) : void 0;
416
+ if (inflightKey) {
417
+ const existing = inFlight.get(inflightKey);
418
+ if (existing) {
419
+ try {
420
+ const stored = await existing;
421
+ return asClientResult(stored, requestUrl, draft, ro, notifier, shouldNotify, emit);
422
+ } catch {
423
+ }
424
+ }
425
+ }
426
+ const promise = (async () => {
427
+ if (wantsCache) {
428
+ const key = inflightKey;
429
+ const cached = cache.get(key);
430
+ const policy = cacheOpts?.policy ?? "cacheFirst";
431
+ if (cached) {
432
+ if (policy === "networkOnly") ; else if (policy === "staleWhileRevalidate") {
433
+ void refreshInBackground(key);
434
+ return { kind: "cache-hit", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };
435
+ } else if (policy === "cacheOnly" || policy === "cacheFirst") {
436
+ return { kind: "cache-hit", url: requestUrl, status: 200, headers: new Headers(), raw: cached.value };
437
+ }
438
+ } else if (policy === "cacheOnly") {
439
+ return { kind: "cache-miss", url: requestUrl, status: 0, headers: new Headers(), raw: null };
440
+ }
441
+ }
442
+ const res = await fetchWithRedirects({
443
+ opts: draft,
444
+ resolvedFetch,
445
+ pipeline,
446
+ method,
447
+ url: requestUrl,
448
+ headers,
449
+ ...body !== void 0 ? { body } : {},
450
+ ...ro?.signal ? { signal: ro.signal } : {},
451
+ ...timeoutMs !== void 0 ? { timeoutMs } : {},
452
+ ...ro?.noAuth ? { noAuth: true } : {}
453
+ });
454
+ const raw = await decodeResponse(res);
455
+ const envelope = { kind: "network", url: res.url || requestUrl, status: res.status, headers: res.headers, raw };
456
+ if (wantsCache && cacheOpts && res.ok) {
457
+ const key = inflightKey;
458
+ cache.set(key, {
459
+ expiresAt: Date.now() + cacheOpts.ttlMs,
460
+ value: raw,
461
+ ...cacheOpts.tags ? { tags: cacheOpts.tags } : {}
462
+ });
463
+ }
464
+ return envelope;
465
+ async function refreshInBackground(key) {
466
+ try {
467
+ const res2 = await fetchWithRedirects({
468
+ opts: draft,
469
+ resolvedFetch,
470
+ pipeline,
471
+ method,
472
+ url: requestUrl,
473
+ headers,
474
+ ...body !== void 0 ? { body } : {},
475
+ ...ro?.signal ? { signal: ro.signal } : {},
476
+ ...timeoutMs !== void 0 ? { timeoutMs } : {},
477
+ ...ro?.noAuth ? { noAuth: true } : {}
478
+ });
479
+ if (!res2.ok) return;
480
+ const raw2 = await decodeResponse(res2);
481
+ cache.set(key, {
482
+ expiresAt: Date.now() + cacheOpts.ttlMs,
483
+ value: raw2,
484
+ ...cacheOpts.tags ? { tags: cacheOpts.tags } : {}
485
+ });
486
+ } catch {
487
+ }
488
+ }
489
+ })();
490
+ if (inflightKey) inFlight.set(inflightKey, promise);
491
+ try {
492
+ const stored = await promise;
493
+ return asClientResult(stored, requestUrl, draft, ro, notifier, shouldNotify, emit);
494
+ } catch (e) {
495
+ const err = e instanceof TimeoutError ? e : new NetworkError(
496
+ isIllegalInvocation(e) ? `Network request failed. ${ILLEGAL_INVOCATION_HINT}` : "Network request failed",
497
+ e
498
+ );
499
+ if (shouldNotify({ ok: false, status: 0 })) emit({ type: "network-error", level: "error", title: "Network error", message: err.message, url: requestUrl });
500
+ return { ok: false, status: 0, headers: new Headers(), errors: [{ message: err.message, details: e }], raw: null, url: requestUrl };
501
+ }
502
+ }
503
+ return {
504
+ get: (path, ro) => request("GET", path, ro),
505
+ post: (path, ro) => request("POST", path, ro),
506
+ put: (path, ro) => request("PUT", path, ro),
507
+ patch: (path, ro) => request("PATCH", path, ro),
508
+ delete: (path, ro) => request("DELETE", path, ro),
509
+ cache: { invalidateByTag: (tag) => cache?.invalidateByTag(tag) ?? 0, clear: () => cache?.clear() }
510
+ };
511
+ }
512
+ async function fetchWithRedirects(args) {
513
+ const policy = args.opts.redirects ?? { mode: "follow", maxHops: 5 };
514
+ const maxHops = policy.maxHops ?? 5;
515
+ if (policy.mode === "follow") return args.pipeline(makeCtx(args, "follow"));
516
+ if (policy.mode === "error") return args.pipeline(makeCtx(args, "error"));
517
+ let currentUrl = args.url;
518
+ let method = args.method;
519
+ let body = args.body;
520
+ for (let hop = 0; hop <= maxHops; hop++) {
521
+ const res = await args.pipeline(
522
+ makeCtx({
523
+ ...args,
524
+ url: currentUrl,
525
+ method,
526
+ ...body !== void 0 ? { body } : {}
527
+ }, "manual")
528
+ );
529
+ if (!REDIRECT_STATUSES.has(res.status)) return res;
530
+ const loc = res.headers.get("location");
531
+ if (!loc) {
532
+ return args.pipeline(
533
+ makeCtx(
534
+ {
535
+ ...args,
536
+ url: currentUrl,
537
+ method,
538
+ ...body !== void 0 ? { body } : {}
539
+ },
540
+ "follow"
541
+ )
542
+ );
543
+ }
544
+ const nextUrl = new URL(loc, currentUrl).toString();
545
+ const decision = policy.onRedirect?.({ fromUrl: currentUrl, toUrl: nextUrl, status: res.status, hop, method });
546
+ if (decision?.action === "deny") throw new RedirectError(decision.reason ?? "Redirect denied by policy", currentUrl, nextUrl);
547
+ const finalUrl = decision?.action === "modify" ? decision.url : nextUrl;
548
+ stripAuthOnCrossOriginRedirect(args.opts, args.url, finalUrl, args.headers);
549
+ if (res.status === 303) {
550
+ method = "GET";
551
+ body = void 0;
552
+ } else if ((res.status === 301 || res.status === 302) && method !== "GET") {
553
+ method = "GET";
554
+ body = void 0;
555
+ }
556
+ currentUrl = finalUrl;
557
+ if (hop === maxHops) throw new RedirectError(`Too many redirects (>${maxHops})`, args.url, currentUrl);
558
+ }
559
+ return args.pipeline(makeCtx(args, "manual"));
560
+ }
561
+ function stripAuthOnCrossOriginRedirect(opts, initialUrl, toUrl, headers) {
562
+ try {
563
+ const initialOrigin = new URL(initialUrl).origin;
564
+ const toOrigin = new URL(toUrl).origin;
565
+ if (initialOrigin === toOrigin) return;
566
+ if (opts.auth.mode === "bearer") {
567
+ const headerName = opts.auth.headerName ?? "Authorization";
568
+ headers.delete(headerName);
569
+ return;
570
+ }
571
+ if (opts.auth.mode === "basic") {
572
+ const headerName = opts.auth.headerName ?? "Authorization";
573
+ headers.delete(headerName);
574
+ return;
575
+ }
576
+ if (opts.auth.mode === "apiKey") {
577
+ const headerName = opts.auth.headerName ?? "X-API-Key";
578
+ headers.delete(headerName);
579
+ return;
580
+ }
581
+ } catch {
582
+ }
583
+ }
584
+ function makeCtx(args, redirect) {
585
+ const baseOrigin = (() => {
586
+ try {
587
+ return new URL(args.opts.baseUrl).origin;
588
+ } catch {
589
+ return "";
590
+ }
591
+ })();
592
+ return {
593
+ baseUrl: args.opts.baseUrl,
594
+ baseOrigin,
595
+ method: args.method,
596
+ url: args.url,
597
+ headers: args.headers,
598
+ ...args.body !== void 0 ? { body: args.body } : {},
599
+ ...args.signal ? { signal: args.signal } : {},
600
+ ...args.timeoutMs !== void 0 ? { timeoutMs: args.timeoutMs } : {},
601
+ ...args.noAuth ? { noAuth: true } : {},
602
+ attempt: 0,
603
+ auth: args.opts.auth,
604
+ redirect,
605
+ _fetch: args.resolvedFetch
606
+ };
607
+ }
608
+ async function decodeResponse(res) {
609
+ if (res.status === 204) return null;
610
+ const ct = res.headers.get("content-type") ?? "";
611
+ if (ct.includes("application/json")) {
612
+ try {
613
+ return await res.json();
614
+ } catch (e) {
615
+ throw new ParseError("Failed to parse JSON response", void 0, e);
616
+ }
617
+ }
618
+ try {
619
+ return await res.text();
620
+ } catch (e) {
621
+ throw new ParseError("Failed to read response body", void 0, e);
622
+ }
623
+ }
624
+ function asClientResult(envelope, requestUrl, opts, ro, notifier, shouldNotify, emit) {
625
+ if (envelope?.kind === "cache-miss") {
626
+ const errors2 = [{ message: "Cache miss" }];
627
+ if (shouldNotify({ ok: false, status: 0 })) emit({ type: "http-error", level: "warning", title: "Cache", message: "Cache miss", status: 0, errors: errors2, url: requestUrl });
628
+ return { ok: false, status: 0, headers: new Headers(), errors: errors2, raw: envelope.raw, url: requestUrl };
629
+ }
630
+ const status = envelope.status ?? 0;
631
+ const raw = envelope.raw;
632
+ const resHeaders = envelope.headers ?? new Headers();
633
+ const finalUrl = envelope.url ?? requestUrl;
634
+ const okByParser = opts.parser.isSuccess(raw, status);
635
+ if (okByParser && status >= 200 && status < 400) {
636
+ const dataRaw = opts.parser.getData(raw);
637
+ try {
638
+ const data = ro?.dataSchema ? ro.dataSchema.parse(dataRaw) : dataRaw;
639
+ return { ok: true, status, headers: resHeaders, data, raw, url: finalUrl };
640
+ } catch (e) {
641
+ const msg2 = e instanceof Error ? e.message : "Schema parse failed";
642
+ if (shouldNotify({ ok: false, status })) emit({ type: "parse-error", level: "error", title: "Parse error", message: msg2, url: finalUrl });
643
+ return { ok: false, status, headers: resHeaders, errors: [{ message: msg2, details: e }], raw, url: finalUrl };
644
+ }
645
+ }
646
+ const errors = opts.parser.getErrors(raw, status);
647
+ const msg = errors[0]?.message ?? (status ? `HTTP ${status}` : "Request failed");
648
+ if (shouldNotify({ ok: false, status })) emit({ type: "http-error", level: status >= 500 ? "error" : "warning", title: "Request failed", message: msg, status, errors, url: finalUrl });
649
+ return { ok: false, status, headers: resHeaders, errors, raw, url: finalUrl };
650
+ }
651
+ function anySignal(signals) {
652
+ const valid = signals.filter(Boolean);
653
+ const any = AbortSignal.any;
654
+ if (any) {
655
+ try {
656
+ return any(valid);
657
+ } catch {
658
+ }
659
+ }
660
+ const controller = new AbortController();
661
+ const onAbort = () => controller.abort();
662
+ for (const s of valid) {
663
+ if (s.aborted) controller.abort();
664
+ s.addEventListener("abort", onAbort, { once: true });
665
+ }
666
+ return controller.signal;
667
+ }
668
+
669
+ // src/auth/authConfig.ts
670
+ function bearerAuth(getToken, opts) {
671
+ return {
672
+ mode: "bearer",
673
+ getToken,
674
+ ...opts?.headerName !== void 0 ? { headerName: opts.headerName } : {},
675
+ ...opts?.prefix !== void 0 ? { prefix: opts.prefix } : {},
676
+ ...opts?.allowedOrigins !== void 0 ? { allowedOrigins: opts.allowedOrigins } : {}
677
+ };
678
+ }
679
+ function cookieAuth(opts) {
680
+ return {
681
+ mode: "cookie",
682
+ ...opts?.credentials !== void 0 ? { credentials: opts.credentials } : {},
683
+ ...opts?.csrf !== void 0 ? { csrf: opts.csrf } : {}
684
+ };
685
+ }
686
+ function basicAuth(getCredentials, opts) {
687
+ return {
688
+ mode: "basic",
689
+ getCredentials,
690
+ ...opts?.headerName !== void 0 ? { headerName: opts.headerName } : {},
691
+ ...opts?.prefix !== void 0 ? { prefix: opts.prefix } : {},
692
+ ...opts?.allowedOrigins !== void 0 ? { allowedOrigins: opts.allowedOrigins } : {}
693
+ };
694
+ }
695
+ function apiKeyAuth(getKey, opts) {
696
+ return {
697
+ mode: "apiKey",
698
+ getKey,
699
+ ...opts?.headerName !== void 0 ? { headerName: opts.headerName } : {},
700
+ ...opts?.prefix !== void 0 ? { prefix: opts.prefix } : {},
701
+ ...opts?.allowedOrigins !== void 0 ? { allowedOrigins: opts.allowedOrigins } : {}
702
+ };
703
+ }
704
+ function noAuth() {
705
+ return { mode: "none" };
706
+ }
707
+
708
+ // src/guards/retryGuard.ts
709
+ function retryGuard(opts) {
710
+ const retries = opts?.retries ?? 0;
711
+ const retryOn = new Set(opts?.retryOn ?? [502, 503, 504]);
712
+ const baseDelay = opts?.baseDelayMs ?? 150;
713
+ const maxDelay = opts?.maxDelayMs ?? 1500;
714
+ return async (ctx, next) => {
715
+ let last;
716
+ for (let attempt = 0; attempt <= retries; attempt++) {
717
+ ctx.attempt = attempt;
718
+ last = await next(ctx);
719
+ if (!retryOn.has(last.status)) return last;
720
+ if (attempt === retries) return last;
721
+ const delay = Math.min(maxDelay, baseDelay * 2 ** attempt);
722
+ await new Promise((r) => setTimeout(r, delay));
723
+ }
724
+ return last;
725
+ };
726
+ }
727
+
728
+ // src/guards/cookieSafeRefreshGuard.ts
729
+ var REFRESH_FLAG = "__hg_is_refresh__";
730
+ var RETRY_FLAG = "__hg_refresh_retry__";
731
+ function isReplayableBody(body) {
732
+ if (body == null) return true;
733
+ if (typeof body === "string") return true;
734
+ if (typeof Blob !== "undefined" && body instanceof Blob) return true;
735
+ if (typeof FormData !== "undefined" && body instanceof FormData) return true;
736
+ if (typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams) return true;
737
+ if (typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer) return true;
738
+ if (typeof Uint8Array !== "undefined" && body instanceof Uint8Array) return true;
739
+ return false;
740
+ }
741
+ function normalizePath(url) {
742
+ try {
743
+ return new URL(url).pathname;
744
+ } catch {
745
+ const q = url.indexOf("?");
746
+ const h = url.indexOf("#");
747
+ const cut = Math.min(q === -1 ? url.length : q, h === -1 ? url.length : h);
748
+ return url.slice(0, cut);
749
+ }
750
+ }
751
+ function parseRetryAfterMs(headers) {
752
+ const ra = headers.get("retry-after");
753
+ if (!ra) return null;
754
+ const seconds = Number(ra);
755
+ if (!Number.isNaN(seconds) && seconds >= 0) return seconds * 1e3;
756
+ const date = Date.parse(ra);
757
+ if (!Number.isNaN(date)) return Math.max(0, date - Date.now());
758
+ return null;
759
+ }
760
+ function isAbsoluteUrl(u) {
761
+ try {
762
+ void new URL(u);
763
+ return true;
764
+ } catch {
765
+ return false;
766
+ }
767
+ }
768
+ function cookieSafeRefreshGuard(opts) {
769
+ const trigger = new Set(opts.triggerStatuses ?? [401, 419]);
770
+ const maxRetry = opts.maxRetry ?? 1;
771
+ const honorRetryAfter = opts.honorRetryAfter ?? true;
772
+ let refreshPromise = null;
773
+ let refreshCooldownUntil = 0;
774
+ return async (ctx, next) => {
775
+ const anyCtx = ctx;
776
+ if (anyCtx[REFRESH_FLAG]) return next(ctx);
777
+ const path = normalizePath(ctx.url);
778
+ if (opts.excludePaths?.includes(path)) return next(ctx);
779
+ if (ctx.noAuth) return next(ctx);
780
+ const refreshPathNorm = normalizePath(resolveRefreshUrl(ctx, opts.refreshPath));
781
+ if (path === refreshPathNorm) return next(ctx);
782
+ if (honorRetryAfter && refreshCooldownUntil > Date.now()) {
783
+ return next(ctx);
784
+ }
785
+ const res = await next(ctx);
786
+ if (!trigger.has(res.status)) return res;
787
+ if (opts.shouldRefresh && !opts.shouldRefresh(ctx, res.status)) return res;
788
+ const alreadyRetried = Number(anyCtx[RETRY_FLAG] ?? 0);
789
+ if (alreadyRetried >= maxRetry) return res;
790
+ if (!isReplayableBody(ctx.body)) return res;
791
+ const decision = await (refreshPromise ??= doRefresh(ctx, next).finally(() => {
792
+ refreshPromise = null;
793
+ }));
794
+ if (!decision.ok) {
795
+ opts.onRefreshFailed?.({
796
+ url: ctx.url,
797
+ ...decision.reason !== void 0 ? { reason: decision.reason } : {},
798
+ ...decision.status !== void 0 ? { status: decision.status } : {}
799
+ });
800
+ return res;
801
+ }
802
+ opts.onRefreshSuccess?.({ url: ctx.url });
803
+ const retryCtx = Object.assign({}, ctx, { attempt: ctx.attempt + 1 });
804
+ retryCtx[RETRY_FLAG] = alreadyRetried + 1;
805
+ await applyCookieAuth(retryCtx);
806
+ return next(retryCtx);
807
+ };
808
+ async function doRefresh(originalCtx, next) {
809
+ if (honorRetryAfter && refreshCooldownUntil > Date.now()) {
810
+ return { ok: false, reason: "Refresh cooldown active" };
811
+ }
812
+ const refreshUrl = resolveRefreshUrl(originalCtx, opts.refreshPath);
813
+ const refreshCtx = {
814
+ ...originalCtx,
815
+ url: refreshUrl,
816
+ method: opts.refreshMethod ?? "POST",
817
+ // Un refresh n’a en général pas besoin du body original
818
+ ...Object.prototype.hasOwnProperty.call(originalCtx, "body") ? { body: void 0 } : {},
819
+ attempt: 0
820
+ };
821
+ refreshCtx[REFRESH_FLAG] = true;
822
+ refreshCtx[RETRY_FLAG] = 0;
823
+ await applyCookieAuth(refreshCtx);
824
+ try {
825
+ const res = await next(refreshCtx);
826
+ if (res.status === 429 && honorRetryAfter) {
827
+ const ms = parseRetryAfterMs(res.headers);
828
+ refreshCooldownUntil = Date.now() + (ms ?? 3e4);
829
+ return { ok: false, reason: "Refresh rate-limited (429)", status: 429 };
830
+ }
831
+ if (!res.ok) {
832
+ return { ok: false, reason: `Refresh failed (HTTP ${res.status})`, status: res.status };
833
+ }
834
+ return { ok: true };
835
+ } catch (e) {
836
+ return { ok: false, reason: e instanceof Error ? e.message : "Refresh exception" };
837
+ }
838
+ }
839
+ function resolveRefreshUrl(ctx, refreshPath) {
840
+ if (isAbsoluteUrl(refreshPath)) return refreshPath;
841
+ return joinUrl(ctx.baseUrl, refreshPath);
842
+ }
843
+ async function applyCookieAuth(ctx) {
844
+ if (ctx.noAuth) return;
845
+ const auth = ctx.auth;
846
+ if (!auth || auth.mode !== "cookie") return;
847
+ if (ctx.credentials === void 0) {
848
+ ctx.credentials = auth.credentials ?? "include";
849
+ }
850
+ if (auth.csrf) {
851
+ const hn = auth.csrf.headerName;
852
+ if (!ctx.headers.has(hn)) {
853
+ const t = await auth.csrf.getToken();
854
+ if (t != null && String(t) !== "") ctx.headers.set(hn, String(t));
855
+ }
856
+ }
857
+ }
858
+ }
859
+
860
+ // src/guards/unauthorizedGuard.ts
861
+ function normalizePath2(url) {
862
+ try {
863
+ return new URL(url).pathname;
864
+ } catch {
865
+ const q = url.indexOf("?");
866
+ const h = url.indexOf("#");
867
+ const cut = Math.min(q === -1 ? url.length : q, h === -1 ? url.length : h);
868
+ return url.slice(0, cut);
869
+ }
870
+ }
871
+ function unauthorizedGuard(opts) {
872
+ const statuses = opts.statuses ?? [401, 419];
873
+ const ignoreNoAuth = opts.ignoreNoAuth ?? true;
874
+ const once = opts.once ?? true;
875
+ const cooldownMs = opts.cooldownMs ?? 250;
876
+ let locked = false;
877
+ let cooldownUntil = 0;
878
+ return async (ctx, next) => {
879
+ let res;
880
+ try {
881
+ res = await next(ctx);
882
+ } catch (e) {
883
+ throw e;
884
+ }
885
+ if (ignoreNoAuth && ctx.noAuth) return res;
886
+ const path = normalizePath2(ctx.url);
887
+ if (opts.excludePaths?.includes(path)) return res;
888
+ if (!statuses.includes(res.status)) return res;
889
+ if (opts.shouldHandle && !opts.shouldHandle(ctx, res.status)) return res;
890
+ const now = Date.now();
891
+ if (now < cooldownUntil) return res;
892
+ if (once) {
893
+ if (locked) return res;
894
+ locked = true;
895
+ }
896
+ try {
897
+ opts.onUnauthorized({ url: ctx.url, status: res.status });
898
+ } finally {
899
+ cooldownUntil = Date.now() + cooldownMs;
900
+ if (once) setTimeout(() => locked = false, cooldownMs);
901
+ }
902
+ return res;
903
+ };
904
+ }
905
+
906
+ // src/guards/instrumentGuard.ts
907
+ var REFRESH_FLAG2 = "__hg_is_refresh__";
908
+ var RETRY_FLAG2 = "__hg_refresh_retry__";
909
+ function safeUrl(u) {
910
+ try {
911
+ const url = new URL(u);
912
+ return url.toString();
913
+ } catch {
914
+ return u;
915
+ }
916
+ }
917
+ function nowMs() {
918
+ return typeof performance !== "undefined" ? performance.now() : Date.now();
919
+ }
920
+ function headersToObject(h) {
921
+ if (!h) return {};
922
+ const out = {};
923
+ try {
924
+ h.forEach((v, k) => {
925
+ const key = String(k).toLowerCase();
926
+ if (key === "authorization" || key === "cookie" || key === "set-cookie" || key === "x-api-key" || key === "proxy-authorization") {
927
+ out[k] = "[REDACTED]";
928
+ } else {
929
+ out[k] = v;
930
+ }
931
+ });
932
+ } catch {
933
+ }
934
+ return out;
935
+ }
936
+ var __hg_req_seq = 0;
937
+ function instrumentGuard(opts, guard) {
938
+ const log = opts.log ?? ((line, data) => data !== void 0 ? console.log(line, data) : console.log(line));
939
+ return async (ctx, next) => {
940
+ const id = ++__hg_req_seq;
941
+ const t0 = nowMs();
942
+ const url = safeUrl(ctx.url);
943
+ const method = ctx.method ?? "GET";
944
+ if (opts.include && !opts.include.test(url)) {
945
+ return guard(ctx, next);
946
+ }
947
+ if (opts.exclude && opts.exclude.test(url)) {
948
+ return guard(ctx, next);
949
+ }
950
+ const anyCtx = ctx;
951
+ const attempt = ctx.attempt ?? 0;
952
+ const isRefresh = Boolean(anyCtx[REFRESH_FLAG2]);
953
+ const retryCount = Number(anyCtx[RETRY_FLAG2] ?? 0);
954
+ const noAuth2 = Boolean(ctx.noAuth);
955
+ const baseLine = `[HG][#${id}][${opts.name}]`;
956
+ log(`${baseLine} \u25B6 start`, {
957
+ method,
958
+ url,
959
+ attempt,
960
+ retryCount,
961
+ isRefresh,
962
+ noAuth: noAuth2,
963
+ baseUrl: ctx.baseUrl,
964
+ ...opts.logInit ? {
965
+ credentials: ctx.credentials,
966
+ redirect: ctx.redirect,
967
+ headers: headersToObject(ctx.headers)
968
+ } : {},
969
+ ...opts.stack ? { stack: new Error().stack } : {}
970
+ });
971
+ try {
972
+ const res = await guard(ctx, next);
973
+ const dt = Math.round(nowMs() - t0);
974
+ log(`${baseLine} \u25C0 end (${dt}ms)`, {
975
+ status: res.status,
976
+ ok: res.ok,
977
+ url: res.url || url,
978
+ redirected: res.redirected,
979
+ location: res.headers?.get?.("location") ?? null,
980
+ retryAfter: res.headers?.get?.("retry-after") ?? null
981
+ });
982
+ return res;
983
+ } catch (e) {
984
+ const dt = Math.round(nowMs() - t0);
985
+ log(`${baseLine} \u2716 throw (${dt}ms)`, {
986
+ error: e instanceof Error ? { name: e.name, message: e.message, stack: e.stack } : e
987
+ });
988
+ throw e;
989
+ }
990
+ };
991
+ }
992
+
993
+ // src/cache/memoryCache.ts
994
+ function memoryCache(opts) {
995
+ const max = opts?.maxEntries ?? 1e3;
996
+ const map = /* @__PURE__ */ new Map();
997
+ const tagIndex = /* @__PURE__ */ new Map();
998
+ function touch(key) {
999
+ const v = map.get(key);
1000
+ if (!v) return;
1001
+ map.delete(key);
1002
+ map.set(key, v);
1003
+ }
1004
+ function deindexTags(key, entry) {
1005
+ const tags = entry?.tags;
1006
+ if (!tags?.length) return;
1007
+ for (const t of tags) {
1008
+ const set = tagIndex.get(t);
1009
+ if (!set) continue;
1010
+ set.delete(key);
1011
+ if (set.size === 0) tagIndex.delete(t);
1012
+ }
1013
+ }
1014
+ function indexTags(key, tags) {
1015
+ if (!tags?.length) return;
1016
+ for (const t of tags) {
1017
+ let set = tagIndex.get(t);
1018
+ if (!set) {
1019
+ set = /* @__PURE__ */ new Set();
1020
+ tagIndex.set(t, set);
1021
+ }
1022
+ set.add(key);
1023
+ }
1024
+ }
1025
+ function del(key) {
1026
+ const prev = map.get(key);
1027
+ if (prev) deindexTags(key, prev);
1028
+ map.delete(key);
1029
+ }
1030
+ function ensureLimit() {
1031
+ while (map.size > max) {
1032
+ const firstKey = map.keys().next().value;
1033
+ if (!firstKey) break;
1034
+ del(firstKey);
1035
+ }
1036
+ }
1037
+ return {
1038
+ get(key) {
1039
+ const entry = map.get(key);
1040
+ if (!entry) return void 0;
1041
+ if (Date.now() > entry.expiresAt) {
1042
+ del(key);
1043
+ return void 0;
1044
+ }
1045
+ touch(key);
1046
+ return entry;
1047
+ },
1048
+ set(key, entry) {
1049
+ const prev = map.get(key);
1050
+ if (prev) deindexTags(key, prev);
1051
+ map.set(key, entry);
1052
+ indexTags(key, entry.tags);
1053
+ ensureLimit();
1054
+ },
1055
+ delete: del,
1056
+ invalidateByTag(tag) {
1057
+ const set = tagIndex.get(tag);
1058
+ if (!set) return 0;
1059
+ const keys = Array.from(set);
1060
+ let n = 0;
1061
+ for (const k of keys) {
1062
+ if (map.has(k)) {
1063
+ del(k);
1064
+ n++;
1065
+ }
1066
+ }
1067
+ return n;
1068
+ },
1069
+ clear() {
1070
+ map.clear();
1071
+ tagIndex.clear();
1072
+ }
1073
+ };
1074
+ }
1075
+
1076
+ // src/parsing/shapeParser.ts
1077
+ function createShapeParser(p) {
1078
+ return p;
1079
+ }
1080
+
1081
+ // src/parsing/schemaAdapter.ts
1082
+ function schema(impl) {
1083
+ return impl;
1084
+ }
1085
+ function fromZod(zodSchema) {
1086
+ if (typeof zodSchema.parse === "function") return { parse: (i) => zodSchema.parse(i) };
1087
+ if (typeof zodSchema.safeParse === "function") {
1088
+ return {
1089
+ parse: (i) => {
1090
+ const r = zodSchema.safeParse(i);
1091
+ if (r && r.success) return r.data;
1092
+ throw new Error("Schema validation failed");
1093
+ }
1094
+ };
1095
+ }
1096
+ throw new Error("Unsupported zod-like schema: expected parse or safeParse");
1097
+ }
1098
+
1099
+ // src/parsing/presets.ts
1100
+ function createApiParserRestClassic(opts) {
1101
+ const successField = opts?.successField ?? "success";
1102
+ const dataField = opts?.dataField ?? "data";
1103
+ const errorField = opts?.errorField ?? "error";
1104
+ const messageField = opts?.messageField ?? "message";
1105
+ return createShapeParser({
1106
+ isSuccess: (raw, status) => {
1107
+ if (raw && typeof raw === "object" && successField in raw) return Boolean(raw[successField]);
1108
+ return status >= 200 && status < 400;
1109
+ },
1110
+ getData: (raw) => raw && typeof raw === "object" && dataField in raw ? raw[dataField] : raw,
1111
+ getErrors: (raw, status) => {
1112
+ if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
1113
+ const r = raw;
1114
+ const err = r[errorField] ?? r[messageField] ?? r["errors"];
1115
+ if (!err) return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
1116
+ if (typeof err === "string") return [{ message: err }];
1117
+ if (Array.isArray(err)) return err.map((e) => ({ message: String(e) }));
1118
+ if (typeof err === "object") {
1119
+ if (typeof err.message === "string") return [{ message: err.message, ...err.code ? { code: String(err.code) } : {}, details: err }];
1120
+ return [{ message: String(r[messageField] ?? "Request failed"), details: err }];
1121
+ }
1122
+ return [{ message: String(err) }];
1123
+ }
1124
+ });
1125
+ }
1126
+ function createApiParserLaravel(opts) {
1127
+ const dataField = opts?.dataField ?? "data";
1128
+ return createShapeParser({
1129
+ isSuccess: (_raw, status) => status >= 200 && status < 400,
1130
+ getData: (raw) => raw && typeof raw === "object" && dataField in raw ? raw[dataField] : raw,
1131
+ getErrors: (raw, status) => laravelErrors(raw, status)
1132
+ });
1133
+ }
1134
+ function laravelErrors(raw, status) {
1135
+ if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
1136
+ const r = raw;
1137
+ if (r.errors && typeof r.errors === "object") {
1138
+ const out = [];
1139
+ for (const [field, msgs] of Object.entries(r.errors)) {
1140
+ if (Array.isArray(msgs)) for (const m of msgs) out.push({ field, message: String(m) });
1141
+ else out.push({ field, message: String(msgs) });
1142
+ }
1143
+ if (out.length) return out;
1144
+ }
1145
+ const msg = typeof r.message === "string" ? r.message : status ? `HTTP ${status}` : "Request failed";
1146
+ return [{ message: msg, details: r }];
1147
+ }
1148
+ function createApiParserNest() {
1149
+ return createShapeParser({
1150
+ isSuccess: (_raw, status) => status >= 200 && status < 400,
1151
+ getData: (raw) => raw,
1152
+ getErrors: (raw, status) => nestErrors(raw, status)
1153
+ });
1154
+ }
1155
+ function nestErrors(raw, status) {
1156
+ if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "Request failed" }];
1157
+ const r = raw;
1158
+ const msg = r.message;
1159
+ if (Array.isArray(msg)) return msg.map((m) => ({ message: String(m), details: r }));
1160
+ if (typeof msg === "string") return [{ message: msg, ...r.error ? { code: String(r.error) } : {}, details: r }];
1161
+ return [{ message: status ? `HTTP ${status}` : "Request failed", details: r }];
1162
+ }
1163
+ function createApiParserGraphQL(opts) {
1164
+ const allowPartial = opts?.allowPartialData ?? false;
1165
+ return createShapeParser({
1166
+ isSuccess: (raw, status) => {
1167
+ if (status < 200 || status >= 400) return false;
1168
+ if (!raw || typeof raw !== "object") return false;
1169
+ const r = raw;
1170
+ const hasErrors = Array.isArray(r.errors) && r.errors.length > 0;
1171
+ if (hasErrors && !allowPartial) return false;
1172
+ return "data" in r;
1173
+ },
1174
+ getData: (raw) => raw && typeof raw === "object" ? raw.data : raw,
1175
+ getErrors: (raw, status) => {
1176
+ if (!raw || typeof raw !== "object") return status ? [{ message: `HTTP ${status}` }] : [{ message: "GraphQL request failed" }];
1177
+ const r = raw;
1178
+ const errs = Array.isArray(r.errors) ? r.errors : [];
1179
+ if (!errs.length) return status >= 400 ? [{ message: `HTTP ${status}` }] : [{ message: "GraphQL request failed" }];
1180
+ return errs.map((e) => ({
1181
+ message: typeof e?.message === "string" ? e.message : "GraphQL error",
1182
+ code: e?.extensions?.code ? String(e.extensions.code) : void 0,
1183
+ details: e
1184
+ }));
1185
+ }
1186
+ });
1187
+ }
1188
+
1189
+ // src/presets/presetBuilder.ts
1190
+ function createPresetBuilder(init) {
1191
+ const state = {
1192
+ baseUrl: init?.baseUrl ?? "",
1193
+ parser: init?.parser ?? createApiParserRestClassic(),
1194
+ auth: init?.auth ?? noAuth(),
1195
+ ...init?.fetch ? { fetch: init.fetch } : {},
1196
+ ...init?.notifier ? { notifier: init.notifier } : { notifier: consoleNotifier },
1197
+ guards: []
1198
+ };
1199
+ function ensureBaseUrl() {
1200
+ if (!state.baseUrl) throw new Error("presetBuilder: baseUrl is required");
1201
+ }
1202
+ const api = {
1203
+ /** Set / override baseUrl */
1204
+ baseUrl(baseUrl) {
1205
+ state.baseUrl = baseUrl;
1206
+ return api;
1207
+ },
1208
+ /** Set / override parser */
1209
+ parser(parser) {
1210
+ state.parser = parser;
1211
+ return api;
1212
+ },
1213
+ /** Set / override fetch */
1214
+ fetch(fetchImpl) {
1215
+ state.fetch = fetchImpl;
1216
+ return api;
1217
+ },
1218
+ /** Set / override notifier */
1219
+ notifier(notifier) {
1220
+ state.notifier = notifier;
1221
+ return api;
1222
+ },
1223
+ /** Add guard(s) (preserves order) */
1224
+ guards(...guards) {
1225
+ state.guards = [...state.guards ?? [], ...guards];
1226
+ return api;
1227
+ },
1228
+ /** Enable in-memory cache */
1229
+ memoryCache(opts) {
1230
+ state.cache = memoryCache(opts);
1231
+ return api;
1232
+ },
1233
+ /** Low-level cache assignment */
1234
+ cache(cache) {
1235
+ if (cache === void 0) {
1236
+ delete state.cache;
1237
+ } else {
1238
+ state.cache = cache;
1239
+ }
1240
+ return api;
1241
+ },
1242
+ /** Default headers / timeouts / notify policy */
1243
+ defaults(defaults) {
1244
+ state.defaults = { ...state.defaults ?? {}, ...defaults ?? {} };
1245
+ return api;
1246
+ },
1247
+ /** Redirect policy */
1248
+ redirects(redirects) {
1249
+ if (redirects === void 0) {
1250
+ delete state.redirects;
1251
+ } else {
1252
+ state.redirects = redirects;
1253
+ }
1254
+ return api;
1255
+ },
1256
+ /** Auth presets */
1257
+ auth(auth) {
1258
+ state.auth = auth;
1259
+ return api;
1260
+ },
1261
+ bearer(getToken, opts) {
1262
+ state.auth = bearerAuth(getToken, opts);
1263
+ return api;
1264
+ },
1265
+ cookie(opts) {
1266
+ state.auth = cookieAuth(opts);
1267
+ return api;
1268
+ },
1269
+ basic(getCred, opts) {
1270
+ state.auth = basicAuth(getCred, opts);
1271
+ return api;
1272
+ },
1273
+ apiKey(getKey, opts) {
1274
+ state.auth = apiKeyAuth(getKey, opts);
1275
+ return api;
1276
+ },
1277
+ none() {
1278
+ state.auth = noAuth();
1279
+ return api;
1280
+ },
1281
+ /**
1282
+ * Cookie refresh guard preset (only meaningful for cookie auth).
1283
+ * You can still add it manually via `.guards(...)`.
1284
+ */
1285
+ cookieRefresh(opts) {
1286
+ state.guards = [...state.guards ?? [], cookieSafeRefreshGuard(opts)];
1287
+ return api;
1288
+ },
1289
+ /** Unauthorized handler preset (redirect/logout). */
1290
+ unauthorized(opts) {
1291
+ state.guards = [...state.guards ?? [], unauthorizedGuard(opts)];
1292
+ return api;
1293
+ },
1294
+ /** Returns the final HttpClientOptions */
1295
+ options() {
1296
+ ensureBaseUrl();
1297
+ return { ...state, guards: [...state.guards ?? []] };
1298
+ },
1299
+ /** Creates the http client */
1300
+ create() {
1301
+ ensureBaseUrl();
1302
+ return createHttpClient(api.options());
1303
+ }
1304
+ };
1305
+ return api;
1306
+ }
1307
+ function createSimpleClient(opts) {
1308
+ return createPresetBuilder(opts).create();
1309
+ }
1310
+ function createBearerClient(opts) {
1311
+ const bearerOpts = {
1312
+ ...opts.headerName !== void 0 ? { headerName: opts.headerName } : {},
1313
+ ...opts.prefix !== void 0 ? { prefix: opts.prefix } : {}
1314
+ };
1315
+ return createPresetBuilder({
1316
+ baseUrl: opts.baseUrl,
1317
+ ...opts.parser ? { parser: opts.parser } : {},
1318
+ ...opts.fetch ? { fetch: opts.fetch } : {},
1319
+ ...opts.notifier ? { notifier: opts.notifier } : {}
1320
+ }).bearer(opts.getToken, Object.keys(bearerOpts).length ? bearerOpts : void 0).create();
1321
+ }
1322
+ function createCookieClient(opts) {
1323
+ const b = createPresetBuilder({
1324
+ baseUrl: opts.baseUrl,
1325
+ ...opts.parser ? { parser: opts.parser } : {},
1326
+ ...opts.fetch ? { fetch: opts.fetch } : {},
1327
+ ...opts.notifier ? { notifier: opts.notifier } : {}
1328
+ }).cookie({
1329
+ ...opts.credentials !== void 0 ? { credentials: opts.credentials } : {},
1330
+ ...opts.csrf !== void 0 ? { csrf: opts.csrf } : {}
1331
+ });
1332
+ if (opts.refresh) b.cookieRefresh(opts.refresh);
1333
+ return b.create();
1334
+ }
1335
+ function createBasicClient(opts) {
1336
+ const basicOpts = {
1337
+ ...opts.headerName !== void 0 ? { headerName: opts.headerName } : {},
1338
+ ...opts.prefix !== void 0 ? { prefix: opts.prefix } : {}
1339
+ };
1340
+ return createPresetBuilder({
1341
+ baseUrl: opts.baseUrl,
1342
+ ...opts.parser ? { parser: opts.parser } : {},
1343
+ ...opts.fetch ? { fetch: opts.fetch } : {},
1344
+ ...opts.notifier ? { notifier: opts.notifier } : {}
1345
+ }).basic(opts.getCredentials, Object.keys(basicOpts).length ? basicOpts : void 0).create();
1346
+ }
1347
+ function createApiKeyClient(opts) {
1348
+ const apiKeyOpts = {
1349
+ ...opts.headerName !== void 0 ? { headerName: opts.headerName } : {},
1350
+ ...opts.prefix !== void 0 ? { prefix: opts.prefix } : {}
1351
+ };
1352
+ return createPresetBuilder({
1353
+ baseUrl: opts.baseUrl,
1354
+ ...opts.parser ? { parser: opts.parser } : {},
1355
+ ...opts.fetch ? { fetch: opts.fetch } : {},
1356
+ ...opts.notifier ? { notifier: opts.notifier } : {}
1357
+ }).apiKey(opts.getKey, Object.keys(apiKeyOpts).length ? apiKeyOpts : void 0).create();
1358
+ }
1359
+
1360
+ // src/aliases.ts
1361
+ function createClient(opts) {
1362
+ const parser = opts.parser ?? createApiParserRestClassic();
1363
+ const auth = opts.auth ?? noAuth();
1364
+ const notifier = opts.notifier ?? consoleNotifier;
1365
+ const cfg = {
1366
+ baseUrl: opts.baseUrl,
1367
+ parser,
1368
+ auth,
1369
+ notifier,
1370
+ ...opts.fetch ? { fetch: opts.fetch } : {},
1371
+ ...opts.defaults ? { defaults: opts.defaults } : {},
1372
+ ...opts.cache ? { cache: opts.cache } : {},
1373
+ ...opts.redirects ? { redirects: opts.redirects } : {},
1374
+ ...opts.guards ? { guards: opts.guards } : {}
1375
+ };
1376
+ return createHttpClient(cfg);
1377
+ }
1378
+
1379
+ exports.ILLEGAL_INVOCATION_HINT = ILLEGAL_INVOCATION_HINT;
1380
+ exports.NetworkError = NetworkError;
1381
+ exports.ParseError = ParseError;
1382
+ exports.RedirectError = RedirectError;
1383
+ exports.TimeoutError = TimeoutError;
1384
+ exports.alertNotifier = alertNotifier;
1385
+ exports.apiKeyAuth = apiKeyAuth;
1386
+ exports.basicAuth = basicAuth;
1387
+ exports.bearerAuth = bearerAuth;
1388
+ exports.consoleNotifier = consoleNotifier;
1389
+ exports.cookieAuth = cookieAuth;
1390
+ exports.cookieSafeRefreshGuard = cookieSafeRefreshGuard;
1391
+ exports.createApiKeyClient = createApiKeyClient;
1392
+ exports.createApiParserGraphQL = createApiParserGraphQL;
1393
+ exports.createApiParserLaravel = createApiParserLaravel;
1394
+ exports.createApiParserNest = createApiParserNest;
1395
+ exports.createApiParserRestClassic = createApiParserRestClassic;
1396
+ exports.createBasicClient = createBasicClient;
1397
+ exports.createBearerClient = createBearerClient;
1398
+ exports.createClient = createClient;
1399
+ exports.createCookieClient = createCookieClient;
1400
+ exports.createHttpClient = createHttpClient;
1401
+ exports.createPresetBuilder = createPresetBuilder;
1402
+ exports.createShapeParser = createShapeParser;
1403
+ exports.createSimpleClient = createSimpleClient;
1404
+ exports.fromZod = fromZod;
1405
+ exports.instrumentGuard = instrumentGuard;
1406
+ exports.memoryCache = memoryCache;
1407
+ exports.noAuth = noAuth;
1408
+ exports.retryGuard = retryGuard;
1409
+ exports.schema = schema;
1410
+ exports.tailwindAlertRenderer = tailwindAlertRenderer;
1411
+ exports.unauthorizedGuard = unauthorizedGuard;
1412
+ //# sourceMappingURL=index.cjs.map
1413
+ //# sourceMappingURL=index.cjs.map