@optimystic/db-p2p 0.24.2 → 0.25.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/{readme.md → README.md} +688 -668
- package/dist/src/cluster/block-transfer-service.d.ts +133 -10
- package/dist/src/cluster/block-transfer-service.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer-service.js +155 -9
- package/dist/src/cluster/block-transfer-service.js.map +1 -1
- package/dist/src/cluster/block-transfer.d.ts +67 -7
- package/dist/src/cluster/block-transfer.d.ts.map +1 -1
- package/dist/src/cluster/block-transfer.js +127 -24
- package/dist/src/cluster/block-transfer.js.map +1 -1
- package/dist/src/cluster/certified-claims.d.ts +170 -0
- package/dist/src/cluster/certified-claims.d.ts.map +1 -0
- package/dist/src/cluster/certified-claims.js +200 -0
- package/dist/src/cluster/certified-claims.js.map +1 -0
- package/dist/src/cluster/client-signature-verifier.d.ts +23 -0
- package/dist/src/cluster/client-signature-verifier.d.ts.map +1 -0
- package/dist/src/cluster/client-signature-verifier.js +33 -0
- package/dist/src/cluster/client-signature-verifier.js.map +1 -0
- package/dist/src/cluster/cluster-policy.d.ts +9 -3
- package/dist/src/cluster/cluster-policy.d.ts.map +1 -1
- package/dist/src/cluster/cluster-policy.js +111 -27
- package/dist/src/cluster/cluster-policy.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +140 -11
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +485 -29
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/commit-proof.d.ts +134 -0
- package/dist/src/cluster/commit-proof.d.ts.map +1 -0
- package/dist/src/cluster/commit-proof.js +263 -0
- package/dist/src/cluster/commit-proof.js.map +1 -0
- package/dist/src/cluster/quorum-restore.d.ts +95 -8
- package/dist/src/cluster/quorum-restore.d.ts.map +1 -1
- package/dist/src/cluster/quorum-restore.js +113 -14
- package/dist/src/cluster/quorum-restore.js.map +1 -1
- package/dist/src/cluster/rebalance-monitor.d.ts +84 -0
- package/dist/src/cluster/rebalance-monitor.d.ts.map +1 -1
- package/dist/src/cluster/rebalance-monitor.js +207 -5
- package/dist/src/cluster/rebalance-monitor.js.map +1 -1
- package/dist/src/cluster/reconcile-block.d.ts +27 -7
- package/dist/src/cluster/reconcile-block.d.ts.map +1 -1
- package/dist/src/cluster/reconcile-block.js +179 -38
- package/dist/src/cluster/reconcile-block.js.map +1 -1
- package/dist/src/cluster/service.d.ts +13 -4
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +17 -18
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cluster/spread-on-churn.d.ts +8 -2
- package/dist/src/cluster/spread-on-churn.d.ts.map +1 -1
- package/dist/src/cluster/spread-on-churn.js +7 -7
- package/dist/src/cluster/spread-on-churn.js.map +1 -1
- package/dist/src/cohort-topic/host.d.ts.map +1 -1
- package/dist/src/cohort-topic/host.js +8 -7
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +7 -6
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +14 -41
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/cascade.d.ts +7 -8
- package/dist/src/dispute/cascade.d.ts.map +1 -1
- package/dist/src/dispute/cascade.js +24 -7
- package/dist/src/dispute/cascade.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts +5 -0
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +13 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/dispute/invalidation.d.ts +33 -15
- package/dist/src/dispute/invalidation.d.ts.map +1 -1
- package/dist/src/dispute/invalidation.js +127 -34
- package/dist/src/dispute/invalidation.js.map +1 -1
- package/dist/src/dispute/service.d.ts.map +1 -1
- package/dist/src/dispute/service.js +2 -1
- package/dist/src/dispute/service.js.map +1 -1
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +7 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +14 -10
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +28 -58
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts +38 -7
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +148 -63
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/network/open-protocol-stream.d.ts +56 -0
- package/dist/src/network/open-protocol-stream.d.ts.map +1 -0
- package/dist/src/network/open-protocol-stream.js +60 -0
- package/dist/src/network/open-protocol-stream.js.map +1 -0
- package/dist/src/network/register-protocol-handler.d.ts +66 -0
- package/dist/src/network/register-protocol-handler.d.ts.map +1 -0
- package/dist/src/network/register-protocol-handler.js +51 -0
- package/dist/src/network/register-protocol-handler.js.map +1 -0
- package/dist/src/peer-address-book.d.ts +60 -2
- package/dist/src/peer-address-book.d.ts.map +1 -1
- package/dist/src/peer-address-book.js +89 -1
- package/dist/src/peer-address-book.js.map +1 -1
- package/dist/src/pend-validation.d.ts +65 -0
- package/dist/src/pend-validation.d.ts.map +1 -0
- package/dist/src/pend-validation.js +69 -0
- package/dist/src/pend-validation.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts.map +1 -1
- package/dist/src/reactivity/notify-transport.js +2 -1
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.d.ts.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -1
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +32 -6
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +44 -10
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +181 -25
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +650 -62
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/repo/served-repo-proxy.d.ts +30 -0
- package/dist/src/repo/served-repo-proxy.d.ts.map +1 -0
- package/dist/src/repo/served-repo-proxy.js +50 -0
- package/dist/src/repo/served-repo-proxy.js.map +1 -0
- package/dist/src/repo/service.d.ts +16 -1
- package/dist/src/repo/service.d.ts.map +1 -1
- package/dist/src/repo/service.js +18 -16
- package/dist/src/repo/service.js.map +1 -1
- package/dist/src/reputation/types.d.ts +3 -1
- package/dist/src/reputation/types.d.ts.map +1 -1
- package/dist/src/reputation/types.js +3 -1
- package/dist/src/reputation/types.js.map +1 -1
- package/dist/src/rn.d.ts +24 -2
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +33 -4
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/block-archive.d.ts +153 -0
- package/dist/src/storage/block-archive.d.ts.map +1 -0
- package/dist/src/storage/block-archive.js +194 -0
- package/dist/src/storage/block-archive.js.map +1 -0
- package/dist/src/storage/block-latch.d.ts +92 -0
- package/dist/src/storage/block-latch.d.ts.map +1 -0
- package/dist/src/storage/block-latch.js +132 -0
- package/dist/src/storage/block-latch.js.map +1 -0
- package/dist/src/storage/block-storage.d.ts +169 -12
- package/dist/src/storage/block-storage.d.ts.map +1 -1
- package/dist/src/storage/block-storage.js +415 -152
- package/dist/src/storage/block-storage.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +5 -1
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/cached-raw-storage.js +16 -1
- package/dist/src/storage/cached-raw-storage.js.map +1 -1
- package/dist/src/storage/cached-store-driver.d.ts +14 -4
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -1
- package/dist/src/storage/cached-store-driver.js +39 -5
- package/dist/src/storage/cached-store-driver.js.map +1 -1
- package/dist/src/storage/i-block-storage.d.ts +70 -19
- package/dist/src/storage/i-block-storage.d.ts.map +1 -1
- package/dist/src/storage/i-block-storage.js +21 -1
- package/dist/src/storage/i-block-storage.js.map +1 -1
- package/dist/src/storage/i-raw-storage.d.ts +38 -0
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.d.ts +14 -8
- package/dist/src/storage/kv-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/kv-raw-storage.js +29 -9
- package/dist/src/storage/kv-raw-storage.js.map +1 -1
- package/dist/src/storage/memory-store-driver.d.ts +4 -1
- package/dist/src/storage/memory-store-driver.d.ts.map +1 -1
- package/dist/src/storage/memory-store-driver.js +22 -2
- package/dist/src/storage/memory-store-driver.js.map +1 -1
- package/dist/src/storage/raw-store-codec.js +2 -2
- package/dist/src/storage/raw-store-driver.d.ts +44 -3
- package/dist/src/storage/raw-store-driver.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +60 -3
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.js +68 -4
- package/dist/src/storage/shared-cache-pool.js.map +1 -1
- package/dist/src/storage/storage-repo.d.ts +204 -26
- package/dist/src/storage/storage-repo.d.ts.map +1 -1
- package/dist/src/storage/storage-repo.js +422 -119
- package/dist/src/storage/storage-repo.js.map +1 -1
- package/dist/src/storage/store-identity.d.ts +21 -0
- package/dist/src/storage/store-identity.d.ts.map +1 -0
- package/dist/src/storage/store-identity.js +33 -0
- package/dist/src/storage/store-identity.js.map +1 -0
- package/dist/src/storage/struct.d.ts +15 -0
- package/dist/src/storage/struct.d.ts.map +1 -1
- package/dist/src/storage/with-read-cache.d.ts +92 -0
- package/dist/src/storage/with-read-cache.d.ts.map +1 -0
- package/dist/src/storage/with-read-cache.js +132 -0
- package/dist/src/storage/with-read-cache.js.map +1 -0
- package/dist/src/sync/service.d.ts +13 -3
- package/dist/src/sync/service.d.ts.map +1 -1
- package/dist/src/sync/service.js +7 -30
- package/dist/src/sync/service.js.map +1 -1
- package/dist/src/testing/mesh-harness.d.ts +75 -2
- package/dist/src/testing/mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/mesh-harness.js +193 -81
- package/dist/src/testing/mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +8 -0
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +97 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +5 -6
- package/src/cluster/block-transfer-service.ts +528 -312
- package/src/cluster/block-transfer.ts +547 -424
- package/src/cluster/certified-claims.ts +319 -0
- package/src/cluster/client-signature-verifier.ts +34 -0
- package/src/cluster/cluster-policy.ts +294 -203
- package/src/cluster/cluster-repo.ts +2501 -2034
- package/src/cluster/commit-proof.ts +344 -0
- package/src/cluster/quorum-restore.ts +348 -223
- package/src/cluster/rebalance-monitor.ts +539 -254
- package/src/cluster/reconcile-block.ts +404 -203
- package/src/cluster/service.ts +23 -20
- package/src/cluster/spread-on-churn.ts +352 -346
- package/src/cohort-topic/host.ts +8 -7
- package/src/cohort-topic/stream-util.ts +14 -43
- package/src/dispute/cascade.ts +31 -15
- package/src/dispute/dispute-service.ts +706 -696
- package/src/dispute/invalidation.ts +162 -52
- package/src/dispute/service.ts +126 -125
- package/src/index.ts +7 -0
- package/src/libp2p-key-network.ts +1204 -1235
- package/src/libp2p-node-base.ts +185 -70
- package/src/network/open-protocol-stream.ts +87 -0
- package/src/network/register-protocol-handler.ts +85 -0
- package/src/peer-address-book.ts +96 -2
- package/src/pend-validation.ts +89 -0
- package/src/reactivity/notify-transport.ts +2 -1
- package/src/reactivity/push-state-gossip.ts +2 -1
- package/src/repo/cluster-coordinator.ts +65 -11
- package/src/repo/coordinator-repo.ts +729 -71
- package/src/repo/served-repo-proxy.ts +55 -0
- package/src/repo/service.ts +25 -16
- package/src/reputation/types.ts +3 -1
- package/src/rn.ts +34 -4
- package/src/storage/block-archive.ts +242 -0
- package/src/storage/block-latch.ts +144 -0
- package/src/storage/block-storage.ts +787 -498
- package/src/storage/cached-raw-storage.ts +21 -1
- package/src/storage/cached-store-driver.ts +898 -859
- package/src/storage/i-block-storage.ts +172 -116
- package/src/storage/i-raw-storage.ts +40 -0
- package/src/storage/kv-raw-storage.ts +34 -9
- package/src/storage/memory-store-driver.ts +26 -2
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +45 -3
- package/src/storage/shared-cache-pool.ts +516 -452
- package/src/storage/storage-repo.ts +1421 -1014
- package/src/storage/store-identity.ts +41 -0
- package/src/storage/struct.ts +52 -34
- package/src/storage/with-read-cache.ts +182 -0
- package/src/sync/service.ts +178 -198
- package/src/testing/mesh-harness.ts +524 -344
- package/src/testing/raw-storage-conformance.ts +114 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A stable, process-scoped name for what a store is backed by — a resolved directory, an
|
|
3
|
+
* open database handle, and so on.
|
|
4
|
+
*
|
|
5
|
+
* Compared for EQUALITY ONLY. Never parsed, never split on its scheme prefix, and never used
|
|
6
|
+
* as a cache key for values — it names the store, not its contents.
|
|
7
|
+
*/
|
|
8
|
+
export type StoreIdentity = string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Per-scheme tag maps. Weak, so tagging a handle never keeps it alive: once the handle is
|
|
12
|
+
* unreachable its entry goes with it.
|
|
13
|
+
*/
|
|
14
|
+
const handleTags = new Map<string, WeakMap<object, StoreIdentity>>();
|
|
15
|
+
let nextHandleOrdinal = 0;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A stable identity for a backend reachable only as an already-open handle object (a SQLite
|
|
19
|
+
* db, an IndexedDB handle, a LevelDB instance). Returns the same string for the same object
|
|
20
|
+
* for the life of the process, and a different one for every other object.
|
|
21
|
+
*
|
|
22
|
+
* `scheme` prefixes the result so identities from different backends can never collide.
|
|
23
|
+
*
|
|
24
|
+
* Object identity is the whole contract here: two handles opened over the SAME underlying
|
|
25
|
+
* file/name/path read as two DIFFERENT identities. That is a deliberate under-approximation —
|
|
26
|
+
* see the `NOTE:` at each handle-based driver for why it is the reachable case in practice.
|
|
27
|
+
*/
|
|
28
|
+
export function identityForHandle(scheme: string, handle: object): StoreIdentity {
|
|
29
|
+
let tags = handleTags.get(scheme);
|
|
30
|
+
if (!tags) {
|
|
31
|
+
tags = new WeakMap<object, StoreIdentity>();
|
|
32
|
+
handleTags.set(scheme, tags);
|
|
33
|
+
}
|
|
34
|
+
const existing = tags.get(handle);
|
|
35
|
+
if (existing !== undefined) return existing;
|
|
36
|
+
// The ordinal is global rather than per-scheme so the same handle under two schemes gets
|
|
37
|
+
// two visibly different suffixes as well as two different prefixes.
|
|
38
|
+
const identity = `${scheme}:${nextHandleOrdinal++}`;
|
|
39
|
+
tags.set(handle, identity);
|
|
40
|
+
return identity;
|
|
41
|
+
}
|
package/src/storage/struct.ts
CHANGED
|
@@ -1,34 +1,52 @@
|
|
|
1
|
-
import type { BlockId, IBlock, ActionId, ActionRev, ActionTransform, ActionTransforms } from "@optimystic/db-core";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
ranges
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
1
|
+
import type { BlockId, IBlock, ActionId, ActionRev, ActionTransform, ActionTransforms } from "@optimystic/db-core";
|
|
2
|
+
import type { BlockCommitProof } from "../cluster/commit-proof.js";
|
|
3
|
+
|
|
4
|
+
export type RevisionRange = [
|
|
5
|
+
/** Inclusive start */
|
|
6
|
+
startRev: number,
|
|
7
|
+
/** Exclusive end, or open-ended if undefined */
|
|
8
|
+
endRev?: number,
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
export type BlockMetadata = {
|
|
12
|
+
// Revision ranges that are present in storage
|
|
13
|
+
ranges: RevisionRange[];
|
|
14
|
+
/** Latest revision - present if the repo is not empty */
|
|
15
|
+
latest?: ActionRev;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type ArchiveRevisions = Record<number, {
|
|
19
|
+
action: ActionTransform;
|
|
20
|
+
block?: IBlock;
|
|
21
|
+
/**
|
|
22
|
+
* The cohort's commit proof for this revision, when the serving repo retained one.
|
|
23
|
+
*
|
|
24
|
+
* Absent in three legitimate cases, all of which every consumer must tolerate exactly as it
|
|
25
|
+
* tolerated the pre-proof shape: a revision committed before proofs were persisted at all; a
|
|
26
|
+
* member whose own materialization diverged from the digest the commit declared (it deliberately
|
|
27
|
+
* stores no proof — see `StorageRepo.persistProofIfContentMatches`); and a peer running an
|
|
28
|
+
* un-upgraded build.
|
|
29
|
+
*
|
|
30
|
+
* Keyed INSIDE the revision entry on purpose: the proof and the `(rev, actionId)` it certifies
|
|
31
|
+
* travel together, so a serving bug or a hostile peer cannot pair a genuine proof with a
|
|
32
|
+
* different revision by construction of the wire shape alone.
|
|
33
|
+
*/
|
|
34
|
+
proof?: BlockCommitProof;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
export type BlockArchive = {
|
|
38
|
+
blockId: BlockId;
|
|
39
|
+
/** Revisions in this archive */
|
|
40
|
+
revisions: ArchiveRevisions;
|
|
41
|
+
/** Explicit range covered by this archive since revisions may be sparse */
|
|
42
|
+
range: RevisionRange;
|
|
43
|
+
/** Pending actions - present if this range is open-ended */
|
|
44
|
+
pending?: Record<ActionId, ActionTransforms>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Should return a BlockRepo with the given rev (materialized) if given,
|
|
48
|
+
* else (no rev) at least the latest revision and any given pending transactions */
|
|
49
|
+
export type RestoreCallback = (blockId: BlockId, rev?: number) => Promise<BlockArchive | undefined>;
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import type { IRawStorage } from "./i-raw-storage.js";
|
|
2
|
+
import { MemoryRawStorage } from "./memory-storage.js";
|
|
3
|
+
import { CachedRawStorage } from "./cached-raw-storage.js";
|
|
4
|
+
import type { SharedCachePool } from "./shared-cache-pool.js";
|
|
5
|
+
import type { StoreIdentity } from "./store-identity.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* One consumer's claim on a shared read cache. Release exactly once, when the consumer departs.
|
|
9
|
+
*
|
|
10
|
+
* The cache behind a lease may be serving OTHER consumers — every lease over one store hands
|
|
11
|
+
* back the same {@link CachedRawStorage} — so a lease holder never disposes the cache itself.
|
|
12
|
+
* It releases its claim; the cache is torn down when the last claim goes.
|
|
13
|
+
*/
|
|
14
|
+
export interface ReadCacheLease {
|
|
15
|
+
/** The cache this lease is a claim on — the same object for every lease over one store. */
|
|
16
|
+
readonly cache: CachedRawStorage;
|
|
17
|
+
/**
|
|
18
|
+
* Drop this claim. Idempotent. The cache is cleared, unregistered from its pool, and
|
|
19
|
+
* forgotten only when the LAST lease releases; until then other consumers keep reading
|
|
20
|
+
* through it. A later {@link withReadCache} over the same store after the last release
|
|
21
|
+
* builds a fresh, cold cache.
|
|
22
|
+
*/
|
|
23
|
+
release(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What {@link withReadCache} resolved: the storage to use, and this caller's claim on the cache
|
|
28
|
+
* behind it — `undefined` when it returned the argument unchanged.
|
|
29
|
+
*
|
|
30
|
+
* The split exists because "the result is a `CachedRawStorage`" and "the result is mine to
|
|
31
|
+
* release" are different questions, and answering the second with the first is a bug: the
|
|
32
|
+
* pass-through branch hands back a cache the CALLER built and may still be sharing with other
|
|
33
|
+
* consumers. Release `lease`; never dispose `storage`.
|
|
34
|
+
*/
|
|
35
|
+
export type ResolvedReadCache = {
|
|
36
|
+
/** The storage to build on — the shared cache, or the argument unchanged. */
|
|
37
|
+
storage: IRawStorage;
|
|
38
|
+
/** This caller's claim, or `undefined` when the argument passed through unwrapped. */
|
|
39
|
+
lease: ReadCacheLease | undefined;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One live shared cache and the number of leases claiming it. `retire` removes it from
|
|
44
|
+
* whichever registry map it lives in — called exactly once, by the last release, BEFORE the
|
|
45
|
+
* async dispose, so a concurrent re-wrap over the same store never finds a dying entry.
|
|
46
|
+
*/
|
|
47
|
+
type RegistryEntry = {
|
|
48
|
+
readonly cache: CachedRawStorage;
|
|
49
|
+
refs: number;
|
|
50
|
+
readonly retire: () => void;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The registry of live shared caches, keyed two ways because the two keys close different
|
|
55
|
+
* holes:
|
|
56
|
+
*
|
|
57
|
+
* - `byIdentity` — by {@link IRawStorage.getStoreIdentity}, so two DIFFERENT storage objects
|
|
58
|
+
* over one backing location (`new FileRawStorage(dir)` twice) resolve to one cache.
|
|
59
|
+
* - `byObject` — by the storage object itself, for backends that report no identity, so one
|
|
60
|
+
* unwrapped instance handed to two consumers still resolves to one cache. Weak: an entry
|
|
61
|
+
* never keeps its storage alive.
|
|
62
|
+
*
|
|
63
|
+
* An identity-bearing storage is keyed by identity only (two wraps of one object have one
|
|
64
|
+
* identity, so the object key would be redundant).
|
|
65
|
+
*
|
|
66
|
+
* NOTE: an entry lives until its last lease releases, so a host that never releases (e.g.
|
|
67
|
+
* never calls `plugin.dispose()`) holds one cache per store for the life of the process. This
|
|
68
|
+
* is the same class of retention as before dedupe — an undisposed cache already kept its pool
|
|
69
|
+
* registration forever — and the pool still evicts its entries under pressure, so it is
|
|
70
|
+
* hygiene, not correctness.
|
|
71
|
+
*/
|
|
72
|
+
const byIdentity = new Map<StoreIdentity, RegistryEntry>();
|
|
73
|
+
const byObject = new WeakMap<IRawStorage, RegistryEntry>();
|
|
74
|
+
|
|
75
|
+
class Lease implements ReadCacheLease {
|
|
76
|
+
private released = false;
|
|
77
|
+
|
|
78
|
+
constructor(private readonly entry: RegistryEntry) {}
|
|
79
|
+
|
|
80
|
+
get cache(): CachedRawStorage {
|
|
81
|
+
return this.entry.cache;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async release(): Promise<void> {
|
|
85
|
+
// Latch first, then decrement, all synchronously: a double release on one lease counts
|
|
86
|
+
// once, and two leases releasing concurrently can only land ONE of them on zero.
|
|
87
|
+
if (this.released) return;
|
|
88
|
+
this.released = true;
|
|
89
|
+
this.entry.refs -= 1;
|
|
90
|
+
if (this.entry.refs > 0) return;
|
|
91
|
+
this.entry.retire();
|
|
92
|
+
await this.entry.cache.dispose();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Wrap a raw storage in the write-through read cache ({@link CachedRawStorage}) at a
|
|
98
|
+
* composition seam — the ONE helper every production seam that resolves an `IRawStorage`
|
|
99
|
+
* goes through, so the exclusion and sharing rules below are stated once rather than
|
|
100
|
+
* re-derived per site.
|
|
101
|
+
*
|
|
102
|
+
* Returns the storage **unchanged**, with no `lease`, when caching would not pay:
|
|
103
|
+
* - `MemoryRawStorage` is already in memory; the cache would duplicate every map entry's
|
|
104
|
+
* bookkeeping with nothing to save (see `CachedStoreDriver`'s class doc).
|
|
105
|
+
* - An already-cached storage (a host that wrapped before handing it over) is not wrapped twice,
|
|
106
|
+
* and stays the host's to dispose.
|
|
107
|
+
*
|
|
108
|
+
* Why this is needed at all: `BlockStorage` re-reads block metadata on essentially every
|
|
109
|
+
* operation and `StorageRepo` builds a fresh `BlockStorage` per block per call, so nothing above
|
|
110
|
+
* this seam memoizes. Over a filesystem backend that is hundreds of reads of the same tiny files
|
|
111
|
+
* per statement. Measured A/B on a create/insert/update/select workload with only the wrap
|
|
112
|
+
* decision changed: 113 → 6 `getMetadata` and 207 → 14 total reads at the `RawStoreDriver` seam;
|
|
113
|
+
* over `FileRawStorage` the same workload went from 184 `readFile` + 29 `readdir` to 9 + 6.
|
|
114
|
+
*
|
|
115
|
+
* **One cache per backing store, shared under a lease.** The cache is write-through, so it is
|
|
116
|
+
* coherent only while every in-process writer to a store goes through the SAME cache; two caches
|
|
117
|
+
* over one store each serve their own stale view forever (measured: peer A still reads 1 row
|
|
118
|
+
* after peer B commits 3). So this helper dedupes: the second call over a store that already
|
|
119
|
+
* has a live cache returns that same cache with a fresh {@link ReadCacheLease}, and constructs
|
|
120
|
+
* only on a miss. "Same store" is decided by `storage.getStoreIdentity()` when the backend
|
|
121
|
+
* reports one (two `FileRawStorage` over one directory), else by storage object identity (one
|
|
122
|
+
* unwrapped instance handed to two consumers). Identity is one-directional — equal proves
|
|
123
|
+
* sameness, unequal proves nothing — so backends whose identity under-approximates (path
|
|
124
|
+
* aliases, two handles over one database; each backend's `NOTE:` lists its gaps) can still end
|
|
125
|
+
* up with two caches; and a host that builds its own `CachedRawStorage` never enters the
|
|
126
|
+
* registry, so a second consumer wrapping a fresh instance over that store still gets a second
|
|
127
|
+
* cache. What remains of Invariant 5 (`packages/db-p2p/docs/storage.md`) is the cross-process
|
|
128
|
+
* case: the filesystem driver takes no lock, and a second process's writes bypass this cache.
|
|
129
|
+
*
|
|
130
|
+
* On a dedupe hit the FIRST caller's `label` and `pool` stick: `pool.stats()` shows whoever
|
|
131
|
+
* wrapped first, and a second caller's different pool is ignored. Identity beats sizing — a
|
|
132
|
+
* same-store-different-pool pair would diverge, which is the thing being removed.
|
|
133
|
+
*
|
|
134
|
+
* **Lifecycle: release {@link ResolvedReadCache.lease} and nothing else.** The cache is
|
|
135
|
+
* cleared, unregistered from its pool, and forgotten when the LAST lease over the store
|
|
136
|
+
* releases; a later wrap then starts cold. Disposing `storage` directly would clear and
|
|
137
|
+
* unregister a cache other consumers are still reading through — the pool would keep charging
|
|
138
|
+
* that store's entries while dropping its row from `stats()`. A skipped release leaks only cold
|
|
139
|
+
* entries the pool evicts under pressure — hygiene, not correctness.
|
|
140
|
+
*
|
|
141
|
+
* Deliberately synchronous, with no `await` between lookup and insert: two seams resolving
|
|
142
|
+
* concurrently cannot both construct. Keep it that way.
|
|
143
|
+
*
|
|
144
|
+
* @param label Shown in `SharedCachePool.stats()` so this store is recognizable. First caller's
|
|
145
|
+
* label wins on a dedupe hit.
|
|
146
|
+
* @param pool Pool to join; defaults to the process-wide `defaultCachePool()`. Pass one only
|
|
147
|
+
* for isolation (tests) or host-specific sizing. First caller's pool wins on a
|
|
148
|
+
* dedupe hit.
|
|
149
|
+
*/
|
|
150
|
+
export function withReadCache(storage: IRawStorage, label?: string, pool?: SharedCachePool): ResolvedReadCache {
|
|
151
|
+
if (storage instanceof MemoryRawStorage || storage instanceof CachedRawStorage) {
|
|
152
|
+
return { storage, lease: undefined };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const identity = storage.getStoreIdentity?.();
|
|
156
|
+
const existing = identity === undefined ? byObject.get(storage) : byIdentity.get(identity);
|
|
157
|
+
if (existing) {
|
|
158
|
+
// NOTE: on a hit the `storage` argument is dropped unused — the FIRST caller's instance is
|
|
159
|
+
// the one the shared cache fronts, and the only one the last release closes. Free today:
|
|
160
|
+
// `IRawStorage` has no close, and the only backend whose identity can make two DISTINCT
|
|
161
|
+
// objects collide is the filesystem one, which holds nothing open. If a backend ever pairs
|
|
162
|
+
// a location-derived identity with an open handle, the discarded instance would leak that
|
|
163
|
+
// handle — close it here, or key such backends by handle (`identityForHandle`) instead.
|
|
164
|
+
existing.refs += 1;
|
|
165
|
+
return { storage: existing.cache, lease: new Lease(existing) };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const cache = new CachedRawStorage(storage, pool, label);
|
|
169
|
+
const entry: RegistryEntry = {
|
|
170
|
+
cache,
|
|
171
|
+
refs: 1,
|
|
172
|
+
retire: identity === undefined
|
|
173
|
+
? () => { byObject.delete(storage); }
|
|
174
|
+
: () => { byIdentity.delete(identity); },
|
|
175
|
+
};
|
|
176
|
+
if (identity === undefined) {
|
|
177
|
+
byObject.set(storage, entry);
|
|
178
|
+
} else {
|
|
179
|
+
byIdentity.set(identity, entry);
|
|
180
|
+
}
|
|
181
|
+
return { storage: cache, lease: new Lease(entry) };
|
|
182
|
+
}
|