@poe-platform/safe-fs 0.1.798 → 0.1.800

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.
@@ -3,7 +3,10 @@ import { dirname, isPathWithin, validatePath } from "../../contracts/virtual-pat
3
3
  import { memoryAtomicView } from "../memory/atomic-view.js";
4
4
  import { compareIdentity } from "../mount/identity.js";
5
5
  import { snapshotConditionalChmod } from "../conditional-chmod.js";
6
+ import { createStagingCleanup } from "../staging-cleanup.js";
6
7
  import { runStagingGuard } from "../staging-ancestry.js";
8
+ // Enumerable ownership tokens survive caller snapshots without retaining stats forever.
9
+ const overlayReceipt = Symbol("overlay observation");
7
10
  /** Stock Memory mutations commit before returning their promise. No user code or
8
11
  * await is allowed between inspecting the two stores and invoking a mutation. */
9
12
  export class OverlayMemoryPublication {
@@ -16,6 +19,7 @@ export class OverlayMemoryPublication {
16
19
  directories = new Map();
17
20
  origins = new Map();
18
21
  stages = new WeakMap();
22
+ stagingAncestors = new WeakMap();
19
23
  constructor(upper, lower, whiteouts, opaque, hidden) {
20
24
  this.upper = upper;
21
25
  this.lower = lower;
@@ -75,7 +79,7 @@ export class OverlayMemoryPublication {
75
79
  const observation = this.observation(path);
76
80
  if (!this.same(physical, this.visible(observation), physical.type === "file"))
77
81
  throw new FsError("EAGAIN", { path });
78
- let logical = physical;
82
+ let logical = { ...physical };
79
83
  if (physical.type === "directory") {
80
84
  let directory = this.directories.get(path);
81
85
  if (!directory || !this.same(directory.upper, observation.upper) || !this.same(directory.lower, observation.lower)) {
@@ -84,7 +88,9 @@ export class OverlayMemoryPublication {
84
88
  }
85
89
  logical = { ...physical, identityScope: directory.identityScope, dev: directory.dev, ino: directory.ino };
86
90
  }
87
- this.receipts.set(logical, observation);
91
+ const token = {};
92
+ logical[overlayReceipt] = token;
93
+ this.receipts.set(token, observation);
88
94
  return logical;
89
95
  }
90
96
  same(actual, expected, unchanged = false) {
@@ -94,7 +100,7 @@ export class OverlayMemoryPublication {
94
100
  && (!unchanged || ["revision", "size", "mode", "nlink", "mtimeMs", "ctimeMs"].every(key => actual[key] === expected[key]));
95
101
  }
96
102
  expect(path, expected, unchanged = false) {
97
- const receipt = this.receipts.get(expected);
103
+ const receipt = this.receipts.get(expected[overlayReceipt] ?? expected);
98
104
  if (!receipt || receipt.path !== path)
99
105
  throw new FsError("ENOTSUP", { path, message: "unowned overlay publication receipt" });
100
106
  const actual = this.observation(path);
@@ -153,7 +159,7 @@ export class OverlayMemoryPublication {
153
159
  this.stores();
154
160
  this.path(path);
155
161
  const retain = (source, snapshot) => {
156
- const receipt = this.receipts.get(source);
162
+ const receipt = this.receipts.get(source[overlayReceipt] ?? source);
157
163
  if (!receipt)
158
164
  throw new FsError("ENOTSUP", { path, message: "unowned overlay chmod receipt" });
159
165
  this.receipts.set(snapshot, receipt);
@@ -175,6 +181,164 @@ export class OverlayMemoryPublication {
175
181
  return true;
176
182
  };
177
183
  }
184
+ prepareWrite(path, options) {
185
+ this.stores();
186
+ this.path(path);
187
+ const ancestors = this.capture(path);
188
+ return () => {
189
+ options.signal?.throwIfAborted();
190
+ this.stores();
191
+ this.check(ancestors);
192
+ this.expect(dirname(path), options.parent);
193
+ const current = this.inspect(path);
194
+ if (options.expected === null) {
195
+ if (current)
196
+ throw new FsError("EAGAIN", { path });
197
+ }
198
+ else {
199
+ this.expect(path, options.expected, true);
200
+ if (current?.type !== "file")
201
+ throw new FsError("EINVAL", { path });
202
+ }
203
+ return true;
204
+ };
205
+ }
206
+ async writeFile(path, data, options, maxBytes, guard) {
207
+ const check = this.prepareWrite(path, options);
208
+ check();
209
+ const current = this.inspect(path);
210
+ if (current && ((current.mode >> 6) & 2) !== 2)
211
+ throw new FsError("EACCES", { path });
212
+ if (current && !this.observation(path).upper && current.nlink !== 1)
213
+ throw new FsError("ENOTSUP", { path, message: "ambiguous hardlink copy-up" });
214
+ const controls = options.signal ? { signal: options.signal } : {};
215
+ let bytes = data;
216
+ if (options.append && current && !this.observation(path).upper) {
217
+ const previous = await this.lower.readFile(path, { ...controls, ...(maxBytes === Infinity ? {} : { maxBytes }) });
218
+ if (previous.byteLength + data.byteLength > maxBytes)
219
+ throw new FsError("EFBIG", { path });
220
+ bytes = new Uint8Array(previous.byteLength + data.byteLength);
221
+ bytes.set(previous);
222
+ bytes.set(data, previous.byteLength);
223
+ }
224
+ const ancestors = this.capture(path);
225
+ await this.copyParents(ancestors, controls);
226
+ guard?.();
227
+ check();
228
+ const upper = this.observation(path).upper;
229
+ if (data.byteLength + (options.append ? current?.size ?? 0 : 0) > maxBytes)
230
+ throw new FsError("EFBIG", { path });
231
+ const pending = this.upper.writeFileConditional(path, bytes, {
232
+ ...options, parent: this.observation(dirname(path)).upper, expected: upper ?? null,
233
+ append: options.append === true && upper !== undefined,
234
+ ...(current ? { mode: current.mode & 0o7777 } : {}),
235
+ ...(options.atimeMs === undefined && current ? { atimeMs: current.atimeMs } : {}),
236
+ });
237
+ // Retain the result before another caller can alter the upper store.
238
+ const result = this.inspect(path);
239
+ const receipt = result ? this.stat(path, result) : undefined;
240
+ await pending;
241
+ return receipt;
242
+ }
243
+ async removeFile(path, options) {
244
+ this.prepareWrite(path, options)();
245
+ const parent = this.inspect(dirname(path));
246
+ if (((parent.mode >> 6) & 3) !== 3)
247
+ throw new FsError("EACCES", { path });
248
+ const upper = this.observation(path).upper;
249
+ const removal = upper ? this.upper.removeFileConditional(path, {
250
+ ...options, parent: this.observation(dirname(path)).upper, expected: upper,
251
+ }) : undefined;
252
+ if (!upper || !this.physical(this.stores().upper, path))
253
+ this.whiteouts.add(path);
254
+ await removal;
255
+ }
256
+ async prepareDirectory(path, options, guard) {
257
+ const ancestors = this.capture(path);
258
+ const check = () => {
259
+ options.signal?.throwIfAborted();
260
+ guard?.();
261
+ this.check(ancestors);
262
+ this.expect(dirname(path), options.parent);
263
+ const current = this.inspect(path);
264
+ if (options.expected === null) {
265
+ if (current)
266
+ throw new FsError("EAGAIN", { path });
267
+ }
268
+ else {
269
+ this.expect(path, options.expected);
270
+ if (current?.type !== "directory")
271
+ throw new FsError("ENOTDIR", { path });
272
+ }
273
+ };
274
+ check();
275
+ const current = this.inspect(path);
276
+ await this.copyParents(current ? [...ancestors, { path, stat: options.expected }] : ancestors, options);
277
+ check();
278
+ const pending = this.upper.prepareDirectory(path, {
279
+ ...options, parent: this.observation(dirname(path)).upper, expected: this.observation(path).upper ?? null,
280
+ });
281
+ const physical = this.inspect(path);
282
+ if (physical && options.expected === null)
283
+ this.opaque.add(path);
284
+ const receipt = physical ? this.stat(path, physical) : undefined;
285
+ await pending;
286
+ return receipt;
287
+ }
288
+ prepareRename(source, destination) {
289
+ const ancestors = [...this.capture(source), ...this.capture(destination)];
290
+ const target = this.observation(destination);
291
+ const entries = [];
292
+ const pending = [source];
293
+ const stores = this.stores();
294
+ while (pending.length) {
295
+ const path = pending.pop();
296
+ const entry = this.observation(path);
297
+ const visible = this.visible(entry);
298
+ if (!visible)
299
+ continue;
300
+ entries.push(entry);
301
+ if (visible.type !== "directory")
302
+ continue;
303
+ const names = new Set([
304
+ ...(entry.upper?.type === "directory" ? stores.upper.names(path) : []),
305
+ ...(entry.lower?.type === "directory" && !this.opaque.has(path) ? stores.lower.names(path) : []),
306
+ ]);
307
+ for (const name of names)
308
+ pending.push(`${path}/${name}`);
309
+ }
310
+ return () => {
311
+ this.stores();
312
+ this.check(ancestors);
313
+ const current = this.observation(destination);
314
+ if (!this.same(current.upper, target.upper, true) || !this.same(current.lower, target.lower, true)) {
315
+ throw new FsError("EAGAIN", { path: destination });
316
+ }
317
+ for (const entry of entries) {
318
+ const actual = this.observation(entry.path);
319
+ // Copy-up may add upper entries; it never changes the lower store.
320
+ if (!this.same(actual.lower, entry.lower, true)
321
+ || (entry.upper && !this.same(actual.upper, entry.upper, entry.upper.type !== "directory"))) {
322
+ throw new FsError("EAGAIN", { path: entry.path });
323
+ }
324
+ }
325
+ };
326
+ }
327
+ async rename(source, destination, options, guard) {
328
+ options.signal?.throwIfAborted();
329
+ this.stores();
330
+ const current = this.inspect(destination);
331
+ if (options.noReplace && current)
332
+ throw new FsError("EEXIST", { path: destination });
333
+ guard();
334
+ if (!this.observation(source).upper)
335
+ throw new FsError("ENOENT", { path: source });
336
+ const moved = this.upper.rename(source, destination, options);
337
+ // Stock Memory commits synchronously; publish the whiteout in that same turn.
338
+ if (!this.physical(this.stores().upper, source))
339
+ this.whiteouts.add(source);
340
+ await moved;
341
+ }
178
342
  async copyParents(entries, options) {
179
343
  for (const entry of entries) {
180
344
  options.signal?.throwIfAborted();
@@ -189,7 +353,7 @@ export class OverlayMemoryPublication {
189
353
  await pending;
190
354
  }
191
355
  }
192
- async create(directory, name, content, options) {
356
+ async create(directory, name, content, options, guard) {
193
357
  options.signal?.throwIfAborted();
194
358
  this.path(directory);
195
359
  this.expect(dirname(directory), options.parent);
@@ -198,24 +362,30 @@ export class OverlayMemoryPublication {
198
362
  const ancestors = this.capture(directory);
199
363
  await this.copyParents(ancestors, options);
200
364
  options.signal?.throwIfAborted();
365
+ guard?.();
201
366
  this.check(ancestors);
202
367
  this.expect(dirname(directory), options.parent);
203
368
  if (this.inspect(directory))
204
369
  throw new FsError("EEXIST", { path: directory });
205
370
  const parent = this.observation(dirname(directory)).upper;
206
- const staging = await this.upper.createStagedFile(directory, name, content, { ...options, parent });
207
- this.stages.set(staging, staging);
371
+ const owned = await this.upper.createStagedFile(directory, name, content, { ...options, parent });
372
+ const staging = Object.freeze({
373
+ parent: owned.parent, directory: owned.directory, file: owned.file,
374
+ ...(owned.cleanup ? { cleanup: createStagingCleanup(directory, controls => this.cleanup(staging, controls, true), () => owned.cleanup.close()) } : {}),
375
+ });
376
+ this.stages.set(staging.directory, owned);
377
+ this.stagingAncestors.set(staging.directory, ancestors);
208
378
  this.hidden.add(directory);
209
379
  return staging;
210
380
  }
211
381
  async publish(staging, path, options) {
212
382
  options.signal?.throwIfAborted();
213
383
  this.path(path);
214
- const owned = this.stages.get(staging);
384
+ const owned = this.stages.get(staging.directory);
215
385
  if (!owned)
216
386
  throw new FsError("ENOTSUP", { path });
217
387
  const paths = this.ancestors(path);
218
- const ancestors = options.ancestors;
388
+ const ancestors = options.ancestors ?? this.stagingAncestors.get(staging.directory);
219
389
  if (!ancestors || ancestors.length !== paths.length || ancestors.some((entry, index) => entry.path !== paths[index])) {
220
390
  throw new FsError("EINVAL", { path, message: "complete overlay ancestry is required" });
221
391
  }
@@ -238,16 +408,22 @@ export class OverlayMemoryPublication {
238
408
  ...options, ancestors: translated, parent: translated.at(-1).stat, destination: target ?? null,
239
409
  });
240
410
  }
241
- async cleanup(staging, options) {
411
+ async cleanup(staging, options, retained = false) {
242
412
  this.stores();
243
- const owned = this.stages.get(staging);
413
+ const owned = this.stages.get(staging.directory);
244
414
  if (!owned)
245
415
  throw new FsError("ENOTSUP", { path: staging.directory.path });
246
- await this.upper.removeStagedFile(owned, options);
416
+ if (retained && owned.cleanup)
417
+ await owned.cleanup.remove(options);
418
+ else {
419
+ await this.upper.removeStagedFile(owned, options);
420
+ await owned.cleanup?.close();
421
+ }
247
422
  this.hidden.delete(owned.directory.path);
248
- this.stages.delete(staging);
423
+ this.stages.delete(staging.directory);
424
+ this.stagingAncestors.delete(staging.directory);
249
425
  }
250
- confine(roots, options, run, fs) {
426
+ confine(roots, options, run, fs, maxBytes) {
251
427
  options.signal?.throwIfAborted();
252
428
  const retained = new Map();
253
429
  for (const root of roots) {
@@ -259,10 +435,60 @@ export class OverlayMemoryPublication {
259
435
  retained.set(entry.path, entry.stat);
260
436
  }
261
437
  }
438
+ const admittedStages = new WeakSet();
439
+ const guard = (path) => {
440
+ this.path(path);
441
+ if (!roots.some(root => isPathWithin(root, path)))
442
+ throw new FsError("EPERM", { path });
443
+ this.check([...retained].filter(([root]) => isPathWithin(root, path)).map(([path, stat]) => ({ path, stat })));
444
+ };
262
445
  return new Proxy(fs, {
263
446
  get: (target, property) => {
264
447
  if (property === "confineExtraction" || property === "objects")
265
448
  return undefined;
449
+ if (property === "prepareDirectory")
450
+ return (path, mutation) => run(mutation, () => this.prepareDirectory(path, { ...mutation }, () => guard(path)));
451
+ if (property === "createStagedFile")
452
+ return (path, name, content, mutation) => run(mutation, async () => {
453
+ guard(path);
454
+ if (content.type === "file" && content.data.byteLength > maxBytes)
455
+ throw new FsError("EFBIG", { path });
456
+ const stage = await this.create(path, name, content, mutation, () => guard(path));
457
+ admittedStages.add(stage.directory);
458
+ return stage;
459
+ });
460
+ if (property === "publishStagedFile")
461
+ return (stage, path, mutation) => run(mutation, async () => {
462
+ guard(path);
463
+ if (!admittedStages.has(stage.directory))
464
+ throw new FsError("ENOTSUP", { path });
465
+ await this.publish(stage, path, mutation);
466
+ });
467
+ if (property === "removeStagedFile")
468
+ return (stage, mutation = {}) => run(mutation, async () => {
469
+ if (!admittedStages.has(stage.directory))
470
+ throw new FsError("ENOTSUP", { path: stage.directory.path });
471
+ await this.cleanup(stage, mutation);
472
+ });
473
+ if (property === "writeFileConditional")
474
+ return async (path, data, mutation) => {
475
+ mutation.signal?.throwIfAborted();
476
+ if (!(data instanceof Uint8Array))
477
+ throw new TypeError("Overlay files require Uint8Array data");
478
+ if (data.byteLength > maxBytes)
479
+ throw new FsError("EFBIG", { path });
480
+ const bytes = new Uint8Array(data), captured = { ...mutation };
481
+ return run(captured, async () => {
482
+ const guard = () => {
483
+ this.path(path);
484
+ if (!roots.some(root => isPathWithin(root, path)))
485
+ throw new FsError("EPERM", { path });
486
+ this.check([...retained].filter(([root]) => isPathWithin(root, path)).map(([path, stat]) => ({ path, stat })));
487
+ };
488
+ guard();
489
+ return this.writeFile(path, bytes, captured, maxBytes, guard);
490
+ });
491
+ };
266
492
  if (["mkdir", "rm", "rmdir"].includes(String(property)))
267
493
  return (path, mutation = {}) => run(mutation, async () => {
268
494
  mutation.signal?.throwIfAborted();
@@ -0,0 +1,4 @@
1
+ import type { RequestOptions } from "node:http";
2
+ import type { RequestScope, WireResponse } from "./request.js";
3
+ import type { S3HttpRequestFactory } from "./types.js";
4
+ export declare function sendRequest(options: RequestOptions, body: Uint8Array, scope: RequestScope, factory?: S3HttpRequestFactory): Promise<WireResponse>;
@@ -0,0 +1,74 @@
1
+ import { S3ServiceError } from "../transport.js";
2
+ import { abortable } from "./request.js";
3
+ export async function sendRequest(options, body, scope, factory) {
4
+ const { signal } = scope;
5
+ signal.throwIfAborted();
6
+ if (factory)
7
+ throw new S3ServiceError("InvalidArgument", 400, "Node request factories are unavailable in fetch runtimes");
8
+ const hostname = String(options.hostname);
9
+ const authority = `${hostname.includes(":") ? `[${hostname}]` : hostname}${options.port ? `:${options.port}` : ""}`;
10
+ const target = `${options.protocol}//${authority}${options.path}`;
11
+ const url = new URL(target);
12
+ // Fetch normalizes dot segments. Never sign one key and send another.
13
+ if (`${url.pathname}${url.search}` !== options.path)
14
+ throw new S3ServiceError("InvalidArgument", 400, "object key cannot be represented by fetch without normalization");
15
+ const response = await abortable(fetch(target, {
16
+ method: options.method, headers: options.headers,
17
+ ...(options.method === "PUT" ? { body: new Uint8Array(body) } : {}),
18
+ signal, redirect: "manual",
19
+ }), signal);
20
+ const reader = response.body?.getReader();
21
+ let closed = false;
22
+ const close = () => {
23
+ if (closed)
24
+ return;
25
+ closed = true;
26
+ signal.removeEventListener("abort", close);
27
+ scope.finish();
28
+ void reader?.cancel().catch(() => { });
29
+ };
30
+ signal.addEventListener("abort", close, { once: true });
31
+ if (signal.aborted) {
32
+ close();
33
+ signal.throwIfAborted();
34
+ }
35
+ const headersDistinct = Object.create(null);
36
+ response.headers.forEach((value, name) => { headersDistinct[name] = [value]; });
37
+ let claimed = false;
38
+ return {
39
+ message: { statusCode: response.status, headersDistinct }, close,
40
+ body: {
41
+ [Symbol.asyncIterator]() {
42
+ if (claimed)
43
+ throw new S3ServiceError("InvalidResponse", 502, "response body is single-use");
44
+ claimed = true;
45
+ return {
46
+ async next() {
47
+ try {
48
+ signal.throwIfAborted();
49
+ if (closed || !reader) {
50
+ close();
51
+ return { done: true, value: undefined };
52
+ }
53
+ const result = await abortable(reader.read(), signal);
54
+ signal.throwIfAborted();
55
+ if (result.done) {
56
+ close();
57
+ return { done: true, value: undefined };
58
+ }
59
+ return { done: false, value: result.value };
60
+ }
61
+ catch (error) {
62
+ close();
63
+ throw error;
64
+ }
65
+ },
66
+ async return() {
67
+ close();
68
+ return { done: true, value: undefined };
69
+ },
70
+ };
71
+ },
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,4 @@
1
+ import type { RequestOptions } from "node:http";
2
+ import type { S3HttpRequestFactory } from "./types.js";
3
+ import type { RequestScope, WireResponse } from "./request.js";
4
+ export declare function sendRequest(options: RequestOptions, body: Uint8Array, scope: RequestScope, factory?: S3HttpRequestFactory): Promise<WireResponse>;
@@ -0,0 +1,111 @@
1
+ import { once } from "node:events";
2
+ import { request as httpRequest } from "node:http";
3
+ import { request as httpsRequest } from "node:https";
4
+ import { S3ServiceError } from "../transport.js";
5
+ import { abortable } from "./request.js";
6
+ export async function sendRequest(options, body, scope, factory) {
7
+ const signal = scope.signal;
8
+ signal.throwIfAborted();
9
+ return new Promise((resolve, reject) => {
10
+ let request;
11
+ let response;
12
+ let headersReceived = false;
13
+ let finished = false;
14
+ const cleanup = () => {
15
+ signal.removeEventListener("abort", abort);
16
+ scope.finish();
17
+ };
18
+ const close = () => {
19
+ if (finished)
20
+ return;
21
+ finished = true;
22
+ cleanup();
23
+ response?.destroy();
24
+ request?.destroy();
25
+ };
26
+ const fail = (error) => {
27
+ reject(signal.aborted ? signal.reason : error);
28
+ close();
29
+ };
30
+ const abort = () => fail(signal.reason);
31
+ signal.addEventListener("abort", abort, { once: true });
32
+ try {
33
+ const create = factory ?? (options.protocol === "https:" ? httpsRequest : httpRequest);
34
+ request = create(options, message => {
35
+ response = message;
36
+ message.on("error", () => { });
37
+ if (finished || signal.aborted) {
38
+ close();
39
+ message.destroy();
40
+ return;
41
+ }
42
+ headersReceived = true;
43
+ message.once("close", cleanup);
44
+ let claimed = false;
45
+ const source = {
46
+ [Symbol.asyncIterator]() {
47
+ if (claimed)
48
+ throw new S3ServiceError("InvalidResponse", 502, "response body is single-use");
49
+ claimed = true;
50
+ const iterator = message[Symbol.asyncIterator]();
51
+ let ended = false;
52
+ return {
53
+ async next() {
54
+ try {
55
+ signal.throwIfAborted();
56
+ if (ended)
57
+ return { done: true, value: undefined };
58
+ const result = await abortable(iterator.next(), signal);
59
+ signal.throwIfAborted();
60
+ if (result.done) {
61
+ ended = true;
62
+ if (!message.complete)
63
+ throw new S3ServiceError("InvalidResponse", 502, "incomplete HTTP response");
64
+ close();
65
+ return { done: true, value: undefined };
66
+ }
67
+ if (!(result.value instanceof Uint8Array))
68
+ throw new S3ServiceError("InvalidResponse", 502, "nonbinary HTTP body");
69
+ return { done: false, value: new Uint8Array(result.value) };
70
+ }
71
+ catch (error) {
72
+ ended = true;
73
+ close();
74
+ throw signal.aborted ? signal.reason : error;
75
+ }
76
+ },
77
+ async return() {
78
+ ended = true;
79
+ close();
80
+ return { done: true, value: undefined };
81
+ },
82
+ };
83
+ },
84
+ };
85
+ resolve({ message, body: source, close });
86
+ });
87
+ request.on("error", fail);
88
+ request.once("close", () => {
89
+ if (!headersReceived)
90
+ fail(new S3ServiceError("InvalidResponse", 502, "connection closed before response headers"));
91
+ });
92
+ if (signal.aborted || finished) {
93
+ abort();
94
+ request.destroy();
95
+ return;
96
+ }
97
+ void (async () => {
98
+ for (let offset = 0; offset < body.length; offset += 64 * 1024) {
99
+ signal.throwIfAborted();
100
+ if (!request.write(body.subarray(offset, offset + 64 * 1024)))
101
+ await once(request, "drain", { signal });
102
+ }
103
+ signal.throwIfAborted();
104
+ request.end();
105
+ })().catch(fail);
106
+ }
107
+ catch (error) {
108
+ fail(error);
109
+ }
110
+ });
111
+ }
@@ -1,5 +1,4 @@
1
- import type { IncomingMessage, RequestOptions } from "node:http";
2
- import type { S3HttpRequestFactory } from "./types.js";
1
+ export { sendRequest } from "#safe-fs-s3-request";
3
2
  export interface RequestScope {
4
3
  readonly signal: AbortSignal;
5
4
  readonly finish: () => void;
@@ -7,10 +6,12 @@ export interface RequestScope {
7
6
  export declare function scopeFor(signal: AbortSignal | undefined, timeout: number | undefined): RequestScope;
8
7
  export declare function abortable<Value>(pending: Promise<Value>, signal: AbortSignal): Promise<Value>;
9
8
  export interface WireResponse {
10
- readonly message: IncomingMessage;
9
+ readonly message: {
10
+ readonly statusCode?: number | undefined;
11
+ readonly headersDistinct: Record<string, string[] | undefined>;
12
+ };
11
13
  readonly body: AsyncIterable<Uint8Array>;
12
14
  readonly close: () => void;
13
15
  }
14
- export declare function sendRequest(options: RequestOptions, body: Uint8Array, scope: RequestScope, factory?: S3HttpRequestFactory): Promise<WireResponse>;
15
16
  export declare function limitedBody(response: WireResponse, maximum: number, expected?: number): AsyncIterable<Uint8Array>;
16
17
  export declare function collect(response: WireResponse, maximum: number, expected?: number): Promise<Uint8Array>;