@feltdb/core 0.4.11 → 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-q43NSTTH.js → components-bHTARBin.js} +160 -89
- package/dist/studio/index.js +31 -28
- package/dist/studio-app/assets/{index-DZpBoVMp.js → index-BqKquLuU.js} +8 -8
- package/dist/studio-app/index.html +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/// Standalone FeltDB node for OS process boundary testing
|
|
2
|
+
///
|
|
3
|
+
/// Simplified runtime that:
|
|
4
|
+
/// 1. Accepts command via stdin (execute-tx, query-state, shutdown)
|
|
5
|
+
/// 2. Outputs state to stdout in JSON format
|
|
6
|
+
/// 3. Runs replication over TCP in background
|
|
7
|
+
/// 4. Executes local transactions and replicates to peers
|
|
8
|
+
/// 5. Receives and applies replicated transactions from peers
|
|
9
|
+
|
|
10
|
+
use feltdb::convergence::VectorClock;
|
|
11
|
+
use feltdb::distributed_transactions::{DistributedTransactionExecutor, ReplicationMessage};
|
|
12
|
+
use feltdb::replication_protocol::ProtocolTransport;
|
|
13
|
+
use feltdb::state_hash::StateHash;
|
|
14
|
+
use feltdb::tcp_transport::TcpTransport;
|
|
15
|
+
use feltdb::transactions::{
|
|
16
|
+
ConsistencyContract, Operation, OperationCommand, OperationId, StateVersion,
|
|
17
|
+
};
|
|
18
|
+
use serde_json::json;
|
|
19
|
+
use std::collections::HashMap;
|
|
20
|
+
use std::io::{self, BufRead, Write};
|
|
21
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
22
|
+
use std::sync::Arc;
|
|
23
|
+
use tokio::sync::{mpsc, Mutex};
|
|
24
|
+
use tokio::time::{sleep, Duration};
|
|
25
|
+
|
|
26
|
+
#[derive(Clone)]
|
|
27
|
+
struct NodeState {
|
|
28
|
+
executor: Arc<Mutex<DistributedTransactionExecutor>>,
|
|
29
|
+
node_id: String,
|
|
30
|
+
transport: Arc<Mutex<TcpTransport>>,
|
|
31
|
+
tx_sequence: Arc<Mutex<u64>>,
|
|
32
|
+
is_server: bool,
|
|
33
|
+
should_exit: Arc<AtomicBool>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[tokio::main]
|
|
37
|
+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
38
|
+
let args: Vec<String> = std::env::args().collect();
|
|
39
|
+
|
|
40
|
+
// Parse arguments
|
|
41
|
+
let mut mode = "server";
|
|
42
|
+
let mut replication_port = 9001u16;
|
|
43
|
+
let mut peer_addr = String::new();
|
|
44
|
+
|
|
45
|
+
let mut i = 1;
|
|
46
|
+
while i < args.len() {
|
|
47
|
+
match args[i].as_str() {
|
|
48
|
+
"--mode" => {
|
|
49
|
+
i += 1;
|
|
50
|
+
mode = &args[i];
|
|
51
|
+
}
|
|
52
|
+
"--port" => {
|
|
53
|
+
i += 1;
|
|
54
|
+
replication_port = args[i].parse()?;
|
|
55
|
+
}
|
|
56
|
+
"--peer" => {
|
|
57
|
+
i += 1;
|
|
58
|
+
peer_addr = args[i].clone();
|
|
59
|
+
}
|
|
60
|
+
_ => {}
|
|
61
|
+
}
|
|
62
|
+
i += 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let is_server = mode == "server";
|
|
66
|
+
let node_id = if is_server {
|
|
67
|
+
"node_a".to_string()
|
|
68
|
+
} else {
|
|
69
|
+
"node_b".to_string()
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Initialize executor
|
|
73
|
+
let initial_state = StateHash::from_hex("hash_initial".to_string());
|
|
74
|
+
let mut executor =
|
|
75
|
+
DistributedTransactionExecutor::new(node_id.clone(), initial_state.clone());
|
|
76
|
+
executor.register_replica("node_a".to_string(), initial_state.clone());
|
|
77
|
+
executor.register_replica("node_b".to_string(), initial_state.clone());
|
|
78
|
+
|
|
79
|
+
let transport = if is_server {
|
|
80
|
+
TcpTransport::new(format!("127.0.0.1:{}", replication_port))
|
|
81
|
+
} else {
|
|
82
|
+
TcpTransport::new("127.0.0.1:0".to_string())
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let node_state = NodeState {
|
|
86
|
+
executor: Arc::new(Mutex::new(executor)),
|
|
87
|
+
node_id: node_id.clone(),
|
|
88
|
+
transport: Arc::new(Mutex::new(transport)),
|
|
89
|
+
tx_sequence: Arc::new(Mutex::new(1)),
|
|
90
|
+
is_server,
|
|
91
|
+
should_exit: Arc::new(AtomicBool::new(false)),
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Start TCP replication in background
|
|
95
|
+
if is_server {
|
|
96
|
+
let replication_addr = format!("127.0.0.1:{}", replication_port);
|
|
97
|
+
let ns = node_state.clone();
|
|
98
|
+
tokio::spawn(async move {
|
|
99
|
+
let mut transport = ns.transport.lock().await;
|
|
100
|
+
if let Err(e) = transport.listen().await {
|
|
101
|
+
eprintln!("ERROR: listen failed: {}", e);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
println!("STARTED server on port {}", replication_port);
|
|
105
|
+
let _ = std::io::stdout().flush();
|
|
106
|
+
} else {
|
|
107
|
+
let ns = node_state.clone();
|
|
108
|
+
let peer = peer_addr.clone();
|
|
109
|
+
tokio::spawn(async move {
|
|
110
|
+
let mut transport = ns.transport.lock().await;
|
|
111
|
+
if let Err(e) = transport.connect(&peer).await {
|
|
112
|
+
eprintln!("ERROR: connect failed: {}", e);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
println!("STARTED client connecting to {}", peer_addr);
|
|
116
|
+
let _ = std::io::stdout().flush();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Spawn background task to receive and apply replicated messages
|
|
120
|
+
let ns = node_state.clone();
|
|
121
|
+
tokio::spawn(async move {
|
|
122
|
+
loop {
|
|
123
|
+
if ns.should_exit.load(Ordering::Relaxed) {
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let messages = {
|
|
128
|
+
let transport = ns.transport.lock().await;
|
|
129
|
+
match transport.receive().await {
|
|
130
|
+
Ok(msgs) => msgs,
|
|
131
|
+
Err(_) => {
|
|
132
|
+
sleep(Duration::from_millis(100)).await;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for msg in messages {
|
|
139
|
+
let mut executor = ns.executor.lock().await;
|
|
140
|
+
let mut parent_vc = VectorClock::new();
|
|
141
|
+
parent_vc.increment(&ns.node_id);
|
|
142
|
+
let parent_version = StateVersion::new(parent_vc, "hash_initial".to_string());
|
|
143
|
+
|
|
144
|
+
if let Err(e) = executor.receive_replicated_transaction(msg, parent_version) {
|
|
145
|
+
eprintln!("ERROR: apply replication failed: {}", e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
sleep(Duration::from_millis(50)).await;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Give replication setup time to establish
|
|
154
|
+
sleep(Duration::from_millis(500)).await;
|
|
155
|
+
|
|
156
|
+
// Create a channel for stdin commands
|
|
157
|
+
let (tx, mut rx) = mpsc::channel::<String>(100);
|
|
158
|
+
|
|
159
|
+
// Spawn a blocking task to read stdin
|
|
160
|
+
tokio::task::spawn_blocking(move || {
|
|
161
|
+
let stdin = io::stdin();
|
|
162
|
+
let handle = stdin.lock();
|
|
163
|
+
|
|
164
|
+
for line in handle.lines() {
|
|
165
|
+
if let Ok(cmd) = line {
|
|
166
|
+
let cmd_str = cmd.trim().to_string();
|
|
167
|
+
if !cmd_str.is_empty() {
|
|
168
|
+
// Non-blocking send, ignore if receiver is gone
|
|
169
|
+
let _ = tx.blocking_send(cmd_str);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Process commands from the channel
|
|
176
|
+
while let Some(cmd) = rx.recv().await {
|
|
177
|
+
match cmd.trim() {
|
|
178
|
+
"execute-tx" => {
|
|
179
|
+
let seq = {
|
|
180
|
+
let mut s = node_state.tx_sequence.lock().await;
|
|
181
|
+
let current = *s;
|
|
182
|
+
*s += 1;
|
|
183
|
+
current
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Create a test operation
|
|
187
|
+
let mut parent_vc = VectorClock::new();
|
|
188
|
+
parent_vc.increment(&node_state.node_id);
|
|
189
|
+
let parent_version = StateVersion::new(parent_vc, "hash_initial".to_string());
|
|
190
|
+
|
|
191
|
+
let mut fields = HashMap::new();
|
|
192
|
+
fields.insert("key".to_string(), json!("value"));
|
|
193
|
+
|
|
194
|
+
let op = Operation::new(
|
|
195
|
+
OperationId::new(node_state.node_id.clone(), seq),
|
|
196
|
+
parent_version.clone(),
|
|
197
|
+
format!("tx_{}", seq),
|
|
198
|
+
OperationCommand {
|
|
199
|
+
op_type: "set".to_string(),
|
|
200
|
+
collection: "items".to_string(),
|
|
201
|
+
record_id: format!("item{}", seq),
|
|
202
|
+
fields,
|
|
203
|
+
},
|
|
204
|
+
node_state.node_id.clone(),
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
{
|
|
208
|
+
let mut executor = node_state.executor.lock().await;
|
|
209
|
+
match executor.execute_local_transaction(
|
|
210
|
+
seq,
|
|
211
|
+
format!("tx_{}", seq),
|
|
212
|
+
parent_version,
|
|
213
|
+
vec![op],
|
|
214
|
+
ConsistencyContract::local(),
|
|
215
|
+
) {
|
|
216
|
+
Ok(envelope) => {
|
|
217
|
+
let replication_msg = ReplicationMessage::new(
|
|
218
|
+
envelope,
|
|
219
|
+
node_state.node_id.clone(),
|
|
220
|
+
if node_state.node_id == "node_a" {
|
|
221
|
+
"node_b".to_string()
|
|
222
|
+
} else {
|
|
223
|
+
"node_a".to_string()
|
|
224
|
+
},
|
|
225
|
+
seq,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
let transport = node_state.transport.lock().await;
|
|
229
|
+
if let Err(e) = transport.send(replication_msg).await {
|
|
230
|
+
println!("ERROR: send replication failed: {}", e);
|
|
231
|
+
} else {
|
|
232
|
+
println!("TX_EXECUTED");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
Err(e) => {
|
|
236
|
+
println!("ERROR: execute transaction failed: {}", e);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
let _ = std::io::stdout().flush();
|
|
241
|
+
}
|
|
242
|
+
"query-state" => {
|
|
243
|
+
let executor = node_state.executor.lock().await;
|
|
244
|
+
if let Some(replica) = executor.get_replica_state(&node_state.node_id) {
|
|
245
|
+
println!("STATE:{},{}", replica.state_hash, replica.operations_applied);
|
|
246
|
+
} else {
|
|
247
|
+
println!("ERROR: node not found");
|
|
248
|
+
}
|
|
249
|
+
let _ = std::io::stdout().flush();
|
|
250
|
+
}
|
|
251
|
+
"shutdown" => {
|
|
252
|
+
node_state.should_exit.store(true, Ordering::Relaxed);
|
|
253
|
+
println!("SHUTDOWN");
|
|
254
|
+
let _ = std::io::stdout().flush();
|
|
255
|
+
sleep(Duration::from_millis(100)).await;
|
|
256
|
+
std::process::exit(0);
|
|
257
|
+
}
|
|
258
|
+
_ => {
|
|
259
|
+
println!("ERROR: unknown command");
|
|
260
|
+
let _ = std::io::stdout().flush();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Ok(())
|
|
266
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/// Concrete capability implementations
|
|
2
|
+
pub mod search;
|
|
3
|
+
pub mod vector;
|
|
4
|
+
|
|
5
|
+
pub use search::{SearchCapability, SearchDocument, SearchResult};
|
|
6
|
+
pub use vector::{
|
|
7
|
+
DemoEmbeddingProvider, EmbeddingProvider, VectorCapability, VectorDocument, VectorSearchResult,
|
|
8
|
+
};
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/// Full-text Search Capability
|
|
2
|
+
///
|
|
3
|
+
/// A concrete implementation of FlowCapability that maintains a full-text search index.
|
|
4
|
+
/// This demonstrates the capability extension model with a projection that doesn't own
|
|
5
|
+
/// canonical state but derives from it.
|
|
6
|
+
use crate::capability::{
|
|
7
|
+
CapabilityAccessModel, CapabilityContext, CapabilityId, CapabilityMetadata,
|
|
8
|
+
CapabilityOperationResult, CapabilityQuery, CapabilityQueryResult, CapabilityRole,
|
|
9
|
+
FlowCapability,
|
|
10
|
+
};
|
|
11
|
+
use crate::{Operation, OperationType, Result};
|
|
12
|
+
use serde::{Deserialize, Serialize};
|
|
13
|
+
use serde_json::{json, Value};
|
|
14
|
+
use std::collections::HashMap;
|
|
15
|
+
|
|
16
|
+
/// Search index document representation
|
|
17
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
18
|
+
pub struct SearchDocument {
|
|
19
|
+
pub key: String,
|
|
20
|
+
pub content: String,
|
|
21
|
+
pub metadata: Value,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Search result from the search capability
|
|
25
|
+
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
26
|
+
pub struct SearchResult {
|
|
27
|
+
pub key: String,
|
|
28
|
+
pub content: String,
|
|
29
|
+
pub score: f32,
|
|
30
|
+
pub metadata: Value,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Full-text search capability implementation
|
|
34
|
+
pub struct SearchCapability {
|
|
35
|
+
capability_id: CapabilityId,
|
|
36
|
+
metadata: CapabilityMetadata,
|
|
37
|
+
/// In-memory document store
|
|
38
|
+
documents: HashMap<String, SearchDocument>,
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
impl SearchCapability {
|
|
42
|
+
pub fn new() -> Self {
|
|
43
|
+
let capability_id = CapabilityId::new("search".to_string());
|
|
44
|
+
let access_model = CapabilityAccessModel::new(
|
|
45
|
+
vec!["*".to_string()],
|
|
46
|
+
vec!["search.index".to_string()],
|
|
47
|
+
vec![],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
let metadata = CapabilityMetadata::new(
|
|
51
|
+
capability_id.clone(),
|
|
52
|
+
"Full-Text Search".to_string(),
|
|
53
|
+
"Maintains a full-text search index over documents".to_string(),
|
|
54
|
+
CapabilityRole::Projection,
|
|
55
|
+
access_model,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
Self {
|
|
59
|
+
capability_id,
|
|
60
|
+
metadata,
|
|
61
|
+
documents: HashMap::new(),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Add or update a document in the search index
|
|
66
|
+
fn index_document(&mut self, doc: &SearchDocument) -> Result<()> {
|
|
67
|
+
self.documents.insert(doc.key.clone(), doc.clone());
|
|
68
|
+
Ok(())
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// Remove a document from the search index
|
|
72
|
+
fn remove_document(&mut self, key: &str) -> Result<()> {
|
|
73
|
+
self.documents.remove(key);
|
|
74
|
+
Ok(())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Search the index - simple term matching
|
|
78
|
+
fn search(&self, query_str: &str, limit: usize) -> Result<Vec<SearchResult>> {
|
|
79
|
+
// Simple tokenization
|
|
80
|
+
let query_terms: Vec<&str> = query_str
|
|
81
|
+
.split_whitespace()
|
|
82
|
+
.filter(|s| !s.is_empty())
|
|
83
|
+
.collect();
|
|
84
|
+
|
|
85
|
+
let mut results = Vec::new();
|
|
86
|
+
|
|
87
|
+
// Search through all documents
|
|
88
|
+
for doc in self.documents.values() {
|
|
89
|
+
let mut score = 0.0f32;
|
|
90
|
+
let content_lower = doc.content.to_lowercase();
|
|
91
|
+
|
|
92
|
+
// Score based on term matches
|
|
93
|
+
for term in &query_terms {
|
|
94
|
+
let term_lower = term.to_lowercase();
|
|
95
|
+
if content_lower.contains(&term_lower) {
|
|
96
|
+
// Count occurrences
|
|
97
|
+
let count = content_lower.matches(&term_lower).count() as f32;
|
|
98
|
+
score += count * 1.0;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if score > 0.0 {
|
|
103
|
+
results.push(SearchResult {
|
|
104
|
+
key: doc.key.clone(),
|
|
105
|
+
content: doc.content.clone(),
|
|
106
|
+
score,
|
|
107
|
+
metadata: doc.metadata.clone(),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Sort by score descending
|
|
113
|
+
results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap());
|
|
114
|
+
results.truncate(limit);
|
|
115
|
+
|
|
116
|
+
Ok(results)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
impl Default for SearchCapability {
|
|
121
|
+
fn default() -> Self {
|
|
122
|
+
Self::new()
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
impl FlowCapability for SearchCapability {
|
|
127
|
+
fn id(&self) -> CapabilityId {
|
|
128
|
+
self.capability_id.clone()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fn metadata(&self) -> CapabilityMetadata {
|
|
132
|
+
self.metadata.clone()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn initialize(&mut self, _context: &CapabilityContext) -> Result<()> {
|
|
136
|
+
Ok(())
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
fn on_operation(
|
|
140
|
+
&mut self,
|
|
141
|
+
operation: &Operation,
|
|
142
|
+
_context: &CapabilityContext,
|
|
143
|
+
) -> Result<CapabilityOperationResult> {
|
|
144
|
+
// Only index Insert and Update operations
|
|
145
|
+
match operation.op_type {
|
|
146
|
+
OperationType::Insert | OperationType::Update => {
|
|
147
|
+
if let Some(value) = &operation.value {
|
|
148
|
+
let content = if value.is_string() {
|
|
149
|
+
value.as_str().unwrap_or("").to_string()
|
|
150
|
+
} else {
|
|
151
|
+
value.to_string()
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
let doc = SearchDocument {
|
|
155
|
+
key: operation.key.clone(),
|
|
156
|
+
content,
|
|
157
|
+
metadata: json!({
|
|
158
|
+
"source_operation": operation.op_id,
|
|
159
|
+
"timestamp": operation.timestamp_ms,
|
|
160
|
+
}),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
self.index_document(&doc)?;
|
|
164
|
+
Ok(CapabilityOperationResult::with_derived_state(json!({
|
|
165
|
+
"indexed": true,
|
|
166
|
+
"key": operation.key.clone(),
|
|
167
|
+
})))
|
|
168
|
+
} else {
|
|
169
|
+
Ok(CapabilityOperationResult::success())
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
OperationType::Delete => {
|
|
173
|
+
self.remove_document(&operation.key)?;
|
|
174
|
+
Ok(CapabilityOperationResult::success())
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn query(
|
|
180
|
+
&self,
|
|
181
|
+
query: &CapabilityQuery,
|
|
182
|
+
_context: &CapabilityContext,
|
|
183
|
+
) -> Result<CapabilityQueryResult> {
|
|
184
|
+
if query.query_type != "search" {
|
|
185
|
+
return Ok(CapabilityQueryResult::error(format!(
|
|
186
|
+
"Unknown query type: {}",
|
|
187
|
+
query.query_type
|
|
188
|
+
)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let query_str = query.params.get("q").and_then(|v| v.as_str()).unwrap_or("");
|
|
192
|
+
|
|
193
|
+
let limit = query
|
|
194
|
+
.params
|
|
195
|
+
.get("limit")
|
|
196
|
+
.and_then(|v| v.as_u64())
|
|
197
|
+
.unwrap_or(10) as usize;
|
|
198
|
+
|
|
199
|
+
let results = self.search(query_str, limit)?;
|
|
200
|
+
let result_values: Vec<Value> = results
|
|
201
|
+
.iter()
|
|
202
|
+
.map(|r| {
|
|
203
|
+
json!({
|
|
204
|
+
"key": r.key,
|
|
205
|
+
"content": r.content,
|
|
206
|
+
"score": r.score,
|
|
207
|
+
"metadata": r.metadata,
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
.collect();
|
|
211
|
+
|
|
212
|
+
Ok(CapabilityQueryResult::success(result_values))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
fn rebuild(&mut self, operations: &[Operation], context: &CapabilityContext) -> Result<()> {
|
|
216
|
+
// Clear existing index
|
|
217
|
+
self.documents.clear();
|
|
218
|
+
|
|
219
|
+
// Replay all operations
|
|
220
|
+
for op in operations {
|
|
221
|
+
self.on_operation(op, context)?;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
Ok(())
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[cfg(test)]
|
|
229
|
+
mod tests {
|
|
230
|
+
use super::*;
|
|
231
|
+
|
|
232
|
+
#[test]
|
|
233
|
+
fn test_search_capability_creation() {
|
|
234
|
+
let cap = SearchCapability::new();
|
|
235
|
+
assert_eq!(cap.id().0, "search");
|
|
236
|
+
assert!(cap.metadata().access_model.can_read("docs"));
|
|
237
|
+
assert!(cap.metadata().access_model.can_write("search.index"));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
#[test]
|
|
241
|
+
fn test_search_capability_initialization() {
|
|
242
|
+
let mut cap = SearchCapability::new();
|
|
243
|
+
let context = CapabilityContext {
|
|
244
|
+
capability_id: cap.id(),
|
|
245
|
+
instance_id: "test-instance".to_string(),
|
|
246
|
+
sequence: 0,
|
|
247
|
+
version: 1,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
assert!(cap.initialize(&context).is_ok());
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn test_search_capability_indexing() {
|
|
255
|
+
let mut cap = SearchCapability::new();
|
|
256
|
+
let context = CapabilityContext {
|
|
257
|
+
capability_id: cap.id(),
|
|
258
|
+
instance_id: "test-instance".to_string(),
|
|
259
|
+
sequence: 0,
|
|
260
|
+
version: 1,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
cap.initialize(&context).unwrap();
|
|
264
|
+
|
|
265
|
+
let op = Operation::insert(
|
|
266
|
+
1,
|
|
267
|
+
"instance-1".to_string(),
|
|
268
|
+
1,
|
|
269
|
+
"docs:1".to_string(),
|
|
270
|
+
json!({"title": "Rust Programming", "content": "Learn Rust"}),
|
|
271
|
+
"Document".to_string(),
|
|
272
|
+
"docs".to_string(),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
let result = cap.on_operation(&op, &context).unwrap();
|
|
276
|
+
assert!(result.success);
|
|
277
|
+
assert!(result.produced_derived_state);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#[test]
|
|
281
|
+
fn test_search_capability_query() {
|
|
282
|
+
let mut cap = SearchCapability::new();
|
|
283
|
+
let context = CapabilityContext {
|
|
284
|
+
capability_id: cap.id(),
|
|
285
|
+
instance_id: "test-instance".to_string(),
|
|
286
|
+
sequence: 0,
|
|
287
|
+
version: 1,
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
cap.initialize(&context).unwrap();
|
|
291
|
+
|
|
292
|
+
let op1 = Operation::insert(
|
|
293
|
+
1,
|
|
294
|
+
"instance-1".to_string(),
|
|
295
|
+
1,
|
|
296
|
+
"docs:1".to_string(),
|
|
297
|
+
json!("Rust Programming Language"),
|
|
298
|
+
"Document".to_string(),
|
|
299
|
+
"docs".to_string(),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
let op2 = Operation::insert(
|
|
303
|
+
2,
|
|
304
|
+
"instance-1".to_string(),
|
|
305
|
+
2,
|
|
306
|
+
"docs:2".to_string(),
|
|
307
|
+
json!("Python for Data Science"),
|
|
308
|
+
"Document".to_string(),
|
|
309
|
+
"docs".to_string(),
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
cap.on_operation(&op1, &context).unwrap();
|
|
313
|
+
cap.on_operation(&op2, &context).unwrap();
|
|
314
|
+
|
|
315
|
+
let query = CapabilityQuery {
|
|
316
|
+
query_type: "search".to_string(),
|
|
317
|
+
params: json!({
|
|
318
|
+
"q": "Rust",
|
|
319
|
+
"limit": 10
|
|
320
|
+
}),
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
let results = cap.query(&query, &context).unwrap();
|
|
324
|
+
assert!(results.success);
|
|
325
|
+
assert!(results.results.len() > 0);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
#[test]
|
|
329
|
+
fn test_search_capability_delete() {
|
|
330
|
+
let mut cap = SearchCapability::new();
|
|
331
|
+
let context = CapabilityContext {
|
|
332
|
+
capability_id: cap.id(),
|
|
333
|
+
instance_id: "test-instance".to_string(),
|
|
334
|
+
sequence: 0,
|
|
335
|
+
version: 1,
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
cap.initialize(&context).unwrap();
|
|
339
|
+
|
|
340
|
+
// Insert a document
|
|
341
|
+
let op1 = Operation::insert(
|
|
342
|
+
1,
|
|
343
|
+
"instance-1".to_string(),
|
|
344
|
+
1,
|
|
345
|
+
"docs:1".to_string(),
|
|
346
|
+
json!("Test Document"),
|
|
347
|
+
"Document".to_string(),
|
|
348
|
+
"docs".to_string(),
|
|
349
|
+
);
|
|
350
|
+
cap.on_operation(&op1, &context).unwrap();
|
|
351
|
+
|
|
352
|
+
// Verify it's indexed
|
|
353
|
+
let query = CapabilityQuery {
|
|
354
|
+
query_type: "search".to_string(),
|
|
355
|
+
params: json!({"q": "Test", "limit": 10}),
|
|
356
|
+
};
|
|
357
|
+
let results = cap.query(&query, &context).unwrap();
|
|
358
|
+
assert_eq!(results.results.len(), 1);
|
|
359
|
+
|
|
360
|
+
// Delete it
|
|
361
|
+
let op2 = Operation::delete(
|
|
362
|
+
2,
|
|
363
|
+
"instance-1".to_string(),
|
|
364
|
+
2,
|
|
365
|
+
"docs:1".to_string(),
|
|
366
|
+
"Document".to_string(),
|
|
367
|
+
"docs".to_string(),
|
|
368
|
+
);
|
|
369
|
+
cap.on_operation(&op2, &context).unwrap();
|
|
370
|
+
|
|
371
|
+
// Verify it's gone
|
|
372
|
+
let results = cap.query(&query, &context).unwrap();
|
|
373
|
+
assert_eq!(results.results.len(), 0);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#[test]
|
|
377
|
+
fn test_search_capability_rebuild() {
|
|
378
|
+
let mut cap = SearchCapability::new();
|
|
379
|
+
let context = CapabilityContext {
|
|
380
|
+
capability_id: cap.id(),
|
|
381
|
+
instance_id: "test-instance".to_string(),
|
|
382
|
+
sequence: 0,
|
|
383
|
+
version: 1,
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
cap.initialize(&context).unwrap();
|
|
387
|
+
|
|
388
|
+
let ops = vec![
|
|
389
|
+
Operation::insert(
|
|
390
|
+
1,
|
|
391
|
+
"instance-1".to_string(),
|
|
392
|
+
1,
|
|
393
|
+
"docs:1".to_string(),
|
|
394
|
+
json!("Rust Guide"),
|
|
395
|
+
"Document".to_string(),
|
|
396
|
+
"docs".to_string(),
|
|
397
|
+
),
|
|
398
|
+
Operation::insert(
|
|
399
|
+
2,
|
|
400
|
+
"instance-1".to_string(),
|
|
401
|
+
2,
|
|
402
|
+
"docs:2".to_string(),
|
|
403
|
+
json!("Go Tutorial"),
|
|
404
|
+
"Document".to_string(),
|
|
405
|
+
"docs".to_string(),
|
|
406
|
+
),
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
cap.rebuild(&ops, &context).unwrap();
|
|
410
|
+
|
|
411
|
+
let query = CapabilityQuery {
|
|
412
|
+
query_type: "search".to_string(),
|
|
413
|
+
params: json!({"q": "Rust", "limit": 10}),
|
|
414
|
+
};
|
|
415
|
+
let results = cap.query(&query, &context).unwrap();
|
|
416
|
+
assert_eq!(results.results.len(), 1);
|
|
417
|
+
}
|
|
418
|
+
}
|