@cedvict/http-guardian 0.0.1-next.10 → 0.0.1-next.12

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/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on *Keep a Changelog*, and this project adheres to *Semantic
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ### Binary responses
10
+
11
+ - Add explicit `blob` and `arrayBuffer` response types for lossless downloads.
12
+ - Keep structured JSON error envelopes readable on binary requests.
13
+ - Separate cache and in-flight deduplication keys by response type.
14
+
9
15
  ### Contract (security)
10
16
 
11
17
  - Refresh single-flight: concurrent 401s trigger only one refresh request.
package/README.md CHANGED
@@ -30,6 +30,30 @@ if (res.ok) console.log(res.data.name);
30
30
  else console.error(res.errors);
31
31
  ```
32
32
 
33
+ ## Binary responses
34
+
35
+ Use an explicit response type for downloads. Successful responses bypass the
36
+ JSON envelope parser so their bytes stay intact. Error responses still use the
37
+ normal content-type decoder, which preserves structured API errors.
38
+
39
+ ```ts
40
+ const report = await api.get<Blob>("/reports/monthly", {
41
+ responseType: "blob",
42
+ headers: { accept: "application/pdf" },
43
+ });
44
+
45
+ if (report.ok) {
46
+ const url = URL.createObjectURL(report.data);
47
+ // Open or download the URL, then call URL.revokeObjectURL(url).
48
+ } else {
49
+ console.error(report.errors);
50
+ }
51
+ ```
52
+
53
+ `responseType: "arrayBuffer"` is also available for byte-oriented processing.
54
+ Binary requests do not receive the default `Accept: application/json` header,
55
+ and cache/deduplication keys keep `blob` and `arrayBuffer` responses separate.
56
+
33
57
  ## Presets
34
58
  - `createApiParserRestClassic()`
35
59
  - `createApiParserLaravel()`
@@ -100,6 +124,18 @@ entre retries** (mémorisé sur le ctx). Si la réponse contient le même header
100
124
  il est copié dans `ctx.meta.correlationId` pour les plugins downstream
101
125
  (logger, sentry, otel).
102
126
 
127
+ > 💡 **Pattern "X-Request-Id"** : si ton backend (ops, ELK, Datadog…) utilise
128
+ > le header `X-Request-Id` plutôt que `X-Correlation-Id` pour stitcher les
129
+ > logs front/back, change juste `headerName` :
130
+ >
131
+ > ```ts
132
+ > correlationIdGuard({ headerName: "X-Request-Id" })
133
+ > ```
134
+ >
135
+ > Pas besoin d'OpenTelemetry côté front, ni d'un second guard : c'est le même
136
+ > mécanisme avec un nom différent. Combinable avec un OTel `traceparent` :
137
+ > les deux headers coexistent sans conflit.
138
+
103
139
  ### `idempotencyGuard({ headerName?, applyTo?, respectExisting? })`
104
140
 
105
141
  Injecte un header `X-Idempotency-Key` (UUID par défaut) sur `POST`/`PUT`/`PATCH`.
@@ -5,6 +5,7 @@ export declare function makeCacheKey(parts: {
5
5
  headers?: Headers | HeadersPolyfill;
6
6
  body?: unknown;
7
7
  idempotencyKey?: string;
8
+ responseType?: string;
8
9
  keyOverride?: string;
9
10
  }): string;
10
11
  //# sourceMappingURL=key.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/cache/key.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IAGZ,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAkBT"}
1
+ {"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/cache/key.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEtE,wBAAgB,YAAY,CAAC,KAAK,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IAGZ,OAAO,CAAC,EAAE,OAAO,GAAG,eAAe,CAAC;IACpC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,MAAM,CAmBT"}
@@ -1 +1 @@
1
- {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/client/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAAc,MAAM,YAAY,CAAC;AA2C9F,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB;UAoOjD,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;WACtC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;UACxC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;YACrC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;aACtC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;;+BAChB,MAAM;;;EAEzC"}
1
+ {"version":3,"file":"httpClient.d.ts","sourceRoot":"","sources":["../../src/client/httpClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AA2C5G,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB;UAuOjD,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;WACtC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;UACxC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;YACrC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;aACtC,CAAC,QAAQ,MAAM,OAAO,cAAc,CAAC,CAAC,CAAC;;+BAChB,MAAM;;;EAEzC"}
@@ -8,6 +8,7 @@ import type { AppError } from "./errors.js";
8
8
  import type { HttpPlugin } from "../plugins/types.js";
9
9
  export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
10
10
  export type RedirectMode = "follow" | "manual" | "error";
11
+ export type ResponseType = "auto" | "blob" | "arrayBuffer";
11
12
  export type RedirectPolicy = {
12
13
  mode: RedirectMode;
13
14
  maxHops?: number;
@@ -43,6 +44,7 @@ export type RequestOptions<T> = {
43
44
  timeoutMs?: number;
44
45
  cache?: CacheOptions;
45
46
  dataSchema?: Schema<T>;
47
+ responseType?: ResponseType;
46
48
  notifier?: Notifier;
47
49
  noAuth?: boolean;
48
50
  idempotencyKey?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -225,7 +225,8 @@ function makeCacheKey(parts) {
225
225
  u: parts.url,
226
226
  h: headerObj,
227
227
  b: parts.body ?? null,
228
- i: parts.idempotencyKey ?? null
228
+ i: parts.idempotencyKey ?? null,
229
+ r: parts.responseType ?? "auto"
229
230
  });
230
231
  }
231
232
 
@@ -375,6 +376,8 @@ function createHttpClient(options) {
375
376
  const pipeline = composeGuards(baseGuards, terminal);
376
377
  async function request(method, path, ro) {
377
378
  const requestUrl = withQuery(joinUrl(draft.baseUrl, path), ro?.query);
379
+ const responseType = ro?.responseType ?? "auto";
380
+ const expectsBinary = isBinaryResponseType(responseType);
378
381
  const headers = createHeaders();
379
382
  if (draft.defaults?.headers) for (const [k, v] of Object.entries(draft.defaults.headers)) headers.set(k, v);
380
383
  if (ro?.headers) {
@@ -384,15 +387,15 @@ function createHttpClient(options) {
384
387
  let bodyForKey = void 0;
385
388
  if (ro?.json !== void 0) {
386
389
  if (!headers.has("content-type")) headers.set("content-type", "application/json");
387
- if (!headers.has("accept")) headers.set("accept", "application/json");
390
+ if (!headers.has("accept") && !expectsBinary) headers.set("accept", "application/json");
388
391
  body = JSON.stringify(ro.json);
389
392
  bodyForKey = ro.json;
390
393
  } else if (ro?.body !== void 0) {
391
394
  body = ro.body;
392
395
  bodyForKey = "[body]";
393
- if (!headers.has("accept")) headers.set("accept", "application/json");
396
+ if (!headers.has("accept") && !expectsBinary) headers.set("accept", "application/json");
394
397
  } else {
395
- if (!headers.has("accept")) headers.set("accept", "application/json");
398
+ if (!headers.has("accept") && !expectsBinary) headers.set("accept", "application/json");
396
399
  }
397
400
  const timeoutMs = ro?.timeoutMs ?? draft.defaults?.timeoutMs;
398
401
  const notifier = ro?.notifier ?? defaultNotifier;
@@ -411,6 +414,7 @@ function createHttpClient(options) {
411
414
  headers,
412
415
  ...method === "GET" ? {} : { body: bodyForKey },
413
416
  ...ro?.idempotencyKey !== void 0 ? { idempotencyKey: ro.idempotencyKey } : {},
417
+ responseType,
414
418
  ...cacheOpts?.key !== void 0 ? { keyOverride: cacheOpts.key } : {}
415
419
  }) : void 0;
416
420
  if (inflightKey) {
@@ -451,7 +455,7 @@ function createHttpClient(options) {
451
455
  ...timeoutMs !== void 0 ? { timeoutMs } : {},
452
456
  ...ro?.noAuth ? { noAuth: true } : {}
453
457
  });
454
- const raw = await decodeResponse(res);
458
+ const raw = await decodeResponse(res, responseType);
455
459
  const envelope = { kind: "network", url: res.url || requestUrl, status: res.status, headers: res.headers, raw };
456
460
  if (wantsCache && cacheOpts && res.ok) {
457
461
  const key = inflightKey;
@@ -477,7 +481,7 @@ function createHttpClient(options) {
477
481
  ...ro?.noAuth ? { noAuth: true } : {}
478
482
  });
479
483
  if (!res2.ok) return;
480
- const raw2 = await decodeResponse(res2);
484
+ const raw2 = await decodeResponse(res2, responseType);
481
485
  cache.set(key, {
482
486
  expiresAt: Date.now() + cacheOpts.ttlMs,
483
487
  value: raw2,
@@ -605,8 +609,25 @@ function makeCtx(args, redirect) {
605
609
  _fetch: args.resolvedFetch
606
610
  };
607
611
  }
608
- async function decodeResponse(res) {
612
+ function isBinaryResponseType(responseType) {
613
+ return responseType === "blob" || responseType === "arrayBuffer";
614
+ }
615
+ async function decodeResponse(res, responseType = "auto") {
609
616
  if (res.status === 204) return null;
617
+ if (res.ok && responseType === "blob") {
618
+ try {
619
+ return await res.blob();
620
+ } catch (e) {
621
+ throw new ParseError("Failed to read Blob response", void 0, e);
622
+ }
623
+ }
624
+ if (res.ok && responseType === "arrayBuffer") {
625
+ try {
626
+ return await res.arrayBuffer();
627
+ } catch (e) {
628
+ throw new ParseError("Failed to read ArrayBuffer response", void 0, e);
629
+ }
630
+ }
610
631
  const ct = res.headers.get("content-type") ?? "";
611
632
  if (ct.includes("application/json")) {
612
633
  try {
@@ -631,9 +652,10 @@ function asClientResult(envelope, requestUrl, opts, ro, notifier, shouldNotify,
631
652
  const raw = envelope.raw;
632
653
  const resHeaders = envelope.headers ?? new Headers();
633
654
  const finalUrl = envelope.url ?? requestUrl;
634
- const okByParser = opts.parser.isSuccess(raw, status);
655
+ const binaryResponse = isBinaryResponseType(ro?.responseType);
656
+ const okByParser = binaryResponse ? status >= 200 && status < 400 : opts.parser.isSuccess(raw, status);
635
657
  if (okByParser && status >= 200 && status < 400) {
636
- const dataRaw = opts.parser.getData(raw);
658
+ const dataRaw = binaryResponse ? raw : opts.parser.getData(raw);
637
659
  try {
638
660
  const data = ro?.dataSchema ? ro.dataSchema.parse(dataRaw) : dataRaw;
639
661
  return { ok: true, status, headers: resHeaders, data, raw, url: finalUrl };