@feltdb/core 0.4.12 → 0.4.14

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.
Files changed (165) hide show
  1. package/dist/cli/commands.js +42 -25
  2. package/dist/cli/index.js +1 -1
  3. package/dist/create/cli.js +39 -4
  4. package/dist/create/create.js +30 -18
  5. package/dist/create/docker-compose-generator.js +68 -11
  6. package/dist/create/package-versions.js +1 -1
  7. package/dist/create/server-source/Cargo.lock +2238 -0
  8. package/dist/create/server-source/Cargo.toml +7 -0
  9. package/dist/create/server-source/crates/feltdb/Cargo.lock +175 -0
  10. package/dist/create/server-source/crates/feltdb/Cargo.toml +79 -0
  11. package/dist/create/server-source/crates/feltdb/benches/baselines/gate-13-redux.json +370 -0
  12. package/dist/create/server-source/crates/feltdb/benches/gate13_baseline.rs +589 -0
  13. package/dist/create/server-source/crates/feltdb/benches/gate13_phase_7_1_release_economics.rs +259 -0
  14. package/dist/create/server-source/crates/feltdb/benches/gate_13_redux.rs +446 -0
  15. package/dist/create/server-source/crates/feltdb/benches/gate_13_regression_runner.rs +272 -0
  16. package/dist/create/server-source/crates/feltdb/benches/gate_14a_concurrent_writer_scaling.rs +378 -0
  17. package/dist/create/server-source/crates/feltdb/benches/gate_14a_production_admission_revalidation.rs +414 -0
  18. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc2_admission_contract.rs +486 -0
  19. package/dist/create/server-source/crates/feltdb/benches/gate_14a_rc_root_cause.rs +273 -0
  20. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync1_queued_prototype.rs +587 -0
  21. package/dist/create/server-source/crates/feltdb/benches/gate_14a_sync_economics.rs +513 -0
  22. package/dist/create/server-source/crates/feltdb/benches/gate_14b_causal_backlog_scaling.rs +395 -0
  23. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_contract_test.rs +469 -0
  24. package/dist/create/server-source/crates/feltdb/benches/gate_14c_replication_scaling.rs +409 -0
  25. package/dist/create/server-source/crates/feltdb/benches/gate_14d_combined_dimension_scaling.rs +627 -0
  26. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_2_optimization_benchmark.rs +383 -0
  27. package/dist/create/server-source/crates/feltdb/benches/phase_7_1_3_crossover_analysis.rs +298 -0
  28. package/dist/create/server-source/crates/feltdb/src/acceptance_tests.rs +1698 -0
  29. package/dist/create/server-source/crates/feltdb/src/acquisition.rs +286 -0
  30. package/dist/create/server-source/crates/feltdb/src/admission.rs +192 -0
  31. package/dist/create/server-source/crates/feltdb/src/admission_contract_tests.rs +477 -0
  32. package/dist/create/server-source/crates/feltdb/src/adversarial_transport.rs +566 -0
  33. package/dist/create/server-source/crates/feltdb/src/analytics.rs +475 -0
  34. package/dist/create/server-source/crates/feltdb/src/application.rs +2244 -0
  35. package/dist/create/server-source/crates/feltdb/src/application_runtime.rs +1070 -0
  36. package/dist/create/server-source/crates/feltdb/src/authorization.rs +1030 -0
  37. package/dist/create/server-source/crates/feltdb/src/bin/feltdb_node.rs +266 -0
  38. package/dist/create/server-source/crates/feltdb/src/capabilities/mod.rs +8 -0
  39. package/dist/create/server-source/crates/feltdb/src/capabilities/search.rs +418 -0
  40. package/dist/create/server-source/crates/feltdb/src/capabilities/vector.rs +482 -0
  41. package/dist/create/server-source/crates/feltdb/src/capability.rs +903 -0
  42. package/dist/create/server-source/crates/feltdb/src/cardinality_diagnostics.rs +261 -0
  43. package/dist/create/server-source/crates/feltdb/src/cardinality_endpoint.rs +78 -0
  44. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier.rs +2214 -0
  45. package/dist/create/server-source/crates/feltdb/src/causal_dependency_barrier_phase_7_1.rs +194 -0
  46. package/dist/create/server-source/crates/feltdb/src/concurrency_fuzzing.rs +427 -0
  47. package/dist/create/server-source/crates/feltdb/src/consistency_contract.rs +453 -0
  48. package/dist/create/server-source/crates/feltdb/src/content_distribution.rs +465 -0
  49. package/dist/create/server-source/crates/feltdb/src/convergence.rs +618 -0
  50. package/dist/create/server-source/crates/feltdb/src/crash_atomic_boundary.rs +418 -0
  51. package/dist/create/server-source/crates/feltdb/src/crash_injection.rs +380 -0
  52. package/dist/create/server-source/crates/feltdb/src/crash_recovery_tests.rs +362 -0
  53. package/dist/create/server-source/crates/feltdb/src/cron.rs +294 -0
  54. package/dist/create/server-source/crates/feltdb/src/distributed_indexing.rs +474 -0
  55. package/dist/create/server-source/crates/feltdb/src/distributed_tests.rs +1003 -0
  56. package/dist/create/server-source/crates/feltdb/src/distributed_transactions.rs +536 -0
  57. package/dist/create/server-source/crates/feltdb/src/durability_guarantees.rs +364 -0
  58. package/dist/create/server-source/crates/feltdb/src/durable_dedup_set.rs +235 -0
  59. package/dist/create/server-source/crates/feltdb/src/durable_operation_log.rs +207 -0
  60. package/dist/create/server-source/crates/feltdb/src/durable_sync.rs +316 -0
  61. package/dist/create/server-source/crates/feltdb/src/execution.rs +426 -0
  62. package/dist/create/server-source/crates/feltdb/src/in_process_transport.rs +219 -0
  63. package/dist/create/server-source/crates/feltdb/src/indexing.rs +779 -0
  64. package/dist/create/server-source/crates/feltdb/src/lib.rs +2838 -0
  65. package/dist/create/server-source/crates/feltdb/src/materialization.rs +184 -0
  66. package/dist/create/server-source/crates/feltdb/src/metrics.rs +267 -0
  67. package/dist/create/server-source/crates/feltdb/src/multi_node_convergence.rs +311 -0
  68. package/dist/create/server-source/crates/feltdb/src/observability.rs +366 -0
  69. package/dist/create/server-source/crates/feltdb/src/operation.rs +251 -0
  70. package/dist/create/server-source/crates/feltdb/src/operation_algebra.rs +438 -0
  71. package/dist/create/server-source/crates/feltdb/src/operation_log.rs +344 -0
  72. package/dist/create/server-source/crates/feltdb/src/partition_reconciliation.rs +477 -0
  73. package/dist/create/server-source/crates/feltdb/src/peer_registry.rs +166 -0
  74. package/dist/create/server-source/crates/feltdb/src/permutation_scheduler.rs +261 -0
  75. package/dist/create/server-source/crates/feltdb/src/persistence_reality.rs +560 -0
  76. package/dist/create/server-source/crates/feltdb/src/phase1b_acceptance.rs +3226 -0
  77. package/dist/create/server-source/crates/feltdb/src/phase1c1_acceptance.rs +201 -0
  78. package/dist/create/server-source/crates/feltdb/src/phase1c2_acceptance.rs +263 -0
  79. package/dist/create/server-source/crates/feltdb/src/phase1c3_acceptance.rs +484 -0
  80. package/dist/create/server-source/crates/feltdb/src/phase1c_atomicity_proof.rs +216 -0
  81. package/dist/create/server-source/crates/feltdb/src/phase5_integration.rs +281 -0
  82. package/dist/create/server-source/crates/feltdb/src/phase5_scenarios.rs +323 -0
  83. package/dist/create/server-source/crates/feltdb/src/phase6_adversarial_scenarios.rs +573 -0
  84. package/dist/create/server-source/crates/feltdb/src/phase6_convergence_validator.rs +404 -0
  85. package/dist/create/server-source/crates/feltdb/src/phase6_persistence.rs +418 -0
  86. package/dist/create/server-source/crates/feltdb/src/phase_1c_real_tcp.rs +381 -0
  87. package/dist/create/server-source/crates/feltdb/src/phase_1c_three_node.rs +523 -0
  88. package/dist/create/server-source/crates/feltdb/src/phase_2a_failures.rs +334 -0
  89. package/dist/create/server-source/crates/feltdb/src/phase_2b_network.rs +306 -0
  90. package/dist/create/server-source/crates/feltdb/src/phase_2c_cascading.rs +355 -0
  91. package/dist/create/server-source/crates/feltdb/src/phase_3_durability.rs +395 -0
  92. package/dist/create/server-source/crates/feltdb/src/phase_4_baseline.rs +346 -0
  93. package/dist/create/server-source/crates/feltdb/src/phase_5_soak.rs +430 -0
  94. package/dist/create/server-source/crates/feltdb/src/production_api.rs +400 -0
  95. package/dist/create/server-source/crates/feltdb/src/provenance.rs +207 -0
  96. package/dist/create/server-source/crates/feltdb/src/query_performance.rs +217 -0
  97. package/dist/create/server-source/crates/feltdb/src/references.rs +404 -0
  98. package/dist/create/server-source/crates/feltdb/src/replay_fuzzing.rs +401 -0
  99. package/dist/create/server-source/crates/feltdb/src/replication_manager.rs +160 -0
  100. package/dist/create/server-source/crates/feltdb/src/replication_protocol.rs +132 -0
  101. package/dist/create/server-source/crates/feltdb/src/routing.rs +409 -0
  102. package/dist/create/server-source/crates/feltdb/src/sharding.rs +523 -0
  103. package/dist/create/server-source/crates/feltdb/src/state_contract.rs +3118 -0
  104. package/dist/create/server-source/crates/feltdb/src/state_hash.rs +291 -0
  105. package/dist/create/server-source/crates/feltdb/src/state_transition_store.rs +376 -0
  106. package/dist/create/server-source/crates/feltdb/src/storage.rs +267 -0
  107. package/dist/create/server-source/crates/feltdb/src/submission.rs +477 -0
  108. package/dist/create/server-source/crates/feltdb/src/sync.rs +483 -0
  109. package/dist/create/server-source/crates/feltdb/src/sync_contract.rs +822 -0
  110. package/dist/create/server-source/crates/feltdb/src/tcp_transport.rs +366 -0
  111. package/dist/create/server-source/crates/feltdb/src/transaction_api.rs +473 -0
  112. package/dist/create/server-source/crates/feltdb/src/transaction_invariants.rs +949 -0
  113. package/dist/create/server-source/crates/feltdb/src/transactions.rs +646 -0
  114. package/dist/create/server-source/crates/feltdb/src/trigger.rs +390 -0
  115. package/dist/create/server-source/crates/feltdb/src/worker_mesh.rs +928 -0
  116. package/dist/create/server-source/crates/feltdb/src/workflow.rs +713 -0
  117. package/dist/create/server-source/crates/feltdb/src/workflow_acceptance_tests.rs +510 -0
  118. package/dist/create/server-source/crates/feltdb/src/workflow_integration.rs +354 -0
  119. package/dist/create/server-source/crates/feltdb/src/workflow_runtime.rs +594 -0
  120. package/dist/create/server-source/crates/feltdb/src/workload.rs +1310 -0
  121. package/dist/create/server-source/crates/feltdb-server/Cargo.toml +23 -0
  122. package/dist/create/server-source/crates/feltdb-server/README.md +79 -0
  123. package/dist/create/server-source/crates/feltdb-server/src/app_state.rs +73 -0
  124. package/dist/create/server-source/crates/feltdb-server/src/application_contract.rs +425 -0
  125. package/dist/create/server-source/crates/feltdb-server/src/artifacts.rs +449 -0
  126. package/dist/create/server-source/crates/feltdb-server/src/audit.rs +59 -0
  127. package/dist/create/server-source/crates/feltdb-server/src/auth.rs +308 -0
  128. package/dist/create/server-source/crates/feltdb-server/src/authorization.rs +228 -0
  129. package/dist/create/server-source/crates/feltdb-server/src/backup.rs +416 -0
  130. package/dist/create/server-source/crates/feltdb-server/src/causal.rs +218 -0
  131. package/dist/create/server-source/crates/feltdb-server/src/certification.rs +223 -0
  132. package/dist/create/server-source/crates/feltdb-server/src/clock.rs +132 -0
  133. package/dist/create/server-source/crates/feltdb-server/src/cluster.rs +165 -0
  134. package/dist/create/server-source/crates/feltdb-server/src/connections.rs +1044 -0
  135. package/dist/create/server-source/crates/feltdb-server/src/content.rs +111 -0
  136. package/dist/create/server-source/crates/feltdb-server/src/identity.rs +250 -0
  137. package/dist/create/server-source/crates/feltdb-server/src/key_management.rs +78 -0
  138. package/dist/create/server-source/crates/feltdb-server/src/key_provider.rs +247 -0
  139. package/dist/create/server-source/crates/feltdb-server/src/leases.rs +123 -0
  140. package/dist/create/server-source/crates/feltdb-server/src/lib.rs +25 -0
  141. package/dist/create/server-source/crates/feltdb-server/src/main.rs +8715 -0
  142. package/dist/create/server-source/crates/feltdb-server/src/metrics.rs +97 -0
  143. package/dist/create/server-source/crates/feltdb-server/src/portable_bundle.rs +350 -0
  144. package/dist/create/server-source/crates/feltdb-server/src/principals.rs +510 -0
  145. package/dist/create/server-source/crates/feltdb-server/src/providers.rs +269 -0
  146. package/dist/create/server-source/crates/feltdb-server/src/releases.rs +2563 -0
  147. package/dist/create/server-source/crates/feltdb-server/src/sessions.rs +156 -0
  148. package/dist/create/server-source/crates/feltdb-server/src/tenancy.rs +1097 -0
  149. package/dist/create/server-source/crates/feltdb-server/src/versions.rs +264 -0
  150. package/dist/create/server-source/crates/feltdb-wasm/Cargo.toml +31 -0
  151. package/dist/create/server-source/crates/feltdb-wasm/src/lib.rs +1086 -0
  152. package/dist/create/server-source/crates/feltdb-wasm/test.db +0 -0
  153. package/dist/flowspec.d.ts +2 -0
  154. package/dist/flowspec.d.ts.map +1 -1
  155. package/dist/flowspec.js +28 -4
  156. package/dist/state-contract.d.ts +7 -1
  157. package/dist/state-contract.d.ts.map +1 -1
  158. package/dist/studio/components/ApplicationDesigner.d.ts.map +1 -1
  159. package/dist/studio/components/ConflictExplorer.d.ts.map +1 -1
  160. package/dist/studio/components/index.js +1 -1
  161. package/dist/studio/{components-Q0Nx6gLE.js → components-9kDSWiGL.js} +163 -47
  162. package/dist/studio/index.js +31 -28
  163. package/dist/studio-app/assets/{index-BHOmLZF-.js → index-DVdvmf69.js} +9 -9
  164. package/dist/studio-app/index.html +1 -1
  165. package/package.json +1 -1
@@ -0,0 +1,2838 @@
1
+ #[cfg(test)]
2
+ mod acceptance_tests;
3
+ #[cfg(test)]
4
+ mod admission_contract_tests;
5
+ mod acquisition;
6
+ pub mod admission;
7
+ pub mod application;
8
+ pub mod application_runtime;
9
+ pub mod authorization;
10
+ pub mod capabilities;
11
+ mod capability;
12
+ mod content_distribution;
13
+ pub mod convergence;
14
+ mod cron;
15
+ #[cfg(test)]
16
+ mod distributed_tests;
17
+ mod execution;
18
+ pub mod analytics;
19
+ pub mod distributed_indexing;
20
+ pub mod indexing;
21
+ pub mod sharding;
22
+ pub mod transactions;
23
+ pub mod state_hash;
24
+ pub mod crash_injection;
25
+ pub mod concurrency_fuzzing;
26
+ pub mod replay_fuzzing;
27
+ pub mod permutation_scheduler;
28
+ pub mod multi_node_convergence;
29
+ pub mod distributed_transactions;
30
+ pub mod durable_operation_log;
31
+ pub mod durable_dedup_set;
32
+ pub mod replication_protocol;
33
+ pub mod in_process_transport;
34
+ #[cfg(not(target_arch = "wasm32"))]
35
+ pub mod tcp_transport;
36
+ pub mod operation_algebra;
37
+ pub mod crash_atomic_boundary;
38
+ pub mod causal_dependency_barrier;
39
+ pub mod partition_reconciliation;
40
+ pub mod consistency_contract;
41
+ pub mod persistence_reality;
42
+ pub mod adversarial_transport;
43
+ pub mod replication_manager;
44
+ pub mod metrics;
45
+ pub mod query_performance;
46
+ pub mod durable_sync;
47
+ pub mod production_api;
48
+ pub mod observability;
49
+ pub mod durability_guarantees;
50
+ #[cfg(test)]
51
+ mod phase1b_acceptance;
52
+ #[cfg(test)]
53
+ mod phase1c1_acceptance;
54
+ #[cfg(test)]
55
+ mod phase1c2_acceptance;
56
+ #[cfg(test)]
57
+ mod phase1c_atomicity_proof;
58
+ #[cfg(test)]
59
+ mod phase1c3_acceptance;
60
+ #[cfg(test)]
61
+ mod phase_1c_three_node;
62
+ #[cfg(test)]
63
+ mod phase_1c_real_tcp;
64
+ #[cfg(test)]
65
+ mod phase_2a_failures;
66
+ #[cfg(test)]
67
+ mod phase_2b_network;
68
+ #[cfg(test)]
69
+ mod phase_2c_cascading;
70
+ #[cfg(test)]
71
+ mod phase_3_durability;
72
+ #[cfg(test)]
73
+ mod phase_4_baseline;
74
+ #[cfg(test)]
75
+ mod phase_5_soak;
76
+ #[cfg(test)]
77
+ mod phase5_integration;
78
+ #[cfg(test)]
79
+ mod phase5_scenarios;
80
+ #[cfg(test)]
81
+ mod phase6_persistence;
82
+ #[cfg(test)]
83
+ mod phase6_adversarial_scenarios;
84
+ #[cfg(test)]
85
+ mod phase6_convergence_validator;
86
+ #[cfg(test)]
87
+ mod transaction_invariants;
88
+ #[cfg(test)]
89
+ mod crash_recovery_tests;
90
+ #[cfg(test)]
91
+ mod causal_dependency_barrier_phase_7_1;
92
+ mod cardinality_diagnostics;
93
+ pub mod cardinality_endpoint;
94
+ mod materialization;
95
+ mod operation;
96
+ pub mod operation_log;
97
+ pub mod state_transition_store;
98
+ pub mod submission;
99
+ pub mod transaction_api;
100
+ mod peer_registry;
101
+ mod provenance;
102
+ mod references;
103
+ mod routing;
104
+ pub mod state_contract;
105
+ mod storage;
106
+ mod sync;
107
+ pub mod sync_contract;
108
+ mod trigger;
109
+ pub mod worker_mesh;
110
+ mod workflow;
111
+ #[cfg(test)]
112
+ mod workflow_acceptance_tests;
113
+ mod workflow_integration;
114
+ mod workflow_runtime;
115
+ pub mod workload;
116
+
117
+ pub use acquisition::{
118
+ AcquisitionDeduplicator, AcquisitionPolicy, AcquisitionResult, ContentRequest, ContentResponse,
119
+ ContentTransferError,
120
+ };
121
+ pub use capability::{
122
+ CapabilityAccessModel, CapabilityCheckpoint, CapabilityContext, CapabilityExecutionContext,
123
+ CapabilityExecutionResult, CapabilityId, CapabilityLifecycleState, CapabilityMetadata,
124
+ CapabilityOperationResult, CapabilityQuery, CapabilityQueryResult, CapabilityRef,
125
+ CapabilityRegistry, CapabilityRequirements, CapabilityResolver, CapabilityRole,
126
+ DistributedCapabilityAdvertisement, ExecutionRef, FlowCapability, StateRequirement,
127
+ };
128
+ pub use content_distribution::{
129
+ CapabilityLocationRegistry, ContentAcquisitionManager, ContentAddressableStore,
130
+ DistributedReferenceResolver, ProjectionDistribution,
131
+ };
132
+ pub use convergence::{
133
+ CausalHistory, ConflictResolution, ConflictResolutionStrategy, MergeResult, StateVersion,
134
+ VectorClock,
135
+ };
136
+ pub use analytics::{AutoTuner, IndexAnalytics, IndexMetrics, PerformanceReport, QueryTrace, TuningRecommendation};
137
+ pub use cron::{CronSchedule, CronScheduler};
138
+ pub use distributed_indexing::{
139
+ ConsistencyLevel, ConsistencyProtocol, DistributedIndexManager, DistributedSyncStatus,
140
+ IndexOperation, IndexReplicationState, IndexSyncMessage, IndexVectorClock, RemoteIndexMetadata,
141
+ RemoteIndexState, ReplicationStatus, VersionConflict,
142
+ };
143
+ pub use execution::{Execution, ExecutionQueue, ExecutionStatus, RetryPolicy};
144
+ pub use indexing::{IndexConfig, IndexManager, IndexStats, IndexType};
145
+ pub use submission::{SubmissionManager, SubmissionMetrics};
146
+ pub use admission::{SubmissionOutcome, RejectionReason, ProductionRetryPolicy};
147
+ pub use sharding::{
148
+ HotspotAlert, RebalanceOperation, ShardDistributionSummary, ShardId, ShardKey, ShardManager,
149
+ ShardMetrics, ShardRange, ShardingStrategy,
150
+ };
151
+ pub use transactions::{
152
+ CommitId, CommitValidator, ConsistencyContract, OperationCommand,
153
+ OperationId, StateTransition, StateDependency, TransactionExecutor,
154
+ TransitionResult,
155
+ };
156
+ pub use state_hash::{CanonicalState, StateHash};
157
+ pub use permutation_scheduler::{OperationSchedule, PermutationScheduler, ScheduleStrategy};
158
+ pub use multi_node_convergence::{
159
+ ConvergenceAggregation, ConvergenceResult, MultiNodeConvergenceSimulator, NodeExecutionResult,
160
+ };
161
+ pub use distributed_transactions::{
162
+ DistributedTransactionExecutor, EnvelopeId, ReplicaState, ReplicationMessage, TransactionEnvelope,
163
+ };
164
+ pub use operation_algebra::{
165
+ ConflictType, ConvergenceSemantic, OperationAlgebra, OperationAlgebraRegistry,
166
+ };
167
+ pub use crash_atomic_boundary::{
168
+ CrashAtomicExecutor, CrashBoundaryPoint, CrashRecoveryState, InvariantCheckResult,
169
+ RecoveryDecision,
170
+ };
171
+ pub use causal_dependency_barrier::{
172
+ ApplyResult, CausalDependencyBarrier, OperationState, PendingOperation, ReceiveResult,
173
+ StateCounters,
174
+ };
175
+ pub use partition_reconciliation::{
176
+ DeterminismCheckResult, PartitionDeterminismChecker, PartitionTrace, PartitionedReplica,
177
+ ReconciliationResult, ReplicaPartition,
178
+ };
179
+ pub use consistency_contract::{
180
+ CommutativeVerificationResult, ConsensusEnforcementResult, ConsensusRequirementChecker,
181
+ ConsistencyContractEnforcer, DeterministicMergeVerificationResult, NoDowngradeVerificationResult,
182
+ OperationApplication,
183
+ };
184
+ pub use persistence_reality::{
185
+ CorruptionDetectionResult, DurablePersistenceLayer, FsyncDurabilityResult,
186
+ PersistenceCrashPoint, PersistenceContractVerifier, RecoveryResult, WriteResult, WALEntry,
187
+ };
188
+ pub use adversarial_transport::{
189
+ AdversarialTransport, ConnectionId, ConnectivityMatrix, FaultSeed, InMemoryTransport,
190
+ LinkState, MessageId, NetworkEvent, NetworkSchedule, NodeId, ReplicationTransport,
191
+ };
192
+ pub use materialization::{MaterializationHandler, ReactiveCollectionIntegration};
193
+ pub use operation::{MutationOrder, Operation, OperationType, DEFAULT_MUTATION_ORDER};
194
+ pub use operation_log::{MemoryOperationLog, OperationLogEntry};
195
+ pub use state_transition_store::{MemoryStateTransitionStore, StateTransitionRecord};
196
+ pub use transaction_api::{TransactionManager, TxError, TxResult};
197
+ pub use peer_registry::PeerRegistry;
198
+ pub use provenance::{
199
+ AcquisitionMetadata, DerivedState, DerivedStateProvenance, DerivedValue, OperationSource,
200
+ };
201
+ pub use references::{
202
+ ContentIdentity, ContentRange, FlowRef, PeerAdvertisement, PeerId, ReferenceResolver,
203
+ ResolutionMetadata, ResolutionResult,
204
+ };
205
+ pub use routing::{
206
+ CapabilityRouter, ExecutionClaim, ExecutionClaimer, ExecutionResultPublisher, PeerStateVersion,
207
+ RouteDecision,
208
+ };
209
+ pub use storage::{CheckpointData, FileStorage, MemoryStorage, Storage};
210
+ pub use sync::{ChangeLog, Conflict, ConflictDetector, PeerState, SyncMessage, SyncState};
211
+ pub use trigger::{Trigger, TriggerFilter, TriggerRegistry};
212
+ pub use workflow::{
213
+ BlockedReason, WorkflowGraph, WorkflowInstance, WorkflowNode, WorkflowOperation, WorkflowRef,
214
+ WorkflowState, WorkflowStepRef, WorkflowStepState,
215
+ };
216
+ pub use workflow_integration::{
217
+ WorkflowCronTrigger, WorkflowIntegration, WorkflowProvenance, WorkflowTrigger,
218
+ };
219
+ pub use workflow_runtime::{WorkflowOperationEntry, WorkflowRuntime};
220
+
221
+ use serde::de::DeserializeOwned;
222
+ use serde::{Deserialize, Serialize};
223
+ use serde_json::Value;
224
+ use sha2::{Digest, Sha256};
225
+ use std::any::type_name;
226
+ use std::collections::hash_map::DefaultHasher;
227
+ use std::collections::{HashMap, HashSet};
228
+ use std::fmt::{Display, Formatter};
229
+ use std::fs::{self, OpenOptions};
230
+ use std::hash::{Hash, Hasher};
231
+ use std::io::{BufRead, BufReader, Write};
232
+ use std::path::{Path, PathBuf};
233
+ use std::sync::{Arc, Mutex};
234
+ use std::time::{SystemTime, UNIX_EPOCH};
235
+ use tokio::sync::broadcast;
236
+
237
+ pub type Result<T> = std::result::Result<T, FlowError>;
238
+
239
+ #[derive(Debug)]
240
+ pub enum FlowError {
241
+ Io(std::io::Error),
242
+ Serde(serde_json::Error),
243
+ CorruptLogLine(String),
244
+ CapabilityError(String),
245
+ }
246
+
247
+ impl Display for FlowError {
248
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
249
+ match self {
250
+ FlowError::Io(e) => write!(f, "io error: {e}"),
251
+ FlowError::Serde(e) => write!(f, "serde error: {e}"),
252
+ FlowError::CorruptLogLine(line) => write!(f, "corrupt log line: {line}"),
253
+ FlowError::CapabilityError(msg) => write!(f, "capability error: {msg}"),
254
+ }
255
+ }
256
+ }
257
+
258
+ impl std::error::Error for FlowError {}
259
+
260
+ impl From<std::io::Error> for FlowError {
261
+ fn from(value: std::io::Error) -> Self {
262
+ FlowError::Io(value)
263
+ }
264
+ }
265
+
266
+ impl From<serde_json::Error> for FlowError {
267
+ fn from(value: serde_json::Error) -> Self {
268
+ FlowError::Serde(value)
269
+ }
270
+ }
271
+
272
+ impl From<String> for FlowError {
273
+ fn from(value: String) -> Self {
274
+ FlowError::CapabilityError(value)
275
+ }
276
+ }
277
+
278
+ #[derive(Clone)]
279
+ pub struct FeltDb {
280
+ inner: Arc<Mutex<Inner>>,
281
+ event_tx: broadcast::Sender<ChangeEvent>,
282
+ }
283
+
284
+ #[derive(Default)]
285
+ #[allow(dead_code)] // Reserved runtime subsystems are initialized before their public APIs land.
286
+ struct Inner {
287
+ path: PathBuf,
288
+ rows: HashMap<String, HashMap<String, StoredRow>>,
289
+ key_counters: HashMap<String, u64>,
290
+ query_count_by_type: HashMap<String, usize>,
291
+ adaptive_indexes: HashSet<String>,
292
+ instance_id: String,
293
+ sequence: u64,
294
+ sync_state: SyncState,
295
+ change_log: ChangeLog,
296
+ execution_queue: ExecutionQueue,
297
+ trigger_registry: TriggerRegistry,
298
+ cron_scheduler: CronScheduler,
299
+ capability_registry: CapabilityRegistry,
300
+ peer_registry: PeerRegistry,
301
+ content_store: ContentAddressableStore,
302
+ capability_locations: CapabilityLocationRegistry,
303
+ bootstrapped: bool,
304
+ applied_transactions: HashSet<String>,
305
+ collection_cardinality: HashMap<String, u64>,
306
+ }
307
+
308
+ #[derive(Debug, Clone, Serialize, Deserialize)]
309
+ pub struct AtomicMutation {
310
+ pub capability: String,
311
+ pub key: String,
312
+ pub value: Option<Value>,
313
+ }
314
+ #[derive(Debug, Clone, Serialize, Deserialize)]
315
+ pub struct AtomicPrecondition {
316
+ pub capability: String,
317
+ pub key: String,
318
+ pub expected_version: Option<u64>,
319
+ }
320
+ #[derive(Debug, Clone, Serialize, Deserialize)]
321
+ pub struct AtomicCommit {
322
+ pub transaction_id: String,
323
+ pub state_before: u64,
324
+ pub state_after: u64,
325
+ pub rows: Vec<StoredRow>,
326
+ pub duplicate: bool,
327
+ }
328
+ #[derive(Debug, Clone, Serialize, Deserialize)]
329
+ struct TransactionLogRecord {
330
+ record_type: String,
331
+ transaction_id: String,
332
+ state_before: u64,
333
+ state_after: u64,
334
+ rows: Vec<StoredRow>,
335
+ #[serde(default)]
336
+ audit: Option<Value>,
337
+ }
338
+
339
+ /// An event representing a change to the database (insert or update of a record).
340
+ ///
341
+ /// This event is broadcast to all live query subscribers so they can update their results.
342
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
343
+ pub struct ChangeEvent {
344
+ /// The capability namespace of the changed record.
345
+ pub capability: String,
346
+ /// The key of the changed record.
347
+ pub key: String,
348
+ /// The Rust type name of the changed record.
349
+ pub rust_type: String,
350
+ /// The timestamp of the change in milliseconds since UNIX epoch.
351
+ pub unix_ms: u128,
352
+ }
353
+
354
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
355
+ pub struct StoredRow {
356
+ pub capability: String,
357
+ pub key: String,
358
+ pub rust_type: String,
359
+ pub value: Value,
360
+ pub unix_ms: u128,
361
+ /// Content hash for deduplication and content addressing
362
+ #[serde(default)]
363
+ pub content_hash: Option<String>,
364
+ /// Reference identity if this row represents a referenced object
365
+ #[serde(default)]
366
+ pub flow_ref: Option<FlowRef>,
367
+ /// Tombstone marker used to durably replay deletions.
368
+ #[serde(default)]
369
+ pub deleted: bool,
370
+ /// Canonical mutation which produced this row. Older logs remain readable.
371
+ #[serde(default)]
372
+ pub operation: Option<Operation>,
373
+ }
374
+
375
+ #[derive(Debug, Clone, Serialize, Deserialize)]
376
+ pub struct DatabaseSnapshot {
377
+ pub rows: Vec<StoredRow>,
378
+ pub versions: HashMap<String, u64>,
379
+ pub content_hash: String,
380
+ }
381
+
382
+ impl DatabaseSnapshot {
383
+ fn compute_hash(rows: &[StoredRow], versions: &HashMap<String, u64>) -> Result<String> {
384
+ let mut rows = rows.to_vec();
385
+ rows.sort_by(|left, right| left.key.cmp(&right.key));
386
+ let versions: std::collections::BTreeMap<_, _> = versions.iter().collect();
387
+ let mut hasher = Sha256::new();
388
+ hasher.update(serde_json::to_vec(&(rows, versions))?);
389
+ Ok(format!("{:x}", hasher.finalize()))
390
+ }
391
+
392
+ pub fn verify(&self) -> Result<()> {
393
+ if self.content_hash != Self::compute_hash(&self.rows, &self.versions)? {
394
+ return Err(FlowError::CapabilityError(
395
+ "snapshot content hash mismatch".to_string(),
396
+ ));
397
+ }
398
+ Ok(())
399
+ }
400
+ }
401
+
402
+ #[derive(Debug, Serialize, Deserialize)]
403
+ struct LogHeader {
404
+ record_type: String,
405
+ local_sequence: u64,
406
+ observed_versions: HashMap<String, u64>,
407
+ }
408
+
409
+ #[derive(Debug, Default, Serialize, Deserialize)]
410
+ struct SyncMetadata {
411
+ local_sequence: u64,
412
+ observed_versions: HashMap<String, u64>,
413
+ peer_acknowledgements: HashMap<String, HashMap<String, u64>>,
414
+ }
415
+
416
+ pub fn open<P: AsRef<Path>>(path: P) -> Result<FeltDb> {
417
+ FeltDb::open(path)
418
+ }
419
+
420
+ impl FeltDb {
421
+ pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
422
+ let path = path.as_ref().to_path_buf();
423
+ if let Some(parent) = path.parent() {
424
+ fs::create_dir_all(parent)?;
425
+ }
426
+ if !path.exists() {
427
+ OpenOptions::new().create(true).append(true).open(&path)?;
428
+ }
429
+
430
+ // Generate instance ID from path hash
431
+ let instance_id = format!("feltdb-{:x}", {
432
+ let mut hasher = DefaultHasher::new();
433
+ path.to_string_lossy().hash(&mut hasher);
434
+ hasher.finish()
435
+ });
436
+
437
+ let mut inner = Inner {
438
+ path: path.clone(),
439
+ instance_id: instance_id.clone(),
440
+ ..Inner::default()
441
+ };
442
+
443
+ // Initialize sync state with instance ID
444
+ inner.sync_state = SyncState::new(instance_id);
445
+ replay_log(&mut inner)?;
446
+ load_sync_metadata(&mut inner)?;
447
+
448
+ let (event_tx, _) = broadcast::channel(1024);
449
+
450
+ Ok(Self {
451
+ inner: Arc::new(Mutex::new(inner)),
452
+ event_tx,
453
+ })
454
+ }
455
+
456
+ pub fn insert<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
457
+ let capability = key
458
+ .split_once(':')
459
+ .map(|(cap, _)| cap)
460
+ .unwrap_or("default")
461
+ .to_string();
462
+ self.insert_internal(capability, key.to_string(), value)?;
463
+ Ok(())
464
+ }
465
+
466
+ /// Update an existing record with vector clock tracking
467
+ pub fn update<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
468
+ let capability = key
469
+ .split_once(':')
470
+ .map(|(cap, _)| cap)
471
+ .unwrap_or("default")
472
+ .to_string();
473
+ self.update_internal(capability, key.to_string(), value)?;
474
+ Ok(())
475
+ }
476
+
477
+ /// Delete a record with vector clock tracking
478
+ pub fn delete(&self, key: &str) -> Result<()> {
479
+ let capability = key
480
+ .split_once(':')
481
+ .map(|(cap, _)| cap)
482
+ .unwrap_or("default")
483
+ .to_string();
484
+ self.delete_internal(capability, key.to_string())?;
485
+ Ok(())
486
+ }
487
+
488
+ pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
489
+ let capability = key
490
+ .split_once(':')
491
+ .map(|(cap, _)| cap)
492
+ .unwrap_or("default")
493
+ .to_string();
494
+ let inner = self.inner.lock().expect("lock poisoned");
495
+ let value = inner
496
+ .rows
497
+ .get(&capability)
498
+ .and_then(|bucket| bucket.get(key))
499
+ .map(|row| serde_json::from_value::<T>(row.value.clone()))
500
+ .transpose()?;
501
+ Ok(value)
502
+ }
503
+
504
+ pub fn query<T, F>(&self, predicate: F) -> Result<Vec<T>>
505
+ where
506
+ T: DeserializeOwned,
507
+ F: Fn(&T) -> bool,
508
+ {
509
+ let rust_type = type_name::<T>().to_string();
510
+ let mut inner = self.inner.lock().expect("lock poisoned");
511
+ *inner
512
+ .query_count_by_type
513
+ .entry(rust_type.clone())
514
+ .or_default() += 1;
515
+ if inner.query_count_by_type[&rust_type] >= 3 {
516
+ inner.adaptive_indexes.insert(rust_type.clone());
517
+ }
518
+
519
+ let mut out = Vec::new();
520
+ for bucket in inner.rows.values() {
521
+ for row in bucket.values() {
522
+ if row.rust_type != rust_type {
523
+ continue;
524
+ }
525
+ let parsed = serde_json::from_value::<T>(row.value.clone())?;
526
+ if predicate(&parsed) {
527
+ out.push(parsed);
528
+ }
529
+ }
530
+ }
531
+ Ok(out)
532
+ }
533
+
534
+ pub fn capability(&self, name: &str) -> Capability {
535
+ Capability {
536
+ db: self.clone(),
537
+ name: name.to_string(),
538
+ }
539
+ }
540
+
541
+ pub fn is_auto_indexed<T>(&self) -> bool {
542
+ let rust_type = type_name::<T>().to_string();
543
+ let inner = self.inner.lock().expect("lock poisoned");
544
+ inner.adaptive_indexes.contains(&rust_type)
545
+ }
546
+
547
+ /// Get the instance ID for this FeltDB instance
548
+ pub fn instance_id(&self) -> Result<String> {
549
+ let inner = self.inner.lock().expect("lock poisoned");
550
+ Ok(inner.instance_id.clone())
551
+ }
552
+
553
+ /// Get the current sequence number for this instance
554
+ pub fn sequence(&self) -> Result<u64> {
555
+ let inner = self.inner.lock().expect("lock poisoned");
556
+ Ok(inner.sequence)
557
+ }
558
+
559
+ /// Get the maintained cardinality (record count) for a collection capability.
560
+ /// Returns the count of non-deleted records in the collection.
561
+ pub fn collection_cardinality(&self, capability: &str) -> Result<u64> {
562
+ let inner = self.inner.lock().expect("lock poisoned");
563
+ Ok(inner.collection_cardinality.get(capability).copied().unwrap_or(0))
564
+ }
565
+
566
+ /// Diagnostic: List all collection capabilities and their cardinalities.
567
+ /// Used to detect capability format mismatches.
568
+ pub fn list_cardinalities(&self) -> Result<Vec<(String, u64)>> {
569
+ let inner = self.inner.lock().expect("lock poisoned");
570
+ let mut result: Vec<_> = inner
571
+ .collection_cardinality
572
+ .iter()
573
+ .map(|(k, v)| (k.clone(), *v))
574
+ .collect();
575
+ result.sort_by(|a, b| b.1.cmp(&a.1)); // Sort by count descending
576
+ Ok(result)
577
+ }
578
+
579
+ /// Diagnostic: Count actual rows for a given capability pattern.
580
+ /// Used to verify that rows exist even if cardinality is 0.
581
+ pub fn diagnostic_row_count(&self, capability_pattern: &str) -> Result<usize> {
582
+ let inner = self.inner.lock().expect("lock poisoned");
583
+ Ok(inner
584
+ .rows
585
+ .iter()
586
+ .filter(|(cap, _)| cap.contains(capability_pattern))
587
+ .map(|(_, bucket)| bucket.values().filter(|row| !row.deleted).count())
588
+ .sum())
589
+ }
590
+
591
+ /// Diagnostic: Get actual persisted capability strings matching a pattern.
592
+ /// Used to identify capability identity formats in use.
593
+ pub fn diagnostic_capability_keys(&self, capability_pattern: &str) -> Result<Vec<String>> {
594
+ let inner = self.inner.lock().expect("lock poisoned");
595
+ let mut keys: Vec<_> = inner
596
+ .rows
597
+ .keys()
598
+ .filter(|k| k.contains(capability_pattern))
599
+ .cloned()
600
+ .collect();
601
+ keys.sort();
602
+ Ok(keys)
603
+ }
604
+
605
+ pub fn has_applied_transaction(&self, transaction_id: &str) -> Result<bool> {
606
+ Ok(self
607
+ .inner
608
+ .lock()
609
+ .expect("lock poisoned")
610
+ .applied_transactions
611
+ .contains(transaction_id))
612
+ }
613
+
614
+ /// Atomically validates and durably applies an ordered mutation batch.
615
+ /// The complete transaction is represented by one log record and becomes
616
+ /// visible in memory only after that record has been appended successfully.
617
+ pub fn apply_atomic_transaction(
618
+ &self,
619
+ transaction_id: &str,
620
+ expected_parent: Option<u64>,
621
+ preconditions: &[AtomicPrecondition],
622
+ mutations: &[AtomicMutation],
623
+ audit: Option<Value>,
624
+ ) -> Result<AtomicCommit> {
625
+ let (commit, events) = {
626
+ let mut inner = self.inner.lock().expect("lock poisoned");
627
+ if inner.applied_transactions.contains(transaction_id) {
628
+ return Ok(AtomicCommit {
629
+ transaction_id: transaction_id.into(),
630
+ state_before: inner.sequence,
631
+ state_after: inner.sequence,
632
+ rows: vec![],
633
+ duplicate: true,
634
+ });
635
+ }
636
+ if let Some(expected) = expected_parent {
637
+ if expected != inner.sequence {
638
+ return Err(FlowError::CapabilityError(format!(
639
+ "PRECONDITION_FAILED:transaction_parent:{expected}:{}",
640
+ inner.sequence
641
+ )));
642
+ }
643
+ }
644
+ for condition in preconditions {
645
+ let actual = inner
646
+ .rows
647
+ .get(&condition.capability)
648
+ .and_then(|bucket| bucket.get(&condition.key))
649
+ .and_then(|row| row.operation.as_ref())
650
+ .map(|op| op.sequence);
651
+ if actual != condition.expected_version {
652
+ return Err(FlowError::CapabilityError(format!(
653
+ "PRECONDITION_FAILED:{}:{}:{:?}:{actual:?}",
654
+ condition.capability, condition.key, condition.expected_version
655
+ )));
656
+ }
657
+ }
658
+ let state_before = inner.sequence;
659
+ let mut next_sequence = state_before;
660
+ let mut rows = Vec::with_capacity(mutations.len());
661
+ let mut vector_clock = inner.sync_state.get_or_init_vector_clock().clone();
662
+ for mutation in mutations {
663
+ next_sequence += 1;
664
+ *vector_clock.entry(inner.instance_id.clone()).or_default() += 1;
665
+ let rust_type = "serde_json::Value".to_string();
666
+ let operation = match &mutation.value {
667
+ Some(value)
668
+ if inner
669
+ .rows
670
+ .get(&mutation.capability)
671
+ .is_some_and(|bucket| bucket.contains_key(&mutation.key)) =>
672
+ {
673
+ Operation::update(
674
+ next_sequence,
675
+ inner.instance_id.clone(),
676
+ next_sequence,
677
+ mutation.key.clone(),
678
+ value.clone(),
679
+ rust_type.clone(),
680
+ mutation.capability.clone(),
681
+ )
682
+ }
683
+ Some(value) => Operation::insert(
684
+ next_sequence,
685
+ inner.instance_id.clone(),
686
+ next_sequence,
687
+ mutation.key.clone(),
688
+ value.clone(),
689
+ rust_type.clone(),
690
+ mutation.capability.clone(),
691
+ ),
692
+ None => Operation::delete(
693
+ next_sequence,
694
+ inner.instance_id.clone(),
695
+ next_sequence,
696
+ mutation.key.clone(),
697
+ rust_type.clone(),
698
+ mutation.capability.clone(),
699
+ ),
700
+ }
701
+ .with_vector_clock(vector_clock.clone());
702
+ rows.push(StoredRow {
703
+ capability: mutation.capability.clone(),
704
+ key: mutation.key.clone(),
705
+ rust_type,
706
+ value: mutation.value.clone().unwrap_or(Value::Null),
707
+ unix_ms: now_ms(),
708
+ content_hash: Some(operation.content_hash.clone()),
709
+ flow_ref: None,
710
+ deleted: mutation.value.is_none(),
711
+ operation: Some(operation),
712
+ });
713
+ }
714
+ let mut audit = audit;
715
+ if let Some(object) = audit.as_mut().and_then(Value::as_object_mut) {
716
+ object.insert("state_before".into(), Value::from(state_before));
717
+ object.insert("state_after".into(), Value::from(next_sequence));
718
+ }
719
+ let record = TransactionLogRecord {
720
+ record_type: "feltdb.transaction.v1".into(),
721
+ transaction_id: transaction_id.into(),
722
+ state_before,
723
+ state_after: next_sequence,
724
+ rows: rows.clone(),
725
+ audit,
726
+ };
727
+ append_transaction(&inner.path, &record)?;
728
+ inner.sequence = next_sequence;
729
+ for row in &rows {
730
+ if let Some(operation) = row.operation.clone() {
731
+ inner.change_log.add_operation(operation);
732
+ }
733
+ if row.deleted {
734
+ if let Some(bucket) = inner.rows.get_mut(&row.capability) {
735
+ bucket.remove(&row.key);
736
+ }
737
+ // Decrement cardinality on delete
738
+ let count = inner.collection_cardinality.entry(row.capability.clone()).or_default();
739
+ *count = count.saturating_sub(1);
740
+ } else {
741
+ // Track whether this is an insert (new row) or update (existing row)
742
+ let is_insert = !inner
743
+ .rows
744
+ .get(&row.capability)
745
+ .is_some_and(|bucket| bucket.contains_key(&row.key));
746
+
747
+ inner
748
+ .rows
749
+ .entry(row.capability.clone())
750
+ .or_default()
751
+ .insert(row.key.clone(), row.clone());
752
+
753
+ // Increment cardinality only on insert, not update
754
+ if is_insert {
755
+ let count = inner.collection_cardinality.entry(row.capability.clone()).or_default();
756
+ *count = count.saturating_add(1);
757
+ }
758
+ }
759
+ }
760
+ inner.sync_state.merge_vector_clock(&vector_clock);
761
+ inner.applied_transactions.insert(transaction_id.into());
762
+ let events = rows
763
+ .iter()
764
+ .map(|row| ChangeEvent {
765
+ capability: row.capability.clone(),
766
+ key: row.key.clone(),
767
+ rust_type: row.rust_type.clone(),
768
+ unix_ms: row.unix_ms,
769
+ })
770
+ .collect::<Vec<_>>();
771
+ (
772
+ AtomicCommit {
773
+ transaction_id: transaction_id.into(),
774
+ state_before,
775
+ state_after: inner.sequence,
776
+ rows,
777
+ duplicate: false,
778
+ },
779
+ events,
780
+ )
781
+ };
782
+ for event in events {
783
+ let _ = self.event_tx.send(event);
784
+ }
785
+ Ok(commit)
786
+ }
787
+
788
+ fn insert_internal<T: Serialize>(
789
+ &self,
790
+ capability: String,
791
+ key: String,
792
+ value: T,
793
+ ) -> Result<()> {
794
+ let rust_type = type_name::<T>().to_string();
795
+ let value_json = serde_json::to_value(value)?;
796
+ let mut row = StoredRow {
797
+ capability: capability.clone(),
798
+ key: key.clone(),
799
+ rust_type: rust_type.clone(),
800
+ value: value_json.clone(),
801
+ unix_ms: now_ms(),
802
+ content_hash: None,
803
+ flow_ref: None,
804
+ deleted: false,
805
+ operation: None,
806
+ };
807
+
808
+ {
809
+ let mut inner = self.inner.lock().expect("lock poisoned");
810
+
811
+ // Increment sequence number
812
+ inner.sequence += 1;
813
+
814
+ // Increment vector clock for this instance
815
+ inner.sync_state.increment_vector_clock();
816
+
817
+ // Get current vector clock to attach to operation
818
+ let vector_clock = inner.sync_state.get_or_init_vector_clock().clone();
819
+
820
+ // Create an operation with vector clock for convergence
821
+ let op = Operation::insert(
822
+ inner.sequence,
823
+ inner.instance_id.clone(),
824
+ inner.sequence,
825
+ key.clone(),
826
+ value_json,
827
+ rust_type.clone(),
828
+ capability.clone(),
829
+ )
830
+ .with_vector_clock(vector_clock);
831
+
832
+ // Add operation to change log for sync
833
+ row.operation = Some(op.clone());
834
+ inner.change_log.add_operation(op.clone());
835
+
836
+ append_event(&inner.path, &row)?;
837
+ inner
838
+ .rows
839
+ .entry(capability.clone())
840
+ .or_default()
841
+ .insert(key.clone(), row);
842
+ let suffix = key.rsplit_once(':').map(|(_, right)| right);
843
+ if let Some(id) = suffix.and_then(|s| s.parse::<u64>().ok()) {
844
+ let entry = inner.key_counters.entry(capability.clone()).or_default();
845
+ *entry = (*entry).max(id + 1);
846
+ }
847
+ }
848
+
849
+ let event = ChangeEvent {
850
+ capability,
851
+ key,
852
+ rust_type,
853
+ unix_ms: now_ms(),
854
+ };
855
+ let _ = self.event_tx.send(event);
856
+
857
+ Ok(())
858
+ }
859
+
860
+ fn update_internal<T: Serialize>(
861
+ &self,
862
+ capability: String,
863
+ key: String,
864
+ value: T,
865
+ ) -> Result<()> {
866
+ let rust_type = type_name::<T>().to_string();
867
+ let value_json = serde_json::to_value(value)?;
868
+ let mut row = StoredRow {
869
+ capability: capability.clone(),
870
+ key: key.clone(),
871
+ rust_type: rust_type.clone(),
872
+ value: value_json.clone(),
873
+ unix_ms: now_ms(),
874
+ content_hash: None,
875
+ flow_ref: None,
876
+ deleted: false,
877
+ operation: None,
878
+ };
879
+
880
+ {
881
+ let mut inner = self.inner.lock().expect("lock poisoned");
882
+
883
+ // Increment sequence number
884
+ inner.sequence += 1;
885
+
886
+ // Increment vector clock for this instance
887
+ inner.sync_state.increment_vector_clock();
888
+
889
+ // Get current vector clock to attach to operation
890
+ let vector_clock = inner.sync_state.get_or_init_vector_clock().clone();
891
+
892
+ // Create an update operation with vector clock for convergence
893
+ let op = Operation::update(
894
+ inner.sequence,
895
+ inner.instance_id.clone(),
896
+ inner.sequence,
897
+ key.clone(),
898
+ value_json,
899
+ rust_type.clone(),
900
+ capability.clone(),
901
+ )
902
+ .with_vector_clock(vector_clock);
903
+
904
+ // Add operation to change log for sync
905
+ row.operation = Some(op.clone());
906
+ inner.change_log.add_operation(op.clone());
907
+
908
+ append_event(&inner.path, &row)?;
909
+ inner
910
+ .rows
911
+ .entry(capability.clone())
912
+ .or_default()
913
+ .insert(key.clone(), row);
914
+ }
915
+
916
+ let event = ChangeEvent {
917
+ capability,
918
+ key,
919
+ rust_type,
920
+ unix_ms: now_ms(),
921
+ };
922
+ let _ = self.event_tx.send(event);
923
+
924
+ Ok(())
925
+ }
926
+
927
+ fn delete_internal(&self, capability: String, key: String) -> Result<()> {
928
+ {
929
+ let mut inner = self.inner.lock().expect("lock poisoned");
930
+
931
+ // Get the row to determine its type
932
+ let rust_type = inner
933
+ .rows
934
+ .get(&capability)
935
+ .and_then(|bucket| bucket.get(&key))
936
+ .map(|row| row.rust_type.clone())
937
+ .unwrap_or_else(|| "Unknown".to_string());
938
+
939
+ // Increment sequence number
940
+ inner.sequence += 1;
941
+
942
+ // Increment vector clock for this instance
943
+ inner.sync_state.increment_vector_clock();
944
+
945
+ // Get current vector clock to attach to operation
946
+ let vector_clock = inner.sync_state.get_or_init_vector_clock().clone();
947
+
948
+ // Create a delete operation with vector clock for convergence
949
+ let op = Operation::delete(
950
+ inner.sequence,
951
+ inner.instance_id.clone(),
952
+ inner.sequence,
953
+ key.clone(),
954
+ rust_type.clone(),
955
+ capability.clone(),
956
+ )
957
+ .with_vector_clock(vector_clock);
958
+
959
+ // Add operation to change log for sync
960
+ inner.change_log.add_operation(op.clone());
961
+
962
+ // Persist a tombstone through the same append-only log used by
963
+ // inserts and updates. Without this, deleted rows reappeared after
964
+ // reopening the database.
965
+ let tombstone = StoredRow {
966
+ capability: capability.clone(),
967
+ key: key.clone(),
968
+ rust_type,
969
+ value: serde_json::Value::Null,
970
+ unix_ms: now_ms(),
971
+ content_hash: None,
972
+ flow_ref: None,
973
+ deleted: true,
974
+ operation: Some(op),
975
+ };
976
+ append_event(&inner.path, &tombstone)?;
977
+
978
+ // Remove from in-memory store
979
+ if let Some(bucket) = inner.rows.get_mut(&capability) {
980
+ bucket.remove(&key);
981
+ }
982
+ }
983
+
984
+ let event = ChangeEvent {
985
+ capability,
986
+ key,
987
+ rust_type: "Deleted".to_string(),
988
+ unix_ms: now_ms(),
989
+ };
990
+ let _ = self.event_tx.send(event);
991
+
992
+ Ok(())
993
+ }
994
+ ///
995
+ /// The subscription returns an initial set of matching results, and provides a stream
996
+ /// of updated results whenever records matching the predicate are inserted or updated.
997
+ ///
998
+ /// # Example
999
+ ///
1000
+ /// ```ignore
1001
+ /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1002
+ /// use feltdb::open;
1003
+ /// let db = open("app.data")?;
1004
+ /// let mut subscription = db.live_query::<User, _>(|u| u.active).await?;
1005
+ /// let initial_users = subscription.initial_results();
1006
+ ///
1007
+ /// while let Some(users) = subscription.next().await {
1008
+ /// println!("Active users: {:?}", users);
1009
+ /// }
1010
+ /// # Ok(())
1011
+ /// # }
1012
+ /// ```
1013
+ pub async fn live_query<T, F>(&self, predicate: F) -> Result<LiveQuerySubscription<T>>
1014
+ where
1015
+ T: DeserializeOwned + Send + Sync + 'static,
1016
+ F: Fn(&T) -> bool + Send + Sync + 'static,
1017
+ {
1018
+ let rust_type = type_name::<T>().to_string();
1019
+
1020
+ let initial_results = {
1021
+ let mut inner = self.inner.lock().expect("lock poisoned");
1022
+ *inner
1023
+ .query_count_by_type
1024
+ .entry(rust_type.clone())
1025
+ .or_default() += 1;
1026
+ if inner.query_count_by_type[&rust_type] >= 3 {
1027
+ inner.adaptive_indexes.insert(rust_type.clone());
1028
+ }
1029
+
1030
+ let mut out = Vec::new();
1031
+ for bucket in inner.rows.values() {
1032
+ for row in bucket.values() {
1033
+ if row.rust_type != rust_type {
1034
+ continue;
1035
+ }
1036
+ let parsed = serde_json::from_value::<T>(row.value.clone())?;
1037
+ if predicate(&parsed) {
1038
+ out.push(parsed);
1039
+ }
1040
+ }
1041
+ }
1042
+ out
1043
+ };
1044
+
1045
+ let rx = self.event_tx.subscribe();
1046
+ let db = self.clone();
1047
+ let subscription = LiveQuerySubscription {
1048
+ initial_results,
1049
+ rx,
1050
+ db,
1051
+ rust_type,
1052
+ predicate: Arc::new(Mutex::new(None)),
1053
+ };
1054
+
1055
+ Ok(subscription)
1056
+ }
1057
+
1058
+ /// Get current sync state information
1059
+ pub fn sync_info(&self) -> Result<SyncState> {
1060
+ let inner = self.inner.lock().expect("lock poisoned");
1061
+ Ok(inner.sync_state.clone())
1062
+ }
1063
+
1064
+ /// Read an untyped value for protocol adapters such as the HTTP server.
1065
+ pub fn get_value(&self, key: &str) -> Result<Option<Value>> {
1066
+ self.get::<Value>(key)
1067
+ }
1068
+
1069
+ /// List the canonical values in one collection/capability namespace.
1070
+ pub fn list_collection(&self, capability: &str) -> Result<Vec<StoredRow>> {
1071
+ let inner = self.inner.lock().expect("lock poisoned");
1072
+ Ok(inner
1073
+ .rows
1074
+ .get(capability)
1075
+ .map(|rows| rows.values().cloned().collect())
1076
+ .unwrap_or_default())
1077
+ }
1078
+
1079
+ /// Subscribe to canonical mutation events.
1080
+ pub fn subscribe_changes(&self) -> broadcast::Receiver<ChangeEvent> {
1081
+ self.event_tx.subscribe()
1082
+ }
1083
+
1084
+ /// Add a peer for synchronization
1085
+ pub fn add_sync_peer(&self, peer_id: String) -> Result<()> {
1086
+ let mut inner = self.inner.lock().expect("lock poisoned");
1087
+ inner.sync_state.add_peer(peer_id);
1088
+ Ok(())
1089
+ }
1090
+
1091
+ /// Remove a peer from synchronization
1092
+ pub fn remove_sync_peer(&self, peer_id: &str) -> Result<()> {
1093
+ let mut inner = self.inner.lock().expect("lock poisoned");
1094
+ inner.sync_state.remove_peer(peer_id);
1095
+ Ok(())
1096
+ }
1097
+
1098
+ /// Get pending operations for a peer
1099
+ pub fn get_pending_for_peer(
1100
+ &self,
1101
+ _peer_id: &str,
1102
+ since_sequence: u64,
1103
+ ) -> Result<Vec<Operation>> {
1104
+ let inner = self.inner.lock().expect("lock poisoned");
1105
+ let pending: Vec<Operation> = inner
1106
+ .change_log
1107
+ .pending_operations()
1108
+ .into_iter()
1109
+ .filter(|op| op.sequence > since_sequence)
1110
+ .collect();
1111
+ Ok(pending)
1112
+ }
1113
+
1114
+ /// Return operations missing from a peer's per-origin version vector.
1115
+ pub fn operations_since(
1116
+ &self,
1117
+ versions: &std::collections::HashMap<String, u64>,
1118
+ ) -> Result<Vec<Operation>> {
1119
+ let inner = self.inner.lock().expect("lock poisoned");
1120
+ Ok(inner.change_log.operations_since(versions))
1121
+ }
1122
+
1123
+ /// Return the highest durable operation sequence observed from each origin.
1124
+ pub fn operation_versions(&self) -> Result<std::collections::HashMap<String, u64>> {
1125
+ let inner = self.inner.lock().expect("lock poisoned");
1126
+ Ok(inner.change_log.versions())
1127
+ }
1128
+
1129
+ pub fn export_snapshot(&self) -> Result<DatabaseSnapshot> {
1130
+ let inner = self.inner.lock().expect("lock poisoned");
1131
+ let mut rows: Vec<StoredRow> = inner
1132
+ .rows
1133
+ .values()
1134
+ .flat_map(|bucket| bucket.values().cloned())
1135
+ .collect();
1136
+ for row in &mut rows {
1137
+ row.operation = None;
1138
+ }
1139
+ rows.sort_by(|left, right| left.key.cmp(&right.key));
1140
+ let versions = inner.change_log.versions();
1141
+ let content_hash = DatabaseSnapshot::compute_hash(&rows, &versions)?;
1142
+ Ok(DatabaseSnapshot {
1143
+ rows,
1144
+ versions,
1145
+ content_hash,
1146
+ })
1147
+ }
1148
+ /// Current materialized rows including their causal operation envelopes.
1149
+ /// State-contract execution uses this for record-version preconditions;
1150
+ /// portable snapshots intentionally continue to strip those envelopes.
1151
+ pub fn state_rows(&self) -> Result<Vec<StoredRow>> {
1152
+ let inner = self.inner.lock().expect("lock poisoned");
1153
+ Ok(inner
1154
+ .rows
1155
+ .values()
1156
+ .flat_map(|bucket| bucket.values().cloned())
1157
+ .collect())
1158
+ }
1159
+
1160
+ pub fn can_install_snapshot(&self) -> Result<bool> {
1161
+ let inner = self.inner.lock().expect("lock poisoned");
1162
+ Ok(!inner.bootstrapped && inner.rows.is_empty() && inner.change_log.pending.is_empty())
1163
+ }
1164
+
1165
+ /// Install a verified bootstrap snapshot into a pristine node. A node with
1166
+ /// local history must converge through operations rather than overwrite it.
1167
+ pub fn install_snapshot(&self, snapshot: DatabaseSnapshot) -> Result<()> {
1168
+ snapshot.verify()?;
1169
+ let mut inner = self.inner.lock().expect("lock poisoned");
1170
+ if !inner.rows.is_empty() || !inner.change_log.pending.is_empty() {
1171
+ return Err(FlowError::CapabilityError(
1172
+ "snapshot bootstrap requires a pristine node".to_string(),
1173
+ ));
1174
+ }
1175
+ for row in &snapshot.rows {
1176
+ if row.deleted
1177
+ || row.operation.is_some()
1178
+ || !row.key.starts_with(&format!("{}:", row.capability))
1179
+ {
1180
+ return Err(FlowError::CapabilityError(
1181
+ "snapshot contains an invalid state row".to_string(),
1182
+ ));
1183
+ }
1184
+ }
1185
+
1186
+ let temporary = inner.path.with_extension("snapshot.tmp");
1187
+ let mut file = OpenOptions::new()
1188
+ .create(true)
1189
+ .write(true)
1190
+ .truncate(true)
1191
+ .open(&temporary)?;
1192
+ write_json_line(
1193
+ &mut file,
1194
+ &LogHeader {
1195
+ record_type: "feltdb.snapshot.v1".to_string(),
1196
+ local_sequence: inner.sequence,
1197
+ observed_versions: snapshot.versions.clone(),
1198
+ },
1199
+ )?;
1200
+ for row in &snapshot.rows {
1201
+ write_json_line(&mut file, row)?;
1202
+ }
1203
+ file.flush()?;
1204
+ file.sync_all()?;
1205
+ fs::rename(temporary, &inner.path)?;
1206
+
1207
+ inner.rows.clear();
1208
+ inner.key_counters.clear();
1209
+ for row in snapshot.rows {
1210
+ if let Some(id) = row
1211
+ .key
1212
+ .rsplit_once(':')
1213
+ .and_then(|(_, right)| right.parse::<u64>().ok())
1214
+ {
1215
+ let counter = inner
1216
+ .key_counters
1217
+ .entry(row.capability.clone())
1218
+ .or_default();
1219
+ *counter = (*counter).max(id + 1);
1220
+ }
1221
+ inner
1222
+ .rows
1223
+ .entry(row.capability.clone())
1224
+ .or_default()
1225
+ .insert(row.key.clone(), row);
1226
+ }
1227
+ inner.change_log.observed_versions = snapshot.versions;
1228
+ inner.bootstrapped = true;
1229
+ persist_sync_metadata(&inner)
1230
+ }
1231
+
1232
+ /// Acknowledge receipt of operations from a peer
1233
+ pub fn acknowledge_peer_operations(&self, peer_id: String, sequence: u64) -> Result<()> {
1234
+ let mut inner = self.inner.lock().expect("lock poisoned");
1235
+ inner.change_log.acknowledge_peer(peer_id, sequence);
1236
+ persist_sync_metadata(&inner)?;
1237
+ Ok(())
1238
+ }
1239
+
1240
+ /// Durably record a peer's per-origin acknowledgement vector.
1241
+ pub fn acknowledge_peer_versions(
1242
+ &self,
1243
+ peer_id: String,
1244
+ versions: HashMap<String, u64>,
1245
+ ) -> Result<()> {
1246
+ let mut inner = self.inner.lock().expect("lock poisoned");
1247
+ inner.change_log.acknowledge_versions(peer_id, versions);
1248
+ persist_sync_metadata(&inner)
1249
+ }
1250
+
1251
+ pub fn acknowledged_peer_versions(&self, peer_id: &str) -> Result<HashMap<String, u64>> {
1252
+ let inner = self.inner.lock().expect("lock poisoned");
1253
+ Ok(inner.change_log.acknowledged_versions(peer_id))
1254
+ }
1255
+
1256
+ pub fn acknowledged_operation_count(&self, active_peers: &[String]) -> Result<usize> {
1257
+ let inner = self.inner.lock().expect("lock poisoned");
1258
+ Ok(inner.change_log.acknowledged_operation_count(active_peers))
1259
+ }
1260
+
1261
+ /// Atomically replace acknowledged history with a state snapshot and any
1262
+ /// operations still needed by at least one configured peer.
1263
+ pub fn compact_operation_log(&self, active_peers: &[String]) -> Result<usize> {
1264
+ let mut inner = self.inner.lock().expect("lock poisoned");
1265
+ let removed = inner.change_log.prune_acknowledged(active_peers);
1266
+ if removed == 0 {
1267
+ return Ok(0);
1268
+ }
1269
+ persist_sync_metadata(&inner)?;
1270
+
1271
+ let temporary = inner.path.with_extension("compact.tmp");
1272
+ let mut file = OpenOptions::new()
1273
+ .create(true)
1274
+ .write(true)
1275
+ .truncate(true)
1276
+ .open(&temporary)?;
1277
+ write_json_line(
1278
+ &mut file,
1279
+ &LogHeader {
1280
+ record_type: "feltdb.snapshot.v1".to_string(),
1281
+ local_sequence: inner.sequence,
1282
+ observed_versions: inner.change_log.versions(),
1283
+ },
1284
+ )?;
1285
+ for operation in inner.change_log.pending_operations() {
1286
+ write_json_line(&mut file, &stored_row_for_operation(operation))?;
1287
+ }
1288
+ for bucket in inner.rows.values() {
1289
+ for row in bucket.values() {
1290
+ let mut snapshot_row = row.clone();
1291
+ snapshot_row.operation = None;
1292
+ write_json_line(&mut file, &snapshot_row)?;
1293
+ }
1294
+ }
1295
+ file.flush()?;
1296
+ file.sync_all()?;
1297
+ fs::rename(temporary, &inner.path)?;
1298
+ inner.bootstrapped = true;
1299
+ Ok(removed)
1300
+ }
1301
+
1302
+ /// Register a peer in the distributed fabric
1303
+ pub fn register_peer(&self, advertisement: PeerAdvertisement) -> Result<()> {
1304
+ let inner = self.inner.lock().expect("lock poisoned");
1305
+ inner
1306
+ .peer_registry
1307
+ .register(advertisement)
1308
+ .map_err(|e| FlowError::CapabilityError(e))
1309
+ }
1310
+
1311
+ /// Get a peer's advertisement
1312
+ pub fn get_peer(&self, peer_id: &PeerId) -> Result<Option<PeerAdvertisement>> {
1313
+ let inner = self.inner.lock().expect("lock poisoned");
1314
+ inner
1315
+ .peer_registry
1316
+ .get(peer_id)
1317
+ .map_err(|e| FlowError::CapabilityError(e))
1318
+ }
1319
+
1320
+ /// Find all peers providing a namespace
1321
+ pub fn peers_for_namespace(&self, namespace: &str) -> Result<Vec<PeerAdvertisement>> {
1322
+ let inner = self.inner.lock().expect("lock poisoned");
1323
+ inner
1324
+ .peer_registry
1325
+ .peers_for_namespace(namespace)
1326
+ .map_err(|e| FlowError::CapabilityError(e))
1327
+ }
1328
+
1329
+ /// Find all peers providing a capability
1330
+ pub fn peers_for_capability(&self, capability: &str) -> Result<Vec<PeerAdvertisement>> {
1331
+ let inner = self.inner.lock().expect("lock poisoned");
1332
+ inner
1333
+ .peer_registry
1334
+ .peers_for_capability(capability)
1335
+ .map_err(|e| FlowError::CapabilityError(e))
1336
+ }
1337
+
1338
+ /// Get all registered peers
1339
+ pub fn all_peers(&self) -> Result<Vec<PeerAdvertisement>> {
1340
+ let inner = self.inner.lock().expect("lock poisoned");
1341
+ inner
1342
+ .peer_registry
1343
+ .all_peers()
1344
+ .map_err(|e| FlowError::CapabilityError(e))
1345
+ }
1346
+
1347
+ /// Resolve a FlowRef locally
1348
+ pub fn resolve_ref<T: DeserializeOwned>(&self, flow_ref: &FlowRef) -> Result<Option<T>> {
1349
+ // First, try to resolve locally
1350
+ let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
1351
+ self.get::<T>(&key)
1352
+ }
1353
+
1354
+ /// Get current sync state
1355
+ pub fn sync_state(&self) -> Result<SyncState> {
1356
+ let inner = self.inner.lock().expect("lock poisoned");
1357
+ Ok(inner.sync_state.clone())
1358
+ }
1359
+
1360
+ /// Apply an operation from a remote peer
1361
+ /// This merges the vector clock, resolves conflicts, and applies the mutation
1362
+ pub fn apply_remote_operation(&self, op: Operation) -> Result<()> {
1363
+ let mut inner = self.inner.lock().expect("lock poisoned");
1364
+
1365
+ if op.content_hash != op.compute_content_hash() {
1366
+ return Err(FlowError::CapabilityError(
1367
+ "remote operation content hash mismatch".to_string(),
1368
+ ));
1369
+ }
1370
+ if inner.change_log.has_operation(&op.instance_id, op.sequence) {
1371
+ return Ok(());
1372
+ }
1373
+ let expected = inner
1374
+ .change_log
1375
+ .versions()
1376
+ .get(&op.instance_id)
1377
+ .copied()
1378
+ .unwrap_or(0)
1379
+ + 1;
1380
+ if op.sequence != expected {
1381
+ return Err(FlowError::CapabilityError(format!(
1382
+ "remote operation sequence gap for {}: expected {}, received {}",
1383
+ op.instance_id, expected, op.sequence
1384
+ )));
1385
+ }
1386
+
1387
+ // Merge the remote vector clock into our own
1388
+ if let Some(remote_clock) = &op.vector_clock {
1389
+ inner.sync_state.merge_vector_clock(remote_clock);
1390
+ }
1391
+
1392
+ // Check if we have a local operation for this key that hasn't been synced yet
1393
+ let local_op_for_key = inner
1394
+ .change_log
1395
+ .pending_operations()
1396
+ .into_iter()
1397
+ .find(|local_op| local_op.key == op.key && local_op.capability == op.capability);
1398
+
1399
+ // Determine which operation to apply (remote or local if conflict)
1400
+ let op_to_apply = if let Some(local_op) = local_op_for_key {
1401
+ // We have a conflict - use LWW with vector clock awareness
1402
+ // Create causal histories for conflict resolution
1403
+ let local_vc = local_op.vector_clock.clone().unwrap_or_default();
1404
+ let remote_vc = op.vector_clock.clone().unwrap_or_default();
1405
+
1406
+ let local_history = CausalHistory::new(
1407
+ StateVersion::from_clocks(local_vc, local_op.timestamp_ms),
1408
+ local_op.instance_id.clone(),
1409
+ local_op.sequence,
1410
+ );
1411
+
1412
+ let remote_history = CausalHistory::new(
1413
+ StateVersion::from_clocks(remote_vc, op.timestamp_ms),
1414
+ op.instance_id.clone(),
1415
+ op.sequence,
1416
+ );
1417
+
1418
+ // Resolve using LWW strategy
1419
+ let resolution = ConflictResolution::resolve_lww(&[local_history, remote_history]);
1420
+
1421
+ // Winner index: 0 = local, 1 = remote
1422
+ if resolution.winner_index == Some(1) {
1423
+ op.clone()
1424
+ } else {
1425
+ // The local value wins, but the received operation still belongs
1426
+ // to the durable causal history so peers do not resend it forever.
1427
+ if let Some(mut history_row) = inner
1428
+ .rows
1429
+ .get(&op.capability)
1430
+ .and_then(|rows| rows.get(&op.key))
1431
+ .cloned()
1432
+ {
1433
+ history_row.operation = Some(op.clone());
1434
+ append_event(&inner.path, &history_row)?;
1435
+ }
1436
+ inner.change_log.add_operation(op);
1437
+ inner.sync_state.increment_received(1);
1438
+ return Ok(());
1439
+ }
1440
+ } else {
1441
+ op.clone()
1442
+ };
1443
+
1444
+ // Apply the winning operation to our state
1445
+ let row = StoredRow {
1446
+ capability: op_to_apply.capability.clone(),
1447
+ key: op_to_apply.key.clone(),
1448
+ rust_type: op_to_apply.rust_type.clone(),
1449
+ value: op_to_apply.value.clone().unwrap_or(serde_json::Value::Null),
1450
+ unix_ms: op_to_apply.timestamp_ms,
1451
+ content_hash: Some(op_to_apply.content_hash.clone()),
1452
+ flow_ref: None,
1453
+ deleted: op_to_apply.op_type == OperationType::Delete,
1454
+ operation: Some(op.clone()),
1455
+ };
1456
+
1457
+ match op_to_apply.op_type {
1458
+ OperationType::Insert | OperationType::Update => {
1459
+ append_event(&inner.path, &row)?;
1460
+ inner
1461
+ .rows
1462
+ .entry(op_to_apply.capability.clone())
1463
+ .or_default()
1464
+ .insert(op_to_apply.key.clone(), row);
1465
+ }
1466
+ OperationType::Delete => {
1467
+ append_event(&inner.path, &row)?;
1468
+ if let Some(bucket) = inner.rows.get_mut(&op_to_apply.capability) {
1469
+ bucket.remove(&op_to_apply.key);
1470
+ }
1471
+ }
1472
+ }
1473
+ if let Some(id) = op_to_apply
1474
+ .key
1475
+ .rsplit_once(':')
1476
+ .and_then(|(_, right)| right.parse::<u64>().ok())
1477
+ {
1478
+ let counter = inner
1479
+ .key_counters
1480
+ .entry(op_to_apply.capability.clone())
1481
+ .or_default();
1482
+ *counter = (*counter).max(id + 1);
1483
+ }
1484
+
1485
+ inner.change_log.add_operation(op);
1486
+ inner.sync_state.increment_received(1);
1487
+ let event = ChangeEvent {
1488
+ capability: op_to_apply.capability,
1489
+ key: op_to_apply.key,
1490
+ rust_type: op_to_apply.rust_type,
1491
+ unix_ms: op_to_apply.timestamp_ms,
1492
+ };
1493
+ drop(inner);
1494
+ let _ = self.event_tx.send(event);
1495
+
1496
+ Ok(())
1497
+ }
1498
+
1499
+ /// Get the current vector clock state
1500
+ pub fn get_vector_clock(&self) -> Result<Option<std::collections::HashMap<String, u64>>> {
1501
+ let inner = self.inner.lock().expect("lock poisoned");
1502
+ Ok(inner.sync_state.vector_clock.clone())
1503
+ }
1504
+ }
1505
+
1506
+ #[derive(Clone)]
1507
+ pub struct Capability {
1508
+ db: FeltDb,
1509
+ name: String,
1510
+ }
1511
+
1512
+ impl Capability {
1513
+ pub fn insert<T: Serialize>(&self, value: T) -> Result<String> {
1514
+ let key = {
1515
+ let mut inner = self.db.inner.lock().expect("lock poisoned");
1516
+ let next = inner.key_counters.entry(self.name.clone()).or_default();
1517
+ let key = format!("{}:{}", self.name, *next);
1518
+ *next += 1;
1519
+ key
1520
+ };
1521
+ self.db
1522
+ .insert_internal(self.name.clone(), key.clone(), value)?;
1523
+ Ok(key)
1524
+ }
1525
+
1526
+ pub fn insert_with_key<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
1527
+ let full_key = if key.contains(':') {
1528
+ key.to_string()
1529
+ } else {
1530
+ format!("{}:{key}", self.name)
1531
+ };
1532
+ self.db.insert_internal(self.name.clone(), full_key, value)
1533
+ }
1534
+
1535
+ pub fn update<T: Serialize>(&self, key: &str, value: T) -> Result<()> {
1536
+ let full_key = if key.contains(':') {
1537
+ key.to_string()
1538
+ } else {
1539
+ format!("{}:{key}", self.name)
1540
+ };
1541
+ self.db.update_internal(self.name.clone(), full_key, value)
1542
+ }
1543
+
1544
+ pub fn delete(&self, key: &str) -> Result<()> {
1545
+ let full_key = if key.contains(':') {
1546
+ key.to_string()
1547
+ } else {
1548
+ format!("{}:{key}", self.name)
1549
+ };
1550
+ self.db.delete_internal(self.name.clone(), full_key)
1551
+ }
1552
+
1553
+ pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
1554
+ let full_key = if key.contains(':') {
1555
+ key.to_string()
1556
+ } else {
1557
+ format!("{}:{key}", self.name)
1558
+ };
1559
+ self.db.get(&full_key)
1560
+ }
1561
+
1562
+ pub fn find<T, F>(&self, predicate: F) -> Result<Vec<T>>
1563
+ where
1564
+ T: DeserializeOwned,
1565
+ F: Fn(&T) -> bool,
1566
+ {
1567
+ let rust_type = type_name::<T>().to_string();
1568
+ let mut inner = self.db.inner.lock().expect("lock poisoned");
1569
+ *inner
1570
+ .query_count_by_type
1571
+ .entry(rust_type.clone())
1572
+ .or_default() += 1;
1573
+ if inner.query_count_by_type[&rust_type] >= 3 {
1574
+ inner.adaptive_indexes.insert(rust_type.clone());
1575
+ }
1576
+
1577
+ let mut out = Vec::new();
1578
+ if let Some(bucket) = inner.rows.get(&self.name) {
1579
+ for row in bucket.values() {
1580
+ if row.rust_type != rust_type {
1581
+ continue;
1582
+ }
1583
+ let parsed = serde_json::from_value::<T>(row.value.clone())?;
1584
+ if predicate(&parsed) {
1585
+ out.push(parsed);
1586
+ }
1587
+ }
1588
+ }
1589
+ Ok(out)
1590
+ }
1591
+
1592
+ /// Creates a live query subscription scoped to this capability.
1593
+ ///
1594
+ /// The subscription returns initial matching results from this capability only, and streams
1595
+ /// updates whenever records within this capability matching the predicate are inserted or updated.
1596
+ ///
1597
+ /// # Example
1598
+ ///
1599
+ /// ```ignore
1600
+ /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
1601
+ /// use feltdb::open;
1602
+ /// let db = open("app.data")?;
1603
+ /// let users = db.capability("users");
1604
+ /// let mut subscription = users.live_find::<User, _>(|u| u.active).await?;
1605
+ /// let initial_users = subscription.initial_results();
1606
+ ///
1607
+ /// while let Some(users) = subscription.next().await {
1608
+ /// println!("Active users: {:?}", users);
1609
+ /// }
1610
+ /// # Ok(())
1611
+ /// # }
1612
+ /// ```
1613
+ pub async fn live_find<T, F>(&self, predicate: F) -> Result<LiveCapabilitySubscription<T>>
1614
+ where
1615
+ T: DeserializeOwned + Send + Sync + 'static,
1616
+ F: Fn(&T) -> bool + Send + Sync + 'static,
1617
+ {
1618
+ let rust_type = type_name::<T>().to_string();
1619
+ let capability_name = self.name.clone();
1620
+
1621
+ let initial_results = {
1622
+ let mut inner = self.db.inner.lock().expect("lock poisoned");
1623
+ *inner
1624
+ .query_count_by_type
1625
+ .entry(rust_type.clone())
1626
+ .or_default() += 1;
1627
+ if inner.query_count_by_type[&rust_type] >= 3 {
1628
+ inner.adaptive_indexes.insert(rust_type.clone());
1629
+ }
1630
+
1631
+ let mut out = Vec::new();
1632
+ if let Some(bucket) = inner.rows.get(&capability_name) {
1633
+ for row in bucket.values() {
1634
+ if row.rust_type != rust_type {
1635
+ continue;
1636
+ }
1637
+ let parsed = serde_json::from_value::<T>(row.value.clone())?;
1638
+ if predicate(&parsed) {
1639
+ out.push(parsed);
1640
+ }
1641
+ }
1642
+ }
1643
+ out
1644
+ };
1645
+
1646
+ let rx = self.db.event_tx.subscribe();
1647
+ let subscription = LiveCapabilitySubscription {
1648
+ initial_results,
1649
+ rx,
1650
+ db: self.db.clone(),
1651
+ capability_name,
1652
+ rust_type,
1653
+ predicate: Arc::new(Mutex::new(None)),
1654
+ };
1655
+
1656
+ Ok(subscription)
1657
+ }
1658
+ }
1659
+
1660
+ // Type alias for boxed predicates
1661
+ type PredicateBox<T> = Arc<Mutex<Option<Box<dyn Fn(&T) -> bool + Send + Sync>>>>;
1662
+
1663
+ /// A subscription to a live query that streams updates whenever matching records change.
1664
+ ///
1665
+ /// This struct holds the initial query results and provides a stream interface to receive
1666
+ /// updates whenever records matching the query predicate are inserted or updated.
1667
+ pub struct LiveQuerySubscription<T> {
1668
+ /// The initial set of records matching the query predicate at subscription time.
1669
+ pub initial_results: Vec<T>,
1670
+ rx: broadcast::Receiver<ChangeEvent>,
1671
+ db: FeltDb,
1672
+ rust_type: String,
1673
+ predicate: PredicateBox<T>,
1674
+ }
1675
+
1676
+ impl<T> LiveQuerySubscription<T>
1677
+ where
1678
+ T: DeserializeOwned + Send + Sync + 'static,
1679
+ {
1680
+ pub fn set_predicate<F>(&mut self, predicate: F)
1681
+ where
1682
+ F: Fn(&T) -> bool + Send + Sync + 'static,
1683
+ {
1684
+ *self.predicate.lock().expect("lock poisoned") = Some(Box::new(predicate));
1685
+ }
1686
+
1687
+ /// Waits for the next change event and returns the updated query results.
1688
+ ///
1689
+ /// This method is async and will block until a change event is received for the
1690
+ /// subscribed type. When an event arrives, it re-evaluates the full dataset and
1691
+ /// returns all matching records.
1692
+ ///
1693
+ /// Returns `None` if the broadcast channel has been dropped (all senders are gone).
1694
+ pub async fn next(&mut self) -> Option<Vec<T>> {
1695
+ loop {
1696
+ match self.rx.recv().await {
1697
+ Ok(event) => {
1698
+ if event.rust_type != self.rust_type {
1699
+ continue;
1700
+ }
1701
+
1702
+ let results = {
1703
+ let inner = self.db.inner.lock().expect("lock poisoned");
1704
+ let mut out = Vec::new();
1705
+ for bucket in inner.rows.values() {
1706
+ for row in bucket.values() {
1707
+ if row.rust_type != self.rust_type {
1708
+ continue;
1709
+ }
1710
+ if let Ok(parsed) = serde_json::from_value::<T>(row.value.clone()) {
1711
+ out.push(parsed);
1712
+ }
1713
+ }
1714
+ }
1715
+ out
1716
+ };
1717
+
1718
+ return Some(results);
1719
+ }
1720
+ Err(_) => {
1721
+ return None;
1722
+ }
1723
+ }
1724
+ }
1725
+ }
1726
+
1727
+ /// Returns a reference to the initial query results captured at subscription time.
1728
+ ///
1729
+ /// This provides access to the baseline set of matching records before any changes
1730
+ /// have been streamed through `next()`.
1731
+ pub fn initial_results(&self) -> &[T] {
1732
+ &self.initial_results
1733
+ }
1734
+ }
1735
+
1736
+ /// A capability-scoped subscription to a live query that streams updates for records within a specific capability.
1737
+ ///
1738
+ /// Similar to `LiveQuerySubscription`, but only receives updates for records within the specified capability.
1739
+ /// This provides more granular control over subscription scope.
1740
+ pub struct LiveCapabilitySubscription<T> {
1741
+ pub initial_results: Vec<T>,
1742
+ rx: broadcast::Receiver<ChangeEvent>,
1743
+ db: FeltDb,
1744
+ capability_name: String,
1745
+ rust_type: String,
1746
+ predicate: PredicateBox<T>,
1747
+ }
1748
+
1749
+ impl<T> LiveCapabilitySubscription<T>
1750
+ where
1751
+ T: DeserializeOwned + Send + Sync + 'static,
1752
+ {
1753
+ pub fn set_predicate<F>(&mut self, predicate: F)
1754
+ where
1755
+ F: Fn(&T) -> bool + Send + Sync + 'static,
1756
+ {
1757
+ *self.predicate.lock().expect("lock poisoned") = Some(Box::new(predicate));
1758
+ }
1759
+
1760
+ /// Waits for the next change event within this capability and returns the updated query results.
1761
+ ///
1762
+ /// Similar to `LiveQuerySubscription::next()`, but only responds to changes within the
1763
+ /// scoped capability.
1764
+ pub async fn next(&mut self) -> Option<Vec<T>> {
1765
+ loop {
1766
+ match self.rx.recv().await {
1767
+ Ok(event) => {
1768
+ if event.rust_type != self.rust_type || event.capability != self.capability_name
1769
+ {
1770
+ continue;
1771
+ }
1772
+
1773
+ let results = {
1774
+ let inner = self.db.inner.lock().expect("lock poisoned");
1775
+ let mut out = Vec::new();
1776
+ if let Some(bucket) = inner.rows.get(&self.capability_name) {
1777
+ for row in bucket.values() {
1778
+ if row.rust_type != self.rust_type {
1779
+ continue;
1780
+ }
1781
+ if let Ok(parsed) = serde_json::from_value::<T>(row.value.clone()) {
1782
+ out.push(parsed);
1783
+ }
1784
+ }
1785
+ }
1786
+ out
1787
+ };
1788
+
1789
+ return Some(results);
1790
+ }
1791
+ Err(_) => {
1792
+ return None;
1793
+ }
1794
+ }
1795
+ }
1796
+ }
1797
+
1798
+ pub fn initial_results(&self) -> &[T] {
1799
+ &self.initial_results
1800
+ }
1801
+ }
1802
+
1803
+ fn replay_log(inner: &mut Inner) -> Result<()> {
1804
+ let file = OpenOptions::new().read(true).open(&inner.path)?;
1805
+ let reader = BufReader::new(file);
1806
+
1807
+ let mut snapshot_log = false;
1808
+ for line in reader.lines() {
1809
+ let line = line?;
1810
+ if line.trim().is_empty() {
1811
+ continue;
1812
+ }
1813
+ let value: Value =
1814
+ serde_json::from_str(&line).map_err(|_| FlowError::CorruptLogLine(line.clone()))?;
1815
+ if matches!(
1816
+ value.get("record_type").and_then(Value::as_str),
1817
+ Some("feltdb.snapshot.v1" | "flowdb.snapshot.v1")
1818
+ ) {
1819
+ let header: LogHeader = serde_json::from_value(value)?;
1820
+ inner.sequence = inner.sequence.max(header.local_sequence);
1821
+ inner.change_log.observed_versions = header.observed_versions;
1822
+ inner.bootstrapped = true;
1823
+ snapshot_log = true;
1824
+ continue;
1825
+ }
1826
+ if value.get("record_type").and_then(Value::as_str) == Some("feltdb.transaction.v1") {
1827
+ let transaction: TransactionLogRecord = serde_json::from_value(value)?;
1828
+ if inner
1829
+ .applied_transactions
1830
+ .insert(transaction.transaction_id)
1831
+ {
1832
+ for row in transaction.rows {
1833
+ if let Some(operation) = row.operation.clone() {
1834
+ inner.sequence = inner.sequence.max(operation.sequence);
1835
+ if let Some(clock) = &operation.vector_clock {
1836
+ inner.sync_state.merge_vector_clock(clock);
1837
+ }
1838
+ inner.change_log.add_operation(operation);
1839
+ }
1840
+ if row.deleted {
1841
+ if let Some(bucket) = inner.rows.get_mut(&row.capability) {
1842
+ bucket.remove(&row.key);
1843
+ }
1844
+ } else {
1845
+ inner
1846
+ .rows
1847
+ .entry(row.capability.clone())
1848
+ .or_default()
1849
+ .insert(row.key.clone(), row);
1850
+ }
1851
+ }
1852
+ }
1853
+ inner.sequence = inner.sequence.max(transaction.state_after);
1854
+ continue;
1855
+ }
1856
+ let row: StoredRow =
1857
+ serde_json::from_value(value).map_err(|_| FlowError::CorruptLogLine(line.clone()))?;
1858
+ if let Some(operation) = row.operation.clone() {
1859
+ inner.sequence = inner.sequence.max(
1860
+ (operation.instance_id == inner.instance_id)
1861
+ .then_some(operation.sequence)
1862
+ .unwrap_or(0),
1863
+ );
1864
+ if let Some(clock) = &operation.vector_clock {
1865
+ inner.sync_state.merge_vector_clock(clock);
1866
+ }
1867
+ inner.change_log.add_operation(operation);
1868
+ } else if !snapshot_log {
1869
+ // Pre-operation-envelope logs were produced only by this local
1870
+ // store. Preserve a monotonic sequence when upgrading them.
1871
+ inner.sequence += 1;
1872
+ }
1873
+ if let Some(id) = row
1874
+ .key
1875
+ .rsplit_once(':')
1876
+ .and_then(|(_, right)| right.parse::<u64>().ok())
1877
+ {
1878
+ let counter = inner
1879
+ .key_counters
1880
+ .entry(row.capability.clone())
1881
+ .or_default();
1882
+ *counter = (*counter).max(id + 1);
1883
+ }
1884
+ if row.deleted {
1885
+ if let Some(bucket) = inner.rows.get_mut(&row.capability) {
1886
+ bucket.remove(&row.key);
1887
+ }
1888
+ } else {
1889
+ inner
1890
+ .rows
1891
+ .entry(row.capability.clone())
1892
+ .or_default()
1893
+ .insert(row.key.clone(), row);
1894
+ }
1895
+ }
1896
+
1897
+ // Compute collection cardinality from final row state (count non-deleted rows per capability)
1898
+ for (capability, bucket) in &inner.rows {
1899
+ let count = bucket.values().filter(|row| !row.deleted).count() as u64;
1900
+ inner.collection_cardinality.insert(capability.clone(), count);
1901
+ }
1902
+
1903
+ Ok(())
1904
+ }
1905
+
1906
+ fn write_json_line<T: Serialize>(file: &mut std::fs::File, value: &T) -> Result<()> {
1907
+ serde_json::to_writer(&mut *file, value)?;
1908
+ file.write_all(b"\n")?;
1909
+ Ok(())
1910
+ }
1911
+
1912
+ fn stored_row_for_operation(operation: Operation) -> StoredRow {
1913
+ StoredRow {
1914
+ capability: operation.capability.clone(),
1915
+ key: operation.key.clone(),
1916
+ rust_type: operation.rust_type.clone(),
1917
+ value: operation.value.clone().unwrap_or(Value::Null),
1918
+ unix_ms: operation.timestamp_ms,
1919
+ content_hash: Some(operation.content_hash.clone()),
1920
+ flow_ref: None,
1921
+ deleted: operation.op_type == OperationType::Delete,
1922
+ operation: Some(operation),
1923
+ }
1924
+ }
1925
+
1926
+ fn sync_metadata_path(path: &Path) -> PathBuf {
1927
+ path.with_extension("sync.json")
1928
+ }
1929
+
1930
+ fn load_sync_metadata(inner: &mut Inner) -> Result<()> {
1931
+ let path = sync_metadata_path(&inner.path);
1932
+ if !path.exists() {
1933
+ return Ok(());
1934
+ }
1935
+ let metadata: SyncMetadata = serde_json::from_slice(&fs::read(path)?)?;
1936
+ inner.sequence = inner.sequence.max(metadata.local_sequence);
1937
+ for (origin, sequence) in metadata.observed_versions {
1938
+ inner
1939
+ .change_log
1940
+ .observed_versions
1941
+ .entry(origin)
1942
+ .and_modify(|current| *current = (*current).max(sequence))
1943
+ .or_insert(sequence);
1944
+ }
1945
+ inner.change_log.peer_acknowledgements = metadata.peer_acknowledgements;
1946
+ Ok(())
1947
+ }
1948
+
1949
+ fn persist_sync_metadata(inner: &Inner) -> Result<()> {
1950
+ let path = sync_metadata_path(&inner.path);
1951
+ let temporary = path.with_extension("sync.tmp");
1952
+ let metadata = SyncMetadata {
1953
+ local_sequence: inner.sequence,
1954
+ observed_versions: inner.change_log.versions(),
1955
+ peer_acknowledgements: inner.change_log.peer_acknowledgements.clone(),
1956
+ };
1957
+ let mut file = OpenOptions::new()
1958
+ .create(true)
1959
+ .write(true)
1960
+ .truncate(true)
1961
+ .open(&temporary)?;
1962
+ file.write_all(&serde_json::to_vec_pretty(&metadata)?)?;
1963
+ file.flush()?;
1964
+ file.sync_all()?;
1965
+ fs::rename(temporary, path)?;
1966
+ Ok(())
1967
+ }
1968
+
1969
+ fn append_event(path: &Path, event: &StoredRow) -> Result<()> {
1970
+ let mut file = OpenOptions::new().create(true).append(true).open(path)?;
1971
+ let mut line = serde_json::to_string(event)?;
1972
+ line.push('\n');
1973
+ file.write_all(line.as_bytes())?;
1974
+ file.flush()?;
1975
+ Ok(())
1976
+ }
1977
+
1978
+ fn append_transaction(path: &Path, transaction: &TransactionLogRecord) -> Result<()> {
1979
+ let mut file = OpenOptions::new().create(true).append(true).open(path)?;
1980
+ write_json_line(&mut file, transaction)?;
1981
+ file.sync_data()?;
1982
+ Ok(())
1983
+ }
1984
+
1985
+ fn now_ms() -> u128 {
1986
+ SystemTime::now()
1987
+ .duration_since(UNIX_EPOCH)
1988
+ .expect("time moved backwards")
1989
+ .as_millis()
1990
+ }
1991
+
1992
+ #[cfg(test)]
1993
+ mod tests {
1994
+ use super::*;
1995
+ use std::sync::atomic::{AtomicU64, Ordering};
1996
+ use std::time::{SystemTime, UNIX_EPOCH};
1997
+
1998
+ static TEMP_FILE_SEQUENCE: AtomicU64 = AtomicU64::new(0);
1999
+
2000
+ #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
2001
+ struct User {
2002
+ email: String,
2003
+ age: u8,
2004
+ active: bool,
2005
+ }
2006
+
2007
+ fn temp_file() -> PathBuf {
2008
+ let nanos = SystemTime::now()
2009
+ .duration_since(UNIX_EPOCH)
2010
+ .expect("time")
2011
+ .as_nanos();
2012
+ let sequence = TEMP_FILE_SEQUENCE.fetch_add(1, Ordering::Relaxed);
2013
+ std::env::temp_dir().join(format!(
2014
+ "feltdb-test-{}-{nanos}-{sequence}.data",
2015
+ std::process::id()
2016
+ ))
2017
+ }
2018
+
2019
+ #[test]
2020
+ fn open_is_zero_init_and_creates_store() {
2021
+ let path = temp_file();
2022
+ let db = open(&path).expect("open");
2023
+ assert!(path.exists());
2024
+ drop(db);
2025
+ let _ = fs::remove_file(path);
2026
+ }
2027
+
2028
+ #[test]
2029
+ fn insert_get_and_reopen_are_durable() {
2030
+ let path = temp_file();
2031
+ let db = open(&path).expect("open");
2032
+ let user = User {
2033
+ email: "a@feltdb.dev".to_string(),
2034
+ age: 33,
2035
+ active: true,
2036
+ };
2037
+ db.insert("user:1", &user).expect("insert");
2038
+ let loaded: User = db.get("user:1").expect("get").expect("some");
2039
+ assert_eq!(loaded, user);
2040
+ drop(db);
2041
+
2042
+ let reopened = open(&path).expect("reopen");
2043
+ let loaded_after_restart: User = reopened.get("user:1").expect("get").expect("some");
2044
+ assert_eq!(loaded_after_restart, user);
2045
+ let _ = fs::remove_file(path);
2046
+ }
2047
+
2048
+ #[test]
2049
+ fn delete_and_reopen_preserves_tombstone() {
2050
+ let path = temp_file();
2051
+ let db = open(&path).expect("open");
2052
+ db.insert("users:deleted", serde_json::json!({"name": "gone"}))
2053
+ .expect("insert");
2054
+ db.delete("users:deleted").expect("delete");
2055
+ drop(db);
2056
+
2057
+ let reopened = open(&path).expect("reopen");
2058
+ assert_eq!(reopened.get_value("users:deleted").expect("get"), None);
2059
+ let _ = fs::remove_file(path);
2060
+ }
2061
+
2062
+ #[test]
2063
+ fn closure_query_and_capability_find_work() {
2064
+ let path = temp_file();
2065
+ let db = open(&path).expect("open");
2066
+ let users = db.capability("users");
2067
+ users
2068
+ .insert(User {
2069
+ email: "one@feltdb.dev".to_string(),
2070
+ age: 28,
2071
+ active: true,
2072
+ })
2073
+ .expect("insert");
2074
+ users
2075
+ .insert(User {
2076
+ email: "two@feltdb.dev".to_string(),
2077
+ age: 41,
2078
+ active: false,
2079
+ })
2080
+ .expect("insert");
2081
+
2082
+ let global = db.query::<User, _>(|u| u.age > 30).expect("query");
2083
+ assert_eq!(global.len(), 1);
2084
+
2085
+ let active = users.find::<User, _>(|u| u.active).expect("find");
2086
+ assert_eq!(active.len(), 1);
2087
+ assert_eq!(active[0].email, "one@feltdb.dev");
2088
+ let _ = fs::remove_file(path);
2089
+ }
2090
+
2091
+ #[test]
2092
+ fn adaptive_index_marks_frequent_query_types() {
2093
+ let path = temp_file();
2094
+ let db = open(&path).expect("open");
2095
+ db.insert(
2096
+ "user:1",
2097
+ User {
2098
+ email: "index@feltdb.dev".to_string(),
2099
+ age: 21,
2100
+ active: true,
2101
+ },
2102
+ )
2103
+ .expect("insert");
2104
+
2105
+ for _ in 0..3 {
2106
+ let _ = db.query::<User, _>(|u| u.active).expect("query");
2107
+ }
2108
+ assert!(db.is_auto_indexed::<User>());
2109
+ let _ = fs::remove_file(path);
2110
+ }
2111
+
2112
+ #[tokio::test]
2113
+ async fn live_query_returns_initial_results() {
2114
+ let path = temp_file();
2115
+ let db = open(&path).expect("open");
2116
+ let users = db.capability("users");
2117
+ users
2118
+ .insert(User {
2119
+ email: "live@feltdb.dev".to_string(),
2120
+ age: 28,
2121
+ active: true,
2122
+ })
2123
+ .expect("insert");
2124
+ users
2125
+ .insert(User {
2126
+ email: "inactive@feltdb.dev".to_string(),
2127
+ age: 35,
2128
+ active: false,
2129
+ })
2130
+ .expect("insert");
2131
+
2132
+ let subscription = db
2133
+ .live_query::<User, _>(|u| u.active)
2134
+ .await
2135
+ .expect("live_query");
2136
+ let initial = subscription.initial_results();
2137
+ assert_eq!(initial.len(), 1);
2138
+ assert_eq!(initial[0].email, "live@feltdb.dev");
2139
+ let _ = fs::remove_file(path);
2140
+ }
2141
+
2142
+ #[tokio::test]
2143
+ async fn live_query_streams_new_changes() {
2144
+ let path = temp_file();
2145
+ let db = open(&path).expect("open");
2146
+ let users = db.capability("users");
2147
+ users
2148
+ .insert(User {
2149
+ email: "initial@feltdb.dev".to_string(),
2150
+ age: 28,
2151
+ active: true,
2152
+ })
2153
+ .expect("insert");
2154
+
2155
+ let mut subscription = db
2156
+ .live_query::<User, _>(|u| u.active)
2157
+ .await
2158
+ .expect("live_query");
2159
+ let initial = subscription.initial_results();
2160
+ assert_eq!(initial.len(), 1);
2161
+
2162
+ let db_for_insert = db.clone();
2163
+ tokio::spawn(async move {
2164
+ tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
2165
+ let users_inner = db_for_insert.capability("users");
2166
+ users_inner
2167
+ .insert(User {
2168
+ email: "new@feltdb.dev".to_string(),
2169
+ age: 25,
2170
+ active: true,
2171
+ })
2172
+ .expect("insert");
2173
+ });
2174
+
2175
+ let updated = subscription.next().await;
2176
+ assert!(updated.is_some());
2177
+ let results = updated.unwrap();
2178
+ assert_eq!(results.len(), 2);
2179
+ let _ = fs::remove_file(path);
2180
+ }
2181
+
2182
+ #[tokio::test]
2183
+ async fn live_query_filters_by_predicate() {
2184
+ let path = temp_file();
2185
+ let db = open(&path).expect("open");
2186
+ let users = db.capability("users");
2187
+ users
2188
+ .insert(User {
2189
+ email: "active@feltdb.dev".to_string(),
2190
+ age: 28,
2191
+ active: true,
2192
+ })
2193
+ .expect("insert");
2194
+ users
2195
+ .insert(User {
2196
+ email: "inactive@feltdb.dev".to_string(),
2197
+ age: 35,
2198
+ active: false,
2199
+ })
2200
+ .expect("insert");
2201
+
2202
+ let subscription = db
2203
+ .live_query::<User, _>(|u| u.active)
2204
+ .await
2205
+ .expect("live_query");
2206
+ let initial = subscription.initial_results();
2207
+ assert_eq!(initial.len(), 1);
2208
+ assert!(initial[0].active);
2209
+ let _ = fs::remove_file(path);
2210
+ }
2211
+
2212
+ #[tokio::test]
2213
+ async fn change_events_are_emitted_on_insert() {
2214
+ let path = temp_file();
2215
+ let db = open(&path).expect("open");
2216
+
2217
+ let mut subscription = db
2218
+ .live_query::<User, _>(|_| true)
2219
+ .await
2220
+ .expect("live_query");
2221
+ let initial = subscription.initial_results();
2222
+ assert_eq!(initial.len(), 0);
2223
+
2224
+ let db_for_insert = db.clone();
2225
+ tokio::spawn(async move {
2226
+ tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
2227
+ let users_inner = db_for_insert.capability("users");
2228
+ users_inner
2229
+ .insert(User {
2230
+ email: "event@feltdb.dev".to_string(),
2231
+ age: 30,
2232
+ active: true,
2233
+ })
2234
+ .expect("insert");
2235
+ });
2236
+
2237
+ let update = subscription.next().await;
2238
+ assert!(update.is_some());
2239
+ let users_result = update.unwrap();
2240
+ assert_eq!(users_result.len(), 1);
2241
+ let _ = fs::remove_file(path);
2242
+ }
2243
+
2244
+ #[tokio::test]
2245
+ async fn capability_live_find_returns_initial_results() {
2246
+ let path = temp_file();
2247
+ let db = open(&path).expect("open");
2248
+ let users = db.capability("users");
2249
+ users
2250
+ .insert(User {
2251
+ email: "cap_live@feltdb.dev".to_string(),
2252
+ age: 28,
2253
+ active: true,
2254
+ })
2255
+ .expect("insert");
2256
+ users
2257
+ .insert(User {
2258
+ email: "cap_inactive@feltdb.dev".to_string(),
2259
+ age: 35,
2260
+ active: false,
2261
+ })
2262
+ .expect("insert");
2263
+
2264
+ let subscription = users
2265
+ .live_find::<User, _>(|u| u.active)
2266
+ .await
2267
+ .expect("live_find");
2268
+ let initial = subscription.initial_results();
2269
+ assert_eq!(initial.len(), 1);
2270
+ assert_eq!(initial[0].email, "cap_live@feltdb.dev");
2271
+ let _ = fs::remove_file(path);
2272
+ }
2273
+
2274
+ #[tokio::test]
2275
+ async fn capability_live_find_streams_scoped_changes() {
2276
+ let path = temp_file();
2277
+ let db = open(&path).expect("open");
2278
+ let users = db.capability("users");
2279
+ let _orders = db.capability("orders");
2280
+ users
2281
+ .insert(User {
2282
+ email: "user_scoped@feltdb.dev".to_string(),
2283
+ age: 28,
2284
+ active: true,
2285
+ })
2286
+ .expect("insert");
2287
+
2288
+ let mut subscription = users
2289
+ .live_find::<User, _>(|u| u.active)
2290
+ .await
2291
+ .expect("live_find");
2292
+ let initial = subscription.initial_results();
2293
+ assert_eq!(initial.len(), 1);
2294
+
2295
+ let db_for_insert = db.clone();
2296
+ tokio::spawn(async move {
2297
+ tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
2298
+ let users_inner = db_for_insert.capability("users");
2299
+ users_inner
2300
+ .insert(User {
2301
+ email: "new_user_scoped@feltdb.dev".to_string(),
2302
+ age: 25,
2303
+ active: true,
2304
+ })
2305
+ .expect("insert");
2306
+ });
2307
+
2308
+ let updated = subscription.next().await;
2309
+ assert!(updated.is_some());
2310
+ let results = updated.unwrap();
2311
+ assert_eq!(results.len(), 2);
2312
+ let _ = fs::remove_file(path);
2313
+ }
2314
+
2315
+ #[test]
2316
+ fn vector_clocks_increment_on_mutations() {
2317
+ let path = temp_file();
2318
+ let db = open(&path).expect("open");
2319
+
2320
+ // Initial vector clock should be empty or have default values
2321
+ let initial_clock = db.get_vector_clock().expect("get_vector_clock");
2322
+ assert!(initial_clock.is_none() || initial_clock.as_ref().unwrap().is_empty());
2323
+
2324
+ // Insert first record
2325
+ db.insert(
2326
+ "users:1",
2327
+ User {
2328
+ email: "user1@feltdb.dev".to_string(),
2329
+ age: 25,
2330
+ active: true,
2331
+ },
2332
+ )
2333
+ .expect("insert");
2334
+
2335
+ // Check pending operations have vector clock
2336
+ let pending = db
2337
+ .get_pending_for_peer("peer-1", 0)
2338
+ .expect("get_pending_for_peer");
2339
+ assert_eq!(pending.len(), 1);
2340
+ assert!(pending[0].vector_clock.is_some());
2341
+
2342
+ // Insert second record
2343
+ db.insert(
2344
+ "users:2",
2345
+ User {
2346
+ email: "user2@feltdb.dev".to_string(),
2347
+ age: 30,
2348
+ active: false,
2349
+ },
2350
+ )
2351
+ .expect("insert");
2352
+
2353
+ // Check we have 2 operations with increasing sequence numbers
2354
+ let pending = db
2355
+ .get_pending_for_peer("peer-1", 0)
2356
+ .expect("get_pending_for_peer");
2357
+ assert_eq!(pending.len(), 2);
2358
+ assert_eq!(pending[0].sequence, 1);
2359
+ assert_eq!(pending[1].sequence, 2);
2360
+
2361
+ let _ = fs::remove_file(path);
2362
+ }
2363
+
2364
+ #[test]
2365
+ fn update_creates_operation_with_vector_clock() {
2366
+ let path = temp_file();
2367
+ let db = open(&path).expect("open");
2368
+
2369
+ // Insert initial record
2370
+ db.insert(
2371
+ "users:1",
2372
+ User {
2373
+ email: "user1@feltdb.dev".to_string(),
2374
+ age: 25,
2375
+ active: true,
2376
+ },
2377
+ )
2378
+ .expect("insert");
2379
+
2380
+ // Update the record
2381
+ db.update(
2382
+ "users:1",
2383
+ User {
2384
+ email: "user1@feltdb.dev".to_string(),
2385
+ age: 26,
2386
+ active: true,
2387
+ },
2388
+ )
2389
+ .expect("update");
2390
+
2391
+ // Check we have operations for both insert and update
2392
+ let pending = db
2393
+ .get_pending_for_peer("peer-1", 0)
2394
+ .expect("get_pending_for_peer");
2395
+ assert_eq!(pending.len(), 2);
2396
+ assert_eq!(pending[0].op_type, OperationType::Insert);
2397
+ assert_eq!(pending[1].op_type, OperationType::Update);
2398
+
2399
+ // Both should have vector clocks
2400
+ assert!(pending[0].vector_clock.is_some());
2401
+ assert!(pending[1].vector_clock.is_some());
2402
+
2403
+ let _ = fs::remove_file(path);
2404
+ }
2405
+
2406
+ #[test]
2407
+ fn delete_creates_operation_with_vector_clock() {
2408
+ let path = temp_file();
2409
+ let db = open(&path).expect("open");
2410
+
2411
+ // Insert record
2412
+ db.insert(
2413
+ "users:1",
2414
+ User {
2415
+ email: "user1@feltdb.dev".to_string(),
2416
+ age: 25,
2417
+ active: true,
2418
+ },
2419
+ )
2420
+ .expect("insert");
2421
+
2422
+ // Delete the record
2423
+ db.delete("users:1").expect("delete");
2424
+
2425
+ // Check we have operations for both insert and delete
2426
+ let pending = db
2427
+ .get_pending_for_peer("peer-1", 0)
2428
+ .expect("get_pending_for_peer");
2429
+ assert_eq!(pending.len(), 2);
2430
+ assert_eq!(pending[0].op_type, OperationType::Insert);
2431
+ assert_eq!(pending[1].op_type, OperationType::Delete);
2432
+
2433
+ // Both should have vector clocks
2434
+ assert!(pending[0].vector_clock.is_some());
2435
+ assert!(pending[1].vector_clock.is_some());
2436
+
2437
+ let _ = fs::remove_file(path);
2438
+ }
2439
+
2440
+ #[test]
2441
+ fn apply_remote_operation_merges_vector_clocks() {
2442
+ let path = temp_file();
2443
+ let db = open(&path).expect("open");
2444
+
2445
+ // Create a remote operation with a vector clock
2446
+ let remote_op = Operation::insert(
2447
+ 1,
2448
+ "remote-instance".to_string(),
2449
+ 1,
2450
+ "users:100".to_string(),
2451
+ serde_json::json!({"email": "remote@feltdb.dev", "age": 30, "active": true}),
2452
+ "User".to_string(),
2453
+ "users".to_string(),
2454
+ )
2455
+ .with_vector_clock({
2456
+ let mut vc = std::collections::HashMap::new();
2457
+ vc.insert("remote-instance".to_string(), 1);
2458
+ vc
2459
+ });
2460
+
2461
+ // Apply the remote operation
2462
+ db.apply_remote_operation(remote_op)
2463
+ .expect("apply_remote_operation");
2464
+
2465
+ // Check the local vector clock was merged
2466
+ let clock = db.get_vector_clock().expect("get_vector_clock");
2467
+ assert!(clock.is_some());
2468
+ let clock = clock.unwrap();
2469
+ assert_eq!(clock.get("remote-instance"), Some(&1));
2470
+
2471
+ // Verify the data was applied
2472
+ let user: Option<User> = db.get("users:100").expect("get");
2473
+ assert!(user.is_some());
2474
+
2475
+ let _ = fs::remove_file(path);
2476
+ }
2477
+
2478
+ #[test]
2479
+ fn operation_history_survives_restart_and_relays_transitively() {
2480
+ let source_path = temp_file();
2481
+ let relay_path = temp_file();
2482
+ let destination_path = temp_file();
2483
+ let source = open(&source_path).expect("open source");
2484
+ source
2485
+ .insert("docs:1", serde_json::json!({"title": "one"}))
2486
+ .unwrap();
2487
+ source
2488
+ .insert("docs:2", serde_json::json!({"title": "two"}))
2489
+ .unwrap();
2490
+
2491
+ let relay = open(&relay_path).expect("open relay");
2492
+ for operation in source.operations_since(&HashMap::new()).unwrap() {
2493
+ relay.apply_remote_operation(operation).unwrap();
2494
+ }
2495
+ drop(relay);
2496
+
2497
+ let relay = open(&relay_path).expect("reopen relay");
2498
+ let relayed = relay.operations_since(&HashMap::new()).unwrap();
2499
+ assert_eq!(relayed.len(), 2);
2500
+ let destination = open(&destination_path).expect("open destination");
2501
+ for operation in relayed {
2502
+ destination.apply_remote_operation(operation).unwrap();
2503
+ }
2504
+ assert_eq!(
2505
+ destination.get_value("docs:2").unwrap(),
2506
+ Some(serde_json::json!({"title": "two"}))
2507
+ );
2508
+
2509
+ let _ = fs::remove_file(source_path);
2510
+ let _ = fs::remove_file(relay_path);
2511
+ let _ = fs::remove_file(destination_path);
2512
+ }
2513
+
2514
+ #[test]
2515
+ fn remote_operation_sequence_gaps_are_rejected() {
2516
+ let path = temp_file();
2517
+ let db = open(&path).unwrap();
2518
+ let operation = Operation::insert(
2519
+ 2,
2520
+ "remote".to_string(),
2521
+ 2,
2522
+ "docs:2".to_string(),
2523
+ serde_json::json!({"title": "two"}),
2524
+ "Value".to_string(),
2525
+ "docs".to_string(),
2526
+ );
2527
+ assert!(db.apply_remote_operation(operation).is_err());
2528
+ assert!(db.get_value("docs:2").unwrap().is_none());
2529
+ let _ = fs::remove_file(path);
2530
+ }
2531
+
2532
+ #[test]
2533
+ fn peer_version_acknowledgements_survive_restart() {
2534
+ let path = temp_file();
2535
+ let db = open(&path).unwrap();
2536
+ db.insert("docs:1", serde_json::json!({"title": "one"}))
2537
+ .unwrap();
2538
+ let versions = db.operation_versions().unwrap();
2539
+ db.acknowledge_peer_versions("peer-a".to_string(), versions.clone())
2540
+ .unwrap();
2541
+ drop(db);
2542
+
2543
+ let reopened = open(&path).unwrap();
2544
+ assert_eq!(
2545
+ reopened.acknowledged_peer_versions("peer-a").unwrap(),
2546
+ versions
2547
+ );
2548
+ assert_eq!(
2549
+ reopened
2550
+ .acknowledged_operation_count(&["peer-a".to_string()])
2551
+ .unwrap(),
2552
+ 1
2553
+ );
2554
+ let _ = fs::remove_file(sync_metadata_path(&path));
2555
+ let _ = fs::remove_file(path);
2556
+ }
2557
+
2558
+ #[test]
2559
+ fn compacted_history_bootstraps_a_new_peer_without_sequence_gaps() {
2560
+ let source_path = temp_file();
2561
+ let destination_path = temp_file();
2562
+ let source = open(&source_path).unwrap();
2563
+ source
2564
+ .insert("docs:1", serde_json::json!({"title": "one"}))
2565
+ .unwrap();
2566
+ source
2567
+ .insert("docs:2", serde_json::json!({"title": "two"}))
2568
+ .unwrap();
2569
+ let versions = source.operation_versions().unwrap();
2570
+ source
2571
+ .acknowledge_peer_versions("peer-a".to_string(), versions.clone())
2572
+ .unwrap();
2573
+ assert_eq!(
2574
+ source
2575
+ .compact_operation_log(&["peer-a".to_string()])
2576
+ .unwrap(),
2577
+ 2
2578
+ );
2579
+ assert!(source.operations_since(&HashMap::new()).unwrap().is_empty());
2580
+ drop(source);
2581
+
2582
+ let source = open(&source_path).unwrap();
2583
+ assert_eq!(source.operation_versions().unwrap(), versions);
2584
+ let snapshot = source.export_snapshot().unwrap();
2585
+ let destination = open(&destination_path).unwrap();
2586
+ destination.install_snapshot(snapshot).unwrap();
2587
+ assert_eq!(destination.operation_versions().unwrap(), versions);
2588
+ assert_eq!(
2589
+ destination.get_value("docs:2").unwrap(),
2590
+ Some(serde_json::json!({"title": "two"}))
2591
+ );
2592
+
2593
+ source
2594
+ .insert("docs:3", serde_json::json!({"title": "three"}))
2595
+ .unwrap();
2596
+ let next = source.operations_since(&versions).unwrap();
2597
+ assert_eq!(next.len(), 1);
2598
+ destination.apply_remote_operation(next[0].clone()).unwrap();
2599
+ assert!(destination.get_value("docs:3").unwrap().is_some());
2600
+
2601
+ let _ = fs::remove_file(sync_metadata_path(&source_path));
2602
+ let _ = fs::remove_file(sync_metadata_path(&destination_path));
2603
+ let _ = fs::remove_file(source_path);
2604
+ let _ = fs::remove_file(destination_path);
2605
+ }
2606
+
2607
+ #[test]
2608
+ fn maintained_cardinality_unfiltered_count_basic() {
2609
+ let path = temp_file();
2610
+ let db = open(&path).unwrap();
2611
+ let capability = "test_collection";
2612
+
2613
+ let mutations = vec![
2614
+ AtomicMutation {
2615
+ capability: capability.to_string(),
2616
+ key: "rec-1".to_string(),
2617
+ value: Some(serde_json::json!({"id": 1, "name": "Record 1"})),
2618
+ },
2619
+ AtomicMutation {
2620
+ capability: capability.to_string(),
2621
+ key: "rec-2".to_string(),
2622
+ value: Some(serde_json::json!({"id": 2, "name": "Record 2"})),
2623
+ },
2624
+ ];
2625
+
2626
+ db.apply_atomic_transaction("txn-1", None, &[], &mutations, None).unwrap();
2627
+
2628
+ let cardinality = db.collection_cardinality(capability).unwrap();
2629
+ assert_eq!(cardinality, 2, "Cardinality should be 2 after 2 inserts");
2630
+
2631
+ let _ = fs::remove_file(path);
2632
+ }
2633
+
2634
+ #[test]
2635
+ fn maintained_cardinality_insert_increments() {
2636
+ let path = temp_file();
2637
+ let db = open(&path).unwrap();
2638
+ let capability = "test_collection";
2639
+
2640
+ for i in 1..=5 {
2641
+ let mutations = vec![AtomicMutation {
2642
+ capability: capability.to_string(),
2643
+ key: format!("rec-{}", i),
2644
+ value: Some(serde_json::json!({"id": i, "name": format!("Record {}", i)})),
2645
+ }];
2646
+
2647
+ db.apply_atomic_transaction(&format!("txn-{}", i), None, &[], &mutations, None).unwrap();
2648
+ let cardinality = db.collection_cardinality(capability).unwrap();
2649
+ assert_eq!(cardinality, i as u64, "Cardinality should be {} after {} inserts", i, i);
2650
+ }
2651
+
2652
+ let _ = fs::remove_file(path);
2653
+ }
2654
+
2655
+ #[test]
2656
+ fn maintained_cardinality_delete_decrements() {
2657
+ let path = temp_file();
2658
+ let db = open(&path).unwrap();
2659
+ let capability = "test_collection";
2660
+
2661
+ let insert_mutations = vec![
2662
+ AtomicMutation {
2663
+ capability: capability.to_string(),
2664
+ key: "rec-1".to_string(),
2665
+ value: Some(serde_json::json!({"id": 1})),
2666
+ },
2667
+ AtomicMutation {
2668
+ capability: capability.to_string(),
2669
+ key: "rec-2".to_string(),
2670
+ value: Some(serde_json::json!({"id": 2})),
2671
+ },
2672
+ AtomicMutation {
2673
+ capability: capability.to_string(),
2674
+ key: "rec-3".to_string(),
2675
+ value: Some(serde_json::json!({"id": 3})),
2676
+ },
2677
+ ];
2678
+
2679
+ db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None).unwrap();
2680
+ assert_eq!(db.collection_cardinality(capability).unwrap(), 3);
2681
+
2682
+ let delete_mutations = vec![
2683
+ AtomicMutation {
2684
+ capability: capability.to_string(),
2685
+ key: "rec-1".to_string(),
2686
+ value: None,
2687
+ },
2688
+ ];
2689
+
2690
+ db.apply_atomic_transaction("txn-delete", None, &[], &delete_mutations, None).unwrap();
2691
+ assert_eq!(db.collection_cardinality(capability).unwrap(), 2, "Cardinality should be 2 after deleting 1 record");
2692
+
2693
+ let _ = fs::remove_file(path);
2694
+ }
2695
+
2696
+ #[test]
2697
+ fn maintained_cardinality_update_does_not_change_count() {
2698
+ let path = temp_file();
2699
+ let db = open(&path).unwrap();
2700
+ let capability = "test_collection";
2701
+
2702
+ let insert_mutations = vec![AtomicMutation {
2703
+ capability: capability.to_string(),
2704
+ key: "rec-1".to_string(),
2705
+ value: Some(serde_json::json!({"id": 1, "status": "active"})),
2706
+ }];
2707
+
2708
+ db.apply_atomic_transaction("txn-insert", None, &[], &insert_mutations, None).unwrap();
2709
+ assert_eq!(db.collection_cardinality(capability).unwrap(), 1);
2710
+
2711
+ let update_mutations = vec![AtomicMutation {
2712
+ capability: capability.to_string(),
2713
+ key: "rec-1".to_string(),
2714
+ value: Some(serde_json::json!({"id": 1, "status": "inactive"})),
2715
+ }];
2716
+
2717
+ db.apply_atomic_transaction("txn-update", None, &[], &update_mutations, None).unwrap();
2718
+ assert_eq!(db.collection_cardinality(capability).unwrap(), 1, "Cardinality should remain 1 after update");
2719
+
2720
+ let _ = fs::remove_file(path);
2721
+ }
2722
+
2723
+ #[test]
2724
+ fn maintained_cardinality_survives_restart() {
2725
+ let path = temp_file();
2726
+
2727
+ {
2728
+ let db = open(&path).unwrap();
2729
+ let capability = "test_collection";
2730
+
2731
+ let mutations = vec![
2732
+ AtomicMutation {
2733
+ capability: capability.to_string(),
2734
+ key: "rec-1".to_string(),
2735
+ value: Some(serde_json::json!({"id": 1})),
2736
+ },
2737
+ AtomicMutation {
2738
+ capability: capability.to_string(),
2739
+ key: "rec-2".to_string(),
2740
+ value: Some(serde_json::json!({"id": 2})),
2741
+ },
2742
+ ];
2743
+
2744
+ db.apply_atomic_transaction("txn-1", None, &[], &mutations, None).unwrap();
2745
+ assert_eq!(db.collection_cardinality(capability).unwrap(), 2);
2746
+ }
2747
+
2748
+ let reopened = open(&path).unwrap();
2749
+ let capability = "test_collection";
2750
+ let cardinality = reopened.collection_cardinality(capability).unwrap();
2751
+ assert_eq!(cardinality, 2, "Cardinality should survive restart and be recomputed from rows");
2752
+
2753
+ let _ = fs::remove_file(path);
2754
+ }
2755
+
2756
+ #[test]
2757
+ fn maintained_cardinality_empty_collection() {
2758
+ let path = temp_file();
2759
+ let db = open(&path).unwrap();
2760
+ let capability = "empty_collection";
2761
+
2762
+ let cardinality = db.collection_cardinality(capability).unwrap();
2763
+ assert_eq!(cardinality, 0, "Empty collection should have cardinality 0");
2764
+
2765
+ let _ = fs::remove_file(path);
2766
+ }
2767
+
2768
+ #[test]
2769
+ fn maintained_cardinality_large_collection() {
2770
+ let path = temp_file();
2771
+ let db = open(&path).unwrap();
2772
+ let capability = "large_collection";
2773
+
2774
+ let mut mutations = Vec::new();
2775
+ for i in 1..=100 {
2776
+ mutations.push(AtomicMutation {
2777
+ capability: capability.to_string(),
2778
+ key: format!("rec-{}", i),
2779
+ value: Some(serde_json::json!({"id": i, "name": format!("Record {}", i)})),
2780
+ });
2781
+ }
2782
+
2783
+ db.apply_atomic_transaction("txn-large", None, &[], &mutations, None).unwrap();
2784
+ let cardinality = db.collection_cardinality(capability).unwrap();
2785
+ assert_eq!(cardinality, 100, "Large collection with 100 records should have cardinality 100");
2786
+
2787
+ let _ = fs::remove_file(path);
2788
+ }
2789
+
2790
+ #[test]
2791
+ fn maintained_cardinality_multiple_collections_isolated() {
2792
+ let path = temp_file();
2793
+ let db = open(&path).unwrap();
2794
+
2795
+ let col1 = "collection_1";
2796
+ let col2 = "collection_2";
2797
+
2798
+ let mutations1 = vec![
2799
+ AtomicMutation {
2800
+ capability: col1.to_string(),
2801
+ key: "rec-1".to_string(),
2802
+ value: Some(serde_json::json!({"id": 1})),
2803
+ },
2804
+ AtomicMutation {
2805
+ capability: col1.to_string(),
2806
+ key: "rec-2".to_string(),
2807
+ value: Some(serde_json::json!({"id": 2})),
2808
+ },
2809
+ ];
2810
+
2811
+ db.apply_atomic_transaction("txn-col1", None, &[], &mutations1, None).unwrap();
2812
+
2813
+ let mutations2 = vec![
2814
+ AtomicMutation {
2815
+ capability: col2.to_string(),
2816
+ key: "rec-1".to_string(),
2817
+ value: Some(serde_json::json!({"id": 1})),
2818
+ },
2819
+ AtomicMutation {
2820
+ capability: col2.to_string(),
2821
+ key: "rec-2".to_string(),
2822
+ value: Some(serde_json::json!({"id": 2})),
2823
+ },
2824
+ AtomicMutation {
2825
+ capability: col2.to_string(),
2826
+ key: "rec-3".to_string(),
2827
+ value: Some(serde_json::json!({"id": 3})),
2828
+ },
2829
+ ];
2830
+
2831
+ db.apply_atomic_transaction("txn-col2", None, &[], &mutations2, None).unwrap();
2832
+
2833
+ assert_eq!(db.collection_cardinality(col1).unwrap(), 2);
2834
+ assert_eq!(db.collection_cardinality(col2).unwrap(), 3);
2835
+
2836
+ let _ = fs::remove_file(path);
2837
+ }
2838
+ }