@muhkoo/connect 0.10.0-alpha.0 → 0.10.2-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.
package/dist/connect.d.ts CHANGED
@@ -1464,6 +1464,17 @@ interface ShardClientOptions {
1464
1464
  * block to peers. Best-effort — any failure falls through to origin.
1465
1465
  */
1466
1466
  peers?: PeerBlockSource;
1467
+ /**
1468
+ * When true, {@link ShardClient.putShard} returns as soon as the shard is
1469
+ * written to {@link cache} and announced to {@link peers} — the origin PUT is
1470
+ * fired in the BACKGROUND rather than awaited. This makes a large write's
1471
+ * manifest available immediately (so a peer/worker can start pulling the
1472
+ * shards over P2P right away) while origin catches up as a durable fallback.
1473
+ * On a background-PUT failure the shard is handed to {@link deferUpload} (if
1474
+ * present) for replay. Requires a {@link cache} so the bytes survive locally
1475
+ * until the background upload lands. No-op without a cache.
1476
+ */
1477
+ backgroundUpload?: boolean;
1467
1478
  }
1468
1479
  /**
1469
1480
  * Compute the lowercase hex SHA-256 of `bytes`. Available via WebCrypto in
@@ -1477,6 +1488,7 @@ declare class ShardClient {
1477
1488
  private readonly cache?;
1478
1489
  private readonly deferUpload?;
1479
1490
  private readonly peers?;
1491
+ private readonly backgroundUpload;
1480
1492
  constructor(opts: ShardClientOptions);
1481
1493
  /** Build the URL for a given shard hash — exposed for callers that want to log or pre-resolve. */
1482
1494
  urlForHash(hash: string): string;
@@ -1489,6 +1501,16 @@ declare class ShardClient {
1489
1501
  putShard(hash: string, bytes: Uint8Array): Promise<{
1490
1502
  dedup: boolean;
1491
1503
  }>;
1504
+ private bgActive;
1505
+ private readonly bgQueue;
1506
+ private static readonly BG_CONCURRENCY;
1507
+ /** Queue a shard's origin PUT to run in the background under a concurrency
1508
+ * cap. Bytes are re-read from {@link cache} at upload time (not held in the
1509
+ * queue) so a large file's backlog doesn't pin a second copy in memory. */
1510
+ private enqueueBackgroundUpload;
1511
+ /** PUT the shard bytes to origin, verifying the server accepted them.
1512
+ * Used by the background-upload path; throws on network/HTTP failure. */
1513
+ private uploadToOrigin;
1492
1514
  /**
1493
1515
  * Fetch a shard by hash. Throws if the server returns a non-200; returns
1494
1516
  * `null` for 404 so callers can distinguish "missing" from "errored".
@@ -4109,6 +4131,13 @@ declare class Space {
4109
4131
  }>): Promise<number>;
4110
4132
  putFile(file: File | Blob | Uint8Array, metadata: SpaceFileMetadata, opts?: {
4111
4133
  onProgress?: (completed: number, total: number) => void;
4134
+ /**
4135
+ * Return the manifest as soon as shards are cached + announced to the
4136
+ * mesh, uploading to origin in the background. Lets a peer/worker start
4137
+ * pulling the file over P2P immediately instead of waiting out the full
4138
+ * origin upload. Requires an offline cache (browser); ignored without.
4139
+ */
4140
+ backgroundUpload?: boolean;
4112
4141
  }): Promise<{
4113
4142
  manifest: FileManifest;
4114
4143
  stat: FileStat;
@@ -2976,7 +2976,7 @@ function base64UrlDecode(s) {
2976
2976
  * expect (and isn't serving a stale cached bundle). Keep this in sync with
2977
2977
  * `package.json` `version`.
2978
2978
  */
2979
- const VERSION = "0.10.0-alpha.0";
2979
+ const VERSION = "0.10.1-alpha.0";
2980
2980
 
2981
2981
  /**
2982
2982
  * `HttpClient` — the one place the unified {@link Client} talks to the
@@ -5523,6 +5523,7 @@ class ShardClient {
5523
5523
  cache;
5524
5524
  deferUpload;
5525
5525
  peers;
5526
+ backgroundUpload;
5526
5527
  constructor(opts) {
5527
5528
  if (!opts?.baseUrl)
5528
5529
  throw new Error("ShardClient: `baseUrl` is required");
@@ -5541,6 +5542,10 @@ class ShardClient {
5541
5542
  this.cache = opts.cache;
5542
5543
  this.deferUpload = opts.deferUpload;
5543
5544
  this.peers = opts.peers;
5545
+ // Background upload needs a local cache to hold the bytes until the PUT
5546
+ // lands; without one it would race the reader against a not-yet-uploaded
5547
+ // origin, so we quietly ignore the flag when there's no cache.
5548
+ this.backgroundUpload = Boolean(opts.backgroundUpload && opts.cache);
5544
5549
  }
5545
5550
  /** Build the URL for a given shard hash — exposed for callers that want to log or pre-resolve. */
5546
5551
  urlForHash(hash) {
@@ -5562,6 +5567,14 @@ class ShardClient {
5562
5567
  // Tell the swarm we now hold this block, so peers can pull it from us.
5563
5568
  if (this.peers)
5564
5569
  this.peers.announce(hash);
5570
+ // Background mode: the bytes are cached + announced, so return now and let
5571
+ // origin catch up asynchronously (a peer can already pull this block). The
5572
+ // PUT is enqueued through a bounded worker pool so a big file doesn't fire
5573
+ // hundreds of concurrent uploads at once.
5574
+ if (this.backgroundUpload) {
5575
+ this.enqueueBackgroundUpload(url, hash);
5576
+ return { dedup: false };
5577
+ }
5565
5578
  let response;
5566
5579
  try {
5567
5580
  response = await this.fetchFn(url, {
@@ -5594,6 +5607,56 @@ class ShardClient {
5594
5607
  }
5595
5608
  return { dedup };
5596
5609
  }
5610
+ // ── bounded background-upload pool (backgroundUpload mode) ──
5611
+ bgActive = 0;
5612
+ bgQueue = [];
5613
+ static BG_CONCURRENCY = 6;
5614
+ /** Queue a shard's origin PUT to run in the background under a concurrency
5615
+ * cap. Bytes are re-read from {@link cache} at upload time (not held in the
5616
+ * queue) so a large file's backlog doesn't pin a second copy in memory. */
5617
+ enqueueBackgroundUpload(url, hash) {
5618
+ const run = async () => {
5619
+ this.bgActive++;
5620
+ try {
5621
+ const bytes = this.cache ? await this.cache.get(hash) : null;
5622
+ if (bytes)
5623
+ await this.uploadToOrigin(url, hash, bytes);
5624
+ }
5625
+ catch {
5626
+ // Origin unreachable/failed — queue for replay if we can; otherwise
5627
+ // the block is still cached + peer-served, so a later re-PUT covers it.
5628
+ if (this.deferUpload) {
5629
+ try {
5630
+ await this.deferUpload(hash);
5631
+ }
5632
+ catch { /* best-effort */ }
5633
+ }
5634
+ }
5635
+ finally {
5636
+ this.bgActive--;
5637
+ const next = this.bgQueue.shift();
5638
+ if (next)
5639
+ next();
5640
+ }
5641
+ };
5642
+ if (this.bgActive < ShardClient.BG_CONCURRENCY)
5643
+ void run();
5644
+ else
5645
+ this.bgQueue.push(() => void run());
5646
+ }
5647
+ /** PUT the shard bytes to origin, verifying the server accepted them.
5648
+ * Used by the background-upload path; throws on network/HTTP failure. */
5649
+ async uploadToOrigin(url, hash, bytes) {
5650
+ const response = await this.fetchFn(url, {
5651
+ method: "PUT",
5652
+ headers: { "content-type": "application/octet-stream" },
5653
+ body: bytes,
5654
+ });
5655
+ if (!response.ok) {
5656
+ const text = await safeText(response);
5657
+ throw new Error(`ShardClient.putShard(${hash}) failed (${response.status}): ${text}`);
5658
+ }
5659
+ }
5597
5660
  /**
5598
5661
  * Fetch a shard by hash. Throws if the server returns a non-200; returns
5599
5662
  * `null` for 404 so callers can distinguish "missing" from "errored".
@@ -7267,12 +7330,13 @@ class Space {
7267
7330
  }
7268
7331
  // -- room-scoped files (no websocket needed) -------------------------------
7269
7332
  async putFile(file, metadata, opts = {}) {
7270
- return this.fileStorage().writeFileToShards({ data: file, metadata, onProgress: opts.onProgress });
7333
+ return this.fileStorage(opts.backgroundUpload)
7334
+ .writeFileToShards({ data: file, metadata, onProgress: opts.onProgress });
7271
7335
  }
7272
7336
  async getFile(manifest) {
7273
7337
  return this.fileStorage().readFileFromShards(manifest);
7274
7338
  }
7275
- fileStorage() {
7339
+ fileStorage(backgroundUpload = false) {
7276
7340
  // Global, content-addressed shard store — same store `client.storage`
7277
7341
  // uses, so a file shared into a channel resolves from anywhere by
7278
7342
  // manifest. (Shards are encrypted ciphertext; the manifest is the
@@ -7284,6 +7348,7 @@ class Space {
7284
7348
  cache: this.deps.shardCache,
7285
7349
  deferUpload: this.deps.deferShardUpload,
7286
7350
  peers: this.peerNet?.exchange,
7351
+ backgroundUpload,
7287
7352
  });
7288
7353
  return new FileStorage({ shards });
7289
7354
  }