@muhkoo/connect 0.10.9-alpha.0 → 0.10.10-alpha.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.
@@ -4120,31 +4120,37 @@ class FileStorage {
4120
4120
  */
4121
4121
  async writeFileToShards(input) {
4122
4122
  await this.codec.ready(this.rsWasmModule);
4123
- const data = await toUint8Array(input.data);
4124
4123
  const fileId = input.fileId ?? generateId();
4125
4124
  const createdAt = Date.now();
4126
4125
  const chunks = [];
4127
- const totalChunks = Math.max(1, Math.ceil(data.length / this.chunkSize));
4126
+ // Read the source per-chunk so the peak memory footprint stays at
4127
+ // ~chunkSize even for multi-GB files. A Blob/File is sliced lazily (only
4128
+ // the current chunk's bytes are materialized) — reading the whole thing
4129
+ // up front would blow past the browser's ~2GB ArrayBuffer cap and throw
4130
+ // "the file could not be read". A caller that already has the bytes in a
4131
+ // Uint8Array just gets zero-copy subarray views.
4132
+ const asBytes = input.data instanceof Uint8Array ? input.data : null;
4133
+ const size = asBytes ? asBytes.length : input.data.size;
4134
+ const readChunk = async (start, end) => asBytes ? asBytes.subarray(start, end)
4135
+ : new Uint8Array(await input.data.slice(start, end).arrayBuffer());
4136
+ const totalChunks = Math.max(1, Math.ceil(size / this.chunkSize));
4128
4137
  input.onProgress?.(0, totalChunks);
4129
- // Slicing + encrypting + encoding + uploading is done per-chunk so
4130
- // the peak memory footprint stays at ~chunkSize even for huge files.
4131
- // Within a chunk, the shard uploads are parallelized up to
4132
- // `this.concurrency`.
4133
- for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, data.length); chunkIndex++) {
4138
+ // Within a chunk, the shard uploads are parallelized up to `this.concurrency`.
4139
+ for (let chunkIndex = 0; chunkIndex * this.chunkSize < Math.max(1, size); chunkIndex++) {
4134
4140
  const start = chunkIndex * this.chunkSize;
4135
- const end = Math.min(start + this.chunkSize, data.length);
4136
- const plaintext = data.subarray(start, end);
4141
+ const end = Math.min(start + this.chunkSize, size);
4142
+ const plaintext = await readChunk(start, end);
4137
4143
  chunks.push(await this.writeChunk(plaintext, chunkIndex));
4138
4144
  input.onProgress?.(chunks.length, totalChunks);
4139
4145
  // Stop after one iteration if the file is empty — we still emit a
4140
4146
  // single zero-length chunk so the manifest has a stable shape.
4141
- if (data.length === 0)
4147
+ if (size === 0)
4142
4148
  break;
4143
4149
  }
4144
4150
  const manifest = {
4145
4151
  id: fileId,
4146
4152
  name: input.metadata.name,
4147
- size: data.length,
4153
+ size,
4148
4154
  type: input.metadata.type,
4149
4155
  path: input.metadata.path,
4150
4156
  lastModified: input.metadata.lastModified ?? createdAt,
@@ -4180,6 +4186,19 @@ class FileStorage {
4180
4186
  }
4181
4187
  return { data: out, stat: manifestToStat(manifest) };
4182
4188
  }
4189
+ /**
4190
+ * Streaming inverse of {@link writeFileToShards}: yields each chunk's
4191
+ * decrypted plaintext in order WITHOUT concatenating. Peak memory stays at
4192
+ * ~one chunk, so multi-GB files (which overflow a single `Uint8Array`) can be
4193
+ * piped straight to a file/socket. Same shard sourcing (cache → peers →
4194
+ * origin) and per-chunk auth as {@link readFileFromShards}.
4195
+ */
4196
+ async *readChunksFromShards(manifest) {
4197
+ await this.codec.ready(this.rsWasmModule);
4198
+ for (const chunkManifest of [...manifest.chunks].sort((a, b) => a.chunkIndex - b.chunkIndex)) {
4199
+ yield await this.readChunk(chunkManifest);
4200
+ }
4201
+ }
4183
4202
  // -------------------------------------------------------------------------
4184
4203
  // Space-backed convenience wrappers — encrypt + upload + write manifest.
4185
4204
  // Require a `SharedSpaceClient` to have been provided at construction.
@@ -4313,14 +4332,6 @@ class FileStorage {
4313
4332
  await Promise.all(workers);
4314
4333
  }
4315
4334
  }
4316
- /** Normalize `data` to a flat `Uint8Array`. */
4317
- async function toUint8Array(data) {
4318
- if (data instanceof Uint8Array)
4319
- return data;
4320
- // Blob covers File too (File extends Blob in every runtime we target).
4321
- const ab = await data.arrayBuffer();
4322
- return new Uint8Array(ab);
4323
- }
4324
4335
 
4325
4336
  /**
4326
4337
  * HTTP client for the gated file-manifest store on a SharedSpace
@@ -5057,7 +5068,7 @@ function base64UrlDecode(s) {
5057
5068
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
5058
5069
  * `package.json` `version`.
5059
5070
  */
5060
- const VERSION = "0.10.9-alpha.0";
5071
+ const VERSION = "0.10.10-alpha.0";
5061
5072
 
5062
5073
  /**
5063
5074
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -7617,6 +7628,13 @@ class Space {
7617
7628
  async getFile(manifest) {
7618
7629
  return this.fileStorage().readFileFromShards(manifest);
7619
7630
  }
7631
+ /** Streaming read: async-iterate a stored file's decrypted chunks in order,
7632
+ * never holding the whole file in memory. Use for multi-GB files (a single
7633
+ * `Uint8Array` can't hold them) — e.g. a transcoder piping the raw to a temp
7634
+ * file. Same peer/origin shard sourcing as {@link getFile}. */
7635
+ getFileStream(manifest) {
7636
+ return this.fileStorage().readChunksFromShards(manifest);
7637
+ }
7620
7638
  /** Declare a file's shards (ref++ + meter) for a manifest already stored —
7621
7639
  * used to back-fill accounting for files written before commit existed, or to
7622
7640
  * register a manifest received via sharing. `putFile` already commits new