@muhkoo/connect 0.10.1-alpha.0 → 0.10.3-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;
|
package/dist/server/index.js
CHANGED
|
@@ -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.
|
|
2979
|
+
const VERSION = "0.10.3-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(
|
|
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
|
}
|