@rei-standard/amsg-client 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -178,6 +178,13 @@ interface DeliverOptions {
178
178
  // / X-Client-Token / Authorization
179
179
  authorization?: string; // 透传成 Authorization header(与 sendInstant 对齐)
180
180
  endpointPath?: string; // 默认 '/instant',可改 '/continue' 续跑
181
+ compressRequest?: boolean | { thresholdBytes?: number }; // 可选请求体 gzip。不传/falsy = 关(行为不变)
182
+ // true / {} = 开,阈值默认 16384 字节(16KB)
183
+ // { thresholdBytes: N } = 开 + 自定义阈值
184
+ // 仅当 body 超阈值且运行时有 CompressionStream 才压;
185
+ // 否则发明文(优雅降级,绝不抛)。压时发 gzip 字节 +
186
+ // 头 X-Amsg-Request-Encoding: gzip(非标准 Content-
187
+ // Encoding),由接收端 gunzip。SSE / JSON 两路通用。
181
188
  }
182
189
 
183
190
  interface ObservedDeliveryReceipt {
@@ -199,6 +206,8 @@ interface RawReadMeta {
199
206
 
200
207
  > `onRawRead` 是诊断钩子:SSE 解析层默认丢弃 `:` 注释行(含每秒一发的 keepalive),出问题时无从判断「静默期里到底有没有字节到达」。挂上它就能在 raw `reader.read()` 这一层看到每次读到的原始字节与 keepalive 帧。不传则零开销、行为不变。
201
208
 
209
+ > `compressRequest` 用于大 body 上传:开启后,要发的 JSON 在上网线前 gzip(中文 + 重复结构压缩比很高),网线上字节小了就能在慢/不稳链路的发送超时之前传完,且上下文一字不动。仅当 body 超阈值且运行时支持 `CompressionStream` 才压,否则照常发明文;压缩出错也兜回明文,永不影响发送。压缩的是请求体,与响应 / `onChunk` / `onRawRead` 无关。接收端需按 `X-Amsg-Request-Encoding: gzip` 头自行解压。
210
+
202
211
  ### `delivery.mode` 必须显式选
203
212
 
204
213
  | mode | 何时用 | outcome 取值 |
package/dist/index.cjs CHANGED
@@ -54,6 +54,25 @@ function classifyContentType(contentType) {
54
54
  if (/^application\/[\w.+-]+\+json$/.test(main)) return "json";
55
55
  return "unknown";
56
56
  }
57
+ var COMPRESS_REQUEST_DEFAULT_THRESHOLD = 16384;
58
+ var COMPRESS_REQUEST_HEADER = "X-Amsg-Request-Encoding";
59
+ async function maybeCompressRequestBody(body, compressRequest) {
60
+ if (!compressRequest) return { body, header: null };
61
+ const threshold = typeof compressRequest === "object" && typeof compressRequest.thresholdBytes === "number" ? compressRequest.thresholdBytes : COMPRESS_REQUEST_DEFAULT_THRESHOLD;
62
+ try {
63
+ if (typeof CompressionStream === "undefined") return { body, header: null };
64
+ const bytes = new TextEncoder().encode(body);
65
+ if (bytes.length <= threshold) return { body, header: null };
66
+ const gz = new Uint8Array(
67
+ await new Response(
68
+ new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"))
69
+ ).arrayBuffer()
70
+ );
71
+ return { body: gz, header: COMPRESS_REQUEST_HEADER };
72
+ } catch {
73
+ return { body, header: null };
74
+ }
75
+ }
57
76
  var ReiClient = class {
58
77
  /**
59
78
  * @param {ReiClientConfig} config
@@ -316,7 +335,8 @@ var ReiClient = class {
316
335
  headers,
317
336
  authorization,
318
337
  endpointPath,
319
- onRawRead
338
+ onRawRead,
339
+ compressRequest
320
340
  } = opts;
321
341
  if (!delivery || typeof delivery !== "object") {
322
342
  throw new TypeError("[rei-standard-amsg-client] deliver() requires opts.delivery (discriminated union)");
@@ -378,7 +398,8 @@ var ReiClient = class {
378
398
  const result = await this._runInstantTransport(built, {
379
399
  signal: internalAbort.signal,
380
400
  onChunk: wrappedOnChunk,
381
- onRawRead
401
+ onRawRead,
402
+ compressRequest
382
403
  });
383
404
  if (finalized) return;
384
405
  transportEnded = true;
@@ -657,14 +678,17 @@ var ReiClient = class {
657
678
  *
658
679
  * @private
659
680
  * @param {{ url: string, headers: Record<string, string>, body: string }} built
660
- * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void }} opts
681
+ * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void, compressRequest?: boolean | { thresholdBytes?: number } }} opts
661
682
  * `onRawRead` is forwarded to the SSE consumer for raw read-loop telemetry (see `DeliverOptions.onRawRead`).
683
+ * `compressRequest` opts the request body into gzip before `fetch` (see `DeliverOptions.compressRequest`).
662
684
  * @returns {Promise<{ kind: 'sse' } | { kind: 'json', body: unknown }>}
663
685
  */
664
686
  async _runInstantTransport(built, opts) {
665
- const { signal, onChunk, onRawRead } = opts;
687
+ const { signal, onChunk, onRawRead, compressRequest } = opts;
666
688
  const { url, headers, body } = built;
667
- const res = await fetch(url, { method: "POST", headers, body, signal });
689
+ const { body: wireBody, header: compressionHeader } = await maybeCompressRequestBody(body, compressRequest);
690
+ const wireHeaders = compressionHeader ? { ...headers, [compressionHeader]: "gzip" } : headers;
691
+ const res = await fetch(url, { method: "POST", headers: wireHeaders, body: wireBody, signal });
668
692
  if (!res.ok) {
669
693
  const text2 = await res.text().catch(() => "");
670
694
  const err = new Error(`Instant request failed: ${res.status} ${text2}`);
package/dist/index.d.cts CHANGED
@@ -201,6 +201,17 @@ const TEXT_ENCODER = new TextEncoder();
201
201
  * silently drops. Use it to tell "connection alive but no business data" apart from "no bytes flowing
202
202
  * at all" when diagnosing stalled streams. Purely observational: throws are swallowed and never affect
203
203
  * transport. Not invoked for the JSON transport.
204
+ * @property {boolean | { thresholdBytes?: number }} [compressRequest] - Opt-in gzip of the request
205
+ * BODY before it is sent (applies to both the SSE and JSON transports — it compresses the request,
206
+ * not the response). Omit / falsy = OFF and behavior is fully unchanged (backward compatible).
207
+ * `true` or `{}` enables it at the default 16384-byte (16 KB) threshold; `{ thresholdBytes: N }`
208
+ * sets a custom threshold. When enabled, the body is gzip-compressed only if its UTF-8 byte length
209
+ * exceeds the threshold AND the runtime provides `CompressionStream`; otherwise it is sent as
210
+ * plaintext (graceful degradation, never throws). On compression the request gains the custom
211
+ * header `X-Amsg-Request-Encoding: gzip` (NOT standard `Content-Encoding`, which CDNs / proxies
212
+ * would auto-decompress and double-decode) and the body is the raw gzip bytes — the receiving
213
+ * worker is responsible for gunzipping. Use it when delivering large bodies over slow / flaky
214
+ * uplinks where a big upload can outrun the connection's send timeout.
204
215
  */
205
216
 
206
217
  /**
@@ -266,6 +277,68 @@ function classifyContentType(contentType) {
266
277
  return 'unknown';
267
278
  }
268
279
 
280
+ /**
281
+ * Default size floor for request-body gzip: bodies at or below this are not
282
+ * worth compressing (the gzip header/overhead can outweigh the gain on tiny
283
+ * payloads). 16 KB matches the contract documented on `DeliverOptions.compressRequest`.
284
+ */
285
+ const COMPRESS_REQUEST_DEFAULT_THRESHOLD = 16384;
286
+
287
+ /**
288
+ * Custom request header used to mark a gzip-compressed body. Deliberately NOT
289
+ * the standard `Content-Encoding` — CDNs / reverse proxies (Cloudflare, etc.)
290
+ * auto-decompress `Content-Encoding: gzip` on the way in, which would double-
291
+ * decompress and corrupt the body. The receiving worker keys off this custom
292
+ * header to know it must gunzip the body itself.
293
+ */
294
+ const COMPRESS_REQUEST_HEADER = 'X-Amsg-Request-Encoding';
295
+
296
+ /**
297
+ * Optionally gzip a request body string before it hits `fetch`.
298
+ *
299
+ * Pure optimization with graceful degradation: returns the original plaintext
300
+ * body (and no extra header) whenever compression is disabled, the body is at
301
+ * or below the threshold, the runtime lacks `CompressionStream`, or anything
302
+ * throws. The wire bytes shrink (Chinese / repetitive JSON compresses ~5-8x)
303
+ * so large uploads finish before flaky links time out — without dropping any
304
+ * context. Decompression is the receiving worker's job (keyed off
305
+ * `X-Amsg-Request-Encoding: gzip`).
306
+ *
307
+ * @param {string} body - The already-serialized request body (plaintext JSON).
308
+ * @param {boolean | { thresholdBytes?: number } | undefined} compressRequest
309
+ * `undefined`/falsy ⇒ disabled (no-op, backward compatible). `true` / `{}` ⇒
310
+ * enabled at the 16 KB default. `{ thresholdBytes: N }` ⇒ enabled at N bytes.
311
+ * @returns {Promise<{ body: string | Uint8Array, header: string | null }>}
312
+ * `header` is the gzip marker header name to set when compression happened,
313
+ * or `null` to send plaintext with no extra header.
314
+ */
315
+ async function maybeCompressRequestBody(body, compressRequest) {
316
+ // Disabled / no opt-in ⇒ behavior unchanged.
317
+ if (!compressRequest) return { body, header: null };
318
+
319
+ const threshold =
320
+ typeof compressRequest === 'object' && typeof compressRequest.thresholdBytes === 'number'
321
+ ? compressRequest.thresholdBytes
322
+ : COMPRESS_REQUEST_DEFAULT_THRESHOLD;
323
+
324
+ try {
325
+ if (typeof CompressionStream === 'undefined') return { body, header: null };
326
+
327
+ const bytes = new TextEncoder().encode(body);
328
+ if (bytes.length <= threshold) return { body, header: null };
329
+
330
+ const gz = new Uint8Array(
331
+ await new Response(
332
+ new Blob([bytes]).stream().pipeThrough(new CompressionStream('gzip'))
333
+ ).arrayBuffer()
334
+ );
335
+ return { body: gz, header: COMPRESS_REQUEST_HEADER };
336
+ } catch {
337
+ // Compression is an optimization, never a failure mode: fall back to plaintext.
338
+ return { body, header: null };
339
+ }
340
+ }
341
+
269
342
  class ReiClient {
270
343
  /**
271
344
  * @param {ReiClientConfig} config
@@ -560,6 +633,7 @@ class ReiClient {
560
633
  const {
561
634
  delivery, timeoutMs, onChunk, postTransportGraceMs,
562
635
  signal, headers, authorization, endpointPath, onRawRead,
636
+ compressRequest,
563
637
  } = opts;
564
638
 
565
639
  if (!delivery || typeof delivery !== 'object') {
@@ -653,6 +727,7 @@ class ReiClient {
653
727
  signal: internalAbort.signal,
654
728
  onChunk: wrappedOnChunk,
655
729
  onRawRead,
730
+ compressRequest,
656
731
  });
657
732
  if (finalized) return;
658
733
  transportEnded = true;
@@ -1005,15 +1080,23 @@ class ReiClient {
1005
1080
  *
1006
1081
  * @private
1007
1082
  * @param {{ url: string, headers: Record<string, string>, body: string }} built
1008
- * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void }} opts
1083
+ * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void, compressRequest?: boolean | { thresholdBytes?: number } }} opts
1009
1084
  * `onRawRead` is forwarded to the SSE consumer for raw read-loop telemetry (see `DeliverOptions.onRawRead`).
1085
+ * `compressRequest` opts the request body into gzip before `fetch` (see `DeliverOptions.compressRequest`).
1010
1086
  * @returns {Promise<{ kind: 'sse' } | { kind: 'json', body: unknown }>}
1011
1087
  */
1012
1088
  async _runInstantTransport(built, opts) {
1013
- const { signal, onChunk, onRawRead } = opts;
1089
+ const { signal, onChunk, onRawRead, compressRequest } = opts;
1014
1090
  const { url, headers, body } = built;
1015
1091
 
1016
- const res = await fetch(url, { method: 'POST', headers, body, signal });
1092
+ // Optionally gzip the request body (opt-in, graceful fallback to plaintext).
1093
+ const { body: wireBody, header: compressionHeader } =
1094
+ await maybeCompressRequestBody(body, compressRequest);
1095
+ const wireHeaders = compressionHeader
1096
+ ? { ...headers, [compressionHeader]: 'gzip' }
1097
+ : headers;
1098
+
1099
+ const res = await fetch(url, { method: 'POST', headers: wireHeaders, body: wireBody, signal });
1017
1100
 
1018
1101
  if (!res.ok) {
1019
1102
  const text = await res.text().catch(() => '');
package/dist/index.d.ts CHANGED
@@ -201,6 +201,17 @@ const TEXT_ENCODER = new TextEncoder();
201
201
  * silently drops. Use it to tell "connection alive but no business data" apart from "no bytes flowing
202
202
  * at all" when diagnosing stalled streams. Purely observational: throws are swallowed and never affect
203
203
  * transport. Not invoked for the JSON transport.
204
+ * @property {boolean | { thresholdBytes?: number }} [compressRequest] - Opt-in gzip of the request
205
+ * BODY before it is sent (applies to both the SSE and JSON transports — it compresses the request,
206
+ * not the response). Omit / falsy = OFF and behavior is fully unchanged (backward compatible).
207
+ * `true` or `{}` enables it at the default 16384-byte (16 KB) threshold; `{ thresholdBytes: N }`
208
+ * sets a custom threshold. When enabled, the body is gzip-compressed only if its UTF-8 byte length
209
+ * exceeds the threshold AND the runtime provides `CompressionStream`; otherwise it is sent as
210
+ * plaintext (graceful degradation, never throws). On compression the request gains the custom
211
+ * header `X-Amsg-Request-Encoding: gzip` (NOT standard `Content-Encoding`, which CDNs / proxies
212
+ * would auto-decompress and double-decode) and the body is the raw gzip bytes — the receiving
213
+ * worker is responsible for gunzipping. Use it when delivering large bodies over slow / flaky
214
+ * uplinks where a big upload can outrun the connection's send timeout.
204
215
  */
205
216
 
206
217
  /**
@@ -266,6 +277,68 @@ function classifyContentType(contentType) {
266
277
  return 'unknown';
267
278
  }
268
279
 
280
+ /**
281
+ * Default size floor for request-body gzip: bodies at or below this are not
282
+ * worth compressing (the gzip header/overhead can outweigh the gain on tiny
283
+ * payloads). 16 KB matches the contract documented on `DeliverOptions.compressRequest`.
284
+ */
285
+ const COMPRESS_REQUEST_DEFAULT_THRESHOLD = 16384;
286
+
287
+ /**
288
+ * Custom request header used to mark a gzip-compressed body. Deliberately NOT
289
+ * the standard `Content-Encoding` — CDNs / reverse proxies (Cloudflare, etc.)
290
+ * auto-decompress `Content-Encoding: gzip` on the way in, which would double-
291
+ * decompress and corrupt the body. The receiving worker keys off this custom
292
+ * header to know it must gunzip the body itself.
293
+ */
294
+ const COMPRESS_REQUEST_HEADER = 'X-Amsg-Request-Encoding';
295
+
296
+ /**
297
+ * Optionally gzip a request body string before it hits `fetch`.
298
+ *
299
+ * Pure optimization with graceful degradation: returns the original plaintext
300
+ * body (and no extra header) whenever compression is disabled, the body is at
301
+ * or below the threshold, the runtime lacks `CompressionStream`, or anything
302
+ * throws. The wire bytes shrink (Chinese / repetitive JSON compresses ~5-8x)
303
+ * so large uploads finish before flaky links time out — without dropping any
304
+ * context. Decompression is the receiving worker's job (keyed off
305
+ * `X-Amsg-Request-Encoding: gzip`).
306
+ *
307
+ * @param {string} body - The already-serialized request body (plaintext JSON).
308
+ * @param {boolean | { thresholdBytes?: number } | undefined} compressRequest
309
+ * `undefined`/falsy ⇒ disabled (no-op, backward compatible). `true` / `{}` ⇒
310
+ * enabled at the 16 KB default. `{ thresholdBytes: N }` ⇒ enabled at N bytes.
311
+ * @returns {Promise<{ body: string | Uint8Array, header: string | null }>}
312
+ * `header` is the gzip marker header name to set when compression happened,
313
+ * or `null` to send plaintext with no extra header.
314
+ */
315
+ async function maybeCompressRequestBody(body, compressRequest) {
316
+ // Disabled / no opt-in ⇒ behavior unchanged.
317
+ if (!compressRequest) return { body, header: null };
318
+
319
+ const threshold =
320
+ typeof compressRequest === 'object' && typeof compressRequest.thresholdBytes === 'number'
321
+ ? compressRequest.thresholdBytes
322
+ : COMPRESS_REQUEST_DEFAULT_THRESHOLD;
323
+
324
+ try {
325
+ if (typeof CompressionStream === 'undefined') return { body, header: null };
326
+
327
+ const bytes = new TextEncoder().encode(body);
328
+ if (bytes.length <= threshold) return { body, header: null };
329
+
330
+ const gz = new Uint8Array(
331
+ await new Response(
332
+ new Blob([bytes]).stream().pipeThrough(new CompressionStream('gzip'))
333
+ ).arrayBuffer()
334
+ );
335
+ return { body: gz, header: COMPRESS_REQUEST_HEADER };
336
+ } catch {
337
+ // Compression is an optimization, never a failure mode: fall back to plaintext.
338
+ return { body, header: null };
339
+ }
340
+ }
341
+
269
342
  class ReiClient {
270
343
  /**
271
344
  * @param {ReiClientConfig} config
@@ -560,6 +633,7 @@ class ReiClient {
560
633
  const {
561
634
  delivery, timeoutMs, onChunk, postTransportGraceMs,
562
635
  signal, headers, authorization, endpointPath, onRawRead,
636
+ compressRequest,
563
637
  } = opts;
564
638
 
565
639
  if (!delivery || typeof delivery !== 'object') {
@@ -653,6 +727,7 @@ class ReiClient {
653
727
  signal: internalAbort.signal,
654
728
  onChunk: wrappedOnChunk,
655
729
  onRawRead,
730
+ compressRequest,
656
731
  });
657
732
  if (finalized) return;
658
733
  transportEnded = true;
@@ -1005,15 +1080,23 @@ class ReiClient {
1005
1080
  *
1006
1081
  * @private
1007
1082
  * @param {{ url: string, headers: Record<string, string>, body: string }} built
1008
- * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void }} opts
1083
+ * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void, compressRequest?: boolean | { thresholdBytes?: number } }} opts
1009
1084
  * `onRawRead` is forwarded to the SSE consumer for raw read-loop telemetry (see `DeliverOptions.onRawRead`).
1085
+ * `compressRequest` opts the request body into gzip before `fetch` (see `DeliverOptions.compressRequest`).
1010
1086
  * @returns {Promise<{ kind: 'sse' } | { kind: 'json', body: unknown }>}
1011
1087
  */
1012
1088
  async _runInstantTransport(built, opts) {
1013
- const { signal, onChunk, onRawRead } = opts;
1089
+ const { signal, onChunk, onRawRead, compressRequest } = opts;
1014
1090
  const { url, headers, body } = built;
1015
1091
 
1016
- const res = await fetch(url, { method: 'POST', headers, body, signal });
1092
+ // Optionally gzip the request body (opt-in, graceful fallback to plaintext).
1093
+ const { body: wireBody, header: compressionHeader } =
1094
+ await maybeCompressRequestBody(body, compressRequest);
1095
+ const wireHeaders = compressionHeader
1096
+ ? { ...headers, [compressionHeader]: 'gzip' }
1097
+ : headers;
1098
+
1099
+ const res = await fetch(url, { method: 'POST', headers: wireHeaders, body: wireBody, signal });
1017
1100
 
1018
1101
  if (!res.ok) {
1019
1102
  const text = await res.text().catch(() => '');
package/dist/index.mjs CHANGED
@@ -32,6 +32,25 @@ function classifyContentType(contentType) {
32
32
  if (/^application\/[\w.+-]+\+json$/.test(main)) return "json";
33
33
  return "unknown";
34
34
  }
35
+ var COMPRESS_REQUEST_DEFAULT_THRESHOLD = 16384;
36
+ var COMPRESS_REQUEST_HEADER = "X-Amsg-Request-Encoding";
37
+ async function maybeCompressRequestBody(body, compressRequest) {
38
+ if (!compressRequest) return { body, header: null };
39
+ const threshold = typeof compressRequest === "object" && typeof compressRequest.thresholdBytes === "number" ? compressRequest.thresholdBytes : COMPRESS_REQUEST_DEFAULT_THRESHOLD;
40
+ try {
41
+ if (typeof CompressionStream === "undefined") return { body, header: null };
42
+ const bytes = new TextEncoder().encode(body);
43
+ if (bytes.length <= threshold) return { body, header: null };
44
+ const gz = new Uint8Array(
45
+ await new Response(
46
+ new Blob([bytes]).stream().pipeThrough(new CompressionStream("gzip"))
47
+ ).arrayBuffer()
48
+ );
49
+ return { body: gz, header: COMPRESS_REQUEST_HEADER };
50
+ } catch {
51
+ return { body, header: null };
52
+ }
53
+ }
35
54
  var ReiClient = class {
36
55
  /**
37
56
  * @param {ReiClientConfig} config
@@ -294,7 +313,8 @@ var ReiClient = class {
294
313
  headers,
295
314
  authorization,
296
315
  endpointPath,
297
- onRawRead
316
+ onRawRead,
317
+ compressRequest
298
318
  } = opts;
299
319
  if (!delivery || typeof delivery !== "object") {
300
320
  throw new TypeError("[rei-standard-amsg-client] deliver() requires opts.delivery (discriminated union)");
@@ -356,7 +376,8 @@ var ReiClient = class {
356
376
  const result = await this._runInstantTransport(built, {
357
377
  signal: internalAbort.signal,
358
378
  onChunk: wrappedOnChunk,
359
- onRawRead
379
+ onRawRead,
380
+ compressRequest
360
381
  });
361
382
  if (finalized) return;
362
383
  transportEnded = true;
@@ -635,14 +656,17 @@ var ReiClient = class {
635
656
  *
636
657
  * @private
637
658
  * @param {{ url: string, headers: Record<string, string>, body: string }} built
638
- * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void }} opts
659
+ * @param {{ signal: AbortSignal, onChunk?: (p: unknown) => Promise<void> | void, onRawRead?: (meta: RawReadMeta) => void, compressRequest?: boolean | { thresholdBytes?: number } }} opts
639
660
  * `onRawRead` is forwarded to the SSE consumer for raw read-loop telemetry (see `DeliverOptions.onRawRead`).
661
+ * `compressRequest` opts the request body into gzip before `fetch` (see `DeliverOptions.compressRequest`).
640
662
  * @returns {Promise<{ kind: 'sse' } | { kind: 'json', body: unknown }>}
641
663
  */
642
664
  async _runInstantTransport(built, opts) {
643
- const { signal, onChunk, onRawRead } = opts;
665
+ const { signal, onChunk, onRawRead, compressRequest } = opts;
644
666
  const { url, headers, body } = built;
645
- const res = await fetch(url, { method: "POST", headers, body, signal });
667
+ const { body: wireBody, header: compressionHeader } = await maybeCompressRequestBody(body, compressRequest);
668
+ const wireHeaders = compressionHeader ? { ...headers, [compressionHeader]: "gzip" } : headers;
669
+ const res = await fetch(url, { method: "POST", headers: wireHeaders, body: wireBody, signal });
646
670
  if (!res.ok) {
647
671
  const text2 = await res.text().catch(() => "");
648
672
  const err = new Error(`Instant request failed: ${res.status} ${text2}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-client",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
4
4
  "description": "ReiStandard Active Messaging browser client SDK — also re-exports shared push types, builders, and guards from @rei-standard/amsg-shared",
5
5
  "repository": {
6
6
  "type": "git",