@optimystic/db-p2p 0.22.0 → 0.24.1
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/src/cluster/client.d.ts +10 -0
- package/dist/src/cluster/client.d.ts.map +1 -1
- package/dist/src/cluster/client.js +30 -1
- package/dist/src/cluster/client.js.map +1 -1
- package/dist/src/cluster/cluster-repo.d.ts +39 -14
- package/dist/src/cluster/cluster-repo.d.ts.map +1 -1
- package/dist/src/cluster/cluster-repo.js +226 -119
- package/dist/src/cluster/cluster-repo.js.map +1 -1
- package/dist/src/cluster/service.d.ts +32 -1
- package/dist/src/cluster/service.d.ts.map +1 -1
- package/dist/src/cluster/service.js +43 -2
- package/dist/src/cluster/service.js.map +1 -1
- package/dist/src/cohort-topic/host.js +34 -11
- package/dist/src/cohort-topic/host.js.map +1 -1
- package/dist/src/cohort-topic/stream-util.d.ts +37 -7
- package/dist/src/cohort-topic/stream-util.d.ts.map +1 -1
- package/dist/src/cohort-topic/stream-util.js +77 -19
- package/dist/src/cohort-topic/stream-util.js.map +1 -1
- package/dist/src/dispute/dispute-service.d.ts.map +1 -1
- package/dist/src/dispute/dispute-service.js +9 -3
- package/dist/src/dispute/dispute-service.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/libp2p-key-network.d.ts +88 -2
- package/dist/src/libp2p-key-network.d.ts.map +1 -1
- package/dist/src/libp2p-key-network.js +134 -28
- package/dist/src/libp2p-key-network.js.map +1 -1
- package/dist/src/libp2p-node-base.d.ts.map +1 -1
- package/dist/src/libp2p-node-base.js +25 -1
- package/dist/src/libp2p-node-base.js.map +1 -1
- package/dist/src/logger.d.ts +17 -1
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +19 -2
- package/dist/src/logger.js.map +1 -1
- package/dist/src/matchmaking/query-transport.js +3 -3
- package/dist/src/matchmaking/query-transport.js.map +1 -1
- package/dist/src/owned-block-seed.d.ts +6 -3
- package/dist/src/owned-block-seed.d.ts.map +1 -1
- package/dist/src/owned-block-seed.js +16 -3
- package/dist/src/owned-block-seed.js.map +1 -1
- package/dist/src/peer-address-book.d.ts +72 -0
- package/dist/src/peer-address-book.d.ts.map +1 -0
- package/dist/src/peer-address-book.js +123 -0
- package/dist/src/peer-address-book.js.map +1 -0
- package/dist/src/reactivity/notify-transport.d.ts +4 -4
- package/dist/src/reactivity/notify-transport.js +6 -6
- package/dist/src/reactivity/notify-transport.js.map +1 -1
- package/dist/src/reactivity/push-state-gossip.js +2 -2
- package/dist/src/reactivity/push-state-gossip.js.map +1 -1
- package/dist/src/reactivity/recover-transport.d.ts +6 -2
- package/dist/src/reactivity/recover-transport.d.ts.map +1 -1
- package/dist/src/reactivity/recover-transport.js +7 -3
- package/dist/src/reactivity/recover-transport.js.map +1 -1
- package/dist/src/repo/client.d.ts.map +1 -1
- package/dist/src/repo/client.js +11 -2
- package/dist/src/repo/client.js.map +1 -1
- package/dist/src/repo/cluster-coordinator.d.ts +30 -0
- package/dist/src/repo/cluster-coordinator.d.ts.map +1 -1
- package/dist/src/repo/cluster-coordinator.js +95 -3
- package/dist/src/repo/cluster-coordinator.js.map +1 -1
- package/dist/src/repo/coordinator-repo.d.ts +62 -9
- package/dist/src/repo/coordinator-repo.d.ts.map +1 -1
- package/dist/src/repo/coordinator-repo.js +242 -73
- package/dist/src/repo/coordinator-repo.js.map +1 -1
- package/dist/src/rn.d.ts +3 -0
- package/dist/src/rn.d.ts.map +1 -1
- package/dist/src/rn.js +3 -0
- package/dist/src/rn.js.map +1 -1
- package/dist/src/storage/cached-raw-storage.d.ts +83 -0
- package/dist/src/storage/cached-raw-storage.d.ts.map +1 -0
- package/dist/src/storage/cached-raw-storage.js +152 -0
- package/dist/src/storage/cached-raw-storage.js.map +1 -0
- package/dist/src/storage/cached-store-driver.d.ts +186 -0
- package/dist/src/storage/cached-store-driver.d.ts.map +1 -0
- package/dist/src/storage/cached-store-driver.js +775 -0
- package/dist/src/storage/cached-store-driver.js.map +1 -0
- package/dist/src/storage/i-raw-storage.d.ts +12 -5
- package/dist/src/storage/i-raw-storage.d.ts.map +1 -1
- package/dist/src/storage/shared-cache-pool.d.ts +234 -0
- package/dist/src/storage/shared-cache-pool.d.ts.map +1 -0
- package/dist/src/storage/shared-cache-pool.js +354 -0
- package/dist/src/storage/shared-cache-pool.js.map +1 -0
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts +13 -6
- package/dist/src/testing/cohort-topic-mesh-harness.d.ts.map +1 -1
- package/dist/src/testing/cohort-topic-mesh-harness.js +15 -6
- package/dist/src/testing/cohort-topic-mesh-harness.js.map +1 -1
- package/dist/src/testing/raw-storage-conformance.d.ts +2 -1
- package/dist/src/testing/raw-storage-conformance.d.ts.map +1 -1
- package/dist/src/testing/raw-storage-conformance.js +35 -2
- package/dist/src/testing/raw-storage-conformance.js.map +1 -1
- package/package.json +3 -3
- package/readme.md +668 -668
- package/src/cluster/block-transfer.ts +424 -424
- package/src/cluster/client.ts +119 -88
- package/src/cluster/cluster-error.ts +64 -64
- package/src/cluster/cluster-policy.ts +203 -203
- package/src/cluster/cluster-repo.ts +242 -122
- package/src/cluster/cluster-size-coupling.ts +45 -45
- package/src/cluster/commit-cert.ts +139 -139
- package/src/cluster/i-transaction-state-store.ts +43 -43
- package/src/cluster/memory-transaction-state-store.ts +56 -56
- package/src/cluster/peer-key-binding.ts +37 -37
- package/src/cluster/persistent-transaction-state-store.ts +92 -92
- package/src/cluster/quorum-restore.ts +223 -223
- package/src/cluster/reconcile-block.ts +203 -203
- package/src/cluster/service.ts +293 -241
- package/src/cluster/supermajority-coupling.ts +37 -37
- package/src/cohort-topic/bootstrap-evidence-builder.ts +122 -122
- package/src/cohort-topic/bootstrap-evidence-verifiers.ts +132 -132
- package/src/cohort-topic/bootstrap-parent-reference.ts +159 -159
- package/src/cohort-topic/change-bridge.ts +109 -109
- package/src/cohort-topic/cohort-gossip-driver.ts +231 -231
- package/src/cohort-topic/cohort-gossip-transport.ts +84 -84
- package/src/cohort-topic/fret-trust-anchor.ts +153 -153
- package/src/cohort-topic/host.ts +42 -11
- package/src/cohort-topic/index.ts +13 -13
- package/src/cohort-topic/membership-publish-sink.ts +20 -20
- package/src/cohort-topic/membership-source.ts +68 -68
- package/src/cohort-topic/peer-codec.ts +31 -31
- package/src/cohort-topic/peer-sig.ts +86 -86
- package/src/cohort-topic/protocols.ts +71 -71
- package/src/cohort-topic/reactivity-membership-gate.ts +77 -77
- package/src/cohort-topic/size-estimator.ts +16 -16
- package/src/cohort-topic/stream-util.ts +79 -19
- package/src/cohort-topic/threshold-crypto.ts +239 -239
- package/src/cohort-topic/topic-router.ts +77 -77
- package/src/dispute/arbitrator-selection.ts +138 -138
- package/src/dispute/cascade.ts +524 -524
- package/src/dispute/dispute-service.ts +11 -5
- package/src/dispute/invalidation.ts +625 -625
- package/src/inbound-authorization.ts +190 -190
- package/src/index.ts +52 -49
- package/src/libp2p-key-network.ts +1120 -990
- package/src/libp2p-node-base.ts +1675 -1651
- package/src/libp2p-node-rn.ts +30 -30
- package/src/libp2p-node.ts +36 -36
- package/src/logger.ts +19 -2
- package/src/matchmaking/aggregate-counts.ts +104 -104
- package/src/matchmaking/index.ts +20 -20
- package/src/matchmaking/module.ts +363 -363
- package/src/matchmaking/protocols.ts +51 -51
- package/src/matchmaking/provider-manager.ts +95 -95
- package/src/matchmaking/query-handler.ts +88 -88
- package/src/matchmaking/query-transport.ts +3 -3
- package/src/matchmaking/seeker-manager.ts +64 -64
- package/src/matchmaking/seeker-walk-client.ts +293 -293
- package/src/matchmaking/traffic-validation.ts +195 -195
- package/src/optimystic-node.ts +36 -36
- package/src/owned-block-seed.ts +53 -40
- package/src/peer-address-book.ts +149 -0
- package/src/protocol-limits.ts +33 -33
- package/src/reactivity/forwarder-host.ts +438 -438
- package/src/reactivity/index.ts +19 -19
- package/src/reactivity/notify-transport.ts +144 -144
- package/src/reactivity/origination-manager.ts +192 -192
- package/src/reactivity/protocols.ts +61 -61
- package/src/reactivity/push-state-gossip.ts +291 -291
- package/src/reactivity/recover-transport.ts +7 -3
- package/src/reactivity/rotation-rereg-scheduler.ts +256 -256
- package/src/reactivity/subscriber-registry.ts +96 -96
- package/src/reactivity/subscription-manager.ts +450 -450
- package/src/reactivity/topic-bytes.ts +37 -37
- package/src/repo/client.ts +12 -2
- package/src/repo/cluster-coordinator.ts +99 -3
- package/src/repo/coordinator-repo.ts +281 -74
- package/src/repo/types.ts +7 -7
- package/src/rn.ts +39 -36
- package/src/rpc-deadline.ts +45 -45
- package/src/storage/arachnode-partition.ts +74 -74
- package/src/storage/cached-raw-storage.ts +180 -0
- package/src/storage/cached-store-driver.ts +859 -0
- package/src/storage/i-kv-store.ts +8 -8
- package/src/storage/i-raw-storage.ts +12 -5
- package/src/storage/kv-raw-storage.ts +135 -135
- package/src/storage/memory-kv-store.ts +28 -28
- package/src/storage/memory-storage.ts +25 -25
- package/src/storage/memory-store-driver.ts +157 -157
- package/src/storage/raw-store-codec.ts +42 -42
- package/src/storage/raw-store-driver.ts +80 -80
- package/src/storage/ring-selector.ts +317 -317
- package/src/storage/ring-shift-coordinator.ts +271 -271
- package/src/storage/shared-cache-pool.ts +452 -0
- package/src/storage/storage-repo.ts +1014 -1014
- package/src/testing/cohort-topic-mesh-harness.ts +673 -663
- package/src/testing/index.ts +8 -8
- package/src/testing/matchmaking-mesh-harness.ts +475 -475
- package/src/testing/raw-storage-conformance.ts +453 -417
- package/src/testing/reactivity-mesh-harness.ts +922 -922
- package/dist/src/storage/restoration-coordinator-v2.d.ts +0 -67
- package/dist/src/storage/restoration-coordinator-v2.d.ts.map +0 -1
- package/dist/src/storage/restoration-coordinator-v2.js +0 -172
- package/dist/src/storage/restoration-coordinator-v2.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** Portable async key-value store. Platform packages provide implementations. */
|
|
2
|
-
export interface IKVStore {
|
|
3
|
-
get(key: string): Promise<string | undefined>;
|
|
4
|
-
set(key: string, value: string): Promise<void>;
|
|
5
|
-
delete(key: string): Promise<void>;
|
|
6
|
-
/** Return all keys matching the given prefix */
|
|
7
|
-
list(prefix: string): Promise<string[]>;
|
|
8
|
-
}
|
|
1
|
+
/** Portable async key-value store. Platform packages provide implementations. */
|
|
2
|
+
export interface IKVStore {
|
|
3
|
+
get(key: string): Promise<string | undefined>;
|
|
4
|
+
set(key: string, value: string): Promise<void>;
|
|
5
|
+
delete(key: string): Promise<void>;
|
|
6
|
+
/** Return all keys matching the given prefix */
|
|
7
|
+
list(prefix: string): Promise<string[]>;
|
|
8
|
+
}
|
|
@@ -39,11 +39,18 @@ export interface IRawStorage {
|
|
|
39
39
|
getApproximateBytesUsed?(): Promise<number>;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
|
-
* Enumerate the block ids that currently have durable state in this backend
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
42
|
+
* Enumerate the block ids that currently have durable state in this backend —
|
|
43
|
+
* one id per block with ANY durable metadata. That includes a block whose only
|
|
44
|
+
* durable state is an uncommitted pending transform, since
|
|
45
|
+
* `BlockStorage.savePendingTransaction` writes metadata for a block that has none
|
|
46
|
+
* before storing the transform. Implementations enumerate metadata keys only; they
|
|
47
|
+
* must NOT read or decode each block's metadata to filter by committed revision
|
|
48
|
+
* (that would turn a cheap key scan into a per-block read).
|
|
49
|
+
*
|
|
50
|
+
* Used at node startup to seed the resilience monitors' owned-block tracked set
|
|
51
|
+
* from blocks already on disk from a previous run, so churn-spread / rebalance
|
|
52
|
+
* protection covers them without waiting for each to be touched again. See the
|
|
53
|
+
* NOTE in `seedOwnedBlocksFromStorage` for why the over-inclusion is accepted.
|
|
47
54
|
*
|
|
48
55
|
* Streamed (AsyncIterable) so a large store does not force the whole id list
|
|
49
56
|
* into memory at once. Order is unspecified. Optional: a backend that omits it
|
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
import type { BlockId, ActionId, ActionRev, Transform, IBlock } from "@optimystic/db-core";
|
|
2
|
-
import type { BlockMetadata } from "./struct.js";
|
|
3
|
-
import type { IRawStorage } from "./i-raw-storage.js";
|
|
4
|
-
import type { RawStoreDriver } from "./raw-store-driver.js";
|
|
5
|
-
import { encodeJson, decodeJson, encodeActionId, decodeActionId } from "./raw-store-codec.js";
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Shared ordered-KV storage kernel. Implements the full {@link IRawStorage}
|
|
9
|
-
* surface over a {@link RawStoreDriver}, owning all value (de)serialization and
|
|
10
|
-
* call orchestration so each backend only has to expose its five logical stores
|
|
11
|
-
* as bytes-valued maps over its native mechanism.
|
|
12
|
-
*
|
|
13
|
-
* The genuinely-shared logic lives ABOVE the storage primitive: the JSON/string
|
|
14
|
-
* codec for the four value types, `listRevisions`' lo/hi/reverse bound
|
|
15
|
-
* computation, `saveMaterializedBlock`'s put-or-delete branch, and the
|
|
16
|
-
* passthroughs. Key layout / storage topology stays in the driver, because the
|
|
17
|
-
* backends do NOT share one — LevelDB is a single ordered byte keyspace, SQLite
|
|
18
|
-
* five relational tables, IndexedDB five object stores, the filesystem a
|
|
19
|
-
* directory tree.
|
|
20
|
-
*/
|
|
21
|
-
export class KvRawStorage implements IRawStorage {
|
|
22
|
-
/**
|
|
23
|
-
* Optional passthroughs are wired in the constructor ONLY when the driver
|
|
24
|
-
* provides them, so a `StorageMonitor` / owned-block seed that feature-detects
|
|
25
|
-
* (`typeof storage.listBlockIds === 'function'`) sees the driver's true
|
|
26
|
-
* capability instead of a stub that silently reports 0 / no seed.
|
|
27
|
-
*/
|
|
28
|
-
listBlockIds?: () => AsyncIterable<BlockId>;
|
|
29
|
-
getApproximateBytesUsed?: () => Promise<number>;
|
|
30
|
-
|
|
31
|
-
constructor(private readonly driver: RawStoreDriver) {
|
|
32
|
-
if (driver.listBlockIds) {
|
|
33
|
-
this.listBlockIds = () => driver.listBlockIds!();
|
|
34
|
-
}
|
|
35
|
-
if (driver.approximateBytesUsed) {
|
|
36
|
-
this.getApproximateBytesUsed = () => driver.approximateBytesUsed!();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// --- Metadata ---
|
|
41
|
-
|
|
42
|
-
async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
|
|
43
|
-
const bytes = await this.driver.getMetadata(blockId);
|
|
44
|
-
return bytes === undefined ? undefined : decodeJson<BlockMetadata>(bytes);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// NOTE: every value write funnels through the driver put/delete calls in the
|
|
48
|
-
// methods below (saveMetadata / saveRevision / save*Transaction /
|
|
49
|
-
// saveMaterializedBlock). This is the single choke point where an incremental
|
|
50
|
-
// byte counter would hook in, replacing the per-driver full-scan
|
|
51
|
-
// getApproximateBytesUsed. The chosen capacity-estimate mechanism is instead a
|
|
52
|
-
// short-TTL cache over the full scan in StorageMonitor (see storage-monitor.ts
|
|
53
|
-
// `usedBytesCacheTtlMs`) — backend-agnostic, no write-path changes, and ring
|
|
54
|
-
// selection tolerates the bounded staleness by design. The counter remains a
|
|
55
|
-
// future option only if TTL staleness ever proves insufficient; do NOT
|
|
56
|
-
// implement it here — leave the write path a single seam.
|
|
57
|
-
async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
|
|
58
|
-
await this.driver.putMetadata(blockId, encodeJson(metadata));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// --- Revisions ---
|
|
62
|
-
|
|
63
|
-
async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
|
|
64
|
-
const bytes = await this.driver.getRevision(blockId, rev);
|
|
65
|
-
return bytes === undefined ? undefined : decodeActionId(bytes);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
|
|
69
|
-
await this.driver.putRevision(blockId, rev, encodeActionId(actionId));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
|
|
73
|
-
// Both bounds inclusive; ordering follows startRev→endRev direction. Empty
|
|
74
|
-
// revs are skipped by the driver's range. Preserves the exact semantics of
|
|
75
|
-
// every existing backend.
|
|
76
|
-
const ascending = startRev <= endRev;
|
|
77
|
-
const lo = ascending ? startRev : endRev;
|
|
78
|
-
const hi = ascending ? endRev : startRev;
|
|
79
|
-
for await (const [rev, bytes] of this.driver.rangeRevisions(blockId, lo, hi, !ascending)) {
|
|
80
|
-
yield { rev, actionId: decodeActionId(bytes) };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// --- Pending transactions ---
|
|
85
|
-
|
|
86
|
-
async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
87
|
-
const bytes = await this.driver.getPending(blockId, actionId);
|
|
88
|
-
return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
92
|
-
await this.driver.putPending(blockId, actionId, encodeJson(transform));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
96
|
-
await this.driver.deletePending(blockId, actionId);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
|
|
100
|
-
yield* this.driver.listPendingActionIds(blockId);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// --- Committed transactions ---
|
|
104
|
-
|
|
105
|
-
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
106
|
-
const bytes = await this.driver.getTransaction(blockId, actionId);
|
|
107
|
-
return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
111
|
-
await this.driver.putTransaction(blockId, actionId, encodeJson(transform));
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// --- Materialized blocks ---
|
|
115
|
-
|
|
116
|
-
async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
|
|
117
|
-
const bytes = await this.driver.getMaterialized(blockId, actionId);
|
|
118
|
-
return bytes === undefined ? undefined : decodeJson<IBlock>(bytes);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
|
|
122
|
-
// Present ⇒ put the materialized block; absent ⇒ delete (tombstone / truncation).
|
|
123
|
-
if (block) {
|
|
124
|
-
await this.driver.putMaterialized(blockId, actionId, encodeJson(block));
|
|
125
|
-
} else {
|
|
126
|
-
await this.driver.deleteMaterialized(blockId, actionId);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// --- Promote (the only cross-key atomic op; driver owns atomicity) ---
|
|
131
|
-
|
|
132
|
-
async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
133
|
-
await this.driver.promote(blockId, actionId);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
1
|
+
import type { BlockId, ActionId, ActionRev, Transform, IBlock } from "@optimystic/db-core";
|
|
2
|
+
import type { BlockMetadata } from "./struct.js";
|
|
3
|
+
import type { IRawStorage } from "./i-raw-storage.js";
|
|
4
|
+
import type { RawStoreDriver } from "./raw-store-driver.js";
|
|
5
|
+
import { encodeJson, decodeJson, encodeActionId, decodeActionId } from "./raw-store-codec.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Shared ordered-KV storage kernel. Implements the full {@link IRawStorage}
|
|
9
|
+
* surface over a {@link RawStoreDriver}, owning all value (de)serialization and
|
|
10
|
+
* call orchestration so each backend only has to expose its five logical stores
|
|
11
|
+
* as bytes-valued maps over its native mechanism.
|
|
12
|
+
*
|
|
13
|
+
* The genuinely-shared logic lives ABOVE the storage primitive: the JSON/string
|
|
14
|
+
* codec for the four value types, `listRevisions`' lo/hi/reverse bound
|
|
15
|
+
* computation, `saveMaterializedBlock`'s put-or-delete branch, and the
|
|
16
|
+
* passthroughs. Key layout / storage topology stays in the driver, because the
|
|
17
|
+
* backends do NOT share one — LevelDB is a single ordered byte keyspace, SQLite
|
|
18
|
+
* five relational tables, IndexedDB five object stores, the filesystem a
|
|
19
|
+
* directory tree.
|
|
20
|
+
*/
|
|
21
|
+
export class KvRawStorage implements IRawStorage {
|
|
22
|
+
/**
|
|
23
|
+
* Optional passthroughs are wired in the constructor ONLY when the driver
|
|
24
|
+
* provides them, so a `StorageMonitor` / owned-block seed that feature-detects
|
|
25
|
+
* (`typeof storage.listBlockIds === 'function'`) sees the driver's true
|
|
26
|
+
* capability instead of a stub that silently reports 0 / no seed.
|
|
27
|
+
*/
|
|
28
|
+
listBlockIds?: () => AsyncIterable<BlockId>;
|
|
29
|
+
getApproximateBytesUsed?: () => Promise<number>;
|
|
30
|
+
|
|
31
|
+
constructor(private readonly driver: RawStoreDriver) {
|
|
32
|
+
if (driver.listBlockIds) {
|
|
33
|
+
this.listBlockIds = () => driver.listBlockIds!();
|
|
34
|
+
}
|
|
35
|
+
if (driver.approximateBytesUsed) {
|
|
36
|
+
this.getApproximateBytesUsed = () => driver.approximateBytesUsed!();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- Metadata ---
|
|
41
|
+
|
|
42
|
+
async getMetadata(blockId: BlockId): Promise<BlockMetadata | undefined> {
|
|
43
|
+
const bytes = await this.driver.getMetadata(blockId);
|
|
44
|
+
return bytes === undefined ? undefined : decodeJson<BlockMetadata>(bytes);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// NOTE: every value write funnels through the driver put/delete calls in the
|
|
48
|
+
// methods below (saveMetadata / saveRevision / save*Transaction /
|
|
49
|
+
// saveMaterializedBlock). This is the single choke point where an incremental
|
|
50
|
+
// byte counter would hook in, replacing the per-driver full-scan
|
|
51
|
+
// getApproximateBytesUsed. The chosen capacity-estimate mechanism is instead a
|
|
52
|
+
// short-TTL cache over the full scan in StorageMonitor (see storage-monitor.ts
|
|
53
|
+
// `usedBytesCacheTtlMs`) — backend-agnostic, no write-path changes, and ring
|
|
54
|
+
// selection tolerates the bounded staleness by design. The counter remains a
|
|
55
|
+
// future option only if TTL staleness ever proves insufficient; do NOT
|
|
56
|
+
// implement it here — leave the write path a single seam.
|
|
57
|
+
async saveMetadata(blockId: BlockId, metadata: BlockMetadata): Promise<void> {
|
|
58
|
+
await this.driver.putMetadata(blockId, encodeJson(metadata));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// --- Revisions ---
|
|
62
|
+
|
|
63
|
+
async getRevision(blockId: BlockId, rev: number): Promise<ActionId | undefined> {
|
|
64
|
+
const bytes = await this.driver.getRevision(blockId, rev);
|
|
65
|
+
return bytes === undefined ? undefined : decodeActionId(bytes);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async saveRevision(blockId: BlockId, rev: number, actionId: ActionId): Promise<void> {
|
|
69
|
+
await this.driver.putRevision(blockId, rev, encodeActionId(actionId));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async *listRevisions(blockId: BlockId, startRev: number, endRev: number): AsyncIterable<ActionRev> {
|
|
73
|
+
// Both bounds inclusive; ordering follows startRev→endRev direction. Empty
|
|
74
|
+
// revs are skipped by the driver's range. Preserves the exact semantics of
|
|
75
|
+
// every existing backend.
|
|
76
|
+
const ascending = startRev <= endRev;
|
|
77
|
+
const lo = ascending ? startRev : endRev;
|
|
78
|
+
const hi = ascending ? endRev : startRev;
|
|
79
|
+
for await (const [rev, bytes] of this.driver.rangeRevisions(blockId, lo, hi, !ascending)) {
|
|
80
|
+
yield { rev, actionId: decodeActionId(bytes) };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- Pending transactions ---
|
|
85
|
+
|
|
86
|
+
async getPendingTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
87
|
+
const bytes = await this.driver.getPending(blockId, actionId);
|
|
88
|
+
return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async savePendingTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
92
|
+
await this.driver.putPending(blockId, actionId, encodeJson(transform));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async deletePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
96
|
+
await this.driver.deletePending(blockId, actionId);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async *listPendingTransactions(blockId: BlockId): AsyncIterable<ActionId> {
|
|
100
|
+
yield* this.driver.listPendingActionIds(blockId);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// --- Committed transactions ---
|
|
104
|
+
|
|
105
|
+
async getTransaction(blockId: BlockId, actionId: ActionId): Promise<Transform | undefined> {
|
|
106
|
+
const bytes = await this.driver.getTransaction(blockId, actionId);
|
|
107
|
+
return bytes === undefined ? undefined : decodeJson<Transform>(bytes);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async saveTransaction(blockId: BlockId, actionId: ActionId, transform: Transform): Promise<void> {
|
|
111
|
+
await this.driver.putTransaction(blockId, actionId, encodeJson(transform));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Materialized blocks ---
|
|
115
|
+
|
|
116
|
+
async getMaterializedBlock(blockId: BlockId, actionId: ActionId): Promise<IBlock | undefined> {
|
|
117
|
+
const bytes = await this.driver.getMaterialized(blockId, actionId);
|
|
118
|
+
return bytes === undefined ? undefined : decodeJson<IBlock>(bytes);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async saveMaterializedBlock(blockId: BlockId, actionId: ActionId, block?: IBlock): Promise<void> {
|
|
122
|
+
// Present ⇒ put the materialized block; absent ⇒ delete (tombstone / truncation).
|
|
123
|
+
if (block) {
|
|
124
|
+
await this.driver.putMaterialized(blockId, actionId, encodeJson(block));
|
|
125
|
+
} else {
|
|
126
|
+
await this.driver.deleteMaterialized(blockId, actionId);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// --- Promote (the only cross-key atomic op; driver owns atomicity) ---
|
|
131
|
+
|
|
132
|
+
async promotePendingTransaction(blockId: BlockId, actionId: ActionId): Promise<void> {
|
|
133
|
+
await this.driver.promote(blockId, actionId);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import type { IKVStore } from "./i-kv-store.js";
|
|
2
|
-
|
|
3
|
-
/** In-memory IKVStore backed by a Map. Used for testing. */
|
|
4
|
-
export class MemoryKVStore implements IKVStore {
|
|
5
|
-
private readonly store = new Map<string, string>();
|
|
6
|
-
|
|
7
|
-
async get(key: string): Promise<string | undefined> {
|
|
8
|
-
return this.store.get(key);
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async set(key: string, value: string): Promise<void> {
|
|
12
|
-
this.store.set(key, value);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async delete(key: string): Promise<void> {
|
|
16
|
-
this.store.delete(key);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async list(prefix: string): Promise<string[]> {
|
|
20
|
-
const result: string[] = [];
|
|
21
|
-
for (const key of this.store.keys()) {
|
|
22
|
-
if (key.startsWith(prefix)) {
|
|
23
|
-
result.push(key);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return result;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
import type { IKVStore } from "./i-kv-store.js";
|
|
2
|
+
|
|
3
|
+
/** In-memory IKVStore backed by a Map. Used for testing. */
|
|
4
|
+
export class MemoryKVStore implements IKVStore {
|
|
5
|
+
private readonly store = new Map<string, string>();
|
|
6
|
+
|
|
7
|
+
async get(key: string): Promise<string | undefined> {
|
|
8
|
+
return this.store.get(key);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async set(key: string, value: string): Promise<void> {
|
|
12
|
+
this.store.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async delete(key: string): Promise<void> {
|
|
16
|
+
this.store.delete(key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async list(prefix: string): Promise<string[]> {
|
|
20
|
+
const result: string[] = [];
|
|
21
|
+
for (const key of this.store.keys()) {
|
|
22
|
+
if (key.startsWith(prefix)) {
|
|
23
|
+
result.push(key);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { KvRawStorage } from "./kv-raw-storage.js";
|
|
2
|
-
import { MemoryStoreDriver } from "./memory-store-driver.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* In-memory {@link IRawStorage}, now a thin shell over the shared
|
|
6
|
-
* {@link KvRawStorage} kernel backed by a {@link MemoryStoreDriver}.
|
|
7
|
-
*
|
|
8
|
-
* @pitfall The old hand-rolled version had to `structuredClone` on every get and
|
|
9
|
-
* put — otherwise callers mutating a returned/stored object corrupted the store
|
|
10
|
-
* (see docs/internals.md "Storage Returns References"). That discipline is GONE
|
|
11
|
-
* and must not be reintroduced: values cross the driver boundary as
|
|
12
|
-
* `Uint8Array` produced by `JSON`-encode and consumed by `JSON`-decode, so every
|
|
13
|
-
* read decodes a fresh object and every write stored an independent byte
|
|
14
|
-
* snapshot BY CONSTRUCTION. The clone-on-store / clone-on-read invariant is now
|
|
15
|
-
* structural, not a rule. The conformance suite asserts it so a future driver
|
|
16
|
-
* that shortcuts the byte boundary is caught.
|
|
17
|
-
*
|
|
18
|
-
* The name is kept stable so existing imports (`from '@optimystic/db-p2p'`)
|
|
19
|
-
* continue to resolve.
|
|
20
|
-
*/
|
|
21
|
-
export class MemoryRawStorage extends KvRawStorage {
|
|
22
|
-
constructor() {
|
|
23
|
-
super(new MemoryStoreDriver());
|
|
24
|
-
}
|
|
25
|
-
}
|
|
1
|
+
import { KvRawStorage } from "./kv-raw-storage.js";
|
|
2
|
+
import { MemoryStoreDriver } from "./memory-store-driver.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* In-memory {@link IRawStorage}, now a thin shell over the shared
|
|
6
|
+
* {@link KvRawStorage} kernel backed by a {@link MemoryStoreDriver}.
|
|
7
|
+
*
|
|
8
|
+
* @pitfall The old hand-rolled version had to `structuredClone` on every get and
|
|
9
|
+
* put — otherwise callers mutating a returned/stored object corrupted the store
|
|
10
|
+
* (see docs/internals.md "Storage Returns References"). That discipline is GONE
|
|
11
|
+
* and must not be reintroduced: values cross the driver boundary as
|
|
12
|
+
* `Uint8Array` produced by `JSON`-encode and consumed by `JSON`-decode, so every
|
|
13
|
+
* read decodes a fresh object and every write stored an independent byte
|
|
14
|
+
* snapshot BY CONSTRUCTION. The clone-on-store / clone-on-read invariant is now
|
|
15
|
+
* structural, not a rule. The conformance suite asserts it so a future driver
|
|
16
|
+
* that shortcuts the byte boundary is caught.
|
|
17
|
+
*
|
|
18
|
+
* The name is kept stable so existing imports (`from '@optimystic/db-p2p'`)
|
|
19
|
+
* continue to resolve.
|
|
20
|
+
*/
|
|
21
|
+
export class MemoryRawStorage extends KvRawStorage {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(new MemoryStoreDriver());
|
|
24
|
+
}
|
|
25
|
+
}
|