@tinycloud/replica 0.4.0-beta.1 → 0.4.0-beta.3
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/index.cjs +27 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +27 -2
- package/dist/index.js.map +1 -1
- package/dist/kv-storage.js +3 -2
- package/dist/kv-storage.js.map +1 -1
- package/dist/{kv-storage.worker.f2ee4476484924b91a94e0f97d77023eca43ed50cb532a0488bafbe68ae591bd.js → kv-storage.worker.71bb85eb4c4d49cbcf82e84da665129c8fcd8585768360ebba434003f650dd20.js} +28 -2
- package/dist/kv-storage.worker.js +28 -2
- package/dist/kv-storage.worker.js.map +1 -1
- package/dist/replica.worker.js +27 -2
- package/dist/replica.worker.js.map +1 -1
- package/package.json +2 -2
|
@@ -3616,6 +3616,8 @@ var DEFAULT_PAGE_LIMIT = 500;
|
|
|
3616
3616
|
var DEFAULT_LEASE_TTL_MS = 12e4;
|
|
3617
3617
|
var FETCH_CHUNK = 100;
|
|
3618
3618
|
var REPAIR_BATCH = 100;
|
|
3619
|
+
var REPAIR_MAX_TRIES = 5;
|
|
3620
|
+
var REPAIR_BACKOFF_BASE_MS = 6e4;
|
|
3619
3621
|
var RETENTION_REVOKED = /* @__PURE__ */ new Set(["retention-grant-revoked", "retention-grant-ancestor-revoked"]);
|
|
3620
3622
|
var PENDING_REFUSALS = /* @__PURE__ */ new Set([
|
|
3621
3623
|
ReplicaErrorCode.GRANT_UNAUTHORIZED,
|
|
@@ -3625,6 +3627,9 @@ var PENDING_REFUSALS = /* @__PURE__ */ new Set([
|
|
|
3625
3627
|
function contentHash(bytes) {
|
|
3626
3628
|
return bytesToHex(blake3(bytes));
|
|
3627
3629
|
}
|
|
3630
|
+
function repairId(entry) {
|
|
3631
|
+
return `${entry.hash}\0${entry.key}`;
|
|
3632
|
+
}
|
|
3628
3633
|
function sameEtag(a, b) {
|
|
3629
3634
|
return a.replace(/^"|"$/g, "") === b.replace(/^"|"$/g, "");
|
|
3630
3635
|
}
|
|
@@ -3641,6 +3646,9 @@ var Replica = class {
|
|
|
3641
3646
|
#now;
|
|
3642
3647
|
#leaseTtlMs;
|
|
3643
3648
|
#onCommit;
|
|
3649
|
+
#contentFetch;
|
|
3650
|
+
/** Failed repair fetches of this session, by key and ETag hash. Never persisted: a new `Replica` retries afresh. */
|
|
3651
|
+
#repairTries = /* @__PURE__ */ new Map();
|
|
3644
3652
|
constructor(options) {
|
|
3645
3653
|
this.#store = options.store;
|
|
3646
3654
|
const transport = options.transport;
|
|
@@ -3648,6 +3656,7 @@ var Replica = class {
|
|
|
3648
3656
|
this.#now = options.now ?? Date.now;
|
|
3649
3657
|
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
3650
3658
|
this.#onCommit = options.onCommit;
|
|
3659
|
+
this.#contentFetch = options.contentFetch ?? "eager";
|
|
3651
3660
|
}
|
|
3652
3661
|
async #state() {
|
|
3653
3662
|
const state = await this.#store.open();
|
|
@@ -3976,6 +3985,7 @@ var Replica = class {
|
|
|
3976
3985
|
changes[candidate.index].content = true;
|
|
3977
3986
|
return false;
|
|
3978
3987
|
});
|
|
3988
|
+
if (this.#contentFetch === "lazy") return { changes, blobs, fetched: 0 };
|
|
3979
3989
|
let fetched2 = 0;
|
|
3980
3990
|
for (let start = 0; start < needed.length; start += FETCH_CHUNK) {
|
|
3981
3991
|
const chunk = needed.slice(start, start + FETCH_CHUNK);
|
|
@@ -3995,7 +4005,15 @@ var Replica = class {
|
|
|
3995
4005
|
return { changes, blobs, fetched: fetched2 };
|
|
3996
4006
|
}
|
|
3997
4007
|
async #repair(attempt, cursor, nodeDid, coverage, retentionGrantCid) {
|
|
3998
|
-
|
|
4008
|
+
if (this.#contentFetch === "lazy") return 0;
|
|
4009
|
+
const all = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
|
|
4010
|
+
const now = this.#now();
|
|
4011
|
+
const pendingIds = new Set(all.map((entry) => repairId(entry)));
|
|
4012
|
+
for (const id of this.#repairTries.keys()) if (!pendingIds.has(id)) this.#repairTries.delete(id);
|
|
4013
|
+
const pending = all.filter((entry) => {
|
|
4014
|
+
const state = this.#repairTries.get(repairId(entry));
|
|
4015
|
+
return state === void 0 || state.tries < REPAIR_MAX_TRIES && now >= state.nextAttemptAt;
|
|
4016
|
+
});
|
|
3999
4017
|
let repaired = 0;
|
|
4000
4018
|
for (let start = 0; start < pending.length; start += REPAIR_BATCH) {
|
|
4001
4019
|
const chunk = pending.slice(start, start + REPAIR_BATCH);
|
|
@@ -4007,7 +4025,14 @@ var Replica = class {
|
|
|
4007
4025
|
const changes = [];
|
|
4008
4026
|
for (const entry of chunk) {
|
|
4009
4027
|
const bytes = acceptContent(entry, contents.get(entry.key));
|
|
4010
|
-
|
|
4028
|
+
const id = repairId(entry);
|
|
4029
|
+
if (bytes === void 0) {
|
|
4030
|
+
const tries = (this.#repairTries.get(id)?.tries ?? 0) + 1;
|
|
4031
|
+
const wait = tries === 1 ? 0 : REPAIR_BACKOFF_BASE_MS * 2 ** (tries - 2);
|
|
4032
|
+
this.#repairTries.set(id, { tries, nextAttemptAt: this.#now() + wait });
|
|
4033
|
+
continue;
|
|
4034
|
+
}
|
|
4035
|
+
this.#repairTries.delete(id);
|
|
4011
4036
|
blobs.set(entry.hash, bytes);
|
|
4012
4037
|
changes.push({ ...entry, content: true });
|
|
4013
4038
|
}
|
|
@@ -6943,6 +6968,7 @@ ${spec.prefix}`);
|
|
|
6943
6968
|
store: handle.store,
|
|
6944
6969
|
now,
|
|
6945
6970
|
transportFor: (record) => deps.transportFor(record, handle.spec, handle.device),
|
|
6971
|
+
...handle.spec.contentFetch === void 0 ? {} : { contentFetch: handle.spec.contentFetch },
|
|
6946
6972
|
onCommit: () => pages += 1
|
|
6947
6973
|
});
|
|
6948
6974
|
let outcome;
|
|
@@ -3616,6 +3616,8 @@ var DEFAULT_PAGE_LIMIT = 500;
|
|
|
3616
3616
|
var DEFAULT_LEASE_TTL_MS = 12e4;
|
|
3617
3617
|
var FETCH_CHUNK = 100;
|
|
3618
3618
|
var REPAIR_BATCH = 100;
|
|
3619
|
+
var REPAIR_MAX_TRIES = 5;
|
|
3620
|
+
var REPAIR_BACKOFF_BASE_MS = 6e4;
|
|
3619
3621
|
var RETENTION_REVOKED = /* @__PURE__ */ new Set(["retention-grant-revoked", "retention-grant-ancestor-revoked"]);
|
|
3620
3622
|
var PENDING_REFUSALS = /* @__PURE__ */ new Set([
|
|
3621
3623
|
ReplicaErrorCode.GRANT_UNAUTHORIZED,
|
|
@@ -3625,6 +3627,9 @@ var PENDING_REFUSALS = /* @__PURE__ */ new Set([
|
|
|
3625
3627
|
function contentHash(bytes) {
|
|
3626
3628
|
return bytesToHex(blake3(bytes));
|
|
3627
3629
|
}
|
|
3630
|
+
function repairId(entry) {
|
|
3631
|
+
return `${entry.hash}\0${entry.key}`;
|
|
3632
|
+
}
|
|
3628
3633
|
function sameEtag(a, b) {
|
|
3629
3634
|
return a.replace(/^"|"$/g, "") === b.replace(/^"|"$/g, "");
|
|
3630
3635
|
}
|
|
@@ -3641,6 +3646,9 @@ var Replica = class {
|
|
|
3641
3646
|
#now;
|
|
3642
3647
|
#leaseTtlMs;
|
|
3643
3648
|
#onCommit;
|
|
3649
|
+
#contentFetch;
|
|
3650
|
+
/** Failed repair fetches of this session, by key and ETag hash. Never persisted: a new `Replica` retries afresh. */
|
|
3651
|
+
#repairTries = /* @__PURE__ */ new Map();
|
|
3644
3652
|
constructor(options) {
|
|
3645
3653
|
this.#store = options.store;
|
|
3646
3654
|
const transport = options.transport;
|
|
@@ -3648,6 +3656,7 @@ var Replica = class {
|
|
|
3648
3656
|
this.#now = options.now ?? Date.now;
|
|
3649
3657
|
this.#leaseTtlMs = options.leaseTtlMs ?? DEFAULT_LEASE_TTL_MS;
|
|
3650
3658
|
this.#onCommit = options.onCommit;
|
|
3659
|
+
this.#contentFetch = options.contentFetch ?? "eager";
|
|
3651
3660
|
}
|
|
3652
3661
|
async #state() {
|
|
3653
3662
|
const state = await this.#store.open();
|
|
@@ -3976,6 +3985,7 @@ var Replica = class {
|
|
|
3976
3985
|
changes[candidate.index].content = true;
|
|
3977
3986
|
return false;
|
|
3978
3987
|
});
|
|
3988
|
+
if (this.#contentFetch === "lazy") return { changes, blobs, fetched: 0 };
|
|
3979
3989
|
let fetched2 = 0;
|
|
3980
3990
|
for (let start = 0; start < needed.length; start += FETCH_CHUNK) {
|
|
3981
3991
|
const chunk = needed.slice(start, start + FETCH_CHUNK);
|
|
@@ -3995,7 +4005,15 @@ var Replica = class {
|
|
|
3995
4005
|
return { changes, blobs, fetched: fetched2 };
|
|
3996
4006
|
}
|
|
3997
4007
|
async #repair(attempt, cursor, nodeDid, coverage, retentionGrantCid) {
|
|
3998
|
-
|
|
4008
|
+
if (this.#contentFetch === "lazy") return 0;
|
|
4009
|
+
const all = await this.#store.pendingRepairs(Number.MAX_SAFE_INTEGER);
|
|
4010
|
+
const now = this.#now();
|
|
4011
|
+
const pendingIds = new Set(all.map((entry) => repairId(entry)));
|
|
4012
|
+
for (const id of this.#repairTries.keys()) if (!pendingIds.has(id)) this.#repairTries.delete(id);
|
|
4013
|
+
const pending = all.filter((entry) => {
|
|
4014
|
+
const state = this.#repairTries.get(repairId(entry));
|
|
4015
|
+
return state === void 0 || state.tries < REPAIR_MAX_TRIES && now >= state.nextAttemptAt;
|
|
4016
|
+
});
|
|
3999
4017
|
let repaired = 0;
|
|
4000
4018
|
for (let start = 0; start < pending.length; start += REPAIR_BATCH) {
|
|
4001
4019
|
const chunk = pending.slice(start, start + REPAIR_BATCH);
|
|
@@ -4007,7 +4025,14 @@ var Replica = class {
|
|
|
4007
4025
|
const changes = [];
|
|
4008
4026
|
for (const entry of chunk) {
|
|
4009
4027
|
const bytes = acceptContent(entry, contents.get(entry.key));
|
|
4010
|
-
|
|
4028
|
+
const id = repairId(entry);
|
|
4029
|
+
if (bytes === void 0) {
|
|
4030
|
+
const tries = (this.#repairTries.get(id)?.tries ?? 0) + 1;
|
|
4031
|
+
const wait = tries === 1 ? 0 : REPAIR_BACKOFF_BASE_MS * 2 ** (tries - 2);
|
|
4032
|
+
this.#repairTries.set(id, { tries, nextAttemptAt: this.#now() + wait });
|
|
4033
|
+
continue;
|
|
4034
|
+
}
|
|
4035
|
+
this.#repairTries.delete(id);
|
|
4011
4036
|
blobs.set(entry.hash, bytes);
|
|
4012
4037
|
changes.push({ ...entry, content: true });
|
|
4013
4038
|
}
|
|
@@ -6943,6 +6968,7 @@ ${spec.prefix}`);
|
|
|
6943
6968
|
store: handle.store,
|
|
6944
6969
|
now,
|
|
6945
6970
|
transportFor: (record) => deps.transportFor(record, handle.spec, handle.device),
|
|
6971
|
+
...handle.spec.contentFetch === void 0 ? {} : { contentFetch: handle.spec.contentFetch },
|
|
6946
6972
|
onCommit: () => pages += 1
|
|
6947
6973
|
});
|
|
6948
6974
|
let outcome;
|