@feltdb/core 0.4.12 → 0.4.13
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/cli/commands.js +5 -2
- package/dist/cli/index.js +1 -1
- package/dist/create/cli.js +16 -1
- package/dist/create/create.js +30 -16
- package/dist/create/docker-compose-generator.js +48 -9
- package/dist/create/package-versions.js +1 -1
- package/dist/create/server-source/Cargo.lock +2238 -0
- package/dist/create/server-source/Cargo.toml +7 -0
- package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
- package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
- package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
- package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
- package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
- package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
- package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
- package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
- package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
- package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
- package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
- package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
- package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
- package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
- package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
- package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
- package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
- package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
- package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
- package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
- package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
- package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
- package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
- package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
- package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
- package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
- package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
- package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
- package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
- package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
- package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
- package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
- package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
- package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
- package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
- package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
- package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
- package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
- package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
- package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
- package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
- package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
- package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
- package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
- package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
- package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
- package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
- package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
- package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
- package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
- package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
- package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
- package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
- package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
- package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
- package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
- package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
- package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
- package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
- package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
- package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
- package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
- package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
- package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
- package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
- package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
- package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
- package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
- package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
- package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
- package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
- package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
- package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
- package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
- package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
- package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
- package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
- package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
- package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
- package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
- package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
- package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
- package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
- package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
- package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
- package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
- package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
- package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
- package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
- package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
- package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
- package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
- package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
- package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
- package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
- package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
- package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
- package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
- package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
- package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
- package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
- package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
- package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
- package/dist/flowspec.d.ts +2 -0
- package/dist/flowspec.d.ts.map +1 -1
- package/dist/flowspec.js +28 -4
- package/dist/state-contract.d.ts +7 -1
- package/dist/state-contract.d.ts.map +1 -1
- package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
- package/dist/studio/components/index.js +1 -1
- package/dist/studio/{components-Q0Nx6gLE.js → components-bHTARBin.js} +156 -46
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-BHOmLZF-.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
use crate::Operation;
|
|
2
|
+
/// Sync Engine module for peer-to-peer synchronization
|
|
3
|
+
use serde::{Deserialize, Serialize};
|
|
4
|
+
use std::collections::{HashMap, VecDeque};
|
|
5
|
+
|
|
6
|
+
/// A change log that tracks pending operations for synchronization
|
|
7
|
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
8
|
+
pub struct ChangeLog {
|
|
9
|
+
/// Operations that have been applied locally but not yet acknowledged by peers
|
|
10
|
+
pub pending: VecDeque<Operation>,
|
|
11
|
+
/// Last sequence number we've applied from each peer
|
|
12
|
+
pub peer_versions: HashMap<String, u64>,
|
|
13
|
+
/// Contiguous high-water mark observed from every operation origin.
|
|
14
|
+
#[serde(default)]
|
|
15
|
+
pub observed_versions: HashMap<String, u64>,
|
|
16
|
+
/// Per-peer acknowledgement vector, keyed by operation origin.
|
|
17
|
+
#[serde(default)]
|
|
18
|
+
pub peer_acknowledgements: HashMap<String, HashMap<String, u64>>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl ChangeLog {
|
|
22
|
+
/// Create a new empty change log
|
|
23
|
+
pub fn new() -> Self {
|
|
24
|
+
Self::default()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Add an operation to the pending queue
|
|
28
|
+
pub fn add_operation(&mut self, op: Operation) {
|
|
29
|
+
if !self.has_operation(&op.instance_id, op.sequence) {
|
|
30
|
+
self.observed_versions
|
|
31
|
+
.entry(op.instance_id.clone())
|
|
32
|
+
.and_modify(|sequence| *sequence = (*sequence).max(op.sequence))
|
|
33
|
+
.or_insert(op.sequence);
|
|
34
|
+
self.pending.push_back(op);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
pub fn has_operation(&self, instance_id: &str, sequence: u64) -> bool {
|
|
39
|
+
self.observed_versions
|
|
40
|
+
.get(instance_id)
|
|
41
|
+
.copied()
|
|
42
|
+
.unwrap_or(0)
|
|
43
|
+
>= sequence
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Return the operations missing from a caller's per-origin version vector.
|
|
47
|
+
pub fn operations_since(&self, versions: &HashMap<String, u64>) -> Vec<Operation> {
|
|
48
|
+
self.pending
|
|
49
|
+
.iter()
|
|
50
|
+
.filter(|op| op.sequence > versions.get(&op.instance_id).copied().unwrap_or(0))
|
|
51
|
+
.cloned()
|
|
52
|
+
.collect()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Highest operation sequence observed for every originating instance.
|
|
56
|
+
pub fn versions(&self) -> HashMap<String, u64> {
|
|
57
|
+
self.observed_versions.clone()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Get all pending operations
|
|
61
|
+
pub fn pending_operations(&self) -> Vec<Operation> {
|
|
62
|
+
self.pending.iter().cloned().collect()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Acknowledge operations up to a given sequence number from a peer
|
|
66
|
+
pub fn acknowledge_peer(&mut self, peer_id: String, sequence: u64) {
|
|
67
|
+
self.peer_versions.insert(peer_id, sequence);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn acknowledge_versions(&mut self, peer_id: String, versions: HashMap<String, u64>) {
|
|
71
|
+
let acknowledgements = self.peer_acknowledgements.entry(peer_id).or_default();
|
|
72
|
+
for (origin, sequence) in versions {
|
|
73
|
+
acknowledgements
|
|
74
|
+
.entry(origin)
|
|
75
|
+
.and_modify(|current| *current = (*current).max(sequence))
|
|
76
|
+
.or_insert(sequence);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pub fn acknowledged_versions(&self, peer_id: &str) -> HashMap<String, u64> {
|
|
81
|
+
self.peer_acknowledgements
|
|
82
|
+
.get(peer_id)
|
|
83
|
+
.cloned()
|
|
84
|
+
.unwrap_or_default()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub fn acknowledged_operation_count(&self, active_peers: &[String]) -> usize {
|
|
88
|
+
if active_peers.is_empty() {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
self.pending
|
|
92
|
+
.iter()
|
|
93
|
+
.filter(|operation| {
|
|
94
|
+
active_peers.iter().all(|peer| {
|
|
95
|
+
self.peer_acknowledgements
|
|
96
|
+
.get(peer)
|
|
97
|
+
.and_then(|versions| versions.get(&operation.instance_id))
|
|
98
|
+
.copied()
|
|
99
|
+
.unwrap_or(0)
|
|
100
|
+
>= operation.sequence
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
.count()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
pub fn prune_acknowledged(&mut self, active_peers: &[String]) -> usize {
|
|
107
|
+
if active_peers.is_empty() {
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
let acknowledgements = &self.peer_acknowledgements;
|
|
111
|
+
let before = self.pending.len();
|
|
112
|
+
self.pending.retain(|operation| {
|
|
113
|
+
!active_peers.iter().all(|peer| {
|
|
114
|
+
acknowledgements
|
|
115
|
+
.get(peer)
|
|
116
|
+
.and_then(|versions| versions.get(&operation.instance_id))
|
|
117
|
+
.copied()
|
|
118
|
+
.unwrap_or(0)
|
|
119
|
+
>= operation.sequence
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
before - self.pending.len()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// Get the last acknowledged sequence for a peer
|
|
126
|
+
pub fn last_peer_sequence(&self, peer_id: &str) -> u64 {
|
|
127
|
+
*self.peer_versions.get(peer_id).unwrap_or(&0)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Remove operations that have been acknowledged by all peers
|
|
131
|
+
pub fn cleanup_acknowledged(&mut self, _active_peers: &[String]) {
|
|
132
|
+
// For now, we keep all operations for replay
|
|
133
|
+
// In production, we'd remove operations that all peers have acknowledged
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// State of a peer's synchronization
|
|
138
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
139
|
+
pub struct PeerState {
|
|
140
|
+
/// Unique peer identifier
|
|
141
|
+
pub peer_id: String,
|
|
142
|
+
/// Last version we received from this peer
|
|
143
|
+
pub last_received_version: u64,
|
|
144
|
+
/// Operations pending delivery to this peer
|
|
145
|
+
pub pending_for_delivery: VecDeque<Operation>,
|
|
146
|
+
/// Whether we're currently syncing with this peer
|
|
147
|
+
pub is_syncing: bool,
|
|
148
|
+
/// Last sync timestamp
|
|
149
|
+
pub last_sync_ms: u128,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
impl PeerState {
|
|
153
|
+
/// Create a new peer state
|
|
154
|
+
pub fn new(peer_id: String) -> Self {
|
|
155
|
+
Self {
|
|
156
|
+
peer_id,
|
|
157
|
+
last_received_version: 0,
|
|
158
|
+
pending_for_delivery: VecDeque::new(),
|
|
159
|
+
is_syncing: false,
|
|
160
|
+
last_sync_ms: 0,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Mark operations for delivery to this peer
|
|
165
|
+
pub fn add_pending(&mut self, op: Operation) {
|
|
166
|
+
self.pending_for_delivery.push_back(op);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/// Get operations pending delivery
|
|
170
|
+
pub fn pending_for_delivery(&self) -> Vec<Operation> {
|
|
171
|
+
self.pending_for_delivery.iter().cloned().collect()
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/// Clear pending deliveries after successful sync
|
|
175
|
+
pub fn clear_pending(&mut self) {
|
|
176
|
+
self.pending_for_delivery.clear();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Represents a conflict between local and remote operations
|
|
181
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
182
|
+
pub struct Conflict {
|
|
183
|
+
/// The entity that has conflicting changes
|
|
184
|
+
pub entity_key: String,
|
|
185
|
+
/// The local operation
|
|
186
|
+
pub local_op: Operation,
|
|
187
|
+
/// The remote operation
|
|
188
|
+
pub remote_op: Operation,
|
|
189
|
+
/// Timestamp when conflict was detected
|
|
190
|
+
pub detected_at_ms: u128,
|
|
191
|
+
/// Whether this conflict has been resolved
|
|
192
|
+
pub resolved: bool,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl Conflict {
|
|
196
|
+
/// Create a new conflict
|
|
197
|
+
pub fn new(entity_key: String, local_op: Operation, remote_op: Operation) -> Self {
|
|
198
|
+
use std::time::{SystemTime, UNIX_EPOCH};
|
|
199
|
+
let now_ms = SystemTime::now()
|
|
200
|
+
.duration_since(UNIX_EPOCH)
|
|
201
|
+
.map(|d| d.as_millis())
|
|
202
|
+
.unwrap_or(0);
|
|
203
|
+
|
|
204
|
+
Self {
|
|
205
|
+
entity_key,
|
|
206
|
+
local_op,
|
|
207
|
+
remote_op,
|
|
208
|
+
detected_at_ms: now_ms,
|
|
209
|
+
resolved: false,
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/// Conflict detector for identifying conflicts between operations
|
|
215
|
+
#[derive(Debug, Default)]
|
|
216
|
+
pub struct ConflictDetector {
|
|
217
|
+
/// Detected conflicts
|
|
218
|
+
conflicts: Vec<Conflict>,
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
impl ConflictDetector {
|
|
222
|
+
/// Create a new conflict detector
|
|
223
|
+
pub fn new() -> Self {
|
|
224
|
+
Self::default()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// Detect conflicts between local and remote operations
|
|
228
|
+
pub fn detect_conflict(
|
|
229
|
+
&mut self,
|
|
230
|
+
local_op: &Operation,
|
|
231
|
+
remote_op: &Operation,
|
|
232
|
+
) -> Option<Conflict> {
|
|
233
|
+
// Detect write-write conflicts (both operations modify the same entity)
|
|
234
|
+
if local_op.key == remote_op.key
|
|
235
|
+
&& local_op.capability == remote_op.capability
|
|
236
|
+
&& local_op.op_type != crate::operation::OperationType::Delete
|
|
237
|
+
&& remote_op.op_type != crate::operation::OperationType::Delete
|
|
238
|
+
{
|
|
239
|
+
let conflict = Conflict::new(local_op.key.clone(), local_op.clone(), remote_op.clone());
|
|
240
|
+
self.conflicts.push(conflict.clone());
|
|
241
|
+
return Some(conflict);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
None
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Get all detected conflicts
|
|
248
|
+
pub fn get_conflicts(&self) -> Vec<Conflict> {
|
|
249
|
+
self.conflicts.clone()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Clear resolved conflicts
|
|
253
|
+
pub fn clear_resolved(&mut self) {
|
|
254
|
+
self.conflicts.retain(|c| !c.resolved);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Sync protocol messages for peer-to-peer synchronization
|
|
259
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
260
|
+
pub enum SyncMessage {
|
|
261
|
+
/// Handshake to establish connection
|
|
262
|
+
Handshake {
|
|
263
|
+
instance_id: String,
|
|
264
|
+
version: String,
|
|
265
|
+
},
|
|
266
|
+
/// Advertise current state version
|
|
267
|
+
StateVersion { instance_id: String, sequence: u64 },
|
|
268
|
+
/// Request missing operations
|
|
269
|
+
MissingOperations {
|
|
270
|
+
instance_id: String,
|
|
271
|
+
since_sequence: u64,
|
|
272
|
+
},
|
|
273
|
+
/// Transfer operations to peer
|
|
274
|
+
OperationTransfer {
|
|
275
|
+
instance_id: String,
|
|
276
|
+
operations: Vec<Operation>,
|
|
277
|
+
},
|
|
278
|
+
/// Acknowledge receipt of operations
|
|
279
|
+
Acknowledgement {
|
|
280
|
+
instance_id: String,
|
|
281
|
+
up_to_sequence: u64,
|
|
282
|
+
},
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Overall sync state and metrics
|
|
286
|
+
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
287
|
+
pub struct SyncState {
|
|
288
|
+
/// This instance's ID
|
|
289
|
+
pub instance_id: String,
|
|
290
|
+
/// Current sequence number for this instance
|
|
291
|
+
pub sequence: u64,
|
|
292
|
+
/// Connected peers
|
|
293
|
+
pub connected_peers: Vec<String>,
|
|
294
|
+
/// Pending operations waiting to be synced
|
|
295
|
+
pub pending_operations: u64,
|
|
296
|
+
/// Operations sent to peers
|
|
297
|
+
pub operations_sent: u64,
|
|
298
|
+
/// Operations received from peers
|
|
299
|
+
pub operations_received: u64,
|
|
300
|
+
/// Conflicts detected
|
|
301
|
+
pub conflicts_detected: u64,
|
|
302
|
+
/// Last sync timestamp
|
|
303
|
+
pub last_sync_ms: u128,
|
|
304
|
+
/// Is currently connected to network
|
|
305
|
+
pub is_connected: bool,
|
|
306
|
+
/// Vector clock for causal tracking (optional for backward compatibility)
|
|
307
|
+
#[serde(default)]
|
|
308
|
+
pub vector_clock: Option<std::collections::HashMap<String, u64>>,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
impl SyncState {
|
|
312
|
+
/// Create a new sync state
|
|
313
|
+
pub fn new(instance_id: String) -> Self {
|
|
314
|
+
Self {
|
|
315
|
+
instance_id,
|
|
316
|
+
sequence: 0,
|
|
317
|
+
connected_peers: Vec::new(),
|
|
318
|
+
pending_operations: 0,
|
|
319
|
+
operations_sent: 0,
|
|
320
|
+
operations_received: 0,
|
|
321
|
+
conflicts_detected: 0,
|
|
322
|
+
last_sync_ms: 0,
|
|
323
|
+
is_connected: false,
|
|
324
|
+
vector_clock: None,
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// Add a connected peer
|
|
329
|
+
pub fn add_peer(&mut self, peer_id: String) {
|
|
330
|
+
if !self.connected_peers.contains(&peer_id) {
|
|
331
|
+
self.connected_peers.push(peer_id);
|
|
332
|
+
self.is_connected = true;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/// Remove a connected peer
|
|
337
|
+
pub fn remove_peer(&mut self, peer_id: &str) {
|
|
338
|
+
self.connected_peers.retain(|p| p != peer_id);
|
|
339
|
+
if self.connected_peers.is_empty() {
|
|
340
|
+
self.is_connected = false;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/// Increment operations sent
|
|
345
|
+
pub fn increment_sent(&mut self, count: u64) {
|
|
346
|
+
self.operations_sent += count;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// Increment operations received
|
|
350
|
+
pub fn increment_received(&mut self, count: u64) {
|
|
351
|
+
self.operations_received += count;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/// Increment conflicts detected
|
|
355
|
+
pub fn increment_conflicts(&mut self) {
|
|
356
|
+
self.conflicts_detected += 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/// Initialize or get the vector clock for this sync state
|
|
360
|
+
pub fn get_or_init_vector_clock(&mut self) -> &mut std::collections::HashMap<String, u64> {
|
|
361
|
+
if self.vector_clock.is_none() {
|
|
362
|
+
self.vector_clock = Some(std::collections::HashMap::new());
|
|
363
|
+
}
|
|
364
|
+
self.vector_clock.as_mut().unwrap()
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/// Increment the vector clock for this instance
|
|
368
|
+
pub fn increment_vector_clock(&mut self) {
|
|
369
|
+
let instance_id = self.instance_id.clone();
|
|
370
|
+
let clock = self.get_or_init_vector_clock();
|
|
371
|
+
let entry = clock.entry(instance_id).or_insert(0);
|
|
372
|
+
*entry += 1;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// Merge another vector clock into ours (takes maximum)
|
|
376
|
+
pub fn merge_vector_clock(&mut self, other: &std::collections::HashMap<String, u64>) {
|
|
377
|
+
let clock = self.get_or_init_vector_clock();
|
|
378
|
+
for (instance_id, other_value) in other {
|
|
379
|
+
let entry = clock.entry(instance_id.clone()).or_insert(0);
|
|
380
|
+
*entry = (*entry).max(*other_value);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#[cfg(test)]
|
|
386
|
+
mod tests {
|
|
387
|
+
use super::*;
|
|
388
|
+
|
|
389
|
+
#[test]
|
|
390
|
+
fn change_log_tracks_operations() {
|
|
391
|
+
let mut log = ChangeLog::new();
|
|
392
|
+
let op = Operation::insert(
|
|
393
|
+
1,
|
|
394
|
+
"inst1".to_string(),
|
|
395
|
+
1,
|
|
396
|
+
"users:123".to_string(),
|
|
397
|
+
serde_json::json!({"name": "Alice"}),
|
|
398
|
+
"User".to_string(),
|
|
399
|
+
"users".to_string(),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
log.add_operation(op.clone());
|
|
403
|
+
assert_eq!(log.pending_operations().len(), 1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
#[test]
|
|
407
|
+
fn peer_state_tracks_pending() {
|
|
408
|
+
let mut peer = PeerState::new("peer1".to_string());
|
|
409
|
+
let op = Operation::insert(
|
|
410
|
+
1,
|
|
411
|
+
"inst1".to_string(),
|
|
412
|
+
1,
|
|
413
|
+
"users:123".to_string(),
|
|
414
|
+
serde_json::json!({"name": "Alice"}),
|
|
415
|
+
"User".to_string(),
|
|
416
|
+
"users".to_string(),
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
peer.add_pending(op);
|
|
420
|
+
assert_eq!(peer.pending_for_delivery().len(), 1);
|
|
421
|
+
peer.clear_pending();
|
|
422
|
+
assert_eq!(peer.pending_for_delivery().len(), 0);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
#[test]
|
|
426
|
+
fn conflict_detector_finds_write_conflicts() {
|
|
427
|
+
let mut detector = ConflictDetector::new();
|
|
428
|
+
let local_op = Operation::update(
|
|
429
|
+
1,
|
|
430
|
+
"inst1".to_string(),
|
|
431
|
+
1,
|
|
432
|
+
"tasks:123".to_string(),
|
|
433
|
+
serde_json::json!({"status": "completed"}),
|
|
434
|
+
"Task".to_string(),
|
|
435
|
+
"tasks".to_string(),
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
let remote_op = Operation::update(
|
|
439
|
+
2,
|
|
440
|
+
"inst2".to_string(),
|
|
441
|
+
1,
|
|
442
|
+
"tasks:123".to_string(),
|
|
443
|
+
serde_json::json!({"status": "blocked"}),
|
|
444
|
+
"Task".to_string(),
|
|
445
|
+
"tasks".to_string(),
|
|
446
|
+
);
|
|
447
|
+
|
|
448
|
+
let conflict = detector.detect_conflict(&local_op, &remote_op);
|
|
449
|
+
assert!(conflict.is_some());
|
|
450
|
+
assert_eq!(detector.get_conflicts().len(), 1);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
#[test]
|
|
454
|
+
fn sync_state_tracks_peers() {
|
|
455
|
+
let mut state = SyncState::new("inst1".to_string());
|
|
456
|
+
assert!(!state.is_connected);
|
|
457
|
+
|
|
458
|
+
state.add_peer("peer1".to_string());
|
|
459
|
+
assert!(state.is_connected);
|
|
460
|
+
assert_eq!(state.connected_peers.len(), 1);
|
|
461
|
+
|
|
462
|
+
state.add_peer("peer2".to_string());
|
|
463
|
+
assert_eq!(state.connected_peers.len(), 2);
|
|
464
|
+
|
|
465
|
+
state.remove_peer("peer1");
|
|
466
|
+
assert_eq!(state.connected_peers.len(), 1);
|
|
467
|
+
assert!(state.is_connected);
|
|
468
|
+
|
|
469
|
+
state.remove_peer("peer2");
|
|
470
|
+
assert!(!state.is_connected);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
#[test]
|
|
474
|
+
fn sync_message_serializes() {
|
|
475
|
+
let msg = SyncMessage::Handshake {
|
|
476
|
+
instance_id: "inst1".to_string(),
|
|
477
|
+
version: "0.1.0".to_string(),
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
let json = serde_json::to_string(&msg).expect("serialize");
|
|
481
|
+
let _deserialized: SyncMessage = serde_json::from_str(&json).expect("deserialize");
|
|
482
|
+
}
|
|
483
|
+
}
|