High-performance implementation of plcbundle written in Rust
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(server): add did_count to status response and improve root endpoint

- Include did_count in status endpoint response
- Refactor mempool stats handling in root endpoint
- Update DID resolver stats display to show bundled + mempool counts
- Add cursor format documentation to root endpoint
- Clean up endpoint descriptions and improve consistency

+68 -37
+11
docs/API.md
··· 890 890 - WebSocket: `/ws?cursor=N` to resume from a specific position 891 891 - Server: Status endpoint showing current position 892 892 893 + #### Global Position Mapping 894 + 895 + - Positions are 0-indexed per bundle (`0..9,999`). 896 + - Global position formula: `global = ((bundle - 1) × 10,000) + position`. 897 + - Conversion back: `bundle = floor(global / 10,000) + 1`, `position = global % 10,000`. 898 + - Examples: 899 + - `global 0 → bundle 1, position 0` 900 + - `global 10000 → bundle 2, position 0` 901 + - `global 88410345 → bundle 8842, position 345` 902 + - Shorthand: Small numbers `< 10000` are treated as `bundle 1` positions. 903 + 893 904 ### Handle & DID Resolution 894 905 895 906 ```rust
+1 -6
src/mempool.rs
··· 521 521 .last() 522 522 .and_then(|op| self.parse_timestamp(&op.created_at).ok()); 523 523 524 - // Calculate size and unique DIDs 525 524 let mut total_size = 0; 526 - let mut did_set: HashSet<String> = HashSet::new(); 527 - 528 525 for op in &self.operations { 529 - // Approximate JSON size 530 526 if let Ok(json) = serde_json::to_string(op) { 531 527 total_size += json.len(); 532 528 } 533 - did_set.insert(op.did.clone()); 534 529 } 535 530 536 531 stats.size_bytes = Some(total_size); 537 - stats.did_count = Some(did_set.len()); 532 + stats.did_count = Some(self.did_index.len()); 538 533 } 539 534 540 535 stats
+55 -31
src/server/handle_root.rs
··· 19 19 let bundle_count = index.bundles.len(); 20 20 let origin = state.manager.get_plc_origin(); 21 21 let uptime = state.start_time.elapsed(); 22 + let mempool_stats_opt = if state.config.sync_mode { 23 + state.manager.get_mempool_stats().ok() 24 + } else { 25 + None 26 + }; 22 27 23 28 let mut response = String::new(); 24 29 25 30 // ASCII art banner 26 31 response.push('\n'); 27 32 response.push_str(&crate::server::get_ascii_art_banner(&state.config.version)); 33 + response.push('\n'); 28 34 response.push_str(&format!(" {} server\n\n", constants::BINARY_NAME)); 29 35 response.push_str("*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*\n"); 30 36 response.push_str("| ⚠️ Preview Version – Do Not Use In Production! |\n"); ··· 82 88 } 83 89 } 84 90 85 - if state.config.sync_mode 86 - && let Ok(mempool_stats) = state.manager.get_mempool_stats() 87 - { 91 + if let Some(mempool_stats) = mempool_stats_opt.as_ref() { 88 92 response.push_str("\nMempool\n"); 89 93 response.push_str("━━━━━━━\n"); 90 94 response.push_str(&format!( ··· 127 131 } 128 132 129 133 if state.config.enable_resolver { 130 - response.push_str("\nResolver\n"); 131 - response.push_str("━━━━━━━━\n"); 132 - response.push_str(" Status: enabled\n"); 134 + response.push_str("\nDID Resolver\n"); 135 + response.push_str("━━━━━━━━━━━━\n"); 136 + response.push_str(" Status: enabled\n"); 133 137 134 138 let did_stats = state.manager.get_did_index_stats(); 135 139 if did_stats ··· 138 142 .unwrap_or(false) 139 143 { 140 144 let indexed_dids = did_stats 141 - .get("indexed_dids") 142 - .and_then(|v| v.as_i64()) 143 - .unwrap_or(0); 144 - let mempool_dids = did_stats 145 - .get("mempool_dids") 146 - .and_then(|v| v.as_i64()) 147 - .unwrap_or(0); 148 - let total_dids = did_stats 149 145 .get("total_dids") 150 146 .and_then(|v| v.as_i64()) 151 - .unwrap_or(0); 147 + .unwrap_or(0) as u64; 148 + let mempool_dids = mempool_stats_opt 149 + .as_ref() 150 + .and_then(|s| s.did_count) 151 + .unwrap_or(0) as u64; 152 152 153 - if mempool_dids > 0 { 154 - response.push_str(&format!( 155 - " Total DIDs: {} ({} indexed + {} mempool)\n", 156 - format_number(total_dids as u64), 157 - format_number(indexed_dids as u64), 158 - format_number(mempool_dids as u64) 159 - )); 160 - } else { 161 - response.push_str(&format!( 162 - " Total DIDs: {}\n", 163 - format_number(total_dids as u64) 164 - )); 165 - } 153 + let total_dids = indexed_dids + mempool_dids; 154 + response.push_str(&format!( 155 + " DIDs: {} (Bundles {} + Mempool {})\n", 156 + format_number(total_dids), 157 + format_number(indexed_dids), 158 + format_number(mempool_dids) 159 + )); 166 160 } 167 161 response.push('\n'); 168 162 } ··· 200 194 response.push_str(" GET /bundle/:number Bundle metadata (JSON)\n"); 201 195 response.push_str(" GET /data/:number Raw bundle (zstd compressed)\n"); 202 196 response.push_str(" GET /jsonl/:number Decompressed JSONL stream\n"); 203 - response.push_str(" GET /op/:pointer Get single operation\n"); 197 + response.push_str(" GET /op/:cursor Get single operation\n"); 204 198 response.push_str(" GET /status Server status\n"); 205 199 response.push_str(" GET /mempool Mempool operations (JSONL)\n"); 206 - response.push_str(" GET /random Random DID sample (JSON)\n"); 207 200 208 201 if state.config.enable_websocket { 209 202 response.push_str("\nWebSocket Endpoints\n"); 210 203 response.push_str("━━━━━━━━━━━━━━━━━━━━━━━━\n"); 211 204 response.push_str(" WS /ws Live stream (new operations only)\n"); 212 205 response.push_str(" WS /ws?cursor=0 Stream all from beginning\n"); 213 - response.push_str(" WS /ws?cursor=N Stream from cursor N\n\n"); 206 + response.push_str(" WS /ws?cursor=N Stream from cursor N\n\n"); 214 207 } 215 208 216 209 if state.config.enable_resolver { ··· 219 212 response.push_str(" GET /:did DID Document (W3C format)\n"); 220 213 response.push_str(" GET /:did/data PLC State (raw format)\n"); 221 214 response.push_str(" GET /:did/log/audit Operation history\n"); 215 + response.push_str(" GET /random Random DID sample (JSON)\n"); 216 + } 217 + 218 + response.push_str("\nCursor Format\n"); 219 + response.push_str("━━━━━━━━━━━━━\n"); 220 + response.push_str(" Global record number: ((bundle - 1) × 10,000) + position\n"); 221 + response.push_str(" Example: global 0 = bundle 1, position 0\n"); 222 + response.push_str(" Default: starts from latest (skips all historical data)\n"); 223 + response.push_str(" Positions are 0-indexed (per bundle: 0..9,999)\n"); 224 + response.push_str(" Example: global 10000 = bundle 2, position 0\n"); 225 + 226 + let bundled_ops = crate::constants::total_operations_from_bundles(index.last_bundle); 227 + let mempool_ops = mempool_stats_opt 228 + .as_ref() 229 + .map(|s| s.count as u64) 230 + .unwrap_or(0); 231 + let current_latest = bundled_ops + mempool_ops; 232 + 233 + if mempool_ops > 0 { 234 + response.push_str(&format!( 235 + " Current latest: {} ({} bundled + {} mempool)\n\n", 236 + format_number(current_latest), 237 + format_number(bundled_ops), 238 + format_number(mempool_ops) 239 + )); 240 + } else { 241 + response.push_str(&format!( 242 + " Current latest: {} ({} bundled)\n\n", 243 + format_number(current_latest), 244 + format_number(bundled_ops) 245 + )); 222 246 } 223 247 224 248 response.push_str("\nExamples\n");
+1
src/server/handle_status.rs
··· 76 76 "progress_percent": (mempool_stats.count as f64 / constants::BUNDLE_SIZE as f64) * 100.0, 77 77 "bundle_size": constants::BUNDLE_SIZE, 78 78 "operations_needed": constants::BUNDLE_SIZE - mempool_stats.count, 79 + "did_count": mempool_stats.did_count, 79 80 }); 80 81 } 81 82